@leyman/nx-plugin 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,266 @@
1
+ const nodeVersion = {
2
+ runtime: 'node --version'
3
+ };
4
+ const declarationExtensions = [
5
+ 'd.ts',
6
+ 'd.mts',
7
+ 'd.cts'
8
+ ];
9
+ const builtExtensions = [
10
+ 'js',
11
+ 'mjs',
12
+ 'cjs',
13
+ ...declarationExtensions,
14
+ 'wasm'
15
+ ];
16
+ const quote = (argument)=>{
17
+ if (/^[\w%+,\-.\/:=@\{\}]+$/v.test(argument)) {
18
+ return argument;
19
+ }
20
+ const escaped = argument.replaceAll("'", String.raw`'\''`);
21
+ return `'${escaped}'`;
22
+ };
23
+ const projectName = '{projectName}';
24
+ const workspaceRoot = '{workspaceRoot}';
25
+ const tsc = '${TSC:-tsc}';
26
+ export const namedInputs = (options)=>{
27
+ const own = {
28
+ 'dependency-files': [
29
+ '{projectRoot}/executors.json',
30
+ '{projectRoot}/*.(c|m)?js',
31
+ '{projectRoot}/*.d.(c|m)?ts',
32
+ '{projectRoot}/data/**/*',
33
+ '{projectRoot}/out/**/*'
34
+ ],
35
+ 'package-files': [
36
+ '{projectRoot}/package.json',
37
+ 'dependency-files'
38
+ ],
39
+ 'eslint-input': [
40
+ '{projectRoot}/eslint.config.js'
41
+ ],
42
+ 'ts-source': [
43
+ nodeVersion,
44
+ ...options.inputs.configs,
45
+ ...options.inputs.tools,
46
+ '{projectRoot}/package.json',
47
+ '{projectRoot}/tsconfig.json',
48
+ '{projectRoot}/src/**/*',
49
+ {
50
+ externalDependencies: []
51
+ }
52
+ ],
53
+ 'dependency-builds': [
54
+ {
55
+ dependentTasksOutputFiles: '**/package-surface.json',
56
+ transitive: true
57
+ },
58
+ ...builtExtensions.map((extension)=>({
59
+ dependentTasksOutputFiles: `**/dist/**/*.${extension}`,
60
+ transitive: true
61
+ }))
62
+ ],
63
+ 'test-source': [
64
+ 'ts-source',
65
+ '{projectRoot}/test/**/*',
66
+ 'package-files',
67
+ 'dependency-builds'
68
+ ]
69
+ };
70
+ return Object.fromEntries(Object.entries(own).map(([name, inputs])=>[
71
+ name,
72
+ [
73
+ ...inputs,
74
+ ...options.namedInputs[name]
75
+ ]
76
+ ]));
77
+ };
78
+ const runCommands = (commands, options = {})=>({
79
+ executor: 'nx:run-commands',
80
+ options: {
81
+ commands,
82
+ ...options,
83
+ color: true,
84
+ cwd: '{projectRoot}'
85
+ }
86
+ });
87
+ const scriptFiles = {
88
+ 'biome-format': 'biome-format.sh',
89
+ 'clean-dist': 'clean-dist.sh',
90
+ 'coverage-report': 'coverage-report.sh',
91
+ 'swc-compile': 'swc-compile.sh',
92
+ 'sync-injected': 'sync-injected.mjs'
93
+ };
94
+ const coverageDirectory = `$NX_WORKSPACE_ROOT/.coverage/project/${projectName}/tmp`;
95
+ const testTarget = (kind, command)=>({
96
+ ...runCommands([
97
+ `rm -rf ${coverageDirectory}/${kind} && NODE_OPTIONS=--enable-source-maps c8 --temp-directory=${coverageDirectory}/${kind} --src=$NX_WORKSPACE_ROOT --clean=false --reporter=none ${command}`
98
+ ], {
99
+ parallel: true
100
+ }),
101
+ cache: true,
102
+ inputs: [
103
+ 'test-source',
104
+ '^dependency-files'
105
+ ],
106
+ outputs: [
107
+ `${workspaceRoot}/.coverage/project/${projectName}/tmp/${kind}`
108
+ ]
109
+ });
110
+ export const targetConfigurations = (options)=>{
111
+ const { checkPackage, configs, coverage, scripts } = options;
112
+ const script = (name)=>scripts === undefined ? `leyman-${name}` : `${scripts}/${scriptFiles[name]}`;
113
+ const thresholds = [
114
+ 'branches',
115
+ 'functions',
116
+ 'lines',
117
+ 'statements'
118
+ ].map((metric)=>{
119
+ const threshold = coverage[metric];
120
+ return threshold === undefined ? '' : ` --${metric} ${threshold}`;
121
+ }).join('');
122
+ const checkPackageFlags = [
123
+ ...checkPackage.profile === undefined ? [] : [
124
+ ` --profile ${checkPackage.profile}`
125
+ ],
126
+ ...checkPackage.floorTsconfig === false ? [] : [
127
+ ` --floor-tsconfig ${quote(checkPackage.floorTsconfig)}`
128
+ ]
129
+ ].join('');
130
+ return {
131
+ biome: {
132
+ ...runCommands([
133
+ script('biome-format')
134
+ ]),
135
+ configurations: {
136
+ check: {
137
+ commands: [
138
+ 'biome format .'
139
+ ]
140
+ }
141
+ },
142
+ cache: false
143
+ },
144
+ eslint: {
145
+ executor: 'nx:run-commands',
146
+ options: {
147
+ commands: [
148
+ '{projectRoot}/node_modules/.bin/eslint {projectRoot} --color'
149
+ ],
150
+ color: true
151
+ },
152
+ configurations: {
153
+ fix: {
154
+ fix: true
155
+ }
156
+ },
157
+ cache: true,
158
+ inputs: [
159
+ 'eslint-input',
160
+ 'ts-source',
161
+ '{projectRoot}/test/**/*',
162
+ 'package-files',
163
+ 'dependency-builds',
164
+ '^dependency-files'
165
+ ],
166
+ outputs: []
167
+ },
168
+ 'check-package': {
169
+ ...runCommands([
170
+ `check-package${checkPackageFlags}`
171
+ ]),
172
+ cache: true,
173
+ inputs: [
174
+ nodeVersion,
175
+ '{projectRoot}/**/*',
176
+ '!{projectRoot}/CHANGELOG.md',
177
+ '!{projectRoot}/eslint.config.js',
178
+ '!{projectRoot}/test/**/*',
179
+ '{workspaceRoot}/.gitignore',
180
+ 'dependency-builds',
181
+ ...checkPackage.inputs
182
+ ],
183
+ outputs: []
184
+ },
185
+ 'update-ts-references': {
186
+ executor: 'nx:noop',
187
+ syncGenerators: [
188
+ "@nx/js:typescript-sync"
189
+ ],
190
+ cache: true,
191
+ inputs: [
192
+ '{projectRoot}/package.json',
193
+ '{projectRoot}/tsconfig.json'
194
+ ],
195
+ outputs: [
196
+ '{projectRoot}/tsconfig.json'
197
+ ]
198
+ },
199
+ tsc: {
200
+ ...runCommands([
201
+ script('clean-dist'),
202
+ `${script('swc-compile')} ./src ./dist ${quote(configs.swc)}`,
203
+ tsc
204
+ ], {
205
+ parallel: false
206
+ }),
207
+ cache: true,
208
+ inputs: [
209
+ 'ts-source',
210
+ 'dependency-builds',
211
+ '^dependency-files'
212
+ ],
213
+ outputs: [
214
+ '{projectRoot}/dist'
215
+ ]
216
+ },
217
+ 'tsc-test': {
218
+ ...runCommands([
219
+ 'rm -rf ./dist-test',
220
+ `${script('swc-compile')} ./test ./dist-test ${quote(configs.swc)}`,
221
+ `${tsc} -p ./test`
222
+ ], {
223
+ parallel: false
224
+ }),
225
+ cache: true,
226
+ inputs: [
227
+ 'test-source',
228
+ '^dependency-files'
229
+ ],
230
+ outputs: [
231
+ '{projectRoot}/dist-test'
232
+ ]
233
+ },
234
+ 'mocha-unit-test': testTarget('unit', 'mocha --recursive "$PWD/dist-test/unit/**/*.spec.*js"'),
235
+ 'mocha-integration-test': testTarget('integration', 'mocha --timeout 10000 --recursive "$PWD/dist-test/integration/**/*.spec.*js"'),
236
+ 'vitest-unit-test': testTarget('unit', `vitest run --config ${quote(configs.vitest)} --root .`),
237
+ 'coverage-report': {
238
+ ...runCommands([
239
+ `${script('coverage-report')} ${quote(configs.c8)}${thresholds}`
240
+ ]),
241
+ cache: true,
242
+ inputs: [
243
+ nodeVersion,
244
+ 'ts-source',
245
+ {
246
+ dependentTasksOutputFiles: '.coverage/project/**/*.json',
247
+ transitive: true
248
+ }
249
+ ],
250
+ outputs: [
251
+ '{workspaceRoot}/.coverage/project/{projectName}/report'
252
+ ]
253
+ },
254
+ 'sync-injected': {
255
+ ...runCommands([
256
+ script('sync-injected')
257
+ ]),
258
+ cache: false,
259
+ outputs: [
260
+ '{workspaceRoot}/.package-surface/{projectName}/package-surface.json'
261
+ ]
262
+ }
263
+ };
264
+ };
265
+
266
+ //# sourceMappingURL=targets.js.map
@@ -0,0 +1,42 @@
1
+ import type { Tree } from '@nx/devkit';
2
+ type Dependencies = Readonly<Record<string, string>>;
3
+ /**
4
+ * Where a generator finds the version of a dependency it adds.
5
+ */
6
+ export interface WorkspaceVersions {
7
+ /**
8
+ * Names of the workspace's own packages.
9
+ */
10
+ readonly packages: ReadonlySet<string>;
11
+ /**
12
+ * Each pnpm catalog, by name, with `default` for `catalog:`.
13
+ */
14
+ readonly catalogs: Readonly<Record<string, Dependencies>>;
15
+ /**
16
+ * A range a workspace package already depends on each package by.
17
+ */
18
+ readonly ranges: ReadonlyMap<string, string>;
19
+ /**
20
+ * Versions this plugin was published with, as a last resort.
21
+ */
22
+ readonly fallback: Dependencies;
23
+ }
24
+ /**
25
+ * Reads the workspace's packages, pnpm catalogs and dependency ranges, for {@link resolveVersion}.
26
+ *
27
+ * @param tree - Nx's view of the files
28
+ * @returns the sources `resolveVersion` picks from
29
+ */
30
+ export declare const readWorkspaceVersions: (tree: Tree) => Promise<WorkspaceVersions>;
31
+ /**
32
+ * The range to depend on a package by: `workspace:^` for one of the workspace's own, else `catalog:` from the default
33
+ * catalog, else a range another workspace package uses, else the version this plugin was published with.
34
+ *
35
+ * @param name - the package
36
+ * @param versions - from {@link readWorkspaceVersions}
37
+ * @returns the range, as `package.json` writes it
38
+ * @throws {Error} when nothing has a version of the package
39
+ */
40
+ export declare const resolveVersion: (name: string, versions: WorkspaceVersions) => string;
41
+ export {};
42
+ //# sourceMappingURL=versions.d.ts.map
@@ -0,0 +1,65 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { getProjects, joinPathFragments, readJson } from '@nx/devkit';
3
+ import { parse } from 'yaml';
4
+ const isProtocol = (range)=>/^(?:catalog|workspace):/v.test(range);
5
+ const ownVersions = async ()=>{
6
+ const path = new URL(import.meta.resolve('@leyman/nx-plugin/package.json'));
7
+ const { name, version, devDependencies = {} } = JSON.parse(await readFile(path, 'utf8'));
8
+ return {
9
+ ...Object.fromEntries(Object.entries(devDependencies).filter(([, range])=>!isProtocol(range))),
10
+ [name]: `^${version}`
11
+ };
12
+ };
13
+ export const readWorkspaceVersions = async (tree)=>{
14
+ const packages = new Set();
15
+ const ranges = new Map();
16
+ for (const { root } of getProjects(tree).values()){
17
+ const path = joinPathFragments(root, 'package.json');
18
+ if (!tree.exists(path)) {
19
+ continue;
20
+ }
21
+ const packageJson = readJson(tree, path);
22
+ if (packageJson.name !== undefined) {
23
+ packages.add(packageJson.name);
24
+ }
25
+ const entries = [
26
+ packageJson.dependencies,
27
+ packageJson.devDependencies,
28
+ packageJson.optionalDependencies,
29
+ packageJson.peerDependencies
30
+ ].flatMap((field)=>field === undefined ? [] : Object.entries(field));
31
+ for (const [name, range] of entries){
32
+ if (!ranges.has(name) && !range.startsWith('workspace:')) {
33
+ ranges.set(name, range);
34
+ }
35
+ }
36
+ }
37
+ const workspaceYaml = tree.read('pnpm-workspace.yaml', 'utf8');
38
+ const { catalog, catalogs } = workspaceYaml === null ? {} : parse(workspaceYaml) ?? {};
39
+ return {
40
+ packages,
41
+ ranges,
42
+ catalogs: {
43
+ ...catalogs,
44
+ ...catalog !== undefined && {
45
+ default: catalog
46
+ }
47
+ },
48
+ fallback: await ownVersions()
49
+ };
50
+ };
51
+ export const resolveVersion = (name, versions)=>{
52
+ if (versions.packages.has(name)) {
53
+ return 'workspace:^';
54
+ }
55
+ if (versions.catalogs.default?.[name] !== undefined) {
56
+ return 'catalog:';
57
+ }
58
+ const range = versions.ranges.get(name) ?? versions.fallback[name];
59
+ if (range === undefined) {
60
+ throw new Error(`No version of ${name} in the workspace: add it to the pnpm catalog or to a package first`);
61
+ }
62
+ return range;
63
+ };
64
+
65
+ //# sourceMappingURL=versions.js.map
package/executors.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "executors": {
3
+ "targets": {
4
+ "implementation": "./dist/executors/targets.js",
5
+ "schema": "./out/executors/targets/schema.json",
6
+ "description": "Writes the plugin's named inputs and target defaults into nx.json, and the targets each project gets into its project.json."
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "generators": {
3
+ "init": {
4
+ "factory": "./dist/generators/init.js",
5
+ "schema": "./out/generators/init/schema.json",
6
+ "description": "Sets an Nx workspace up for the plugin: nx.json, root devDependencies, the Biome config, pnpm injection and the principles skill."
7
+ },
8
+ "package": {
9
+ "factory": "./dist/generators/package.js",
10
+ "schema": "./out/generators/package/schema.json",
11
+ "description": "Adds a TypeScript ESM package, with a project.json declaring the targets the plugin infers."
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "version": 1,
3
+ "outputCapture": "direct-nodejs",
4
+ "cli": "nx",
5
+ "type": "object",
6
+ "title": "targets",
7
+ "description": "Writes the plugin's named inputs and target defaults into nx.json, and the targets each project gets into its project.json",
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "config": {
11
+ "type": "string",
12
+ "description": "Path of the file holding the plugin options, relative to the workspace root"
13
+ },
14
+ "check": {
15
+ "type": "boolean",
16
+ "description": "Fail rather than write when a file is out of date. Defaults to true in CI"
17
+ }
18
+ },
19
+ "required": [
20
+ "config"
21
+ ]
22
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "cli": "nx",
3
+ "type": "object",
4
+ "title": "init",
5
+ "description": "Sets an Nx workspace up for the plugin: nx.json, root devDependencies, the Biome config, pnpm injection and the principles skill",
6
+ "additionalProperties": false,
7
+ "properties": {
8
+ "lifecycle": {
9
+ "type": "boolean",
10
+ "default": true,
11
+ "description": "Wire the targets' order with nx-lifecycle"
12
+ },
13
+ "skill": {
14
+ "type": "boolean",
15
+ "default": true,
16
+ "description": "Write a .claude/skills/principles/SKILL.md pointing coding agents at the principles behind the setup"
17
+ },
18
+ "skipInstall": {
19
+ "type": "boolean",
20
+ "default": false,
21
+ "description": "Don't install dependencies afterwards"
22
+ }
23
+ },
24
+ "$schema": "https://json-schema.org/draft/2020-12/schema"
25
+ }
@@ -0,0 +1,79 @@
1
+ {
2
+ "cli": "nx",
3
+ "type": "object",
4
+ "title": "package",
5
+ "description": "Adds a TypeScript ESM package, with a project.json declaring the targets the plugin infers",
6
+ "additionalProperties": false,
7
+ "properties": {
8
+ "directory": {
9
+ "$default": {
10
+ "$source": "argv",
11
+ "index": 0
12
+ },
13
+ "x-prompt": "Which directory should the package go in?",
14
+ "type": "string",
15
+ "description": "Where the package goes, relative to the workspace root"
16
+ },
17
+ "name": {
18
+ "type": "string",
19
+ "description": "Name of the package. Defaults to the directory's last segment"
20
+ },
21
+ "description": {
22
+ "x-prompt": "What does the package do?",
23
+ "type": "string",
24
+ "description": "Description, for package.json and the README"
25
+ },
26
+ "published": {
27
+ "type": "boolean",
28
+ "default": false,
29
+ "description": "Published to npm, rather than private"
30
+ },
31
+ "floor": {
32
+ "type": "integer",
33
+ "default": 22,
34
+ "description": "Major of the oldest Node.js a published package supports"
35
+ },
36
+ "test": {
37
+ "default": "mocha",
38
+ "description": "Test runner, or none",
39
+ "enum": [
40
+ "mocha",
41
+ "vitest",
42
+ "none"
43
+ ]
44
+ },
45
+ "repository": {
46
+ "type": "string",
47
+ "description": "URL of the workspace's GitHub repository, for a published package's repository, homepage and bugs"
48
+ },
49
+ "author": {
50
+ "type": "string",
51
+ "description": "author of package.json"
52
+ },
53
+ "license": {
54
+ "type": "string",
55
+ "description": "license of package.json. A published package also gets a copy of the workspace's LICENSE"
56
+ },
57
+ "devDependencies": {
58
+ "type": "array",
59
+ "description": "More packages to add to devDependencies",
60
+ "items": {
61
+ "type": "string"
62
+ }
63
+ },
64
+ "config": {
65
+ "type": "string",
66
+ "description": "File of the plugin options, relative to the workspace root, for a workspace running the targets executor. Defaults to the options in nx.json"
67
+ },
68
+ "skipInstall": {
69
+ "type": "boolean",
70
+ "default": false,
71
+ "description": "Don't install dependencies afterwards"
72
+ }
73
+ },
74
+ "required": [
75
+ "directory",
76
+ "description"
77
+ ],
78
+ "$schema": "https://json-schema.org/draft/2020-12/schema"
79
+ }