From dc3685be73b7fd215d9571ee050c0585dc8a7cd5 Mon Sep 17 00:00:00 2001 From: Le Minh Tri Date: Tue, 10 Jan 2023 03:38:06 +0700 Subject: [PATCH 1/2] fix(create-astro): auto update field name in `package.json` --- packages/create-astro/package.json | 3 + packages/create-astro/src/index.ts | 68 +++++++- .../create-astro/test/package-step.test.js | 154 ++++++++++++++++++ packages/create-astro/test/utils.js | 2 + pnpm-lock.yaml | 72 +++++++- 5 files changed, 289 insertions(+), 10 deletions(-) create mode 100644 packages/create-astro/test/package-step.test.js diff --git a/packages/create-astro/package.json b/packages/create-astro/package.json index b009db1a8865..0b507a76a90b 100644 --- a/packages/create-astro/package.json +++ b/packages/create-astro/package.json @@ -38,6 +38,7 @@ "ora": "^6.1.0", "prompts": "^2.4.2", "strip-ansi": "^7.0.1", + "validate-npm-package-name": "^5.0.0", "which-pm-runs": "^1.1.0", "yargs-parser": "^21.0.1" }, @@ -46,10 +47,12 @@ "@types/degit": "^2.8.3", "@types/mocha": "^9.1.1", "@types/prompts": "^2.0.14", + "@types/validate-npm-package-name": "^4.0.0", "@types/which-pm-runs": "^1.0.0", "@types/yargs-parser": "^21.0.0", "astro-scripts": "workspace:*", "chai": "^4.3.6", + "cli-prompts-test": "^0.3.0", "mocha": "^9.2.2", "uvu": "^0.5.3" }, diff --git a/packages/create-astro/src/index.ts b/packages/create-astro/src/index.ts index 1e934fc9b6b9..5b906bd8dfff 100644 --- a/packages/create-astro/src/index.ts +++ b/packages/create-astro/src/index.ts @@ -10,6 +10,7 @@ import ora from 'ora'; import { platform } from 'os'; import path from 'path'; import prompts from 'prompts'; +import validate from 'validate-npm-package-name'; import detectPackageManager from 'which-pm-runs'; import yargs from 'yargs-parser'; import { loadWithRocketGradient, rocketAscii } from './gradient.js'; @@ -89,6 +90,19 @@ function isValidProjectDirectory(dirPath: string) { return conflicts.length === 0; } +async function validatePackageName(pkgName: string) { + const isValidPackageName = await validate(pkgName); + const isValid = isValidPackageName.validForNewPackages + const notices: string[] = [] + if (!isValid) { + notices.push(...[ + ...isValidPackageName.warnings || [], + ...isValidPackageName.errors || [] + ]); + } + return { isValid, notices } +} + const FILES_TO_REMOVE = ['.stackblitzrc', 'sandbox.config.json', 'CHANGELOG.md']; // some files are only needed for online editors when using astro.new. Remove for create-astro installs. // Please also update the installation instructions in the docs at https://github.com/withastro/docs/blob/main/src/pages/en/install/auto.md if you make any changes to the flow or wording here. @@ -97,6 +111,11 @@ export async function main() { const [username, version] = await Promise.all([getName(), getVersion()]); logger.debug('Verbose logging turned on'); + + if (args.dryRun) { + ora().info('Running in dry mode'); + } + if (!args.skipHouston) { await say( [ @@ -154,6 +173,8 @@ export async function main() { process.exit(1); } + let projectDir = path.relative(process.cwd(), cwd); + const options = await prompts( [ { @@ -208,10 +229,50 @@ export async function main() { } }) ); - } - templateSpinner.text = green('Template copied!'); - templateSpinner.succeed(); + templateSpinner.text = green('Template copied!'); + templateSpinner.succeed(); + + const printIgnoreUpdatingPkgNameMsg = () => info('Opps', 'You have to update the NPM package name manually.'); + // Validate and update the NPM package name + let pkgName: string = projectDir; + if (args.package) { + pkgName = args.package; + } + + const validatePackageNameResult = await validatePackageName(pkgName); + if (!validatePackageNameResult.isValid) { + const pkgNameResponse = await prompts( + { + type: 'text', + name: 'package', + message: 'What is the new NPM package name?', + initial: generateProjectName(), + async validate(value) { + const { isValid, notices } = await validatePackageName(value); + if (!isValid) { + const errorMsg = `"${bold(value)}" is an invalid NPM package name`; + const errors = notices.join(', '); + return `${errorMsg}: ${errors}`; + } + return true; + }, + }, + { onCancel: printIgnoreUpdatingPkgNameMsg } + ); + pkgName = pkgNameResponse.package as string; + } + + if (typeof pkgName === 'string' && !!pkgName) { + // Change project name in package.json + try { + await execaCommand(`${pkgManager} pkg set name=${pkgName}`, { cwd }); + await info('Sounds good!', `The NPM package name was updated to ${bold(pkgName)}.`); + } catch(error) { + printIgnoreUpdatingPkgNameMsg(); + } + } + } const installResponse = await prompts( { @@ -346,7 +407,6 @@ export async function main() { ora().succeed('TypeScript settings applied!'); } - let projectDir = path.relative(process.cwd(), cwd); const devCmd = pkgManager === 'npm' ? 'npm run dev' : `${pkgManager} dev`; await nextSteps({ projectDir, devCmd }); diff --git a/packages/create-astro/test/package-step.test.js b/packages/create-astro/test/package-step.test.js new file mode 100644 index 000000000000..078fc76b7838 --- /dev/null +++ b/packages/create-astro/test/package-step.test.js @@ -0,0 +1,154 @@ +import runTest, { ENTER } from "cli-prompts-test"; +import { expect } from 'chai'; +import { deleteSync } from 'del'; +import { existsSync, mkdirSync, readdirSync, readFileSync } from 'fs'; +import path from 'path'; +import { PROMPT_MESSAGES, testDir, timeout } from './utils.js'; +import stripAnsi from 'strip-ansi'; + +const cliPath = path.join(testDir, "../create-astro.mjs"); + +const inputs = { + emptyDir: './fixtures/update-package-name/empty-dir', +}; + +function isEmpty(dirPath) { + return !existsSync(dirPath) || readdirSync(dirPath).length === 0; +} + +function ensureEmptyDir() { + const dirPath = path.resolve(testDir, inputs.emptyDir); + if (!existsSync(dirPath)) { + mkdirSync(dirPath, { recursive: true }); + } else if (!isEmpty(dirPath)) { + const globPath = path.resolve(dirPath, '*'); + deleteSync(globPath, { dot: true }); + } +} + +function getPackageJson(installDir) { + const filePath = path.resolve(testDir, installDir, 'package.json'); + return JSON.parse(readFileSync(filePath, 'utf-8')); +} + +const testArgs = [ + inputs.emptyDir, + '--skip-houston', + '--install', + '0', + '--git', + '0', + '--typescript', + 'strict', +]; + +describe('[create-astro] update package name', function () { + this.timeout(30000); + + beforeEach(ensureEmptyDir); + + afterEach(ensureEmptyDir); + + it('should the package name is changed if no value is given by the user', async function () { + const template = 'minimal'; + const args = [ + ...testArgs, + ...['--template', template] + ] + const { exitCode, stdout } = await runTest([cliPath].concat(args), [ + ENTER, + ], { + testPath: testDir, + timeout, + }); + + await expect(exitCode).to.equal(0); + // TODO: This should true, so we have to fix the implementation + // await expect(stripAnsi(stderr).trim()).to.be.empty; + await expect(stripAnsi(stdout).trim()).to.contain(PROMPT_MESSAGES.packageSucceed); + + const packageJson = getPackageJson(inputs.emptyDir); + const pkgName = packageJson.name; + await expect(pkgName).to.not.equal(`@example/${template}`); + }); + + it('should the package name is not changed if the user cancels the operation', async function () { + const template = 'minimal'; + const args = [ + ...testArgs, + ...[ + '--template', + template, + ] + ] + const { exitCode, stdout } = await runTest([cliPath].concat(args), [ + "\x1B", // ESC hex code + ], { + testPath: testDir, + timeout, + }); + + await expect(exitCode).to.equal(0); + // TODO: This should true, so we have to fix the implementation + // await expect(stripAnsi(stderr).trim()).to.be.empty; + await expect(stripAnsi(stdout).trim()).to.contain(PROMPT_MESSAGES.packageIgnored); + + const packageJson = getPackageJson(inputs.emptyDir); + const pkgName = packageJson.name; + await expect(pkgName).to.equal(`@example/${template}`); + }); + + it('should the package name matches the passed argument --package', async function () { + const expectedPkgName = 'astro-test'; + const args = [ + ...testArgs, + ...[ + '--template', + 'minimal', + '--package', + expectedPkgName, + ] + ] + const { exitCode, stdout } = await runTest([cliPath].concat(args), [ + ENTER, + ], { + testPath: testDir, + timeout, + }); + + await expect(exitCode).to.equal(0); + // TODO: This should true, so we have to fix the implementation + // await expect(stripAnsi(stderr).trim()).to.be.empty; + await expect(stripAnsi(stdout).trim()).to.contain(PROMPT_MESSAGES.packageSucceed); + + const packageJson = getPackageJson(inputs.emptyDir); + const pkgName = packageJson.name; + await expect(pkgName).to.equal(expectedPkgName); + }); + + it('should the package name matches the user input', async function () { + const expectedPkgName = 'astro-test'; + const args = [ + ...testArgs, + ...[ + '--template', + 'minimal', + ] + ] + const { exitCode, stdout } = await runTest([cliPath].concat(args), [ + `${expectedPkgName}${ENTER}`, + ], { + testPath: testDir, + timeout, + }); + + await expect(exitCode).to.equal(0); + // TODO: This should true, so we have to fix the implementation + // await expect(stripAnsi(stderr).trim()).to.be.empty; + await expect(stripAnsi(stdout).trim()).to.contain(PROMPT_MESSAGES.packageSucceed); + + const packageJson = getPackageJson(inputs.emptyDir); + const pkgName = packageJson.name; + await expect(pkgName).to.equal(expectedPkgName); + }); +}); diff --git a/packages/create-astro/test/utils.js b/packages/create-astro/test/utils.js index 1f437fa01565..f5a1fb9c338c 100644 --- a/packages/create-astro/test/utils.js +++ b/packages/create-astro/test/utils.js @@ -37,6 +37,8 @@ export function promiseWithTimeout(testFn) { export const PROMPT_MESSAGES = { directory: 'Where would you like to create your new project?', template: 'How would you like to setup your new project?', + packageSucceed: 'Sounds good! The NPM package name was updated', + packageIgnored: 'You have to update the NPM package name manually.', typescript: 'How would you like to setup TypeScript?', typescriptSucceed: 'next', }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e83d9f7a2a2c..17d46996dc7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2554,11 +2554,13 @@ importers: '@types/degit': ^2.8.3 '@types/mocha': ^9.1.1 '@types/prompts': ^2.0.14 + '@types/validate-npm-package-name': ^4.0.0 '@types/which-pm-runs': ^1.0.0 '@types/yargs-parser': ^21.0.0 astro-scripts: workspace:* chai: ^4.3.6 chalk: ^5.0.1 + cli-prompts-test: ^0.3.0 comment-json: ^4.2.3 execa: ^6.1.0 giget: ^1.0.0 @@ -2568,6 +2570,7 @@ importers: prompts: ^2.4.2 strip-ansi: ^7.0.1 uvu: ^0.5.3 + validate-npm-package-name: ^5.0.0 which-pm-runs: ^1.1.0 yargs-parser: ^21.0.1 dependencies: @@ -2580,6 +2583,7 @@ importers: ora: 6.1.2 prompts: 2.4.2 strip-ansi: 7.0.1 + validate-npm-package-name: 5.0.0 which-pm-runs: 1.1.0 yargs-parser: 21.1.1 devDependencies: @@ -2587,10 +2591,12 @@ importers: '@types/degit': 2.8.3 '@types/mocha': 9.1.1 '@types/prompts': 2.4.2 + '@types/validate-npm-package-name': 4.0.0 '@types/which-pm-runs': 1.0.0 '@types/yargs-parser': 21.0.0 astro-scripts: link:../../scripts chai: 4.3.7 + cli-prompts-test: 0.3.0 mocha: 9.2.2 uvu: 0.5.6 @@ -7176,6 +7182,10 @@ packages: /@types/unist/2.0.6: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} + /@types/validate-npm-package-name/4.0.0: + resolution: {integrity: sha512-RpO62vB2lkjEkyLbwTheA2+uwYmtVMWTr/kWRI++UAgVdZqNqdAuIQl/SxBCGeMKfdjWaXPbyhZbiCc4PAj+KA==} + dev: true + /@types/which-pm-runs/1.0.0: resolution: {integrity: sha512-BXfdlYLWvRhngJbih4N57DjO+63Z7AxiFiip8yq3rD46U7V4I2W538gngPvBsZiMehhD8sfGf4xLI6k7TgXvNw==} dev: true @@ -8225,7 +8235,6 @@ packages: resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} dependencies: semver: 7.3.8 - dev: true /busboy/1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} @@ -8438,6 +8447,13 @@ packages: restore-cursor: 4.0.0 dev: false + /cli-prompts-test/0.3.0: + resolution: {integrity: sha512-qVbbJewDhmjVHc0fAE76ydsYvIdSaxazbwXlLX9cavCkBugnKMZ6CHyeAaZvhGQ+RRd4Skp+6yy953sxIaYatw==} + dependencies: + concat-stream: 2.0.0 + execa: 4.1.0 + dev: true + /cli-spinners/2.7.0: resolution: {integrity: sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==} engines: {node: '>=6'} @@ -8548,6 +8564,16 @@ packages: /concat-map/0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + /concat-stream/2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.0 + typedarray: 0.0.6 + dev: true + /concurrently/7.6.0: resolution: {integrity: sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==} engines: {node: ^12.20.0 || ^14.13.0 || >=16.0.0} @@ -9842,6 +9868,21 @@ packages: engines: {node: '>=0.8.x'} dev: false + /execa/4.1.0: + resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: true + /execa/5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -10168,6 +10209,13 @@ packages: resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} dev: false + /get-stream/5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + dependencies: + pump: 3.0.0 + dev: true + /get-stream/6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -10674,6 +10722,11 @@ packages: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} dev: true + /human-signals/1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + dev: true + /human-signals/2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -10989,7 +11042,6 @@ packages: /is-stream/2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} - dev: false /is-stream/3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} @@ -12020,7 +12072,6 @@ packages: /mimic-fn/2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - dev: false /mimic-fn/4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} @@ -12347,7 +12398,6 @@ packages: engines: {node: '>=8'} dependencies: path-key: 3.1.1 - dev: false /npm-run-path/5.1.0: resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} @@ -12420,7 +12470,6 @@ packages: engines: {node: '>=6'} dependencies: mimic-fn: 2.1.0 - dev: false /onetime/6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} @@ -14154,6 +14203,7 @@ packages: /source-map/0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + requiresBuild: true /source-map/0.7.4: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} @@ -14338,7 +14388,6 @@ packages: /strip-final-newline/2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} - dev: false /strip-final-newline/3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} @@ -14885,6 +14934,10 @@ packages: for-each: 0.3.3 is-typed-array: 1.1.10 + /typedarray/0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + dev: true + /typescript/4.7.4: resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} engines: {node: '>=4.2.0'} @@ -15182,6 +15235,13 @@ packages: builtins: 5.0.1 dev: true + /validate-npm-package-name/5.0.0: + resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + builtins: 5.0.1 + dev: false + /vfile-location/4.0.1: resolution: {integrity: sha512-JDxPlTbZrZCQXogGheBHjbRWjESSPEak770XwWPfw5mTc1v1nWGLB/apzZxsx8a0SJVfF8HK8ql8RD308vXRUw==} dependencies: From ec420a44fcaf06f7518bedc1e27a87c5af132c37 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 5 Jan 2023 10:49:16 -0600 Subject: [PATCH 2/2] Create fast-ligers-impress.md --- .changeset/fast-ligers-impress.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fast-ligers-impress.md diff --git a/.changeset/fast-ligers-impress.md b/.changeset/fast-ligers-impress.md new file mode 100644 index 000000000000..57158637e61d --- /dev/null +++ b/.changeset/fast-ligers-impress.md @@ -0,0 +1,5 @@ +--- +"create-astro": patch +--- + +Update `package.json` `name` field according to project name