@leyman/nx-plugin 0.1.0 → 0.2.0
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.
- package/README.md +6 -4
- package/dist/generators/init.d.ts +3 -2
- package/dist/generators/init.js +77 -12
- package/dist/lib/versions.d.ts +2 -1
- package/dist/lib/versions.js +6 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -136,10 +136,12 @@ nx g @leyman/nx-plugin:init
|
|
|
136
136
|
|
|
137
137
|
Sets the workspace up for the plugin, leaving alone anything already set:
|
|
138
138
|
|
|
139
|
-
- `nx.json`: the plugin, and [nx-lifecycle](https://www.npmjs.com/package/nx-lifecycle)'s plugin with the stages install, prepare (generate, format), check (lint), build (run, post, sync), test (compile, run, report) and verify, and each target bound to one, under the name the options give it. `--lifecycle=false` leaves nx-lifecycle out.
|
|
140
|
-
- `devDependencies` of the root `package.json`: the plugin, `@leyman/configs`, `@biomejs/biome`, `@leyman/check-package`, `@nx/js` and `nx-lifecycle`.
|
|
139
|
+
- `nx.json`: the plugin, with `checkPackage.profile` `esm-only` for the packages the `package` generator writes, and [nx-lifecycle](https://www.npmjs.com/package/nx-lifecycle)'s plugin with the stages install, prepare (generate, format), check (lint), build (run, post, sync), test (compile, run, report) and verify, and each target bound to one, under the name the options give it. As every target is bound before any project has it, nx-lifecycle's `warnUnusedBindings` is off. `--lifecycle=false` leaves nx-lifecycle out.
|
|
140
|
+
- `devDependencies` of the root `package.json`: the plugin, `@leyman/configs`, `@biomejs/biome`, `@leyman/check-package`, `@nx/js`, `@typescript/native` (TypeScript 7, the `tsc` the targets run) and `nx-lifecycle`.
|
|
141
141
|
- `biome.json`, extending `@leyman/configs/biome.base.json`.
|
|
142
|
-
- `
|
|
142
|
+
- `tsconfig.json`, with no files, for Nx's TypeScript sync generator to keep a reference to each package in.
|
|
143
|
+
- `.gitignore`: `node_modules`, `dist`, `dist-test`, and what the targets and Nx write at the root (`.coverage`, `.package-surface`, `.nx/cache` and `.nx/workspace-data`). check-package reads it for the files a package doesn't publish.
|
|
144
|
+
- `pnpm-workspace.yaml`, if there is one: `injectWorkspacePackages: true` and `dedupeInjectedDeps: false`, so dependents get workspace packages as published, which `sync-injected` keeps current, and `allowBuilds` `false` for `nx` and `@swc/core`, whose install scripts pnpm otherwise stops on.
|
|
143
145
|
- `.claude/skills/principles/SKILL.md`, pointing coding agents at the [principles](https://jacobley.github.io/leyman/framework/principles) of the setup. `--skill=false` leaves it out.
|
|
144
146
|
|
|
145
147
|
Then it installs the dependencies, unless `--skipInstall`.
|
|
@@ -179,7 +181,7 @@ Set the ones a workspace always passes as defaults in `nx.json`:
|
|
|
179
181
|
}
|
|
180
182
|
```
|
|
181
183
|
|
|
182
|
-
Each dependency's version comes from the workspace if it can: `workspace:^` for one of its own packages, `catalog:` for one in the pnpm catalog (`catalog:node-{floor}` for a published package's `@types/node`), or the range another package already uses. Otherwise it is the version this plugin was published with, and a package with none of those fails.
|
|
184
|
+
Each dependency's version comes from the workspace if it can: `workspace:^` for one of its own packages, `catalog:` for one in the pnpm catalog (`catalog:node-{floor}` for a published package's `@types/node`), or the range another package (or the root `package.json`) already uses. Nx's own plugins, such as `@nx/js`, otherwise take the range of `nx`. Otherwise it is the version this plugin was published with, and a package with none of those fails.
|
|
183
185
|
|
|
184
186
|
## The targets executor
|
|
185
187
|
|
|
@@ -68,8 +68,9 @@ export declare const lifecycleBindings: {
|
|
|
68
68
|
};
|
|
69
69
|
/**
|
|
70
70
|
* Nx generator setting an Nx workspace up for the plugin: adds it (and nx-lifecycle, with stages and bindings for its
|
|
71
|
-
* targets) to `nx.json`, installs it and the tools its targets run at the root, extends the shared Biome config,
|
|
72
|
-
* pnpm inject workspace packages
|
|
71
|
+
* targets) to `nx.json`, installs it and the tools its targets run at the root, extends the shared Biome config, writes
|
|
72
|
+
* a root `tsconfig.json`, has pnpm inject workspace packages and skip install scripts the targets don't need, ignores
|
|
73
|
+
* what the targets write in `.gitignore`, and points coding agents at the principles. Run by `nx add @leyman/nx-plugin`.
|
|
73
74
|
* Leaves alone what is already set.
|
|
74
75
|
*
|
|
75
76
|
* @param tree - Nx's view of the files
|
package/dist/generators/init.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { installPackagesTask, readJson, readNxJson, writeJson } from '@nx/devkit';
|
|
2
|
-
import {
|
|
2
|
+
import { parseDocument, stringify } from 'yaml';
|
|
3
3
|
import { resolveOptions } from '../lib/options.js';
|
|
4
4
|
import { name as pluginName } from '../lib/plugin.js';
|
|
5
5
|
import { assertOptions } from '../lib/schema.js';
|
|
@@ -76,7 +76,12 @@ const rootDependencies = [
|
|
|
76
76
|
'@biomejs/biome',
|
|
77
77
|
'@leyman/check-package',
|
|
78
78
|
'@leyman/configs',
|
|
79
|
-
'@nx/js'
|
|
79
|
+
'@nx/js',
|
|
80
|
+
"@typescript/native"
|
|
81
|
+
];
|
|
82
|
+
const skippedBuilds = [
|
|
83
|
+
'@swc/core',
|
|
84
|
+
'nx'
|
|
80
85
|
];
|
|
81
86
|
const principlesSkill = `---
|
|
82
87
|
name: principles
|
|
@@ -95,25 +100,73 @@ injectWorkspacePackages: true
|
|
|
95
100
|
const dedupeSettings = `# Otherwise pnpm symlinks to the source again whenever peers allow it
|
|
96
101
|
dedupeInjectedDeps: false
|
|
97
102
|
`;
|
|
98
|
-
const
|
|
103
|
+
const ignoredPaths = [
|
|
104
|
+
'node_modules',
|
|
105
|
+
'dist',
|
|
106
|
+
'dist-test',
|
|
107
|
+
'/.coverage',
|
|
108
|
+
'/.package-surface',
|
|
109
|
+
'/.nx/cache',
|
|
110
|
+
'/.nx/workspace-data'
|
|
111
|
+
];
|
|
112
|
+
const ignoreOutputs = (tree)=>{
|
|
113
|
+
const gitignore = tree.read('.gitignore', 'utf8') ?? '';
|
|
114
|
+
const lines = new Set(gitignore.split('\n').map((line)=>line.trim()));
|
|
115
|
+
const missing = ignoredPaths.filter((path)=>!lines.has(path));
|
|
116
|
+
if (missing.length === 0) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const separator = gitignore === '' || gitignore.endsWith('\n') ? '' : '\n';
|
|
120
|
+
const added = missing.map((path)=>`${path}\n`).join('');
|
|
121
|
+
tree.write('.gitignore', `${gitignore}${separator}${added}`);
|
|
122
|
+
};
|
|
123
|
+
const configurePnpm = (tree)=>{
|
|
99
124
|
const yaml = tree.read('pnpm-workspace.yaml', 'utf8');
|
|
100
125
|
if (yaml === null) {
|
|
101
126
|
return;
|
|
102
127
|
}
|
|
103
|
-
const
|
|
128
|
+
const document = parseDocument(yaml);
|
|
129
|
+
const missingBuilds = skippedBuilds.filter((name)=>!document.hasIn([
|
|
130
|
+
'allowBuilds',
|
|
131
|
+
name
|
|
132
|
+
]));
|
|
133
|
+
const hasAllowBuilds = document.has('allowBuilds');
|
|
134
|
+
let updated = yaml;
|
|
135
|
+
if (hasAllowBuilds && missingBuilds.length > 0) {
|
|
136
|
+
for (const name of missingBuilds){
|
|
137
|
+
document.setIn([
|
|
138
|
+
'allowBuilds',
|
|
139
|
+
name
|
|
140
|
+
], false);
|
|
141
|
+
}
|
|
142
|
+
updated = document.toString({
|
|
143
|
+
singleQuote: true
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const allowBuilds = Object.fromEntries(missingBuilds.map((name)=>[
|
|
147
|
+
name,
|
|
148
|
+
false
|
|
149
|
+
]));
|
|
104
150
|
const added = [
|
|
105
|
-
...
|
|
151
|
+
...document.has('injectWorkspacePackages') ? [] : [
|
|
106
152
|
injectSettings
|
|
107
153
|
],
|
|
108
|
-
...
|
|
154
|
+
...document.has('dedupeInjectedDeps') ? [] : [
|
|
109
155
|
dedupeSettings
|
|
156
|
+
],
|
|
157
|
+
...hasAllowBuilds ? [] : [
|
|
158
|
+
`# Install scripts of the packages the targets run, which they don't need\n${stringify({
|
|
159
|
+
allowBuilds
|
|
160
|
+
}, {
|
|
161
|
+
singleQuote: true
|
|
162
|
+
})}`
|
|
110
163
|
]
|
|
111
164
|
];
|
|
112
|
-
if (added.length === 0) {
|
|
165
|
+
if (updated === yaml && added.length === 0) {
|
|
113
166
|
return;
|
|
114
167
|
}
|
|
115
|
-
const separator =
|
|
116
|
-
tree.write('pnpm-workspace.yaml', `${
|
|
168
|
+
const separator = updated === '' || updated.endsWith('\n') ? '' : '\n';
|
|
169
|
+
tree.write('pnpm-workspace.yaml', `${updated}${separator}${added.join('')}`);
|
|
117
170
|
};
|
|
118
171
|
const initGenerator = async (tree, options = {})=>{
|
|
119
172
|
const { lifecycle = true, skill = true } = options;
|
|
@@ -125,7 +178,11 @@ const initGenerator = async (tree, options = {})=>{
|
|
|
125
178
|
if (existing === undefined) {
|
|
126
179
|
plugins.push({
|
|
127
180
|
plugin: pluginName,
|
|
128
|
-
options: {
|
|
181
|
+
options: {
|
|
182
|
+
checkPackage: {
|
|
183
|
+
profile: 'esm-only'
|
|
184
|
+
}
|
|
185
|
+
}
|
|
129
186
|
});
|
|
130
187
|
}
|
|
131
188
|
const pluginOptions = typeof existing === 'object' ? existing.options ?? {} : {};
|
|
@@ -145,7 +202,8 @@ const initGenerator = async (tree, options = {})=>{
|
|
|
145
202
|
plugin: lifecyclePlugin,
|
|
146
203
|
options: {
|
|
147
204
|
stages: lifecycleStages,
|
|
148
|
-
bindings: Object.fromEntries(bindings)
|
|
205
|
+
bindings: Object.fromEntries(bindings),
|
|
206
|
+
warnUnusedBindings: false
|
|
149
207
|
}
|
|
150
208
|
});
|
|
151
209
|
}
|
|
@@ -178,7 +236,14 @@ const initGenerator = async (tree, options = {})=>{
|
|
|
178
236
|
]
|
|
179
237
|
});
|
|
180
238
|
}
|
|
181
|
-
|
|
239
|
+
if (!tree.exists('tsconfig.json')) {
|
|
240
|
+
writeJson(tree, 'tsconfig.json', {
|
|
241
|
+
files: [],
|
|
242
|
+
references: []
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
configurePnpm(tree);
|
|
246
|
+
ignoreOutputs(tree);
|
|
182
247
|
const skillPath = '.claude/skills/principles/SKILL.md';
|
|
183
248
|
if (skill && !tree.exists(skillPath)) {
|
|
184
249
|
tree.write(skillPath, principlesSkill);
|
package/dist/lib/versions.d.ts
CHANGED
|
@@ -30,7 +30,8 @@ export interface WorkspaceVersions {
|
|
|
30
30
|
export declare const readWorkspaceVersions: (tree: Tree) => Promise<WorkspaceVersions>;
|
|
31
31
|
/**
|
|
32
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
|
|
33
|
+
* catalog, else a range a workspace package uses, else, for one of Nx's own plugins (`@nx/*`), the range of `nx`,
|
|
34
|
+
* else the version this plugin was published with.
|
|
34
35
|
*
|
|
35
36
|
* @param name - the package
|
|
36
37
|
* @param versions - from {@link readWorkspaceVersions}
|
package/dist/lib/versions.js
CHANGED
|
@@ -13,13 +13,15 @@ const ownVersions = async ()=>{
|
|
|
13
13
|
export const readWorkspaceVersions = async (tree)=>{
|
|
14
14
|
const packages = new Set();
|
|
15
15
|
const ranges = new Map();
|
|
16
|
-
|
|
16
|
+
const roots = new Set(getProjects(tree).values().map(({ root })=>root));
|
|
17
|
+
roots.add('.');
|
|
18
|
+
for (const root of roots){
|
|
17
19
|
const path = joinPathFragments(root, 'package.json');
|
|
18
20
|
if (!tree.exists(path)) {
|
|
19
21
|
continue;
|
|
20
22
|
}
|
|
21
23
|
const packageJson = readJson(tree, path);
|
|
22
|
-
if (packageJson.name !== undefined) {
|
|
24
|
+
if (root !== '.' && packageJson.name !== undefined) {
|
|
23
25
|
packages.add(packageJson.name);
|
|
24
26
|
}
|
|
25
27
|
const entries = [
|
|
@@ -29,7 +31,7 @@ export const readWorkspaceVersions = async (tree)=>{
|
|
|
29
31
|
packageJson.peerDependencies
|
|
30
32
|
].flatMap((field)=>field === undefined ? [] : Object.entries(field));
|
|
31
33
|
for (const [name, range] of entries){
|
|
32
|
-
if (!ranges.has(name) && !range
|
|
34
|
+
if (!ranges.has(name) && !isProtocol(range)) {
|
|
33
35
|
ranges.set(name, range);
|
|
34
36
|
}
|
|
35
37
|
}
|
|
@@ -55,7 +57,7 @@ export const resolveVersion = (name, versions)=>{
|
|
|
55
57
|
if (versions.catalogs.default?.[name] !== undefined) {
|
|
56
58
|
return 'catalog:';
|
|
57
59
|
}
|
|
58
|
-
const range = versions.ranges.get(name) ?? versions.fallback[name];
|
|
60
|
+
const range = versions.ranges.get(name) ?? (name.startsWith('@nx/') ? versions.ranges.get('nx') ?? versions.catalogs.default?.nx : undefined) ?? versions.fallback[name];
|
|
59
61
|
if (range === undefined) {
|
|
60
62
|
throw new Error(`No version of ${name} in the workspace: add it to the pnpm catalog or to a package first`);
|
|
61
63
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leyman/nx-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Nx plugin inferring the build, test, lint and package-check targets of TypeScript ESM packages: SWC and tsc builds, Mocha or Vitest under c8, ESLint, Biome and check-package.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.13.0"
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"@biomejs/biome": "^2.6.4",
|
|
69
69
|
"@leyman/check-package": "^0.1.0",
|
|
70
70
|
"@leyman/configs": "^0.1.0",
|
|
71
|
-
"@leyman/eslint-config": "^0.1.
|
|
71
|
+
"@leyman/eslint-config": "^0.1.1",
|
|
72
72
|
"@leyman/expect": "^0.1.0",
|
|
73
73
|
"@nx/js": "^23.3.1",
|
|
74
74
|
"@swc/cli": "^0.8.1",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"mocha": "^12.0.3",
|
|
83
83
|
"mocha-chain": "^1.1.0",
|
|
84
84
|
"nx": "^23.3.1",
|
|
85
|
-
"nx-lifecycle": "^1.
|
|
85
|
+
"nx-lifecycle": "^1.3.0",
|
|
86
86
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
87
87
|
"vitest": "^5.0.3"
|
|
88
88
|
}
|