From acf82e26b9d517083df1835a3bf8b35a6e5627c4 Mon Sep 17 00:00:00 2001 From: Vladimir Starkov Date: Mon, 13 Aug 2018 13:57:08 +0200 Subject: [PATCH] fix(cli-create): fix scoped creation (#6239) * fix(cli-create): fix scoped creation This one works as it should: yarn create @scope/name => @scope/create-name There were problems when name was omitted: Before: yarn create @scope => create- yarn create @scope/ => @scope/create- After: yarn create @scope => @scope/create yarn create @scope/ => @scope/create Fixes #6233 * refactor(cli-create): make parsePackageName more robust and add tests * refactor(cli-create): add missing typyings --- __tests__/commands/create.js | 101 +++++++++++++++++++++++++++++++++++ src/cli/commands/create.js | 38 +++++++++++-- 2 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 __tests__/commands/create.js diff --git a/__tests__/commands/create.js b/__tests__/commands/create.js new file mode 100644 index 0000000000..d5503a4061 --- /dev/null +++ b/__tests__/commands/create.js @@ -0,0 +1,101 @@ +// @flow + +import {parsePackageName, coerceCreatePackageName} from '../../src/cli/commands/create'; + +describe(`parsePackageName`, () => { + test('invalid', () => { + expect(() => { + parsePackageName('@/name'); + }).toThrowError(`Scope should not be empty, got "@/name"`); + expect(() => { + parsePackageName('/name'); + }).toThrowError(`Name should not start with "/", got "/name"`); + expect(() => { + parsePackageName('./name'); + }).toThrowError(`Name should not start with ".", got "./name"`); + }); + + test('basic', () => { + expect(parsePackageName('name')).toEqual({ + fullName: 'name', + name: 'name', + scope: '', + path: '', + full: 'name', + }); + expect(parsePackageName('@scope/name')).toEqual({ + fullName: '@scope/name', + name: 'name', + scope: '@scope', + path: '', + full: '@scope/name', + }); + expect(parsePackageName('@scope/name/path/to/file.js')).toEqual({ + fullName: '@scope/name', + name: 'name', + scope: '@scope', + path: 'path/to/file.js', + full: '@scope/name/path/to/file.js', + }); + }); + + test('without name', () => { + expect(parsePackageName('@scope/')).toEqual({ + fullName: '@scope', + name: '', + scope: '@scope', + path: '', + full: '@scope', + }); + expect(parsePackageName('@scope')).toEqual({ + fullName: '@scope', + name: '', + scope: '@scope', + path: '', + full: '@scope', + }); + }); +}); + +describe(`coerceCreatePackageName`, () => { + test('invalid', () => { + expect(() => { + coerceCreatePackageName('@/name'); + }).toThrow(); + expect(() => { + coerceCreatePackageName('/name'); + }).toThrow(); + expect(() => { + coerceCreatePackageName('./name'); + }).toThrow(); + }); + + test('basic', () => { + expect(coerceCreatePackageName('name')).toEqual({ + fullName: 'create-name', + name: 'create-name', + scope: '', + path: '', + full: 'create-name', + }); + expect(coerceCreatePackageName('@scope/name')).toEqual({ + fullName: '@scope/create-name', + name: 'create-name', + scope: '@scope', + path: '', + full: '@scope/create-name', + }); + expect(coerceCreatePackageName('@scope/name/path/to/file.js')).toEqual({ + fullName: '@scope/create-name', + name: 'create-name', + scope: '@scope', + path: 'path/to/file.js', + full: '@scope/create-name/path/to/file.js', + }); + }); + + test('not postfixing with "-" if name is emptu', () => { + expect(coerceCreatePackageName('@scope/').fullName).toEqual('@scope/create'); + expect(coerceCreatePackageName('@scope').fullName).toEqual('@scope/create'); + }); +}); diff --git a/src/cli/commands/create.js b/src/cli/commands/create.js index 0f998965fa..01a573bc09 100644 --- a/src/cli/commands/create.js +++ b/src/cli/commands/create.js @@ -16,6 +16,39 @@ export function hasWrapper(commander: Object, args: Array): boolean { return true; } +export function parsePackageName(str: string): Object { + if (str.charAt(0) === '/') { + throw new Error(`Name should not start with "/", got "${str}"`); + } + if (str.charAt(0) === '.') { + throw new Error(`Name should not start with ".", got "${str}"`); + } + const parts = str.split('/'); + const isScoped = str.charAt(0) === '@'; + if (isScoped && parts[0] === '@') { + throw new Error(`Scope should not be empty, got "${str}"`); + } + const scope = isScoped ? parts[0] : ''; + const name = parts[isScoped ? 1 : 0] || ''; + const path = parts.slice(isScoped ? 2 : 1).join('/'); + const fullName = [scope, name].filter(Boolean).join('/'); + const full = [scope, name, path].filter(Boolean).join('/'); + + return {fullName, name, scope, path, full}; +} + +export function coerceCreatePackageName(str: string): Object { + const pkgNameObj = parsePackageName(str); + const coercedName = pkgNameObj.name !== '' ? `create-${pkgNameObj.name}` : `create`; + const coercedPkgNameObj = { + ...pkgNameObj, + name: coercedName, + fullName: [pkgNameObj.scope, coercedName].filter(Boolean).join('/'), + full: [pkgNameObj.scope, coercedName, pkgNameObj.path].filter(Boolean).join('/'), + }; + return coercedPkgNameObj; +} + export async function run(config: Config, reporter: Reporter, flags: Object, args: Array): Promise { const [builderName, ...rest] = args; @@ -23,10 +56,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg throw new MessageError(reporter.lang('invalidPackageName')); } - const packageName = builderName.replace(/^(@[^\/]+\/)?/, '$1create-'); - const packageDir = packageName.replace(/^(?:(@[^\/]+)\/)?.*/, '$1'); - const commandName = packageName.replace(/^@[^\/]+\//, ''); - + const {fullName: packageName, scope: packageDir, name: commandName} = coerceCreatePackageName(builderName); await runGlobal(config, reporter, {}, ['add', packageName]); const binFolder = await getBinFolder(config, {});