diff --git a/README.md b/README.md index eb07a52..395fb89 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,14 @@ import {formatNpmrc} from 'dotnpmrc' const contents = formatNpmrc({ format: 'npm9', - scopes: [{ - name: 'example', - registry: 'https://npm-registry.example.com/', - auth: 'base64-of-username-and-password', - authToken: 'npmToken.bearer', - alwaysAuth: true, - }], + scopes: { + example: { + registry: 'https://npm-registry.example.com/', + auth: 'base64-of-username-and-password', + authToken: 'npmToken.bearer', + alwaysAuth: true, + } + }, root: { registry: 'https://registry.yarnpkg.com' } diff --git a/package.json b/package.json index d09d245..6594de2 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,11 @@ "type": "git", "url": "https://github.com/semrel-extra/npmrc.git" }, + "dependencies": { + "ini": "^4.0.0" + }, "devDependencies": { + "@types/ini": "^1.3.31", "@types/node": "^18.15.10", "c8": "^7.13.0", "concurrently": "^7.6.0", diff --git a/src/main/ts/index.ts b/src/main/ts/index.ts index 7212fb6..040ead5 100644 --- a/src/main/ts/index.ts +++ b/src/main/ts/index.ts @@ -1 +1,53 @@ -export const foo = 'bar' \ No newline at end of file +import {encode} from 'ini' + +export interface INpmrcData { + format?: string, + root?: Record, + scopes?: Record +} + +const credKeys = new Set(['_auth', '_authToken', 'username', '_password']) +const credAliases = new Set(['auth', 'authToken', 'password']) + +// https://docs.npmjs.com/cli/v9/configuring-npm/npmrc +export const formatNpmrc = ({scopes = {}, root = {}, format}: INpmrcData): string => { + const config: Record = {} + const registry = normalizeRegistry(root.registry || 'registry.npmjs.org') + + for (const [name, scope] of Object.entries(scopes)) { + for (const [k, v] of Object.entries(scope)) { + if (k === 'registry') { + config[`@${normalizeScope(name)}:${k}`] = v as string + continue + } + config[`${normalizeRegistry(scope.registry)}:${normalizeKey(k)}`] = v || 'true' + } + } + + for (const [_k, v, k = normalizeKey(_k)] of Object.entries(root)) { + if (format === 'npm9' && credKeys.has(k)) { + config[`${registry}:${k}`] = v || 'true' + continue + } + config[k] = v || 'true' + } + + return encode(config) +} + +const normalizeRegistry = (value: string): string => value + .replace(/^(https?:\/\/)?/, '//') + .replace(/\/*$/, '/') + +const normalizeScope = (value: string): string => value.replace(/^@/, '') +const normalizeKey = (value: string): string => + credAliases.has(value) + ? `_${value}` + : value diff --git a/src/test/js/index.cjs b/src/test/js/index.cjs index ecfc3a6..1451d12 100644 --- a/src/test/js/index.cjs +++ b/src/test/js/index.cjs @@ -1,9 +1,9 @@ const { test } = require('uvu') const assert = require('node:assert') -const toposource = require('@semrel-extra/npmrc') +const npmrc = require('@semrel-extra/npmrc') test('index (cjs)', () => { - assert.ok(typeof toposource.foo === 'string') + assert.ok(typeof npmrc.formatNpmrc === 'function') }) test.run() diff --git a/src/test/js/index.mjs b/src/test/js/index.mjs index a81a013..3cb94da 100644 --- a/src/test/js/index.mjs +++ b/src/test/js/index.mjs @@ -1,9 +1,9 @@ import { test } from 'uvu' import assert from 'node:assert' -import {foo} from '@semrel-extra/npmrc' +import { formatNpmrc } from '@semrel-extra/npmrc' test('index (mjs)', () => { - assert.ok(typeof foo === 'string') + assert.ok(typeof formatNpmrc === 'function') }) test.run() diff --git a/src/test/ts/index.test.ts b/src/test/ts/index.test.ts index 583fa5b..19f71e8 100644 --- a/src/test/ts/index.test.ts +++ b/src/test/ts/index.test.ts @@ -1,10 +1,63 @@ import { test } from 'uvu' import * as assert from 'uvu/assert' -import { foo } from '../../main/ts' +import {formatNpmrc, INpmrcData} from '../../main/ts' test('index has proper index', () => { - assert.ok(typeof foo === 'string') + assert.ok(typeof formatNpmrc === 'function') +}) + +test('`formatNpmrc()` returns proper contents', () => { + const cases: [INpmrcData, string][] = [ + [ + {}, + '' + ], + [ + { + scopes: { + test: { + registry: 'https://registry.example.com', + 'always-auth': '', + auth: 'basic', + _authToken: 'bearer' + } + } + }, +`@test:registry=https://registry.example.com +//registry.example.com/:always-auth=true +//registry.example.com/:_auth=basic +//registry.example.com/:_authToken=bearer +` + ], + [ + { + format: 'npm9', + root: { + 'always-auth': '', + 'auth': 'basic' + } + }, +`always-auth=true +//registry.npmjs.org/:_auth=basic +` + ], + [ + { + root: { + 'always-auth': '', + 'auth': 'basic' + } + }, +`always-auth=true +_auth=basic +` + ] + ]; + + for (const [data, expected] of cases) { + assert.equal(formatNpmrc(data), expected) + } }) test.run() diff --git a/yarn.lock b/yarn.lock index 967b6b8..92ebe44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -243,6 +243,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@types/ini@^1.3.31": + version "1.3.31" + resolved "https://registry.yarnpkg.com/@types/ini/-/ini-1.3.31.tgz#c78541a187bd88d5c73e990711c9d85214800d1b" + integrity sha512-8ecxxaG4AlVEM1k9+BsziMw8UsX0qy3jYI1ad/71RrDZ+rdL6aZB0wLfAuflQiDhkD5o4yJ0uPK3OSUic3fG0w== + "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" @@ -1395,6 +1400,11 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-4.0.0.tgz#35b4b0ba3bb9a3feb8c50dbf92fb1671efda88eb" + integrity sha512-t0ikzf5qkSFqRl1e6ejKBe+Tk2bsQd8ivEkcisyGXsku2t8NvXZ1Y3RRz5vxrDgOrTBOi13CvGsVoI5wVpd7xg== + internal-slot@^1.0.3, internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986"