diff --git a/README.md b/README.md index 5be3af1..9662caa 100644 --- a/README.md +++ b/README.md @@ -254,13 +254,19 @@ When using nxapi as a TypeScript/JavaScript library, the `addUserAgent` function import { addUserAgent } from 'nxapi'; addUserAgent('your-script/1.0.0 (+https://github.com/...)'); +``` -// This could also be read from a package.json file -import { fileURLToPath } from 'node:url'; -import { resolve } from 'node:path'; -import { readFile } from 'node:fs/promises': -const pkg = JSON.parse(await readFile(resolve(fileURLToPath(import.meta.url), '..', 'package.json'), 'utf-8')); -addUserAgent(pkg.name + '/' + pkg.version + ' (+' + pkg.repository.url + ')'); +The `addUserAgentFromPackageJson` function can be used to add data from a package.json file. + +```ts +import { addUserAgentFromPackageJson } from 'nxapi'; + +await addUserAgentFromPackageJson(new URL('../package.json', import.meta.url)); +await addUserAgentFromPackageJson(path.resolve(fileURLToString(import.meta.url), '..', 'package.json')); +// adds "test-package/0.1.0 (+https://github.com/ghost/example.git)" + +await addUserAgentFromPackageJson(new URL('../package.json', import.meta.url), 'additional information'); +// adds "test-package/0.1.0 (+https://github.com/ghost/example.git; additional information)" ``` ### Usage as a TypeScript/JavaScript library diff --git a/docs/lib/index.md b/docs/lib/index.md index 9cc174a..6f32f8d 100644 --- a/docs/lib/index.md +++ b/docs/lib/index.md @@ -151,6 +151,10 @@ try { This function is used to set the user agent string to use for non-Nintendo API requests. Any project using nxapi (including as a dependency of another project) must call this function with an appropriate user agent string segment. See [user agent strings](../../README.md#user-agent-strings). +#### `addUserAgentFromPackageJson` + +This function is used to set the user agent string to use for non-Nintendo API requests using data from a package.json file. A string, URL object or the package.json data can be provided, as well as optional additional data. If a string/URL is provided this will return a Promise that will be resolved once the user agent is updated. See [user agent strings](../../README.md#user-agent-strings). + #### `version` nxapi's version number. diff --git a/src/exports/index.ts b/src/exports/index.ts index 6e120fd..011f8fe 100644 --- a/src/exports/index.ts +++ b/src/exports/index.ts @@ -1,6 +1,6 @@ export { getTitleIdFromEcUrl } from '../util/misc.js'; export { ErrorResponse, ResponseSymbol } from '../api/util.js'; -export { addUserAgent } from '../util/useragent.js'; +export { addUserAgent, addUserAgentFromPackageJson } from '../util/useragent.js'; export { version } from '../util/product.js'; diff --git a/src/util/useragent.ts b/src/util/useragent.ts index 4cea42d..f65d7b0 100644 --- a/src/util/useragent.ts +++ b/src/util/useragent.ts @@ -1,5 +1,6 @@ import * as process from 'node:process'; import * as os from 'node:os'; +import * as fs from 'node:fs/promises'; import { docker, git, release, version } from '../util/product.js'; const default_useragent = 'nxapi/' + version + ' (' + @@ -19,6 +20,68 @@ export function addUserAgent(...useragent: string[]) { additional_useragents.push(...useragent); } +export function addUserAgentFromPackageJson(pkg: string | URL, additional?: string): Promise; +export function addUserAgentFromPackageJson(pkg: object, additional?: string): void; +export function addUserAgentFromPackageJson(pkg: string | URL | object, additional?: string) { + if (typeof pkg === 'string' || pkg instanceof URL) { + return fs.readFile(pkg, 'utf-8').then(pkg => addUserAgentFromPackageJson(JSON.parse(pkg))); + } + + const name = 'name' in pkg && typeof pkg.name === 'string' ? pkg.name : null; + const version = 'version' in pkg && typeof pkg.version === 'string' ? pkg.version : null; + if (!name || !version) throw new Error('package.json does not contain valid name and version fields'); + + const homepage = 'homepage' in pkg ? pkg.homepage : null; + if (homepage != null && typeof homepage !== 'string') throw new Error('package.json contains an invalid homepage field'); + const repository = 'repository' in pkg && pkg.repository != null ? + getPackageJsonRepository(pkg.repository) : null; + + const end = + (homepage ? '+' + homepage : repository ? '+' + repository.url : '') + + (repository && additional ? '; ' : '') + + additional; + + const useragent = name + '/' + version + (end ? ' (' + end + ')' : ''); + + addUserAgent(useragent); +} + +function getPackageJsonRepository(repository: unknown): { + type: string; url: string; directory?: string | null; +} { + if (typeof repository === 'string') { + if (repository.startsWith('github:')) { + return {type: 'git', url: 'https://github.com/' + repository.substr(7)}; + } + if (repository.startsWith('gist:')) { + return {type: 'git', url: 'https://gist.github.com/' + repository.substr(5)}; + } + if (repository.startsWith('bitbucket:')) { + return {type: 'git', url: 'https://bitbucket.org/' + repository.substr(10)}; + } + if (repository.startsWith('gitlab:')) { + return {type: 'git', url: 'https://gitlab.com/' + repository.substr(7)}; + } + if (repository.match(/^[0-9a-z-.]+\/[0-9a-z-.]+$/i)) { + return {type: 'git', url: 'https://github.com/' + repository}; + } + } + + if (typeof repository === 'object' && repository) { + if ('type' in repository && typeof repository.type === 'string' && + 'url' in repository && typeof repository.url === 'string' && + (!('directory' in repository) || repository.directory == null || typeof repository.directory === 'string') + ) { + return { + type: repository.type, url: repository.url, + directory: 'directory' in repository ? repository.directory as string : null, + }; + } + } + + throw new Error('package.json contains an invalid repository field'); +} + /** * Only used by cli/nso/http-server.ts. * This command is intended to be run automatically and doesn't make any requests itself, so this function removes