diff --git a/.changeset/great-trains-doubt.md b/.changeset/great-trains-doubt.md index 21b5d0e6c..f49af4497 100644 --- a/.changeset/great-trains-doubt.md +++ b/.changeset/great-trains-doubt.md @@ -4,4 +4,20 @@ deps: Prettier 3.2 +A notable change in this release is the application of trailing newlines to JSONC files, including `tsconfig.json`: + +```diff +{ + "compilerOptions": { + "lib": ["ES2022"], + "outDir": "lib", +- "target": "ES2022" ++ "target": "ES2022", + }, + "exclude": ["lib*/**/*"], +- "extends": "skuba/config/tsconfig.json" ++ "extends": "skuba/config/tsconfig.json", +} +``` + See the [release notes](https://prettier.io/blog/2024/01/12/3.2.0) for more information. diff --git a/.changeset/little-goats-chew.md b/.changeset/little-goats-chew.md new file mode 100644 index 000000000..08987fb19 --- /dev/null +++ b/.changeset/little-goats-chew.md @@ -0,0 +1,7 @@ +--- +'skuba': patch +--- + +build: Apply trailing newlines to `skuba/config/tsconfig.json` + +This aligns with Prettier 3.2 formatting behaviour. If your project references this file in a non-standard manner outside of the `tsconfig.json#/extends` field, ensure that you are using a JSONC-compatible parser. diff --git a/config/tsconfig.json b/config/tsconfig.json index f31eab441..5e8a762ef 100644 --- a/config/tsconfig.json +++ b/config/tsconfig.json @@ -5,7 +5,7 @@ "moduleResolution": "node", "resolveJsonModule": true, "noUnusedLocals": false, - "noUnusedParameters": false + "noUnusedParameters": false, }, - "extends": "tsconfig-seek" + "extends": "tsconfig-seek", } diff --git a/integration/base/fixable/tsconfig.json b/integration/base/fixable/tsconfig.json index abaf52a88..b435e0620 100644 --- a/integration/base/fixable/tsconfig.json +++ b/integration/base/fixable/tsconfig.json @@ -3,7 +3,7 @@ "incremental": true, "moduleResolution": "node", "outDir": "lib", - "skipLibCheck": true + "skipLibCheck": true, }, - "extends": "tsconfig-seek" + "extends": "tsconfig-seek", } diff --git a/integration/base/ok/tsconfig.json b/integration/base/ok/tsconfig.json index abaf52a88..b435e0620 100644 --- a/integration/base/ok/tsconfig.json +++ b/integration/base/ok/tsconfig.json @@ -3,7 +3,7 @@ "incremental": true, "moduleResolution": "node", "outDir": "lib", - "skipLibCheck": true + "skipLibCheck": true, }, - "extends": "tsconfig-seek" + "extends": "tsconfig-seek", } diff --git a/integration/base/patch/tsconfig.json b/integration/base/patch/tsconfig.json index abaf52a88..b435e0620 100644 --- a/integration/base/patch/tsconfig.json +++ b/integration/base/patch/tsconfig.json @@ -3,7 +3,7 @@ "incremental": true, "moduleResolution": "node", "outDir": "lib", - "skipLibCheck": true + "skipLibCheck": true, }, - "extends": "tsconfig-seek" + "extends": "tsconfig-seek", } diff --git a/integration/base/unfixable/tsconfig.json b/integration/base/unfixable/tsconfig.json index abaf52a88..b435e0620 100644 --- a/integration/base/unfixable/tsconfig.json +++ b/integration/base/unfixable/tsconfig.json @@ -3,7 +3,7 @@ "incremental": true, "moduleResolution": "node", "outDir": "lib", - "skipLibCheck": true + "skipLibCheck": true, }, - "extends": "tsconfig-seek" + "extends": "tsconfig-seek", } diff --git a/package.json b/package.json index 986e83891..298e1c28a 100644 --- a/package.json +++ b/package.json @@ -102,8 +102,8 @@ "npm-run-path": "^4.0.1", "npm-which": "^3.0.1", "picomatch": "^3.0.0", - "prettier": "~3.2.0", - "prettier-plugin-packagejson": "^2.4.6", + "prettier": "~3.2.4", + "prettier-plugin-packagejson": "^2.4.9", "read-pkg-up": "^7.0.1", "semantic-release": "^21.0.0", "serialize-error": "^8.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e4cd5700..dab4ffd69 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,11 +111,11 @@ dependencies: specifier: ^3.0.0 version: 3.0.1 prettier: - specifier: ~3.2.0 - version: 3.2.2 + specifier: ~3.2.4 + version: 3.2.4 prettier-plugin-packagejson: - specifier: ^2.4.6 - version: 2.4.8(prettier@3.2.2) + specifier: ^2.4.9 + version: 2.4.9(prettier@3.2.4) read-pkg-up: specifier: ^7.0.1 version: 7.0.1 @@ -7741,17 +7741,17 @@ packages: engines: {node: '>= 0.8.0'} dev: false - /prettier-plugin-packagejson@2.4.8(prettier@3.2.2): - resolution: {integrity: sha512-ZK37c6pRUKeUIpQWNEdMgNUiGSG5BTfeeAIA01mRjVGTfWxxVzM55Cs+LaHyweFJbEgkgCNsqMA3LGEAjfOPtA==} + /prettier-plugin-packagejson@2.4.9(prettier@3.2.4): + resolution: {integrity: sha512-b3Q7agXVqxK3UpYEJr0xLD51SxriYXESWUCjmxOBUGqnPFZOg9jZGZ+Ptzq252I6OqzXN2rj1tJIFq6KOGLLJw==} peerDependencies: prettier: '>= 1.16.0' peerDependenciesMeta: prettier: optional: true dependencies: - prettier: 3.2.2 + prettier: 3.2.4 sort-package-json: 2.6.0 - synckit: 0.8.8 + synckit: 0.9.0 dev: false /prettier@2.8.8: @@ -7760,8 +7760,8 @@ packages: hasBin: true dev: true - /prettier@3.2.2: - resolution: {integrity: sha512-HTByuKZzw7utPiDO523Tt2pLtEyK7OibUD9suEJQrPUCYQqrHr74GGX6VidMrovbf/I50mPqr8j/II6oBAuc5A==} + /prettier@3.2.4: + resolution: {integrity: sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==} engines: {node: '>=14'} hasBin: true dev: false @@ -9052,6 +9052,14 @@ packages: tslib: 2.6.2 dev: false + /synckit@0.9.0: + resolution: {integrity: sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==} + engines: {node: ^14.18.0 || >=16.0.0} + dependencies: + '@pkgr/core': 0.1.0 + tslib: 2.6.2 + dev: false + /tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} diff --git a/src/cli/configure/analysis/__snapshots__/project.test.ts.snap b/src/cli/configure/analysis/__snapshots__/project.test.ts.snap index 64b823e40..0e1fe4e3a 100644 --- a/src/cli/configure/analysis/__snapshots__/project.test.ts.snap +++ b/src/cli/configure/analysis/__snapshots__/project.test.ts.snap @@ -145,32 +145,6 @@ export {}; "version": "0.0.0-semantically-released" } } -", - "operation": "A", - }, - "tsconfig.build.json": { - "data": "{ - "exclude": ["**/__mocks__/**/*", "**/*.test.ts", "src/testing/**/*"], - "extends": "./tsconfig.json", - "include": ["src/**/*"] -} -", - "operation": "A", - }, - "tsconfig.json": { - "data": "{ - "compilerOptions": { - "baseUrl": ".", - "lib": ["ES2022"], - "outDir": "lib", - "paths": { - "src": ["src"] - }, - "target": "ES2022" - }, - "exclude": ["lib*/**/*"], - "extends": "skuba/config/tsconfig.json" -} ", "operation": "A", }, diff --git a/src/cli/configure/modules/index.ts b/src/cli/configure/modules/index.ts index b87c7722f..7dd637b87 100644 --- a/src/cli/configure/modules/index.ts +++ b/src/cli/configure/modules/index.ts @@ -9,7 +9,6 @@ import { prettierModule } from './prettier'; import { renovateModule } from './renovate'; import { serverlessModule } from './serverless'; import { skubaDiveModule } from './skubaDive'; -import { tsconfigModule } from './tsconfig'; import { tslintModule } from './tslint'; export const loadModules = (opts: Options): Promise => @@ -24,7 +23,6 @@ export const loadModules = (opts: Options): Promise => renovateModule, serverlessModule, skubaDiveModule, - tsconfigModule, tslintModule, ].map((createModule) => createModule(opts)), ); diff --git a/src/cli/configure/modules/tsconfig.test.ts b/src/cli/configure/modules/tsconfig.test.ts deleted file mode 100644 index e472e30b9..000000000 --- a/src/cli/configure/modules/tsconfig.test.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { parseObject } from '../processing/json'; -import { - assertDefined, - defaultOpts, - defaultPackageOpts, - executeModule, -} from '../testing/module'; -import type { TsConfigJson } from '../types'; - -import { tsconfigModule } from './tsconfig'; - -describe('tsconfigModule', () => { - it('works from scratch', async () => { - const inputFiles = {}; - - const outputFiles = await executeModule( - tsconfigModule, - inputFiles, - defaultOpts, - ); - - expect(outputFiles['tsconfig.build.json']).toContain('./tsconfig.json'); - expect(outputFiles['tsconfig.json']).toContain( - 'skuba/config/tsconfig.json', - ); - - const outputData = parseObject( - outputFiles['tsconfig.json'], - ) as TsConfigJson; - - assertDefined(outputData); - expect(outputData.compilerOptions!.baseUrl).toBe('.'); - expect(outputData.compilerOptions!.paths).toEqual({ src: ['src'] }); - }); - - it('disables module aliasing and retains comments for packages', async () => { - const inputFiles = {}; - - const outputFiles = await executeModule( - tsconfigModule, - inputFiles, - defaultPackageOpts, - ); - - expect(outputFiles['tsconfig.build.json']).toContain('./tsconfig.json'); - expect(outputFiles['tsconfig.json']).toContain( - 'skuba/config/tsconfig.json', - ); - - const outputData = parseObject( - outputFiles['tsconfig.json'], - ) as TsConfigJson; - - assertDefined(outputData); - expect(outputData.compilerOptions!.baseUrl).toBeUndefined(); - expect(outputData.compilerOptions!.paths).toBeUndefined(); - expect(outputData.compilerOptions!.removeComments).toBe(false); - }); - - it('respects explicit comment removal for packages', async () => { - const inputFiles = { - 'tsconfig.json': '{"compilerOptions": {"removeComments": true}}', - }; - - const outputFiles = await executeModule( - tsconfigModule, - inputFiles, - defaultPackageOpts, - ); - - const outputData = parseObject( - outputFiles['tsconfig.json'], - ) as TsConfigJson; - - assertDefined(outputData); - expect(outputData.compilerOptions!.removeComments).toBe(true); - }); - - it('augments existing config', async () => { - const inputFiles = { - 'tsconfig.build.json': '{}', - 'tsconfig.json': JSON.stringify({ - compilerOptions: { - lib: ['ES2020'], - target: 'ES2020', - }, - exclude: ['.idea'], - include: ['src'], - }), - '.eslintrc.js': undefined, - '.prettierrc.toml': undefined, - 'package.json': JSON.stringify({ - name: 'secret-service', - prettier: { - extends: [], - }, - }), - }; - - const outputFiles = await executeModule( - tsconfigModule, - inputFiles, - defaultOpts, - ); - - expect(outputFiles['tsconfig.build.json']).toBe( - inputFiles['tsconfig.build.json'], - ); - - const outputData = parseObject( - outputFiles['tsconfig.json'], - ) as TsConfigJson; - - expect(outputData).toMatchInlineSnapshot(` - { - "compilerOptions": { - "baseUrl": ".", - "lib": [ - "ES2020", - ], - "outDir": "lib", - "paths": { - "src": [ - "src", - ], - }, - "target": "ES2020", - }, - "exclude": [ - ".idea", - "lib*/**/*", - ], - "extends": "skuba/config/tsconfig.json", - } - `); - }); - - it('migrates divergent outDir', async () => { - const inputFiles = { - Dockerfile: ` -ARG DIR dist - -RUN echo redist - -FROM gcr.io/distroless/nodejs:16 AS runtime - -COPY --from=build /workdir/dist './dist' - -CMD ["dist/listen.js"] -`, - 'tsconfig.json': '{"compilerOptions": {"outDir": "dist/"}}', - }; - - const outputFiles = await executeModule( - tsconfigModule, - inputFiles, - defaultOpts, - ); - - expect(outputFiles.Dockerfile).toMatchInlineSnapshot(` - " - ARG DIR lib - - RUN echo redist - - FROM gcr.io/distroless/nodejs:16 AS runtime - - COPY --from=build /workdir/lib './lib' - - CMD ["lib/listen.js"] - " - `); - - const outputData = parseObject( - outputFiles['tsconfig.json'], - ) as TsConfigJson; - - assertDefined(outputData); - expect(outputData.compilerOptions!.outDir).toBe('lib'); - }); - - it('removes duplicate lib patterns from exclude option', async () => { - const inputFiles = { - 'tsconfig.json': - '{"extends": "skuba/config/tsconfig.json", "exclude": ["lib", "lib/**/*"]}', - }; - - const outputFiles = await executeModule( - tsconfigModule, - inputFiles, - defaultOpts, - ); - - const outputData = parseObject( - outputFiles['tsconfig.json'], - ) as TsConfigJson; - - assertDefined(outputData); - expect(outputData.extends).toBe('skuba/config/tsconfig.json'); - expect(outputData.exclude).toStrictEqual(['lib*/**/*']); - }); - - it('retains include option after initial setup', async () => { - const inputFiles = { - 'tsconfig.json': - '{"extends": "skuba/config/tsconfig.json", "include": ["src"]}', - }; - - const outputFiles = await executeModule( - tsconfigModule, - inputFiles, - defaultOpts, - ); - - const outputData = parseObject( - outputFiles['tsconfig.json'], - ) as TsConfigJson; - - assertDefined(outputData); - expect(outputData.extends).toBe('skuba/config/tsconfig.json'); - expect(outputData.include).toStrictEqual(['src']); - }); -}); diff --git a/src/cli/configure/modules/tsconfig.ts b/src/cli/configure/modules/tsconfig.ts deleted file mode 100644 index 76b3a07b5..000000000 --- a/src/cli/configure/modules/tsconfig.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { readBaseTemplateFile } from '../../../utils/template'; -import { hasProp, hasStringProp, isObject } from '../../../utils/validation'; -import { formatObject, parseObject } from '../processing/json'; -import { loadFiles } from '../processing/loadFiles'; -import { merge } from '../processing/record'; -import type { Module, Options } from '../types'; - -export const tsconfigModule = async ({ - firstRun, - type, -}: Options): Promise => { - const [buildFile, baseFile] = await Promise.all([ - readBaseTemplateFile('tsconfig.build.json'), - readBaseTemplateFile('tsconfig.json'), - ]); - - const baseData = parseObject(baseFile); - - // packages should not use module aliases - if ( - type === 'package' && - hasProp(baseData, 'compilerOptions') && - isObject(baseData.compilerOptions) - ) { - delete baseData.compilerOptions.baseUrl; - delete baseData.compilerOptions.paths; - } - - return { - ...loadFiles('Dockerfile'), - - 'tsconfig.build.json': (inputFile) => inputFile ?? buildFile, - - 'tsconfig.json': (inputFile, files, initialFiles) => { - const inputData = parseObject(inputFile); - - let outDir: string | undefined; - - if ( - hasProp(inputData, 'compilerOptions') && - hasStringProp(inputData.compilerOptions, 'outDir') - ) { - outDir = inputData.compilerOptions.outDir.replace(/\/$/, ''); - } - - // optimistically rewire Dockerfile for new output directory - if (outDir !== undefined && outDir !== 'lib') { - files.Dockerfile = files.Dockerfile?.replace( - new RegExp(`([^\\w])${outDir}([^\\w])`, 'g'), - '$1lib$2', - ); - } - - // existing project may target earlier Node.js versions than skuba - if (hasProp(baseData, 'compilerOptions')) { - if ( - hasProp(baseData.compilerOptions, 'lib') && - hasProp(inputData?.compilerOptions, 'lib') - ) { - delete baseData.compilerOptions.lib; - } - - if ( - hasProp(baseData.compilerOptions, 'target') && - hasProp(inputData?.compilerOptions, 'target') - ) { - delete baseData.compilerOptions.target; - } - } - - const outputData = merge(inputData ?? {}, baseData); - - // Remove `lib/**/*` and `lib`, which duplicate `lib*/**/*` - if (hasProp(outputData, 'exclude') && Array.isArray(outputData.exclude)) { - const { exclude } = outputData; - - const hasLibStar = exclude.includes('lib*/**/*'); - - outputData.exclude = exclude.filter( - (pattern: unknown) => - !(hasLibStar && new Set(['lib', 'lib/**/*']).has(pattern)), - ); - } - - // for optimal ESLinting, base config should compile all files and leave - // exclusions to .eslintignore and tsconfig.build.json - if ( - hasProp(outputData, 'include') && - !initialFiles['tsconfig.json']?.includes('skuba/config/tsconfig.json') - ) { - delete outputData.include; - } - - // Retain comments for package documentation - if ( - firstRun && - type === 'package' && - hasProp(outputData, 'compilerOptions') && - isObject(outputData.compilerOptions) && - !outputData.compilerOptions.removeComments - ) { - outputData.compilerOptions.removeComments = false; - } - - return formatObject(outputData); - }, - }; -}; diff --git a/template/base/tsconfig.json b/template/base/tsconfig.json index 04898243f..7d0f6daf7 100644 --- a/template/base/tsconfig.json +++ b/template/base/tsconfig.json @@ -4,10 +4,10 @@ "lib": ["ES2022"], "outDir": "lib", "paths": { - "src": ["src"] + "src": ["src"], }, - "target": "ES2022" + "target": "ES2022", }, "exclude": ["lib*/**/*"], - "extends": "skuba/config/tsconfig.json" + "extends": "skuba/config/tsconfig.json", } diff --git a/template/koa-rest-api/tsconfig.json b/template/koa-rest-api/tsconfig.json index 2e9b58e7d..6e2253f3b 100644 --- a/template/koa-rest-api/tsconfig.json +++ b/template/koa-rest-api/tsconfig.json @@ -5,14 +5,14 @@ // open-telemetry/opentelemetry-js#3580 "DOM", - "ES2022" + "ES2022", ], "outDir": "lib", "paths": { - "src": ["src"] + "src": ["src"], }, - "target": "ES2022" + "target": "ES2022", }, "exclude": ["lib*/**/*"], - "extends": "skuba/config/tsconfig.json" + "extends": "skuba/config/tsconfig.json", } diff --git a/template/lambda-sqs-worker-cdk/tsconfig.json b/template/lambda-sqs-worker-cdk/tsconfig.json index 04898243f..7d0f6daf7 100644 --- a/template/lambda-sqs-worker-cdk/tsconfig.json +++ b/template/lambda-sqs-worker-cdk/tsconfig.json @@ -4,10 +4,10 @@ "lib": ["ES2022"], "outDir": "lib", "paths": { - "src": ["src"] + "src": ["src"], }, - "target": "ES2022" + "target": "ES2022", }, "exclude": ["lib*/**/*"], - "extends": "skuba/config/tsconfig.json" + "extends": "skuba/config/tsconfig.json", } diff --git a/template/lambda-sqs-worker/tsconfig.json b/template/lambda-sqs-worker/tsconfig.json index 04898243f..7d0f6daf7 100644 --- a/template/lambda-sqs-worker/tsconfig.json +++ b/template/lambda-sqs-worker/tsconfig.json @@ -4,10 +4,10 @@ "lib": ["ES2022"], "outDir": "lib", "paths": { - "src": ["src"] + "src": ["src"], }, - "target": "ES2022" + "target": "ES2022", }, "exclude": ["lib*/**/*"], - "extends": "skuba/config/tsconfig.json" + "extends": "skuba/config/tsconfig.json", } diff --git a/template/oss-npm-package/_package.json b/template/oss-npm-package/_package.json index dd0aa5582..000c891d2 100644 --- a/template/oss-npm-package/_package.json +++ b/template/oss-npm-package/_package.json @@ -34,7 +34,7 @@ }, "dependencies": {}, "devDependencies": { - "@types/node": "^16.18.3", + "@types/node": "^18.19.8", "commitizen": "^4.2.4", "skuba": "*" }, diff --git a/template/oss-npm-package/tsconfig.json b/template/oss-npm-package/tsconfig.json index 54fe8e8df..a5fddcf46 100644 --- a/template/oss-npm-package/tsconfig.json +++ b/template/oss-npm-package/tsconfig.json @@ -3,8 +3,8 @@ "lib": ["ES2022"], "outDir": "lib", "removeComments": false, - "target": "ES2022" + "target": "ES2022", }, "exclude": ["lib*/**/*"], - "extends": "skuba/config/tsconfig.json" + "extends": "skuba/config/tsconfig.json", } diff --git a/template/private-npm-package/_package.json b/template/private-npm-package/_package.json index eb28ecd96..7ce9c9781 100644 --- a/template/private-npm-package/_package.json +++ b/template/private-npm-package/_package.json @@ -34,7 +34,7 @@ }, "dependencies": {}, "devDependencies": { - "@types/node": "^16.18.3", + "@types/node": "^18.19.8", "commitizen": "^4.2.4", "skuba": "*" }, diff --git a/template/private-npm-package/tsconfig.json b/template/private-npm-package/tsconfig.json index 54fe8e8df..a5fddcf46 100644 --- a/template/private-npm-package/tsconfig.json +++ b/template/private-npm-package/tsconfig.json @@ -3,8 +3,8 @@ "lib": ["ES2022"], "outDir": "lib", "removeComments": false, - "target": "ES2022" + "target": "ES2022", }, "exclude": ["lib*/**/*"], - "extends": "skuba/config/tsconfig.json" + "extends": "skuba/config/tsconfig.json", } diff --git a/tsconfig.json b/tsconfig.json index a3e7729ee..145cba16b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,8 +4,8 @@ "lib": ["ES2022"], "outDir": "lib", "removeComments": false, - "target": "ES2022" + "target": "ES2022", }, "exclude": ["integration/**/*", "lib*/**/*", "template/**/*"], - "extends": "./config/tsconfig.json" + "extends": "./config/tsconfig.json", }