feat(js): add the setup-prettier generator (#27996)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
3e1a87917f
commit
72cd1c15e6
@ -8185,6 +8185,14 @@
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "setup-prettier",
|
||||
"path": "/nx-api/js/generators/setup-prettier",
|
||||
"name": "setup-prettier",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
|
||||
@ -1291,6 +1291,15 @@
|
||||
"originalFilePath": "/packages/js/src/generators/typescript-sync/schema.json",
|
||||
"path": "/nx-api/js/generators/typescript-sync",
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/js/generators/setup-prettier": {
|
||||
"description": "Setup Prettier as the formatting tool.",
|
||||
"file": "generated/packages/js/generators/setup-prettier.json",
|
||||
"hidden": false,
|
||||
"name": "setup-prettier",
|
||||
"originalFilePath": "/packages/js/src/generators/setup-prettier/schema.json",
|
||||
"path": "/nx-api/js/generators/setup-prettier",
|
||||
"type": "generator"
|
||||
}
|
||||
},
|
||||
"path": "/nx-api/js"
|
||||
|
||||
@ -1273,6 +1273,15 @@
|
||||
"originalFilePath": "/packages/js/src/generators/typescript-sync/schema.json",
|
||||
"path": "js/generators/typescript-sync",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Setup Prettier as the formatting tool.",
|
||||
"file": "generated/packages/js/generators/setup-prettier.json",
|
||||
"hidden": false,
|
||||
"name": "setup-prettier",
|
||||
"originalFilePath": "/packages/js/src/generators/setup-prettier/schema.json",
|
||||
"path": "js/generators/setup-prettier",
|
||||
"type": "generator"
|
||||
}
|
||||
],
|
||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||
|
||||
33
docs/generated/packages/js/generators/setup-prettier.json
Normal file
33
docs/generated/packages/js/generators/setup-prettier.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "setup-prettier",
|
||||
"factory": "./src/generators/setup-prettier/generator",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxJsSetupPrettier",
|
||||
"title": "Setup Prettier",
|
||||
"description": "Setup Prettier as the formatting tool.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"skipFormat": {
|
||||
"description": "Skip formatting files.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"skipPackageJson": {
|
||||
"description": "Do not add dependencies to `package.json`.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"x-priority": "internal"
|
||||
}
|
||||
},
|
||||
"required": [],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Setup Prettier as the formatting tool.",
|
||||
"implementation": "/packages/js/src/generators/setup-prettier/generator.ts",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/js/src/generators/setup-prettier/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
@ -487,6 +487,7 @@
|
||||
- [setup-verdaccio](/nx-api/js/generators/setup-verdaccio)
|
||||
- [setup-build](/nx-api/js/generators/setup-build)
|
||||
- [typescript-sync](/nx-api/js/generators/typescript-sync)
|
||||
- [setup-prettier](/nx-api/js/generators/setup-prettier)
|
||||
- [nest](/nx-api/nest)
|
||||
- [documents](/nx-api/nest/documents)
|
||||
- [Overview](/nx-api/nest/documents/overview)
|
||||
|
||||
@ -48,6 +48,11 @@
|
||||
"description": "Synchronize TypeScript project references based on the project graph",
|
||||
"alias": ["sync"],
|
||||
"hidden": true
|
||||
},
|
||||
"setup-prettier": {
|
||||
"factory": "./src/generators/setup-prettier/generator",
|
||||
"schema": "./src/generators/setup-prettier/schema.json",
|
||||
"description": "Setup Prettier as the formatting tool."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,14 +5,14 @@ import {
|
||||
generateFiles,
|
||||
GeneratorCallback,
|
||||
readJson,
|
||||
stripIndents,
|
||||
runTasksInSerial,
|
||||
Tree,
|
||||
updateJson,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver';
|
||||
import { readModulePackageJson } from 'nx/src/utils/package-json';
|
||||
import { join } from 'path';
|
||||
import { satisfies, valid } from 'semver';
|
||||
import { generatePrettierSetup } from '../../utils/prettier';
|
||||
import { getRootTsConfigFileName } from '../../utils/typescript/ts-config';
|
||||
import {
|
||||
nxVersion,
|
||||
@ -24,7 +24,6 @@ import {
|
||||
typescriptVersion,
|
||||
} from '../../utils/versions';
|
||||
import { InitSchema } from './schema';
|
||||
import { join } from 'path';
|
||||
|
||||
async function getInstalledTypescriptVersion(
|
||||
tree: Tree
|
||||
@ -105,53 +104,10 @@ export async function initGeneratorInternal(
|
||||
}
|
||||
|
||||
if (schema.setUpPrettier) {
|
||||
devDependencies['prettier'] = prettierVersion;
|
||||
|
||||
// https://prettier.io/docs/en/configuration.html
|
||||
const prettierrcNameOptions = [
|
||||
'.prettierrc',
|
||||
'.prettierrc.json',
|
||||
'.prettierrc.yml',
|
||||
'.prettierrc.yaml',
|
||||
'.prettierrc.json5',
|
||||
'.prettierrc.js',
|
||||
'.prettierrc.cjs',
|
||||
'.prettierrc.mjs',
|
||||
'.prettierrc.toml',
|
||||
'prettier.config.js',
|
||||
'prettier.config.cjs',
|
||||
'prettier.config.mjs',
|
||||
];
|
||||
|
||||
if (prettierrcNameOptions.every((name) => !tree.exists(name))) {
|
||||
writeJson(tree, '.prettierrc', {
|
||||
singleQuote: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (!tree.exists(`.prettierignore`)) {
|
||||
tree.write(
|
||||
'.prettierignore',
|
||||
stripIndents`
|
||||
# Add files here to ignore them from prettier formatting
|
||||
/dist
|
||||
/coverage
|
||||
/.nx/cache
|
||||
/.nx/workspace-data
|
||||
`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (tree.exists('.vscode/extensions.json')) {
|
||||
updateJson(tree, '.vscode/extensions.json', (json) => {
|
||||
json.recommendations ??= [];
|
||||
const extension = 'esbenp.prettier-vscode';
|
||||
if (!json.recommendations.includes(extension)) {
|
||||
json.recommendations.push(extension);
|
||||
}
|
||||
return json;
|
||||
const prettierTask = generatePrettierSetup(tree, {
|
||||
skipPackageJson: schema.skipPackageJson,
|
||||
});
|
||||
tasks.push(prettierTask);
|
||||
}
|
||||
|
||||
const installTask = !schema.skipPackageJson
|
||||
@ -165,16 +121,16 @@ export async function initGeneratorInternal(
|
||||
: () => {};
|
||||
tasks.push(installTask);
|
||||
|
||||
if (schema.setUpPrettier) {
|
||||
ensurePackage('prettier', prettierVersion);
|
||||
if (!schema.skipFormat) await formatFiles(tree);
|
||||
if (!schema.skipFormat) {
|
||||
if (!schema.skipPackageJson) {
|
||||
ensurePackage('prettier', prettierVersion);
|
||||
}
|
||||
// even if skipPackageJson === true, we can safely run formatFiles, prettier might
|
||||
// have been installed earlier and if not, the formatFiles function still handles it
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
return async () => {
|
||||
for (const task of tasks) {
|
||||
await task();
|
||||
}
|
||||
};
|
||||
return runTasksInSerial(...tasks);
|
||||
}
|
||||
|
||||
export default initGenerator;
|
||||
|
||||
86
packages/js/src/generators/setup-prettier/generator.spec.ts
Normal file
86
packages/js/src/generators/setup-prettier/generator.spec.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { readJson, writeJson, type Tree } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { prettierVersion } from '../../utils/versions';
|
||||
import { setupPrettierGenerator } from './generator';
|
||||
|
||||
describe('setup-prettier generator', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
// remove the default generated .prettierrc file
|
||||
tree.delete('.prettierrc');
|
||||
});
|
||||
|
||||
it('should install prettier package', async () => {
|
||||
await setupPrettierGenerator(tree, { skipFormat: true });
|
||||
|
||||
const packageJson = readJson(tree, 'package.json');
|
||||
expect(packageJson.devDependencies['prettier']).toBe(prettierVersion);
|
||||
});
|
||||
|
||||
it('should create .prettierrc and .prettierignore files', async () => {
|
||||
await setupPrettierGenerator(tree, { skipFormat: true });
|
||||
|
||||
const prettierrc = readJson(tree, '.prettierrc');
|
||||
expect(prettierrc).toEqual({ singleQuote: true });
|
||||
const prettierignore = tree.read('.prettierignore', 'utf-8');
|
||||
expect(prettierignore).toMatch(/\n\/coverage/);
|
||||
expect(prettierignore).toMatch(/\n\/dist/);
|
||||
expect(prettierignore).toMatch(/\n\/\.nx\/cache/);
|
||||
});
|
||||
|
||||
it('should not overwrite existing .prettierrc and .prettierignore files', async () => {
|
||||
writeJson(tree, '.prettierrc', { singleQuote: false });
|
||||
tree.write('.prettierignore', `# custom ignore file`);
|
||||
|
||||
await setupPrettierGenerator(tree, { skipFormat: true });
|
||||
|
||||
const prettierrc = readJson(tree, '.prettierrc');
|
||||
expect(prettierrc).toEqual({ singleQuote: false });
|
||||
const prettierignore = tree.read('.prettierignore', 'utf-8');
|
||||
expect(prettierignore).toContain('# custom ignore file');
|
||||
});
|
||||
|
||||
it('should not overwrite prettier configuration specified in other formats', async () => {
|
||||
tree.delete('.prettierrc');
|
||||
tree.delete('.prettierignore');
|
||||
tree.write('.prettierrc.js', `module.exports = { singleQuote: true };`);
|
||||
|
||||
await setupPrettierGenerator(tree, { skipFormat: true });
|
||||
|
||||
expect(tree.exists('.prettierrc')).toBeFalsy();
|
||||
expect(tree.exists('.prettierignore')).toBeTruthy();
|
||||
expect(tree.read('.prettierrc.js', 'utf-8')).toContain(
|
||||
`module.exports = { singleQuote: true };`
|
||||
);
|
||||
});
|
||||
|
||||
it('should add prettier vscode extension if .vscode/extensions.json file exists', async () => {
|
||||
// No existing recommendations
|
||||
writeJson(tree, '.vscode/extensions.json', {});
|
||||
|
||||
await setupPrettierGenerator(tree, { skipFormat: true });
|
||||
|
||||
let json = readJson(tree, '.vscode/extensions.json');
|
||||
expect(json).toEqual({
|
||||
recommendations: ['esbenp.prettier-vscode'],
|
||||
});
|
||||
|
||||
// Existing recommendations
|
||||
writeJson(tree, '.vscode/extensions.json', { recommendations: ['foo'] });
|
||||
|
||||
await setupPrettierGenerator(tree, { skipFormat: true });
|
||||
|
||||
json = readJson(tree, '.vscode/extensions.json');
|
||||
expect(json).toEqual({
|
||||
recommendations: ['foo', 'esbenp.prettier-vscode'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip adding prettier extension if .vscode/extensions.json file does not exist', async () => {
|
||||
await setupPrettierGenerator(tree, { skipFormat: true });
|
||||
|
||||
expect(tree.exists('.vscode/extensions.json')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
31
packages/js/src/generators/setup-prettier/generator.ts
Normal file
31
packages/js/src/generators/setup-prettier/generator.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import {
|
||||
ensurePackage,
|
||||
formatFiles,
|
||||
type GeneratorCallback,
|
||||
type Tree,
|
||||
} from '@nx/devkit';
|
||||
import { generatePrettierSetup } from '../../utils/prettier';
|
||||
import { prettierVersion } from '../../utils/versions';
|
||||
import type { GeneratorOptions } from './schema';
|
||||
|
||||
export async function setupPrettierGenerator(
|
||||
tree: Tree,
|
||||
options: GeneratorOptions
|
||||
): Promise<GeneratorCallback> {
|
||||
const prettierTask = generatePrettierSetup(tree, {
|
||||
skipPackageJson: options.skipPackageJson,
|
||||
});
|
||||
|
||||
if (!options.skipFormat) {
|
||||
if (!options.skipPackageJson) {
|
||||
ensurePackage('prettier', prettierVersion);
|
||||
}
|
||||
// even if skipPackageJson === true, we can safely run formatFiles, prettier might
|
||||
// have been installed earlier and if not, the formatFiles function still handles it
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
return prettierTask;
|
||||
}
|
||||
|
||||
export default setupPrettierGenerator;
|
||||
4
packages/js/src/generators/setup-prettier/schema.d.ts
vendored
Normal file
4
packages/js/src/generators/setup-prettier/schema.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
export interface GeneratorOptions {
|
||||
skipFormat?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
}
|
||||
22
packages/js/src/generators/setup-prettier/schema.json
Normal file
22
packages/js/src/generators/setup-prettier/schema.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxJsSetupPrettier",
|
||||
"title": "Setup Prettier",
|
||||
"description": "Setup Prettier as the formatting tool.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"skipFormat": {
|
||||
"description": "Skip formatting files.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"skipPackageJson": {
|
||||
"description": "Do not add dependencies to `package.json`.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"x-priority": "internal"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
@ -13,6 +13,7 @@ export * from './utils/package-json/update-package-json';
|
||||
export * from './utils/package-json/create-entry-points';
|
||||
export { libraryGenerator } from './generators/library/library';
|
||||
export { initGenerator } from './generators/init/init';
|
||||
export { setupPrettierGenerator } from './generators/setup-prettier/generator';
|
||||
export { setupVerdaccio } from './generators/setup-verdaccio/generator';
|
||||
export { isValidVariable } from './utils/is-valid-variable';
|
||||
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
stripIndents,
|
||||
updateJson,
|
||||
writeJson,
|
||||
type GeneratorCallback,
|
||||
type Tree,
|
||||
} from '@nx/devkit';
|
||||
import type { Options } from 'prettier';
|
||||
|
||||
let prettier: typeof import('prettier');
|
||||
try {
|
||||
prettier = require('prettier');
|
||||
} catch {}
|
||||
import { prettierVersion } from './versions';
|
||||
|
||||
export interface ExistingPrettierConfig {
|
||||
sourceFilepath: string;
|
||||
@ -11,9 +15,13 @@ export interface ExistingPrettierConfig {
|
||||
}
|
||||
|
||||
export async function resolveUserExistingPrettierConfig(): Promise<ExistingPrettierConfig | null> {
|
||||
if (!prettier) {
|
||||
let prettier: typeof import('prettier');
|
||||
try {
|
||||
prettier = require('prettier');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const filepath = await prettier.resolveConfigFile();
|
||||
if (!filepath) {
|
||||
@ -36,3 +44,55 @@ export async function resolveUserExistingPrettierConfig(): Promise<ExistingPrett
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function generatePrettierSetup(
|
||||
tree: Tree,
|
||||
options: { skipPackageJson?: boolean }
|
||||
): GeneratorCallback {
|
||||
// https://prettier.io/docs/en/configuration.html
|
||||
const prettierrcNameOptions = [
|
||||
'.prettierrc',
|
||||
'.prettierrc.json',
|
||||
'.prettierrc.yml',
|
||||
'.prettierrc.yaml',
|
||||
'.prettierrc.json5',
|
||||
'.prettierrc.js',
|
||||
'.prettierrc.cjs',
|
||||
'.prettierrc.mjs',
|
||||
'.prettierrc.toml',
|
||||
'prettier.config.js',
|
||||
'prettier.config.cjs',
|
||||
'prettier.config.mjs',
|
||||
];
|
||||
|
||||
if (prettierrcNameOptions.every((name) => !tree.exists(name))) {
|
||||
writeJson(tree, '.prettierrc', { singleQuote: true });
|
||||
}
|
||||
|
||||
if (!tree.exists('.prettierignore')) {
|
||||
tree.write(
|
||||
'.prettierignore',
|
||||
stripIndents`# Add files here to ignore them from prettier formatting
|
||||
/dist
|
||||
/coverage
|
||||
/.nx/cache
|
||||
/.nx/workspace-data
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
if (tree.exists('.vscode/extensions.json')) {
|
||||
updateJson(tree, '.vscode/extensions.json', (json) => {
|
||||
json.recommendations ??= [];
|
||||
const extension = 'esbenp.prettier-vscode';
|
||||
if (!json.recommendations.includes(extension)) {
|
||||
json.recommendations.push(extension);
|
||||
}
|
||||
return json;
|
||||
});
|
||||
}
|
||||
|
||||
return options.skipPackageJson
|
||||
? () => {}
|
||||
: addDependenciesToPackageJson(tree, {}, { prettier: prettierVersion });
|
||||
}
|
||||
|
||||
@ -12,14 +12,17 @@ async function main() {
|
||||
|
||||
// This assumes "<%= preset %>" and "<%= projectName %>" are at the same version
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const presetVersion = require('../package.json').version,
|
||||
const presetVersion = require('../package.json').version;
|
||||
|
||||
// TODO: update below to customize the workspace
|
||||
const { directory } = await createWorkspace(`<%= preset %>@${presetVersion}`, {
|
||||
name,
|
||||
nxCloud: 'skip',
|
||||
packageManager: 'npm',
|
||||
});
|
||||
const { directory } = await createWorkspace(
|
||||
`<%= preset %>@${presetVersion}`,
|
||||
{
|
||||
name,
|
||||
nxCloud: 'skip',
|
||||
packageManager: 'npm',
|
||||
}
|
||||
);
|
||||
|
||||
console.log(`Successfully created the workspace: ${directory}.`);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user