From ce807a2bfef325683bfdb01065a73c4e2b0a5fe5 Mon Sep 17 00:00:00 2001 From: Phumrapee Limpianchop Date: Tue, 24 Oct 2023 05:14:33 +0700 Subject: [PATCH 1/2] feat(create-astro): automatically configure `astro check` (#8853) Co-authored-by: Nate Moore --- .changeset/ninety-onions-bow.md | 5 + .../create-astro/src/actions/typescript.ts | 105 +++++++++++++----- .../test/fixtures/not-empty/package.json | 7 +- .../create-astro/test/project-name.test.js | 2 +- packages/create-astro/test/typescript.test.js | 38 ++++++- packages/create-astro/test/utils.js | 27 +++++ 6 files changed, 148 insertions(+), 36 deletions(-) create mode 100644 .changeset/ninety-onions-bow.md diff --git a/.changeset/ninety-onions-bow.md b/.changeset/ninety-onions-bow.md new file mode 100644 index 000000000000..c651c584bd25 --- /dev/null +++ b/.changeset/ninety-onions-bow.md @@ -0,0 +1,5 @@ +--- +'create-astro': minor +--- + +Automatically installs the required dependencies to run the astro check command when the user indicates they plan to write TypeScript. diff --git a/packages/create-astro/src/actions/typescript.ts b/packages/create-astro/src/actions/typescript.ts index 97ae243032b7..1b6f5cc80a4a 100644 --- a/packages/create-astro/src/actions/typescript.ts +++ b/packages/create-astro/src/actions/typescript.ts @@ -1,15 +1,18 @@ import type { Context } from './context.js'; import { color } from '@astrojs/cli-kit'; -import fs from 'node:fs'; -import { readFile } from 'node:fs/promises'; +import { readFile, writeFile, rm } from 'node:fs/promises'; import path from 'node:path'; import stripJsonComments from 'strip-json-comments'; import { error, info, spinner, title, typescriptByDefault } from '../messages.js'; +import { shell } from '../shell.js'; -export async function typescript( - ctx: Pick -) { +type PickedTypeScriptContext = Pick< + Context, + 'typescript' | 'yes' | 'prompt' | 'dryRun' | 'cwd' | 'exit' | 'packageManager' | 'install' +>; + +export async function typescript(ctx: PickedTypeScriptContext) { let ts = ctx.typescript ?? (typeof ctx.yes !== 'undefined' ? 'strict' : undefined); if (ts === undefined) { const { useTs } = await ctx.prompt({ @@ -39,7 +42,7 @@ export async function typescript( } else { if (!['strict', 'strictest', 'relaxed', 'default', 'base'].includes(ts)) { if (!ctx.dryRun) { - fs.rmSync(ctx.cwd, { recursive: true, force: true }); + await rm(ctx.cwd, { recursive: true, force: true }); } error( 'Error', @@ -62,7 +65,7 @@ export async function typescript( start: 'TypeScript customizing...', end: 'TypeScript customized', while: () => - setupTypeScript(ts!, { cwd: ctx.cwd }).catch((e) => { + setupTypeScript(ts!, ctx).catch((e) => { error('error', e); process.exit(1); }), @@ -71,29 +74,73 @@ export async function typescript( } } -export async function setupTypeScript(value: string, { cwd }: { cwd: string }) { - const templateTSConfigPath = path.join(cwd, 'tsconfig.json'); - try { - const data = await readFile(templateTSConfigPath, { encoding: 'utf-8' }); - const templateTSConfig = JSON.parse(stripJsonComments(data)); - if (templateTSConfig && typeof templateTSConfig === 'object') { - const result = Object.assign(templateTSConfig, { - extends: `astro/tsconfigs/${value}`, - }); +const FILES_TO_UPDATE = { + 'package.json': async ( + file: string, + options: { value: string; ctx: PickedTypeScriptContext } + ) => { + try { + // add required dependencies for astro check + if (options.ctx.install) + await shell(options.ctx.packageManager, ['install', '@astrojs/check', 'typescript'], { + cwd: path.dirname(file), + stdio: 'ignore', + }); - fs.writeFileSync(templateTSConfigPath, JSON.stringify(result, null, 2)); - } else { - throw new Error( - "There was an error applying the requested TypeScript settings. This could be because the template's tsconfig.json is malformed" - ); + // inject addtional command to build script + const data = await readFile(file, { encoding: 'utf-8' }); + const indent = /(^\s+)/m.exec(data)?.[1] ?? '\t'; + const parsedPackageJson = JSON.parse(data); + + const buildScript = parsedPackageJson.scripts?.build; + + // in case of any other template already have astro checks defined, we don't want to override it + if (typeof buildScript === 'string' && !buildScript.includes('astro check')) { + const newPackageJson = Object.assign(parsedPackageJson, { + scripts: { + build: 'astro check && ' + buildScript, + }, + }); + + await writeFile(file, JSON.stringify(newPackageJson, null, indent), 'utf-8'); + } + } catch (err) { + // if there's no package.json (which is very unlikely), then do nothing + if (err && (err as any).code === 'ENOENT') return; + if (err instanceof Error) throw new Error(err.message); } - } catch (err) { - if (err && (err as any).code === 'ENOENT') { - // If the template doesn't have a tsconfig.json, let's add one instead - fs.writeFileSync( - templateTSConfigPath, - JSON.stringify({ extends: `astro/tsconfigs/${value}` }, null, 2) - ); + }, + 'tsconfig.json': async (file: string, options: { value: string }) => { + try { + const data = await readFile(file, { encoding: 'utf-8' }); + const templateTSConfig = JSON.parse(stripJsonComments(data)); + if (templateTSConfig && typeof templateTSConfig === 'object') { + const result = Object.assign(templateTSConfig, { + extends: `astro/tsconfigs/${options.value}`, + }); + + await writeFile(file, JSON.stringify(result, null, 2)); + } else { + throw new Error( + "There was an error applying the requested TypeScript settings. This could be because the template's tsconfig.json is malformed" + ); + } + } catch (err) { + if (err && (err as any).code === 'ENOENT') { + // If the template doesn't have a tsconfig.json, let's add one instead + await writeFile( + file, + JSON.stringify({ extends: `astro/tsconfigs/${options.value}` }, null, 2) + ); + } } - } + }, +}; + +export async function setupTypeScript(value: string, ctx: PickedTypeScriptContext) { + await Promise.all( + Object.entries(FILES_TO_UPDATE).map(async ([file, update]) => + update(path.resolve(path.join(ctx.cwd, file)), { value, ctx }) + ) + ); } diff --git a/packages/create-astro/test/fixtures/not-empty/package.json b/packages/create-astro/test/fixtures/not-empty/package.json index 5edb64fea0b5..4c5b89162027 100644 --- a/packages/create-astro/test/fixtures/not-empty/package.json +++ b/packages/create-astro/test/fixtures/not-empty/package.json @@ -1,4 +1,7 @@ { "name": "@test/create-astro-not-empty", - "private": true -} + "private": true, + "scripts": { + "build": "astro build" + } +} \ No newline at end of file diff --git a/packages/create-astro/test/project-name.test.js b/packages/create-astro/test/project-name.test.js index 1672fce66501..905f4a158d8a 100644 --- a/packages/create-astro/test/project-name.test.js +++ b/packages/create-astro/test/project-name.test.js @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { projectName } from '../dist/index.js'; import { setup } from './utils.js'; -describe('project name', () => { +describe('project name', async () => { const fixture = setup(); it('pass in name', async () => { diff --git a/packages/create-astro/test/typescript.test.js b/packages/create-astro/test/typescript.test.js index be89a499d242..8145e22519f0 100644 --- a/packages/create-astro/test/typescript.test.js +++ b/packages/create-astro/test/typescript.test.js @@ -4,7 +4,8 @@ import fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { typescript, setupTypeScript } from '../dist/index.js'; -import { setup } from './utils.js'; +import { setup, resetFixtures } from './utils.js'; +import { describe } from 'node:test'; describe('typescript', () => { const fixture = setup(); @@ -82,7 +83,7 @@ describe('typescript', () => { }); }); -describe('typescript: setup', () => { +describe('typescript: setup tsconfig', () => { it('none', async () => { const root = new URL('./fixtures/empty/', import.meta.url); const tsconfig = new URL('./tsconfig.json', root); @@ -91,7 +92,8 @@ describe('typescript: setup', () => { expect(JSON.parse(fs.readFileSync(tsconfig, { encoding: 'utf-8' }))).to.deep.eq({ extends: 'astro/tsconfigs/strict', }); - fs.rmSync(tsconfig); + + await resetFixtures(); }); it('exists', async () => { @@ -101,6 +103,34 @@ describe('typescript: setup', () => { expect(JSON.parse(fs.readFileSync(tsconfig, { encoding: 'utf-8' }))).to.deep.eq({ extends: 'astro/tsconfigs/strict', }); - fs.writeFileSync(tsconfig, `{}`); + + await resetFixtures(); + }); +}); + +describe('typescript: setup package', () => { + it('none', async () => { + const root = new URL('./fixtures/empty/', import.meta.url); + const packageJson = new URL('./package.json', root); + + await setupTypeScript('strictest', { cwd: fileURLToPath(root), install: false }); + expect(fs.existsSync(packageJson)).to.be.false; + + await resetFixtures(); + }); + + it('none', async () => { + const root = new URL('./fixtures/not-empty/', import.meta.url); + const packageJson = new URL('./package.json', root); + + expect( + JSON.parse(fs.readFileSync(packageJson, { encoding: 'utf-8' })).scripts.build + ).to.be.eq('astro build'); + await setupTypeScript('strictest', { cwd: fileURLToPath(root), install: false }); + expect(JSON.parse(fs.readFileSync(packageJson, { encoding: 'utf-8' })).scripts.build).to.be.eq( + 'astro check && astro build' + ); + + await resetFixtures(); }); }); diff --git a/packages/create-astro/test/utils.js b/packages/create-astro/test/utils.js index ff5d5dd832af..56ef556050f6 100644 --- a/packages/create-astro/test/utils.js +++ b/packages/create-astro/test/utils.js @@ -1,3 +1,4 @@ +import fs from 'node:fs'; import { setStdout } from '../dist/index.js'; import stripAnsi from 'strip-ansi'; @@ -29,3 +30,29 @@ export function setup() { }, }; } + +const resetEmptyFixture = () => + fs.promises.rm(new URL('./fixtures/empty/tsconfig.json', import.meta.url)); +const resetNotEmptyFixture = async () => { + const packagePath = new URL('./fixtures/not-empty/package.json', import.meta.url); + const tsconfigPath = new URL('./fixtures/not-empty/tsconfig.json', import.meta.url); + + const overriddenPackageJson = Object.assign( + JSON.parse(await fs.promises.readFile(packagePath, { encoding: 'utf-8' })), + { + scripts: { + build: 'astro build', + }, + } + ); + + return Promise.all([ + fs.promises.writeFile(packagePath, JSON.stringify(overriddenPackageJson, null, 2), { + encoding: 'utf-8', + }), + fs.promises.writeFile(tsconfigPath, '{}', { encoding: 'utf-8' }), + ]); +}; + +export const resetFixtures = () => + Promise.allSettled([resetEmptyFixture(), resetNotEmptyFixture()]); From 99b683aec11bb5409e5c64c89f47cb437892b5ee Mon Sep 17 00:00:00 2001 From: natemoo-re Date: Mon, 23 Oct 2023 22:16:51 +0000 Subject: [PATCH 2/2] [ci] format --- packages/create-astro/src/actions/typescript.ts | 2 +- packages/create-astro/test/typescript.test.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/create-astro/src/actions/typescript.ts b/packages/create-astro/src/actions/typescript.ts index 1b6f5cc80a4a..cc00f20e5807 100644 --- a/packages/create-astro/src/actions/typescript.ts +++ b/packages/create-astro/src/actions/typescript.ts @@ -1,7 +1,7 @@ import type { Context } from './context.js'; import { color } from '@astrojs/cli-kit'; -import { readFile, writeFile, rm } from 'node:fs/promises'; +import { readFile, rm, writeFile } from 'node:fs/promises'; import path from 'node:path'; import stripJsonComments from 'strip-json-comments'; import { error, info, spinner, title, typescriptByDefault } from '../messages.js'; diff --git a/packages/create-astro/test/typescript.test.js b/packages/create-astro/test/typescript.test.js index 8145e22519f0..a6c09af99d2a 100644 --- a/packages/create-astro/test/typescript.test.js +++ b/packages/create-astro/test/typescript.test.js @@ -123,9 +123,9 @@ describe('typescript: setup package', () => { const root = new URL('./fixtures/not-empty/', import.meta.url); const packageJson = new URL('./package.json', root); - expect( - JSON.parse(fs.readFileSync(packageJson, { encoding: 'utf-8' })).scripts.build - ).to.be.eq('astro build'); + expect(JSON.parse(fs.readFileSync(packageJson, { encoding: 'utf-8' })).scripts.build).to.be.eq( + 'astro build' + ); await setupTypeScript('strictest', { cwd: fileURLToPath(root), install: false }); expect(JSON.parse(fs.readFileSync(packageJson, { encoding: 'utf-8' })).scripts.build).to.be.eq( 'astro check && astro build'