Skip to content

Commit

Permalink
feat(js): add the setup-prettier generator (#27996)
Browse files Browse the repository at this point in the history
<!-- 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 #
  • Loading branch information
leosvelperez authored Sep 24, 2024
1 parent 3e1a879 commit 72cd1c1
Show file tree
Hide file tree
Showing 14 changed files with 298 additions and 70 deletions.
8 changes: 8 additions & 0 deletions docs/generated/manifests/menus.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 9 additions & 0 deletions docs/generated/manifests/nx-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
9 changes: 9 additions & 0 deletions docs/generated/packages-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
33 changes: 33 additions & 0 deletions docs/generated/packages/js/generators/setup-prettier.json
Original file line number Diff line number Diff line change
@@ -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"
}
1 change: 1 addition & 0 deletions docs/shared/reference/sitemap.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions packages/js/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
}
}
72 changes: 14 additions & 58 deletions packages/js/src/generators/init/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -24,7 +24,6 @@ import {
typescriptVersion,
} from '../../utils/versions';
import { InitSchema } from './schema';
import { join } from 'path';

async function getInstalledTypescriptVersion(
tree: Tree
Expand Down Expand Up @@ -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
Expand All @@ -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 changes: 86 additions & 0 deletions packages/js/src/generators/setup-prettier/generator.spec.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 31 additions & 0 deletions packages/js/src/generators/setup-prettier/generator.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 4 additions & 0 deletions packages/js/src/generators/setup-prettier/schema.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface GeneratorOptions {
skipFormat?: boolean;
skipPackageJson?: boolean;
}
22 changes: 22 additions & 0 deletions packages/js/src/generators/setup-prettier/schema.json
Original file line number Diff line number Diff line change
@@ -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": []
}
1 change: 1 addition & 0 deletions packages/js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
Loading

0 comments on commit 72cd1c1

Please sign in to comment.