From 173f1807dcb5ef1d1ffae4829d650ccba4600734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Fri, 17 May 2019 10:07:21 +0200 Subject: [PATCH 1/9] Adds js-yaml --- .pnp.js | 456 +++++++------------------ packages/berry-parsers/package.json | 3 + packages/berry-parsers/sources/syml.ts | 9 +- yarn.lock | 1 + 4 files changed, 131 insertions(+), 338 deletions(-) diff --git a/.pnp.js b/.pnp.js index a371bf2dd5a..619be7e5241 100755 --- a/.pnp.js +++ b/.pnp.js @@ -16,323 +16,7 @@ function $$SETUP_STATE(hydrateRuntimeState) { "it either without using the @berry/pnp package, as the data layout", "is entirely unspecified and WILL change from a version to another." ], - "enableTopLevelFallback": true, "ignorePatternData": null, - "fallbackExclusionList": [ - [ - "@berry/builder", - [ - "workspace:packages/berry-builder" - ] - ], - [ - "@berry/cli", - [ - "workspace:packages/berry-cli" - ] - ], - [ - "@berry/core", - [ - "workspace:packages/berry-core" - ] - ], - [ - "@berry/fslib", - [ - "workspace:packages/berry-fslib" - ] - ], - [ - "@berry/gatsby", - [ - "workspace:packages/gatsby" - ] - ], - [ - "@berry/json-proxy", - [ - "workspace:packages/berry-json-proxy" - ] - ], - [ - "@berry/libzip", - [ - "workspace:packages/berry-libzip" - ] - ], - [ - "@berry/monorepo", - [ - "workspace:." - ] - ], - [ - "@berry/parsers", - [ - "workspace:packages/berry-parsers" - ] - ], - [ - "@berry/plugin-constraints", - [ - "workspace:packages/plugin-constraints" - ] - ], - [ - "@berry/plugin-dlx", - [ - "workspace:packages/plugin-dlx" - ] - ], - [ - "@berry/plugin-essentials", - [ - "workspace:packages/plugin-essentials" - ] - ], - [ - "@berry/plugin-exec", - [ - "workspace:packages/plugin-exec" - ] - ], - [ - "@berry/plugin-file", - [ - "workspace:packages/plugin-file" - ] - ], - [ - "@berry/plugin-github", - [ - "workspace:packages/plugin-github" - ] - ], - [ - "@berry/plugin-http", - [ - "workspace:packages/plugin-http" - ] - ], - [ - "@berry/plugin-hub", - [ - "workspace:packages/plugin-hub" - ] - ], - [ - "@berry/plugin-init", - [ - "workspace:packages/plugin-init" - ] - ], - [ - "@berry/plugin-link", - [ - "workspace:packages/plugin-link" - ] - ], - [ - "@berry/plugin-npm", - [ - "workspace:packages/plugin-npm" - ] - ], - [ - "@berry/plugin-npm-cli", - [ - "workspace:packages/plugin-npm-cli" - ] - ], - [ - "@berry/plugin-pack", - [ - "workspace:packages/plugin-pack" - ] - ], - [ - "@berry/plugin-pnp", - [ - "workspace:packages/plugin-pnp" - ] - ], - [ - "@berry/plugin-stage", - [ - "workspace:packages/plugin-stage" - ] - ], - [ - "@berry/plugin-typescript", - [ - "workspace:packages/plugin-typescript" - ] - ], - [ - "@berry/pnp", - [ - "workspace:packages/berry-pnp" - ] - ], - [ - "@berry/pnpify", - [ - "workspace:packages/berry-pnpify" - ] - ], - [ - "@berry/shell", - [ - "workspace:packages/berry-shell" - ] - ], - [ - "@berry/ui", - [ - "virtual:451e10e454d9fda3e395127fd945a02bbebfed7b1b93118275b1395dffb60092b0a4b3230b0dbc75becfcc013b274d1a9d0b596fabc2999a7307a6d9f626120f#workspace:packages/berry-ui", - "workspace:packages/berry-ui" - ] - ], - [ - "acceptance-tests-06d141", - [ - "workspace:packages/acceptance-tests" - ] - ], - [ - "pkg-tests-core", - [ - "workspace:packages/acceptance-tests/pkg-tests-core" - ] - ], - [ - "pkg-tests-fixtures", - [ - "workspace:packages/acceptance-tests/pkg-tests-fixtures" - ] - ], - [ - "pkg-tests-specs", - [ - "workspace:packages/acceptance-tests/pkg-tests-specs" - ] - ], - [ - "vscode-zipfs", - [ - "workspace:packages/vscode-zipfs" - ] - ] - ], - "locationBlacklistData": [], - "locationLengthData": [ - 220, - 212, - 208, - 204, - 203, - 202, - 200, - 198, - 196, - 194, - 192, - 191, - 190, - 189, - 188, - 186, - 185, - 184, - 183, - 182, - 181, - 180, - 179, - 178, - 177, - 176, - 174, - 173, - 172, - 171, - 170, - 169, - 168, - 166, - 165, - 164, - 163, - 162, - 161, - 160, - 159, - 158, - 157, - 156, - 155, - 154, - 153, - 152, - 151, - 150, - 149, - 148, - 147, - 146, - 145, - 144, - 143, - 142, - 141, - 140, - 139, - 138, - 137, - 136, - 135, - 134, - 133, - 132, - 131, - 130, - 129, - 128, - 127, - 126, - 125, - 124, - 123, - 122, - 121, - 120, - 119, - 118, - 117, - 116, - 115, - 114, - 112, - 110, - 99, - 47, - 46, - 44, - 43, - 30, - 29, - 28, - 26, - 25, - 24, - 23, - 22, - 21, - 20, - 18, - 2 - ], "packageRegistryData": [ [ null, @@ -9621,6 +9305,10 @@ function $$SETUP_STATE(hydrateRuntimeState) { "@berry/parsers", "workspace:packages/berry-parsers" ], + [ + "js-yaml", + "npm:3.12.2" + ], [ "pegjs", "npm:0.10.0" @@ -67020,6 +66708,114 @@ function $$SETUP_STATE(hydrateRuntimeState) { ] ] ] + ], + "locationBlacklistData": [], + "locationLengthData": [ + 220, + 212, + 208, + 204, + 203, + 202, + 200, + 198, + 196, + 194, + 192, + 191, + 190, + 189, + 188, + 186, + 185, + 184, + 183, + 182, + 181, + 180, + 179, + 178, + 177, + 176, + 174, + 173, + 172, + 171, + 170, + 169, + 168, + 166, + 165, + 164, + 163, + 162, + 161, + 160, + 159, + 158, + 157, + 156, + 155, + 154, + 153, + 152, + 151, + 150, + 149, + 148, + 147, + 146, + 145, + 144, + 143, + 142, + 141, + 140, + 139, + 138, + 137, + 136, + 135, + 134, + 133, + 132, + 131, + 130, + 129, + 128, + 127, + 126, + 125, + 124, + 123, + 122, + 121, + 120, + 119, + 118, + 117, + 116, + 115, + 114, + 112, + 110, + 99, + 47, + 46, + 44, + 43, + 30, + 29, + 28, + 26, + 25, + 24, + 23, + 22, + 21, + 20, + 18, + 2 ] }, {basePath: __dirname}); } @@ -69823,7 +69619,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const fslib_1 = __webpack_require__(1); function hydrateRuntimeState(data, { basePath }) { const portablePath = fslib_1.NodeFS.toPortablePath(basePath); - const ignorePattern = data.ignorePatternData !== null + const ignorePattern = data.ignorePatternData ? new RegExp(data.ignorePatternData) : null; const packageRegistry = new Map(data.packageRegistryData.map(([packageName, packageStoreData]) => { @@ -69846,19 +69642,13 @@ function hydrateRuntimeState(data, { basePath }) { packageLocatorsByLocations.set(packageInformationData.packageLocation, packageLocator); } } - const fallbackExclusionList = new Map(data.fallbackExclusionList.map(([packageName, packageReferences]) => { - return [packageName, new Set(packageReferences)]; - })); - const enableTopLevelFallback = data.enableTopLevelFallback; const packageLocationLengths = data.locationLengthData; return { basePath: portablePath, - enableTopLevelFallback, - fallbackExclusionList, ignorePattern, - packageLocationLengths, - packageLocatorsByLocations, packageRegistry, + packageLocatorsByLocations, + packageLocationLengths, }; } exports.hydrateRuntimeState = hydrateRuntimeState; @@ -69893,9 +69683,7 @@ function makeApi(runtimeState, opts) { // We only instantiate one of those so that we can use strict-equal comparisons const topLevelLocator = { name: null, reference: null }; // Used for compatibility purposes - cf setupCompatibilityLayer - const fallbackLocators = []; - if (runtimeState.enableTopLevelFallback === true) - fallbackLocators.push(topLevelLocator); + const fallbackLocators = [topLevelLocator]; if (opts.compatibilityMode) { // ESLint currently doesn't have any portable way for shared configs to // specify their own plugins that should be used (cf issue #10125). This @@ -70230,15 +70018,9 @@ function makeApi(runtimeState, opts) { // It's a bit of a hack, but it improves compatibility with the existing Node ecosystem. Hopefully we should eventually be able // to kill this logic and become stricter once pnp gets enough traction and the affected packages fix themselves. if (issuerLocator.name !== null) { - // To allow programs to become gradually stricter, starting from the v2 we enforce that workspaces cannot depend on fallbacks. - // This works by having a list containing all their locators, and checking when a fallback is required whether it's one of them. - const exclusionEntry = runtimeState.fallbackExclusionList.get(issuerLocator.name); - const canUseFallbacks = !exclusionEntry || !exclusionEntry.has(issuerLocator.reference); - if (canUseFallbacks) { - for (let t = 0, T = fallbackLocators.length; dependencyReference === undefined && t < T; ++t) { - const fallbackInformation = getPackageInformationSafe(fallbackLocators[t]); - dependencyReference = fallbackInformation.packageDependencies.get(dependencyName); - } + for (let t = 0, T = fallbackLocators.length; dependencyReference === undefined && t < T; ++t) { + const fallbackInformation = getPackageInformationSafe(fallbackLocators[t]); + dependencyReference = fallbackInformation.packageDependencies.get(dependencyName); } } // If we can't find the path, and if the package making the request is the top-level, we can offer nicer error messages diff --git a/packages/berry-parsers/package.json b/packages/berry-parsers/package.json index d393b58171d..4bbfb109c16 100644 --- a/packages/berry-parsers/package.json +++ b/packages/berry-parsers/package.json @@ -10,5 +10,8 @@ "grammar:resolution": "run pegjs -o sources/grammars/resolution.js sources/grammars/resolution.pegjs", "grammar:syml": "run pegjs -o sources/grammars/syml.js sources/grammars/syml.pegjs", "test:parsers": "run test:unit packages/berry-parsers" + }, + "dependencies": { + "js-yaml": "npm:^3.10.0" } } diff --git a/packages/berry-parsers/sources/syml.ts b/packages/berry-parsers/sources/syml.ts index b9d340218de..bef0b86b64d 100644 --- a/packages/berry-parsers/sources/syml.ts +++ b/packages/berry-parsers/sources/syml.ts @@ -1,3 +1,6 @@ +// @ts-ignore +import {load} from 'js-yaml'; + import {parse} from './grammars/syml'; const simpleStringPattern = /^(?![-?:,\][{}#&*!|>'"%@` \t\r\n]).([ \t]*(?![,\][{}:# \t\r\n]).)*$/; @@ -86,7 +89,11 @@ export function stringifySyml(value: any) { export function parseSyml(source: string) { try { - return parse(source.endsWith(`\n`) ? source : `${source}\n`); + try { + return load(source) as {[key: string]: any}; + } catch (error) { + return parse(source.endsWith(`\n`) ? source : `${source}\n`); + } } catch (error) { if (error.location) error.message = error.message.replace(/(\.)?$/, ` (line ${error.location.start.line}, column ${error.location.start.column})$1`); diff --git a/yarn.lock b/yarn.lock index 6f6dc3dea7c..abc1bcd58fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1722,6 +1722,7 @@ __metadata: version: 0.0.0-use.local resolution: "@berry/parsers@workspace:packages/berry-parsers" dependencies: + js-yaml: "npm:^3.10.0" pegjs: "npm:^0.10.0" languageName: unknown linkType: soft From 5c6c036a3a049b0ee558a763c16b2bde2b0ef91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Sun, 19 May 2019 12:28:24 +0200 Subject: [PATCH 2/9] Optimizes a bit the lockfile parsing --- packages/berry-core/sources/Project.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/berry-core/sources/Project.ts b/packages/berry-core/sources/Project.ts index 5bf622fb08f..fb070fca7c3 100644 --- a/packages/berry-core/sources/Project.ts +++ b/packages/berry-core/sources/Project.ts @@ -34,6 +34,8 @@ import {LinkType} // the Package type; no more no less. const LOCKFILE_VERSION = 3; +const MULTIPLE_KEYS_REGEXP = / *, */g; + export type InstallOptions = { cache: Cache, fetcher?: Fetcher, @@ -117,6 +119,7 @@ export class Project { this.storedPackages = new Map(); const lockfilePath = ppath.join(this.cwd, this.configuration.get(`lockfileFilename`)); + const defaultLanguageName = this.configuration.get(`defaultLanguageName`); if (xfs.existsSync(lockfilePath)) { const content = await xfs.readFilePromise(lockfilePath, `utf8`); @@ -138,7 +141,7 @@ export class Project { const version = manifest.version; - const languageName = manifest.languageName || this.configuration.get(`defaultLanguageName`); + const languageName = manifest.languageName || defaultLanguageName; const linkType = data.linkType as LinkType; const dependencies = manifest.dependencies; @@ -157,7 +160,7 @@ export class Project { this.storedPackages.set(pkg.locatorHash, pkg); } - for (const entry of key.split(/ *, */g)) { + for (const entry of key.split(MULTIPLE_KEYS_REGEXP)) { const descriptor = structUtils.parseDescriptor(entry); this.storedDescriptors.set(descriptor.descriptorHash, descriptor); From 4d02b86805b196d6f064bcfed3fc1475244fe93d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Sun, 19 May 2019 21:57:34 +0200 Subject: [PATCH 3/9] Supports more cases --- packages/berry-parsers/sources/syml.ts | 45 ++++++++++++++----- .../sources/commands/npm/whoami.ts | 3 +- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/packages/berry-parsers/sources/syml.ts b/packages/berry-parsers/sources/syml.ts index bef0b86b64d..5972034e7bb 100644 --- a/packages/berry-parsers/sources/syml.ts +++ b/packages/berry-parsers/sources/syml.ts @@ -1,7 +1,7 @@ // @ts-ignore -import {load} from 'js-yaml'; +import {safeLoad} from 'js-yaml'; -import {parse} from './grammars/syml'; +import {parse} from './grammars/syml'; const simpleStringPattern = /^(?![-?:,\][{}#&*!|>'"%@` \t\r\n]).([ \t]*(?![,\][{}:# \t\r\n]).)*$/; @@ -84,19 +84,42 @@ function stringifyValue(value: any, indentLevel: number): string { } export function stringifySyml(value: any) { - return stringifyValue(value, 0); -} - -export function parseSyml(source: string) { try { - try { - return load(source) as {[key: string]: any}; - } catch (error) { - return parse(source.endsWith(`\n`) ? source : `${source}\n`); - } + return stringifyValue(value, 0); } catch (error) { if (error.location) error.message = error.message.replace(/(\.)?$/, ` (line ${error.location.start.line}, column ${error.location.start.column})$1`); throw error; } } + +function parseViaPeg(source: string) { + if (!source.endsWith(`\n`)) + source += `\n`; + + return parse(source); +} + +function parseViaJsYaml(source: string) { + let value; + + try { + value = safeLoad(source); + } catch (error) { + return parseViaPeg(source); + } + + // Empty files are parsed as `null` instead of an empty object + if (value === null) + return {} as {[key: string]: string}; + + // Files that contain single invalid line are treated as a raw string + if (typeof value === `string`) + return parseViaPeg(source); + + return value as {[key: string]: string}; +} + +export function parseSyml(source: string) { + return parseViaJsYaml(source); +} diff --git a/packages/plugin-npm-cli/sources/commands/npm/whoami.ts b/packages/plugin-npm-cli/sources/commands/npm/whoami.ts index 4138646b871..902924b5729 100644 --- a/packages/plugin-npm-cli/sources/commands/npm/whoami.ts +++ b/packages/plugin-npm-cli/sources/commands/npm/whoami.ts @@ -34,9 +34,8 @@ export default (clipanion: Clipanion, pluginConfiguration: PluginConfiguration) const report = await StreamReport.start({configuration, stdout}, async report => { let ident: Ident | null = null; - if (scope) { + if (scope) ident = structUtils.makeIdent(scope, ``); - } try { const response = await npmHttpUtils.get(`/-/whoami`, { configuration, ident, authType: npmHttpUtils.AuthType.ALWAYS_AUTH, json: true }); From c4182bad34626a82d38e5d856b1b63d800ca458c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Sun, 19 May 2019 22:21:01 +0200 Subject: [PATCH 4/9] Supports more cases --- packages/berry-parsers/sources/syml.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/berry-parsers/sources/syml.ts b/packages/berry-parsers/sources/syml.ts index 5972034e7bb..6df1e0f1c52 100644 --- a/packages/berry-parsers/sources/syml.ts +++ b/packages/berry-parsers/sources/syml.ts @@ -109,14 +109,18 @@ function parseViaJsYaml(source: string) { return parseViaPeg(source); } - // Empty files are parsed as `null` instead of an empty object - if (value === null) + // Empty files are parsed as `undefined` instead of an empty object + // Empty files with 2 newlines or more are `null` instead + if (value === undefined || value === null) return {} as {[key: string]: string}; // Files that contain single invalid line are treated as a raw string if (typeof value === `string`) return parseViaPeg(source); + if (typeof value !== `object` || Array.isArray(value)) + throw new Error(`Invalid content type: expected an indexed object`); + return value as {[key: string]: string}; } From 6653ef60da6af9be3694b610bae0a43a693c70f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Mon, 20 May 2019 09:51:11 +0200 Subject: [PATCH 5/9] Branches the parsing depending on the v1 tag --- .yarnrc | 9 ++++++--- packages/berry-parsers/sources/syml.ts | 20 +++++++++----------- packages/plugin-dlx/sources/commands/dlx.ts | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/.yarnrc b/.yarnrc index 63a29467dff..88d46dd1550 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,9 +1,12 @@ +## General settings + init-scope: berry +yarn-path: scripts/run-yarn.js + +## List of plugins enabled in the current repository + plugins: - scripts/plugin-exec.js - scripts/plugin-stage.js - scripts/plugin-typescript.js - -yarn-path: ./scripts/run-yarn.js - diff --git a/packages/berry-parsers/sources/syml.ts b/packages/berry-parsers/sources/syml.ts index 6df1e0f1c52..fa285933e76 100644 --- a/packages/berry-parsers/sources/syml.ts +++ b/packages/berry-parsers/sources/syml.ts @@ -100,26 +100,24 @@ function parseViaPeg(source: string) { return parse(source); } -function parseViaJsYaml(source: string) { - let value; +const LEGACY_REGEXP = /^(#.*\n)*?#\s+yarn\s+lockfile\s+v1\n/i; - try { - value = safeLoad(source); - } catch (error) { +function parseViaJsYaml(source: string) { + if (LEGACY_REGEXP.test(source)) return parseViaPeg(source); - } + + let value = safeLoad(source); // Empty files are parsed as `undefined` instead of an empty object // Empty files with 2 newlines or more are `null` instead if (value === undefined || value === null) return {} as {[key: string]: string}; - // Files that contain single invalid line are treated as a raw string - if (typeof value === `string`) - return parseViaPeg(source); + if (typeof value !== `object`) + throw new Error(`Invalid content type: expected an indexed object, got ${typeof value} instead`); - if (typeof value !== `object` || Array.isArray(value)) - throw new Error(`Invalid content type: expected an indexed object`); + if (Array.isArray(value)) + throw new Error(`Invalid content type: expected an indexed object, got array instead`); return value as {[key: string]: string}; } diff --git a/packages/plugin-dlx/sources/commands/dlx.ts b/packages/plugin-dlx/sources/commands/dlx.ts index c6a2d8e7a31..19363259a37 100644 --- a/packages/plugin-dlx/sources/commands/dlx.ts +++ b/packages/plugin-dlx/sources/commands/dlx.ts @@ -34,7 +34,7 @@ export default (clipanion: any, pluginConfiguration: PluginConfiguration) => cli try { await xfs.writeFilePromise(ppath.join(tmpDir, toFilename(`package.json`)), `{}\n`); await xfs.writeFilePromise(ppath.join(tmpDir, toFilename(`yarn.lock`)), ``); - await xfs.writeFilePromise(ppath.join(tmpDir, toFilename(`.yarnrc`)), `enable-global-cache true\n`); + await xfs.writeFilePromise(ppath.join(tmpDir, toFilename(`.yarnrc`)), `enable-global-cache: true\n`); if (packages.length === 0) { packages = [command]; From d7c51ec826a3c17fca6cc9f4d3d04b6b9804f000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Mon, 20 May 2019 09:51:48 +0200 Subject: [PATCH 6/9] Updates the npm tests --- .../npm/__snapshots__/login.test.js.snap | 71 ++++ .../npm/__snapshots__/whoami.test.js.snap | 31 ++ .../sources/commands/npm/login.test.js | 191 +++++++++++ .../sources/commands/npm/whoami.test.js | 122 +++++++ .../__snapshots__/plugin-npm-cli.test.js.snap | 105 ------ .../sources/plugins/plugin-npm-cli.test.js | 307 ------------------ 6 files changed, 415 insertions(+), 412 deletions(-) create mode 100644 packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/__snapshots__/login.test.js.snap create mode 100644 packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/__snapshots__/whoami.test.js.snap create mode 100644 packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/login.test.js create mode 100644 packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/whoami.test.js delete mode 100644 packages/acceptance-tests/pkg-tests-specs/sources/plugins/__snapshots__/plugin-npm-cli.test.js.snap delete mode 100644 packages/acceptance-tests/pkg-tests-specs/sources/plugins/plugin-npm-cli.test.js diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/__snapshots__/login.test.js.snap b/packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/__snapshots__/login.test.js.snap new file mode 100644 index 00000000000..4bdc82b8da4 --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/__snapshots__/login.test.js.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Commands npm login it should login a user with OTP setup 1`] = ` +"npmAuthToken: 686159dc-64b3-413e-a244-2de2b8d1c36f +" +`; + +exports[`Commands npm login it should login a user with OTP setup 2`] = ` +Object { + "code": 0, + "stderr": "", + "stdout": "➤ YN0000: Successfully logged in +➤ YN0000: Done +", +} +`; + +exports[`Commands npm login it should login a user with OTP to a specific scope 1`] = ` +"init-scope: berry-test + +npmScopes: + testScope: + npmAuthToken: 686159dc-64b3-413e-a244-2de2b8d1c36f + npmRegistryServer: \\"http://yarn.test.registry\\" +" +`; + +exports[`Commands npm login it should login a user with OTP to a specific scope 2`] = ` +Object { + "code": 0, + "stderr": "", + "stdout": "➤ YN0000: Successfully logged in +➤ YN0000: Done +", +} +`; + +exports[`Commands npm login it should login a user with no OTP setup 1`] = ` +"npmAuthToken: 316158de-64b3-413e-a244-2de2b8d1c80f +" +`; + +exports[`Commands npm login it should login a user with no OTP setup 2`] = ` +Object { + "code": 0, + "stderr": "", + "stdout": "➤ YN0000: Successfully logged in +➤ YN0000: Done +", +} +`; + +exports[`Commands npm login it should login a user with no OTP setup to a specific scope 1`] = ` +"init-scope: berry-test + +npmScopes: + testScope: + npmAuthToken: 316158de-64b3-413e-a244-2de2b8d1c80f + npmRegistryServer: \\"http://yarn.test.registry\\" +" +`; + +exports[`Commands npm login it should login a user with no OTP setup to a specific scope 2`] = ` +Object { + "code": 0, + "stderr": "", + "stdout": "➤ YN0000: Successfully logged in +➤ YN0000: Done +", +} +`; diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/__snapshots__/whoami.test.js.snap b/packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/__snapshots__/whoami.test.js.snap new file mode 100644 index 00000000000..beeeb6baaca --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/__snapshots__/whoami.test.js.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Commands npm whoami it should print the npm registry username for a given scope 1`] = ` +Object { + "code": 0, + "stderr": "", + "stdout": "➤ YN0000: username +➤ YN0000: Done +", +} +`; + +exports[`Commands npm whoami it should print the npm registry username when config has a valid npmAuthIdent 1`] = ` +Object { + "code": 0, + "stderr": "", + "stdout": "➤ YN0000: username +➤ YN0000: Done +", +} +`; + +exports[`Commands npm whoami it should print the npm registry username when config has a valid npmAuthToken 1`] = ` +Object { + "code": 0, + "stderr": "", + "stdout": "➤ YN0000: username +➤ YN0000: Done +", +} +`; diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/login.test.js b/packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/login.test.js new file mode 100644 index 00000000000..05d2a93f342 --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/login.test.js @@ -0,0 +1,191 @@ +const { + fs: {createTemporaryFolder, readFile, writeFile}, + tests: {startPackageServer} +} = require('pkg-tests-core'); + +const SPEC_RC_FILENAME = `.spec-yarnrc`; +const FAKE_REGISTRY_URL = `http://yarn.test.registry`; + +function cleanupFileContent(fileContent) { + return fileContent.replace(/http:\/\/localhost:\d+/g, FAKE_REGISTRY_URL); +} + +describe(`Commands`, () => { + describe(`npm login`, () => { + test( + `it should login a user with no OTP setup`, + makeTemporaryEnv({}, async ({ path, run, source }) => { + const homePath = await createTemporaryFolder(); + await writeFile(`${homePath}/${SPEC_RC_FILENAME}`, ``); + + let code; + let stdout; + let stderr; + + try { + ({ code, stdout, stderr } = await run(`npm`, `login`, { + env: { + HOME: homePath, + USERPROFILE: homePath, + TEST_NPM_USER: `anotherTestUser`, + TEST_NPM_PASSWORD: `password123`, + YARN_RC_FILENAME: SPEC_RC_FILENAME, + } + })); + } catch (error) { + ({ code, stdout, stderr } = error); + } + + const finalRcFileContent = await readFile(`${homePath}/${SPEC_RC_FILENAME}`, `utf8`); + const cleanFileContent = cleanupFileContent(finalRcFileContent); + + expect(cleanFileContent).toMatchSnapshot(); + expect({code, stdout, stderr}).toMatchSnapshot(); + }) + ); + + test( + `it should login a user with OTP setup`, + makeTemporaryEnv({}, async ({ path, run, source }) => { + const homePath = await createTemporaryFolder(); + + await writeFile(`${homePath}/${SPEC_RC_FILENAME}`, ``); + + let code; + let stdout; + let stderr; + + try { + ({ code, stdout, stderr } = await run(`npm`, `login`, { + env: { + HOME: homePath, + USERPROFILE: homePath, + TEST_NPM_USER: `testUser`, + TEST_NPM_PASSWORD: `password`, + TEST_NPM_2FA_TOKEN: `1234`, + YARN_RC_FILENAME: SPEC_RC_FILENAME, + } + })); + } catch (error) { + ({code, stdout, stderr} = error); + } + + const finalRcFileContent = await readFile(`${homePath}/${SPEC_RC_FILENAME}`, `utf8`); + const cleanFileContent = cleanupFileContent(finalRcFileContent); + + expect(cleanFileContent).toMatchSnapshot(); + expect({code, stdout, stderr}).toMatchSnapshot(); + }) + ); + + test( + `it should throw an error when credentials are incorrect`, + makeTemporaryEnv({}, async ({ path, run, source }) => { + await expect( + run(`npm`, `login`, { + env: { + TEST_NPM_USER: `anotherTestUser`, + TEST_NPM_PASSWORD: `incorrect password` + } + }) + ).rejects.toThrowError(/Invalid Authentication/); + }) + ); + + test( + `it should throw an error with incorrect OTP`, + makeTemporaryEnv({}, async ({ path, run, source }) => { + await expect( + run(`npm`, `login`, { + env: { + TEST_NPM_USER: `testUser`, + TEST_NPM_PASSWORD: `password`, + TEST_NPM_2FA_TOKEN: `incorrect OTP`, + } + }) + ).rejects.toThrowError(/Invalid Authentication/); + }) + ); + + test( + `it should login a user with no OTP setup to a specific scope`, + makeTemporaryEnv({}, async ({ path, run, source }) => { + const url = startPackageServer(); + const homePath = await createTemporaryFolder(); + + await writeFile(`${homePath}/${SPEC_RC_FILENAME}`, [ + `init-scope: berry-test\n`, + `npmScopes:\n`, + ` testScope:\n`, + ` npmRegistryServer: "${url}"\n`, + ].join(``)); + + let code; + let stdout; + let stderr; + + try { + ({ code, stdout, stderr } = await run(`npm`, `login`, `--scope`, `testScope`, { + env: { + HOME: homePath, + USERPROFILE: homePath, + TEST_NPM_USER: `anotherTestUser`, + TEST_NPM_PASSWORD: `password123`, + YARN_RC_FILENAME: SPEC_RC_FILENAME, + } + })); + } catch (error) { + ({ code, stdout, stderr } = error); + } + + const finalRcFileContent = await readFile(`${homePath}/${SPEC_RC_FILENAME}`, `utf8`); + const cleanFileContent = cleanupFileContent(finalRcFileContent); + + expect(cleanFileContent).toMatchSnapshot(); + expect({code, stdout, stderr}).toMatchSnapshot(); + }) + ); + + test( + `it should login a user with OTP to a specific scope`, + makeTemporaryEnv({}, async ({ path, run, source }) => { + const url = startPackageServer(); + const homePath = await createTemporaryFolder(); + + const initialRcFileContent = [ + `init-scope: berry-test\n`, + `npmScopes:\n`, + ` testScope:\n`, + ` npmRegistryServer: "${url}"\n`, + ].join(``); + + await writeFile(`${homePath}/${SPEC_RC_FILENAME}`, initialRcFileContent); + + let code; + let stdout; + let stderr; + + try { + ({ code, stdout, stderr } = await run(`npm`, `login`, `--scope`, `testScope`, { + env: { + HOME: homePath, + USERPROFILE: homePath, + TEST_NPM_USER: `testUser`, + TEST_NPM_PASSWORD: `password`, + TEST_NPM_2FA_TOKEN: `1234`, + YARN_RC_FILENAME: SPEC_RC_FILENAME + } + })); + } catch (error) { + ({ code, stdout, stderr } = error); + } + + const finalRcFileContent = await readFile(`${homePath}/${SPEC_RC_FILENAME}`, `utf8`); + const cleanFileContent = cleanupFileContent(finalRcFileContent); + + expect(cleanFileContent).toMatchSnapshot(); + expect({code, stdout, stderr}).toMatchSnapshot(); + }) + ); + }); +}); diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/whoami.test.js b/packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/whoami.test.js new file mode 100644 index 00000000000..623a94e2f28 --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-specs/sources/commands/npm/whoami.test.js @@ -0,0 +1,122 @@ +const { + fs: {createTemporaryFolder, readFile, writeFile}, + tests: {startPackageServer} +} = require('pkg-tests-core'); + +const AUTH_TOKEN = `686159dc-64b3-413e-a244-2de2b8d1c36f`; +const AUTH_IDENT = `dXNlcm5hbWU6YSB2ZXJ5IHNlY3VyZSBwYXNzd29yZA==`; + +const INVALID_AUTH_TOKEN = `a24cb960-e6a5-45fc-b9ab-0f9fe0aaae57`; +const INVALID_AUTH_IDENT = `dXNlcm5hbWU6bm90IHRoZSByaWdodCBwYXNzd29yZA==`; // username:not the right password + +describe(`Commands`, () => { + describe(`npm whoami`, () => { + test( + `it should print the npm registry username when config has a valid npmAuthToken`, + makeTemporaryEnv({}, async ({ path, run, source }) => { + await writeFile(`${path}/.yarnrc`, `npmAuthToken: "${AUTH_TOKEN}"\n`); + + let code; + let stdout; + let stderr; + + try { + ({code, stdout, stderr} = await run(`npm`, `whoami`)); + } catch (error) { + ({code, stdout, stderr} = error); + } + + expect({code, stdout, stderr}).toMatchSnapshot(); + }) + ); + + test( + `it should print the npm registry username when config has a valid npmAuthIdent`, + makeTemporaryEnv({}, async ({ path, run, source }) => { + await writeFile(`${path}/.yarnrc`, `npmAuthIdent: "${AUTH_IDENT}"\n`); + + let code; + let stdout; + let stderr; + + try { + ({code, stdout, stderr} = await run(`npm`, `whoami`)); + } catch (error) { + ({code, stdout, stderr} = error); + } + + expect({code, stdout, stderr}).toMatchSnapshot(); + }) + ); + + test( + `it should print the npm registry username for a given scope`, + makeTemporaryEnv({}, async ({ path, run, source }) => { + const url = startPackageServer(); + + await writeFile(`${path}/.yarnrc`, [ + `npmScopes:\n`, + ` testScope:\n`, + ` npmRegistryServer: "${url}"\n`, + `npmRegistries:\n`, + ` "${url}":\n`, + ` npmAuthToken: ${AUTH_TOKEN}`, + ].join(``)); + + let code; + let stdout; + let stderr; + + try { + ({code, stdout, stderr} = await run(`npm`, `whoami`, `--scope`, `testScope`)); + } catch (error) { + ({code, stdout, stderr} = error); + } + + expect({code, stdout, stderr}).toMatchSnapshot(); + }) + ); + + test( + `it should throw an error when no auth config is found`, + makeTemporaryEnv({}, async ({ path, run, source }) => { + await expect(run(`npm`, `whoami`)).rejects.toThrowError(/No authentication configured/); + }) + ); + + test( + `it should throw an error when config has an invalid npmAuthToken`, + makeTemporaryEnv({}, async ({ path, run, source }) => { + await writeFile(`${path}/.yarnrc`, `npmAuthToken: "${INVALID_AUTH_TOKEN}"\n`); + await expect(run(`npm`, `whoami`)).rejects.toThrowError(/Authentication failed/); + }) + ); + + test( + `it should throw an error when config has an invalid npmAuthIdent`, + makeTemporaryEnv({}, async ({ path, run, source }) => { + await writeFile(`${path}/.yarnrc`, `npmAuthIdent: "${INVALID_AUTH_IDENT}"\n`); + await expect(run(`npm`, `whoami`)).rejects.toThrowError(/Authentication failed/); + }) + ); + + test( + `it should thow an error when invalid auth config is found for a scope`, + makeTemporaryEnv({}, async ({ path, run, source }) => { + const url = startPackageServer(); + + await writeFile(`${path}/.yarnrc`, [ + `npmScopes:\n`, + ` testScope:\n`, + ` npmRegistryServer: "${url}"\n`, + `npmRegistries:\n`, + ` "${url}":\n`, + ` npmAuthToken: ${INVALID_AUTH_TOKEN}`, + ].join(``)); + + await expect(run(`npm`, `whoami`, `--scope`, `testScope`)).rejects.toThrowError(/Authentication failed/); + await expect(run(`npm`, `whoami`)).rejects.toThrowError(/Authentication failed/); + }) + ); + }); +}); diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/plugins/__snapshots__/plugin-npm-cli.test.js.snap b/packages/acceptance-tests/pkg-tests-specs/sources/plugins/__snapshots__/plugin-npm-cli.test.js.snap deleted file mode 100644 index b8e5b8539cc..00000000000 --- a/packages/acceptance-tests/pkg-tests-specs/sources/plugins/__snapshots__/plugin-npm-cli.test.js.snap +++ /dev/null @@ -1,105 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Plugins npm-cli login should login a user with OTP setup 1`] = ` -"init-scope: berry-test - -npm-auth-token: 686159dc-64b3-413e-a244-2de2b8d1c36f -" -`; - -exports[`Plugins npm-cli login should login a user with OTP setup 2`] = ` -Object { - "code": 0, - "stderr": "", - "stdout": "➤ YN0000: Successfully logged in -➤ YN0000: Done -", -} -`; - -exports[`Plugins npm-cli login should login a user with OTP to a specific scope 1`] = ` -"init-scope: berry-test - -npmScopes: - testScope: - npmAuthToken: 686159dc-64b3-413e-a244-2de2b8d1c36f - npmRegistryServer: \\"http://yarn.test.registry\\" -" -`; - -exports[`Plugins npm-cli login should login a user with OTP to a specific scope 2`] = ` -Object { - "code": 0, - "stderr": "", - "stdout": "➤ YN0000: Successfully logged in -➤ YN0000: Done -", -} -`; - -exports[`Plugins npm-cli login should login a user with no OTP setup 1`] = ` -"init-scope: berry-test - -npm-auth-token: 316158de-64b3-413e-a244-2de2b8d1c80f -" -`; - -exports[`Plugins npm-cli login should login a user with no OTP setup 2`] = ` -Object { - "code": 0, - "stderr": "", - "stdout": "➤ YN0000: Successfully logged in -➤ YN0000: Done -", -} -`; - -exports[`Plugins npm-cli login should login a user with no OTP setup to a specific scope 1`] = ` -"init-scope: berry-test - -npmScopes: - testScope: - npmAuthToken: 316158de-64b3-413e-a244-2de2b8d1c80f - npmRegistryServer: \\"http://yarn.test.registry\\" -" -`; - -exports[`Plugins npm-cli login should login a user with no OTP setup to a specific scope 2`] = ` -Object { - "code": 0, - "stderr": "", - "stdout": "➤ YN0000: Successfully logged in -➤ YN0000: Done -", -} -`; - -exports[`Plugins npm-cli whoami should print the npm registry username for a given scope 1`] = ` -Object { - "code": 0, - "stderr": "", - "stdout": "➤ YN0000: username -➤ YN0000: Done -", -} -`; - -exports[`Plugins npm-cli whoami should print the npm registry username when config has a valid npmAuthIdent 1`] = ` -Object { - "code": 0, - "stderr": "", - "stdout": "➤ YN0000: username -➤ YN0000: Done -", -} -`; - -exports[`Plugins npm-cli whoami should print the npm registry username when config has a valid npmAuthToken 1`] = ` -Object { - "code": 0, - "stderr": "", - "stdout": "➤ YN0000: username -➤ YN0000: Done -", -} -`; diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/plugins/plugin-npm-cli.test.js b/packages/acceptance-tests/pkg-tests-specs/sources/plugins/plugin-npm-cli.test.js deleted file mode 100644 index 9b2344ff868..00000000000 --- a/packages/acceptance-tests/pkg-tests-specs/sources/plugins/plugin-npm-cli.test.js +++ /dev/null @@ -1,307 +0,0 @@ -const { - fs: {createTemporaryFolder, readFile, writeFile}, - tests: {startPackageServer} -} = require('pkg-tests-core'); - -const AUTH_TOKEN = `686159dc-64b3-413e-a244-2de2b8d1c36f`; -const AUTH_IDENT = `dXNlcm5hbWU6YSB2ZXJ5IHNlY3VyZSBwYXNzd29yZA==`; - -const INVALID_AUTH_TOKEN = `a24cb960-e6a5-45fc-b9ab-0f9fe0aaae57`; -const INVALID_AUTH_IDENT = `dXNlcm5hbWU6bm90IHRoZSByaWdodCBwYXNzd29yZA==`; // username:not the right password - -const RC_FILENAME = `.spec-yarnrc`; -const FAKE_REGISTRY_URL = `http://yarn.test.registry`; - -describe(`Plugins`, () => { - describe(`npm-cli whoami`, () => { - test( - `should print the npm registry username when config has a valid npmAuthToken`, - makeTemporaryEnv({}, async ({ path, run, source }) => { - await writeFile(`${path}/.yarnrc`, `npmAuthToken "${AUTH_TOKEN}"\n`); - - let code; - let stdout; - let stderr; - - try { - ({code, stdout, stderr} = await run(`npm`, `whoami`)); - } catch (error) { - ({code, stdout, stderr} = error); - } - - expect({code, stdout, stderr}).toMatchSnapshot(); - }) - ); - - test( - `should print the npm registry username when config has a valid npmAuthIdent`, - makeTemporaryEnv({}, async ({ path, run, source }) => { - await writeFile(`${path}/.yarnrc`, `npmAuthIdent "${AUTH_IDENT}"\n`); - - let code; - let stdout; - let stderr; - - try { - ({code, stdout, stderr} = await run(`npm`, `whoami`)); - } catch (error) { - ({code, stdout, stderr} = error); - } - - expect({code, stdout, stderr}).toMatchSnapshot(); - }) - ); - - test( - `should print the npm registry username for a given scope`, - makeTemporaryEnv({}, async ({ path, run, source }) => { - const url = startPackageServer(); - const rcFileContent = [ - `npmScopes:\n`, - ` testScope:\n`, - ` npmRegistryServer: "${url}"\n`, - `npmRegistries:\n`, - ` "${url}":\n`, - ` npmAuthToken: ${AUTH_TOKEN}`, - ].join(``); - - await writeFile(`${path}/.yarnrc`, rcFileContent); - - let code; - let stdout; - let stderr; - - try { - ({code, stdout, stderr} = await run(`npm`, `whoami`, `--scope`, `testScope`)); - } catch (error) { - ({code, stdout, stderr} = error); - } - - expect({code, stdout, stderr}).toMatchSnapshot(); - }) - ); - - test( - `should throw an error when no auth config is found`, - makeTemporaryEnv({}, async ({ path, run, source }) => { - await expect(run(`npm`, `whoami`)).rejects.toThrowError(/No authentication configured/); - }) - ); - - test( - `should throw an error when config has an invalid npmAuthToken`, - makeTemporaryEnv({}, async ({ path, run, source }) => { - await writeFile(`${path}/.yarnrc`, `npmAuthToken "${INVALID_AUTH_TOKEN}"\n`); - await expect(run(`npm`, `whoami`)).rejects.toThrowError(/Authentication failed/); - }) - ); - - test( - `should throw an error when config has an invalid npmAuthIdent`, - makeTemporaryEnv({}, async ({ path, run, source }) => { - await writeFile(`${path}/.yarnrc`, `npmAuthIdent "${INVALID_AUTH_IDENT}"\n`); - await expect(run(`npm`, `whoami`)).rejects.toThrowError(/Authentication failed/); - }) - ); - - test( - `should thow an error when invalid auth config is found for a scope`, - makeTemporaryEnv({}, async ({ path, run, source }) => { - const url = startPackageServer(); - const rcFileContent = [ - `npmScopes:\n`, - ` testScope:\n`, - ` npmRegistryServer: "${url}"\n`, - `npmRegistries:\n`, - ` "${url}":\n`, - ` npmAuthToken: ${INVALID_AUTH_TOKEN}`, - ].join(``); - - await writeFile(`${path}/.yarnrc`, rcFileContent); - await expect(run(`npm`, `whoami`, `--scope`, `testScope`)).rejects.toThrowError(/Authentication failed/); - await expect(run(`npm`, `whoami`)).rejects.toThrowError(/Authentication failed/); - }) - ); - }); - - describe(`npm-cli login`, () => { - test( - `should login a user with no OTP setup`, - makeTemporaryEnv({}, async ({ path, run, source }) => { - const homePath = await createTemporaryFolder(); - await writeFile(`${homePath}/${RC_FILENAME}`, `init-scope berry-test\n`); - - let code; - let stdout; - let stderr; - - try { - ({ code, stdout, stderr } = await run(`npm`, `login`, { - env: { - HOME: homePath, - USERPROFILE: homePath, - TEST_NPM_USER: `anotherTestUser`, - TEST_NPM_PASSWORD: `password123`, - YARN_RC_FILENAME: RC_FILENAME - } - })); - } catch (error) { - ({ code, stdout, stderr } = error); - } - - const rcFileContent = await readFile(`${homePath}/${RC_FILENAME}`); - - expect(rcFileContent.toString()).toMatchSnapshot(); - expect({code, stdout, stderr}).toMatchSnapshot(); - }) - ); - - test( - `should login a user with OTP setup`, - makeTemporaryEnv({}, async ({ path, run, source }) => { - const homePath = await createTemporaryFolder(); - await writeFile(`${homePath}/${RC_FILENAME}`, `init-scope berry-test\n`); - - let code; - let stdout; - let stderr; - - try { - ({ code, stdout, stderr } = await run(`npm`, `login`, { - env: { - HOME: homePath, - USERPROFILE: homePath, - TEST_NPM_USER: `testUser`, - TEST_NPM_PASSWORD: `password`, - TEST_NPM_2FA_TOKEN: `1234`, - YARN_RC_FILENAME: RC_FILENAME - } - })); - } catch (error) { - ({code, stdout, stderr} = error); - } - - const rcFileContent = await readFile(`${homePath}/${RC_FILENAME}`); - - expect(rcFileContent.toString()).toMatchSnapshot(); - expect({code, stdout, stderr}).toMatchSnapshot(); - }) - ); - - test( - `should throw an error when credentials are incorrect`, - makeTemporaryEnv({}, async ({ path, run, source }) => { - await expect( - run(`npm`, `login`, { - env: { - TEST_NPM_USER: `anotherTestUser`, - TEST_NPM_PASSWORD: `incorrect password` - } - }) - ).rejects.toThrowError(/Invalid Authentication/); - }) - ); - - test( - `should throw an error with incorrect OTP`, - makeTemporaryEnv({}, async ({ path, run, source }) => { - await expect( - run(`npm`, `login`, { - env: { - TEST_NPM_USER: `testUser`, - TEST_NPM_PASSWORD: `password`, - TEST_NPM_2FA_TOKEN: `incorrect OTP`, - } - }) - ).rejects.toThrowError(/Invalid Authentication/); - }) - ); - - test( - `should login a user with no OTP setup to a specific scope`, - makeTemporaryEnv({}, async ({ path, run, source }) => { - const url = startPackageServer(); - const homePath = await createTemporaryFolder(); - - const initialRcFileContent = [ - `init-scope berry-test\n`, - `npmScopes:\n`, - ` testScope:\n`, - ` npmRegistryServer: "${url}"\n`, - ].join(``); - - await writeFile(`${homePath}/${RC_FILENAME}`, initialRcFileContent); - - let code; - let stdout; - let stderr; - - try { - ({ code, stdout, stderr } = await run(`npm`, `login`, `--scope`, `testScope`, { - env: { - HOME: homePath, - USERPROFILE: homePath, - TEST_NPM_USER: `anotherTestUser`, - TEST_NPM_PASSWORD: `password123`, - YARN_RC_FILENAME: RC_FILENAME - } - })); - } catch (error) { - ({ code, stdout, stderr } = error); - } - - const finalRcFileContent = await readFile(`${homePath}/${RC_FILENAME}`); - const cleanFileContent = cleanupFileContent(finalRcFileContent.toString()); - - expect(cleanFileContent).toMatchSnapshot(); - expect({code, stdout, stderr}).toMatchSnapshot(); - }) - ); - - test( - `should login a user with OTP to a specific scope`, - makeTemporaryEnv({}, async ({ path, run, source }) => { - const url = startPackageServer(); - const homePath = await createTemporaryFolder(); - - const initialRcFileContent = [ - `init-scope berry-test\n`, - `npmScopes:\n`, - ` testScope:\n`, - ` npmRegistryServer: "${url}"\n`, - ].join(``); - - await writeFile(`${homePath}/${RC_FILENAME}`, initialRcFileContent); - - let code; - let stdout; - let stderr; - - try { - ({ code, stdout, stderr } = await run(`npm`, `login`, `--scope`, `testScope`, { - env: { - HOME: homePath, - USERPROFILE: homePath, - TEST_NPM_USER: `testUser`, - TEST_NPM_PASSWORD: `password`, - TEST_NPM_2FA_TOKEN: `1234`, - YARN_RC_FILENAME: RC_FILENAME - } - })); - } catch (error) { - ({ code, stdout, stderr } = error); - } - - const finalRcFileContent = await readFile(`${homePath}/${RC_FILENAME}`); - const cleanFileContent = cleanupFileContent(finalRcFileContent.toString()); - - expect(cleanFileContent).toMatchSnapshot(); - expect({code, stdout, stderr}).toMatchSnapshot(); - }) - ); - }); -}); - -function cleanupFileContent(fileContent) { - return fileContent.replace(/http:\/\/localhost:\d+/g, FAKE_REGISTRY_URL); -} From 5eeb367278f010f17238c19ead77e1ccf85ffbe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Mon, 20 May 2019 11:00:47 +0200 Subject: [PATCH 7/9] Fixes more tests --- .../pkg-tests-specs/sources/auth.test.js | 18 +++++++++--------- .../sources/commands/config.test.js | 16 ++++++++-------- packages/berry-core/sources/Configuration.ts | 3 +++ 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/auth.test.js b/packages/acceptance-tests/pkg-tests-specs/sources/auth.test.js index ffc6f37c25c..331433ef976 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/auth.test.js +++ b/packages/acceptance-tests/pkg-tests-specs/sources/auth.test.js @@ -43,7 +43,7 @@ describe(`Auth tests`, () => { dependencies: {[`no-deps`]: `1.0.0`}, }, async ({path, run, source}) => { - await writeFile(`${path}/.yarnrc`, `npmAlwaysAuth true\n`); + await writeFile(`${path}/.yarnrc`, `npmAlwaysAuth: true\n`); await expect(run(`install`)).rejects.toThrowError(/No authentication configured for request/); }, @@ -57,7 +57,7 @@ describe(`Auth tests`, () => { dependencies: {[`private-package`]: `1.0.0`}, }, async ({path, run, source}) => { - await writeFile(`${path}/.yarnrc`, `npmAuthToken "${AUTH_TOKEN}"\n`); + await writeFile(`${path}/.yarnrc`, `npmAuthToken: "${AUTH_TOKEN}"\n`); // Rejected by 401 error from registry so no validation on the error message await expect(run(`install`)).rejects.toThrow(); @@ -72,7 +72,7 @@ describe(`Auth tests`, () => { dependencies: {[`@private/package`]: `1.0.0`}, }, async ({path, run, source}) => { - await writeFile(`${path}/.yarnrc`, `npmAuthToken "${AUTH_TOKEN}"\n`); + await writeFile(`${path}/.yarnrc`, `npmAuthToken: "${AUTH_TOKEN}"\n`); await run(`install`); @@ -91,7 +91,7 @@ describe(`Auth tests`, () => { dependencies: {[`private-package`]: `1.0.0`}, }, async ({path, run, source}) => { - await writeFile(`${path}/.yarnrc`, `npmAuthToken "${AUTH_TOKEN}"\nnpmAlwaysAuth true\n`); + await writeFile(`${path}/.yarnrc`, `npmAuthToken: "${AUTH_TOKEN}"\nnpmAlwaysAuth: true\n`); await run(`install`); @@ -110,7 +110,7 @@ describe(`Auth tests`, () => { dependencies: {[`private-package`]: `1.0.0`}, }, async ({path, run, source}) => { - await writeFile(`${path}/.yarnrc`, `npmAuthIdent "${AUTH_IDENT}"\n`); + await writeFile(`${path}/.yarnrc`, `npmAuthIdent: "${AUTH_IDENT}"\n`); // Rejected by 401 error from registry so no validation on the error message await expect(run(`install`)).rejects.toThrow(); @@ -125,7 +125,7 @@ describe(`Auth tests`, () => { dependencies: {[`@private/package`]: `1.0.0`}, }, async ({path, run, source}) => { - await writeFile(`${path}/.yarnrc`, `npmAuthIdent "${AUTH_IDENT}"\n`); + await writeFile(`${path}/.yarnrc`, `npmAuthIdent: "${AUTH_IDENT}"\n`); await run(`install`); @@ -144,7 +144,7 @@ describe(`Auth tests`, () => { dependencies: {[`private-package`]: `1.0.0`}, }, async ({path, run, source}) => { - await writeFile(`${path}/.yarnrc`, `npmAuthIdent "${AUTH_IDENT}"\nnpmAlwaysAuth true\n`); + await writeFile(`${path}/.yarnrc`, `npmAuthIdent: "${AUTH_IDENT}"\nnpmAlwaysAuth true\n`); await run(`install`); @@ -163,7 +163,7 @@ describe(`Auth tests`, () => { dependencies: {[`private-package`]: `1.0.0`}, }, async ({path, run, source}) => { - await writeFile(`${path}/.yarnrc`, `npmAuthToken "${INVALID_AUTH_TOKEN}"\nnpmAlwaysAuth true\n`); + await writeFile(`${path}/.yarnrc`, `npmAuthToken: "${INVALID_AUTH_TOKEN}"\nnpmAlwaysAuth true\n`); await expect(run(`install`)).rejects.toThrow(); }, @@ -177,7 +177,7 @@ describe(`Auth tests`, () => { dependencies: {[`private-package`]: `1.0.0`}, }, async ({path, run, source}) => { - await writeFile(`${path}/.yarnrc`, `npmAuthIdent "${INVALID_AUTH_IDENT}"\nnpmAlwaysAuth true\n`); + await writeFile(`${path}/.yarnrc`, `npmAuthIdent: "${INVALID_AUTH_IDENT}"\nnpmAlwaysAuth true\n`); await expect(run(`install`)).rejects.toThrow(); }, diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/commands/config.test.js b/packages/acceptance-tests/pkg-tests-specs/sources/commands/config.test.js index 666b974fd4a..a469c37df05 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/commands/config.test.js +++ b/packages/acceptance-tests/pkg-tests-specs/sources/commands/config.test.js @@ -14,22 +14,22 @@ const environments = { // Nothing to do }, [`folder with rcfile`]: async ({path}) => { - await writeFile(`${path}/${SUBFOLDER}/${SUBFOLDER}/${RC_FILENAME}`, `init-scope berry-test\n`); + await writeFile(`${path}/${SUBFOLDER}/${SUBFOLDER}/${RC_FILENAME}`, `init-scope: berry-test\n`); }, [`folder with rcfile without trailing newline`]: async ({path}) => { - await writeFile(`${path}/${SUBFOLDER}/${SUBFOLDER}/${RC_FILENAME}`, `init-scope berry-test`); + await writeFile(`${path}/${SUBFOLDER}/${SUBFOLDER}/${RC_FILENAME}`, `init-scope: berry-test`); }, [`folder with rcfile and rc in parent`]: async ({path}) => { - await writeFile(`${path}/${SUBFOLDER}/${SUBFOLDER}/${RC_FILENAME}`, `init-scope berry-test\n`); - await writeFile(`${path}/${SUBFOLDER}/${RC_FILENAME}`, `init-scope value-to-override\nlastUpdateCheck 1555784893958\n`); + await writeFile(`${path}/${SUBFOLDER}/${SUBFOLDER}/${RC_FILENAME}`, `init-scope: berry-test\n`); + await writeFile(`${path}/${SUBFOLDER}/${RC_FILENAME}`, `init-scope: value-to-override\nlast-update-check: 1555784893958\n`); }, [`folder with rcfile and rc in ancestor parent`]: async ({path}) => { - await writeFile(`${path}/${SUBFOLDER}/${SUBFOLDER}/${RC_FILENAME}`, `init-scope berry-test\n`); - await writeFile(`${path}/${RC_FILENAME}`, `init-scope value-to-override\nlastUpdateCheck 1555784893958\n`); + await writeFile(`${path}/${SUBFOLDER}/${SUBFOLDER}/${RC_FILENAME}`, `init-scope: berry-test\n`); + await writeFile(`${path}/${RC_FILENAME}`, `init-scope: value-to-override\nlast-update-check: 1555784893958\n`); }, [`folder with rcfile and rc in home folder`]: async ({path, homePath}) => { - await writeFile(`${homePath}/${RC_FILENAME}`, `init-scope value-to-override\ndefaultLanguageName python\n`); - await writeFile(`${path}/${RC_FILENAME}`, `init-scope berry-test\nlastUpdateCheck 1555784893958\n`); + await writeFile(`${homePath}/${RC_FILENAME}`, `init-scope: value-to-override\ndefault-language-name: python\n`); + await writeFile(`${path}/${RC_FILENAME}`, `init-scope: berry-test\nlast-update-check: 1555784893958\n`); }, }; diff --git a/packages/berry-core/sources/Configuration.ts b/packages/berry-core/sources/Configuration.ts index 61b9e989c75..d455dfceaca 100644 --- a/packages/berry-core/sources/Configuration.ts +++ b/packages/berry-core/sources/Configuration.ts @@ -310,6 +310,9 @@ function parseSingleValue(configuration: Configuration, path: string, value: unk if (definition.type === SettingsType.BOOLEAN) return parseBoolean(value); + if (typeof value === `number`) + value = String(value); + if (typeof value !== `string`) throw new Error(`Expected value to be a string`); From 7b9eae3e02a493438f05c5116e3ac189825a76f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Mon, 20 May 2019 11:57:04 +0200 Subject: [PATCH 8/9] More tests fixed --- .../pkg-tests-specs/sources/auth.test.js | 6 +++--- .../sources/features/rcFiles.test.js | 4 ++-- packages/berry-core/sources/Configuration.ts | 13 ++++++++++++- packages/berry-parsers/sources/syml.ts | 4 ++-- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/auth.test.js b/packages/acceptance-tests/pkg-tests-specs/sources/auth.test.js index 331433ef976..293810134c4 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/auth.test.js +++ b/packages/acceptance-tests/pkg-tests-specs/sources/auth.test.js @@ -144,7 +144,7 @@ describe(`Auth tests`, () => { dependencies: {[`private-package`]: `1.0.0`}, }, async ({path, run, source}) => { - await writeFile(`${path}/.yarnrc`, `npmAuthIdent: "${AUTH_IDENT}"\nnpmAlwaysAuth true\n`); + await writeFile(`${path}/.yarnrc`, `npmAuthIdent: "${AUTH_IDENT}"\nnpmAlwaysAuth: true\n`); await run(`install`); @@ -163,7 +163,7 @@ describe(`Auth tests`, () => { dependencies: {[`private-package`]: `1.0.0`}, }, async ({path, run, source}) => { - await writeFile(`${path}/.yarnrc`, `npmAuthToken: "${INVALID_AUTH_TOKEN}"\nnpmAlwaysAuth true\n`); + await writeFile(`${path}/.yarnrc`, `npmAuthToken: "${INVALID_AUTH_TOKEN}"\nnpmAlwaysAuth: true\n`); await expect(run(`install`)).rejects.toThrow(); }, @@ -177,7 +177,7 @@ describe(`Auth tests`, () => { dependencies: {[`private-package`]: `1.0.0`}, }, async ({path, run, source}) => { - await writeFile(`${path}/.yarnrc`, `npmAuthIdent: "${INVALID_AUTH_IDENT}"\nnpmAlwaysAuth true\n`); + await writeFile(`${path}/.yarnrc`, `npmAuthIdent: "${INVALID_AUTH_IDENT}"\nnpmAlwaysAuth: true\n`); await expect(run(`install`)).rejects.toThrow(); }, diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/features/rcFiles.test.js b/packages/acceptance-tests/pkg-tests-specs/sources/features/rcFiles.test.js index 96589e856bf..e11488777fa 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/features/rcFiles.test.js +++ b/packages/acceptance-tests/pkg-tests-specs/sources/features/rcFiles.test.js @@ -10,7 +10,7 @@ describe(`Features`, () => { makeTemporaryEnv( {}, async ({path, run, source}) => { - await writeFile(`${path}/.yarnrc`, `pnp-shebang "Hello World!"\n`); + await writeFile(`${path}/.yarnrc`, `pnp-shebang: "Hello World!"\n`); expect(parseJsonStream( (await run(`config`, `--json`)).stdout, @@ -29,7 +29,7 @@ describe(`Features`, () => { makeTemporaryEnv( {}, async ({path, run, source}) => { - await writeFile(`${path}/.foobarrc`, `pnp-shebang "Hello World!"\n`); + await writeFile(`${path}/.foobarrc`, `pnp-shebang: "Hello World!"\n`); expect(parseJsonStream( (await run(`config`, `--json`, {rcFilename: `.foobarrc`})).stdout, diff --git a/packages/berry-core/sources/Configuration.ts b/packages/berry-core/sources/Configuration.ts index d455dfceaca..fc0443b4ecc 100644 --- a/packages/berry-core/sources/Configuration.ts +++ b/packages/berry-core/sources/Configuration.ts @@ -575,7 +575,18 @@ export class Configuration { if (xfs.existsSync(rcPath)) { const content = await xfs.readFilePromise(rcPath, `utf8`); - const data = parseSyml(content) as any; + + let data; + try { + data = parseSyml(content) as any; + } catch (error) { + let tip = ``; + + if (content.match(/^\s+(?!-)[^:]+\s+\S+/m)) + tip = ` (in particular, make sure you list the colons after each key name)` + + throw new UsageError(`Parse error when loading ${rcPath}; please check it's proper Yaml${tip}`); + } rcFiles.push({path: rcPath, cwd: currentCwd, data}); } diff --git a/packages/berry-parsers/sources/syml.ts b/packages/berry-parsers/sources/syml.ts index fa285933e76..86df8772d29 100644 --- a/packages/berry-parsers/sources/syml.ts +++ b/packages/berry-parsers/sources/syml.ts @@ -114,10 +114,10 @@ function parseViaJsYaml(source: string) { return {} as {[key: string]: string}; if (typeof value !== `object`) - throw new Error(`Invalid content type: expected an indexed object, got ${typeof value} instead`); + throw new Error(`Expected an indexed object, got a ${typeof value} instead. Does your file follow Yaml's rules?`); if (Array.isArray(value)) - throw new Error(`Invalid content type: expected an indexed object, got array instead`); + throw new Error(`Expected an indexed object, got an array instead. Does your file follow Yaml's rules?`); return value as {[key: string]: string}; } From 1015653513a4d89d57f3a67c677ea408a0ada627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Mon, 20 May 2019 18:44:35 +0200 Subject: [PATCH 9/9] Other tests + documentation --- packages/berry-core/sources/Configuration.ts | 3 --- packages/berry-parsers/sources/syml.ts | 8 +++--- packages/berry-parsers/tests/syml.test.js | 12 ++++++--- packages/gatsby/content/advanced/migration.md | 26 +++++++++++++++++++ 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/packages/berry-core/sources/Configuration.ts b/packages/berry-core/sources/Configuration.ts index fc0443b4ecc..d2d4472897a 100644 --- a/packages/berry-core/sources/Configuration.ts +++ b/packages/berry-core/sources/Configuration.ts @@ -310,9 +310,6 @@ function parseSingleValue(configuration: Configuration, path: string, value: unk if (definition.type === SettingsType.BOOLEAN) return parseBoolean(value); - if (typeof value === `number`) - value = String(value); - if (typeof value !== `string`) throw new Error(`Expected value to be a string`); diff --git a/packages/berry-parsers/sources/syml.ts b/packages/berry-parsers/sources/syml.ts index 86df8772d29..bc811d32d40 100644 --- a/packages/berry-parsers/sources/syml.ts +++ b/packages/berry-parsers/sources/syml.ts @@ -1,7 +1,7 @@ // @ts-ignore -import {safeLoad} from 'js-yaml'; +import {safeLoad, FAILSAFE_SCHEMA} from 'js-yaml'; -import {parse} from './grammars/syml'; +import {parse} from './grammars/syml'; const simpleStringPattern = /^(?![-?:,\][{}#&*!|>'"%@` \t\r\n]).([ \t]*(?![,\][{}:# \t\r\n]).)*$/; @@ -106,7 +106,9 @@ function parseViaJsYaml(source: string) { if (LEGACY_REGEXP.test(source)) return parseViaPeg(source); - let value = safeLoad(source); + let value = safeLoad(source, { + schema: FAILSAFE_SCHEMA, + }); // Empty files are parsed as `undefined` instead of an empty object // Empty files with 2 newlines or more are `null` instead diff --git a/packages/berry-parsers/tests/syml.test.js b/packages/berry-parsers/tests/syml.test.js index 02f31a8f5f8..c940a6036c0 100644 --- a/packages/berry-parsers/tests/syml.test.js +++ b/packages/berry-parsers/tests/syml.test.js @@ -2,13 +2,19 @@ import {parseSyml} from '@berry/parsers'; describe(`Syml parser`, () => { it(`shouldn't confuse old-style values with new-style keys`, () => { - expect(parseSyml(`foo "C:\\\\foobar"\n`)).toEqual({ + expect(parseSyml(`# yarn lockfile v1\nfoo "C:\\\\foobar"\n`)).toEqual({ foo: `C:\\foobar`, }); }); - it(`should parse objects`, () => { - expect(parseSyml(`foo:\n bar true\n baz "quux"\n`)).toEqual({ + it(`should parse new-style objects`, () => { + expect(parseSyml(`foo:\n bar: true\n baz: "quux"\n`)).toEqual({ + foo: {bar: `true`, baz: `quux`}, + }) + }); + + it(`should parse old-style objects`, () => { + expect(parseSyml(`# yarn lockfile v1\nfoo:\n bar true\n baz "quux"\n`)).toEqual({ foo: {bar: `true`, baz: `quux`}, }) }); diff --git a/packages/gatsby/content/advanced/migration.md b/packages/gatsby/content/advanced/migration.md index b72dd060886..fdab2042458 100644 --- a/packages/gatsby/content/advanced/migration.md +++ b/packages/gatsby/content/advanced/migration.md @@ -16,6 +16,32 @@ The long term fix is of course to submit a pull request upstream to add the miss Note that the short-term fix isn't meant to be long-term: you'll need to reapply it each time the package version changes and its metadata are downloaded from the registry again. +## Yarn & lockfile format + +> Parse error when loading {path}; please check it's proper Yaml (in particular, make sure you list the colons after each key name) + +Starting from the v2 Yarn expects proper Yaml files by default. We still can read old-style files, but they must start with a particular comment: + +```yaml +# yarn lockfile v1 +``` + +Old-style files are very similar to Yaml, but with one major distinction: keys don't contain colons when their value is on the same line. The following old-style file: + +``` +hello-world: + setting-a foo + setting-b bar +``` + +Would look like the following once converted into Yaml: + +```yaml +hello-world: + settings-a: foo + settings-b: foo +``` + ## Yarnrc file detection The Yarnrc files mechanisms have been changed and simplified. In particular: