@nx/js 24.0.0-beta.2 → 24.0.0-canary.20261016-5ef7804
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/dist/src/plugins/scripts/create-script-nodes.d.ts +6 -2
- package/dist/src/plugins/scripts/create-script-nodes.js +18 -71
- package/dist/src/plugins/scripts/node-plugin.d.ts +2 -1
- package/dist/src/plugins/scripts/shebang.d.ts +7 -2
- package/dist/src/plugins/scripts/shebang.js +27 -10
- package/dist/src/plugins/scripts/target-name.d.ts +9 -0
- package/dist/src/plugins/scripts/target-name.js +19 -0
- package/dist/src/plugins/scripts/tsx-plugin.d.ts +3 -2
- package/package.json +11 -11
- package/dist/src/plugins/scripts/frontmatter.d.ts +0 -6
- package/dist/src/plugins/scripts/frontmatter.js +0 -52
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { type CreateNodes, type TargetConfiguration } from '@nx/devkit';
|
|
2
2
|
import { type Shebang } from './shebang';
|
|
3
|
-
export interface
|
|
3
|
+
export interface ScriptPluginOptions {
|
|
4
|
+
/** Runs claimed scripts in watch mode as continuous targets. */
|
|
5
|
+
watch?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ScriptPluginDefinition<T extends ScriptPluginOptions> {
|
|
4
8
|
/** Prefix of the plugin's cache file. */
|
|
5
9
|
cacheName: string;
|
|
6
10
|
technology: string;
|
|
@@ -12,7 +16,7 @@ export interface ScriptPluginDefinition<T> {
|
|
|
12
16
|
extraTargets?(projectRoot: string, workspaceRoot: string, options: T): Promise<ScriptTargets>;
|
|
13
17
|
}
|
|
14
18
|
export type ScriptTargets = Record<string, TargetConfiguration>;
|
|
15
|
-
export declare function createScriptNodes<T extends
|
|
19
|
+
export declare function createScriptNodes<T extends ScriptPluginOptions>(definition: ScriptPluginDefinition<T>): CreateNodes<T>;
|
|
16
20
|
/**
|
|
17
21
|
* Quotes a path for a shell command when the shell would split or expand it.
|
|
18
22
|
* Double quotes keep spaces intact in sh and cmd.exe; cmd.exe still expands `%VAR%`.
|
|
@@ -2,14 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createScriptNodes = createScriptNodes;
|
|
4
4
|
exports.quoteShellArg = quoteShellArg;
|
|
5
|
-
const devkit_1 = require("@nx/devkit");
|
|
6
5
|
const internal_1 = require("@nx/devkit/internal");
|
|
7
|
-
const node_fs_1 = require("node:fs");
|
|
8
6
|
const node_path_1 = require("node:path");
|
|
9
|
-
const frontmatter_1 = require("./frontmatter");
|
|
10
7
|
const shebang_1 = require("./shebang");
|
|
11
|
-
|
|
12
|
-
const RESERVED_KEYS = ['name', 'description', 'disable', 'watch', 'cwd'];
|
|
8
|
+
const target_name_1 = require("./target-name");
|
|
13
9
|
const SCRIPT_GLOB = '**/*.{js,cjs,mjs,ts,cts,mts}';
|
|
14
10
|
const PROJECT_MARKERS = ['package.json', 'project.json'];
|
|
15
11
|
function createScriptNodes(definition) {
|
|
@@ -36,7 +32,7 @@ function createScriptNodes(definition) {
|
|
|
36
32
|
if (!cache.has(key)) {
|
|
37
33
|
cache.set(key, {
|
|
38
34
|
...(await definition.extraTargets?.(projectRoot, context.workspaceRoot, options)),
|
|
39
|
-
...createTargets(definition, context.workspaceRoot, projectRoot, scripts),
|
|
35
|
+
...createTargets(definition, context.workspaceRoot, projectRoot, scripts, options?.watch === true),
|
|
40
36
|
});
|
|
41
37
|
}
|
|
42
38
|
const targets = cache.get(key);
|
|
@@ -91,31 +87,23 @@ function groupScriptsByProjectRoot(files, includeAllRoots) {
|
|
|
91
87
|
}
|
|
92
88
|
return byRoot;
|
|
93
89
|
}
|
|
94
|
-
function createTargets(definition, workspaceRoot, projectRoot, scripts) {
|
|
90
|
+
function createTargets(definition, workspaceRoot, projectRoot, scripts, watch) {
|
|
95
91
|
const claimed = [];
|
|
96
92
|
for (const script of scripts) {
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const shebang = line === null ? null : (0, shebang_1.parseShebang)(line);
|
|
93
|
+
const header = (0, shebang_1.readScriptHeader)((0, node_path_1.join)(workspaceRoot, script));
|
|
94
|
+
const shebang = header === null ? null : (0, shebang_1.parseShebang)((0, shebang_1.getShebangLine)(header));
|
|
100
95
|
if (!shebang || !definition.claims(shebang)) {
|
|
101
96
|
continue;
|
|
102
97
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
catch (e) {
|
|
108
|
-
devkit_1.logger.warn(`Ignoring the nx frontmatter in ${script}: ${e.message}`);
|
|
109
|
-
}
|
|
110
|
-
frontmatter = dropInvalidKeys(frontmatter, script);
|
|
111
|
-
if (frontmatter.disable === true) {
|
|
112
|
-
continue;
|
|
98
|
+
const { name, warnings } = (0, target_name_1.parseTargetName)(header);
|
|
99
|
+
for (const warning of warnings) {
|
|
100
|
+
(0, internal_1.emitPluginWorkerLog)('warn', `${script}: ${warning}.`);
|
|
113
101
|
}
|
|
114
102
|
claimed.push({
|
|
115
103
|
path: script,
|
|
116
104
|
relativePath: node_path_1.posix.relative(projectRoot, script),
|
|
117
105
|
shebang,
|
|
118
|
-
|
|
106
|
+
targetName: name,
|
|
119
107
|
});
|
|
120
108
|
}
|
|
121
109
|
const baseNameCounts = new Map();
|
|
@@ -126,67 +114,26 @@ function createTargets(definition, workspaceRoot, projectRoot, scripts) {
|
|
|
126
114
|
const targets = {};
|
|
127
115
|
for (const script of claimed) {
|
|
128
116
|
const baseName = withoutExtension((0, node_path_1.basename)(script.relativePath));
|
|
129
|
-
const name =
|
|
130
|
-
|
|
131
|
-
: baseNameCounts.get(baseName) > 1
|
|
117
|
+
const name = script.targetName ??
|
|
118
|
+
(baseNameCounts.get(baseName) > 1
|
|
132
119
|
? withoutExtension(script.relativePath)
|
|
133
|
-
: baseName;
|
|
134
|
-
targets[name] = createTarget(definition, projectRoot, script);
|
|
120
|
+
: baseName);
|
|
121
|
+
targets[name] = createTarget(definition, projectRoot, script, watch);
|
|
135
122
|
}
|
|
136
123
|
return targets;
|
|
137
124
|
}
|
|
138
|
-
function createTarget(definition, projectRoot, { path, relativePath, shebang,
|
|
139
|
-
const { description, watch, cwd, options, metadata } = frontmatter;
|
|
140
|
-
const rest = { ...frontmatter };
|
|
141
|
-
for (const key of [...RESERVED_KEYS, 'options', 'metadata']) {
|
|
142
|
-
delete rest[key];
|
|
143
|
-
}
|
|
144
|
-
const targetCwd = cwd ?? (typeof options?.cwd === 'string' ? options.cwd : projectRoot);
|
|
125
|
+
function createTarget(definition, projectRoot, { path, relativePath, shebang }, watch) {
|
|
145
126
|
return {
|
|
146
|
-
command: definition.command(quoteShellArg(node_path_1.posix.relative(
|
|
147
|
-
options: {
|
|
127
|
+
command: definition.command(quoteShellArg(node_path_1.posix.relative(projectRoot, path)), shebang, watch),
|
|
128
|
+
options: { cwd: projectRoot },
|
|
148
129
|
cache: false,
|
|
149
|
-
...(watch
|
|
130
|
+
...(watch && { continuous: true }),
|
|
150
131
|
metadata: {
|
|
151
132
|
technologies: [definition.technology],
|
|
152
|
-
description:
|
|
153
|
-
...metadata,
|
|
133
|
+
description: `Runs ${relativePath}`,
|
|
154
134
|
},
|
|
155
|
-
...rest,
|
|
156
135
|
};
|
|
157
136
|
}
|
|
158
|
-
const EXPECTED_TYPES = {
|
|
159
|
-
name: 'string',
|
|
160
|
-
description: 'string',
|
|
161
|
-
cwd: 'string',
|
|
162
|
-
cache: 'boolean',
|
|
163
|
-
continuous: 'boolean',
|
|
164
|
-
watch: 'boolean',
|
|
165
|
-
disable: 'boolean',
|
|
166
|
-
dependsOn: 'array',
|
|
167
|
-
inputs: 'array',
|
|
168
|
-
outputs: 'array',
|
|
169
|
-
options: 'object',
|
|
170
|
-
metadata: 'object',
|
|
171
|
-
};
|
|
172
|
-
/** Drops known keys with the wrong type so they cannot break the project graph. */
|
|
173
|
-
function dropInvalidKeys(frontmatter, script) {
|
|
174
|
-
const valid = {};
|
|
175
|
-
for (const [key, value] of Object.entries(frontmatter)) {
|
|
176
|
-
const expected = EXPECTED_TYPES[key];
|
|
177
|
-
const actual = Array.isArray(value)
|
|
178
|
-
? 'array'
|
|
179
|
-
: value === null
|
|
180
|
-
? 'null'
|
|
181
|
-
: typeof value;
|
|
182
|
-
if (expected && actual !== expected) {
|
|
183
|
-
devkit_1.logger.warn(`Ignoring nx frontmatter key "${key}" in ${script}: expected ${expected}, got ${actual}.`);
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
valid[key] = value;
|
|
187
|
-
}
|
|
188
|
-
return valid;
|
|
189
|
-
}
|
|
190
137
|
/**
|
|
191
138
|
* Quotes a path for a shell command when the shell would split or expand it.
|
|
192
139
|
* Double quotes keep spaces intact in sh and cmd.exe; cmd.exe still expands `%VAR%`.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { type ScriptPluginOptions } from './create-script-nodes';
|
|
2
|
+
export interface NodeScriptsPluginOptions extends ScriptPluginOptions {
|
|
2
3
|
/**
|
|
3
4
|
* `dependsOn` for `bin` targets whose file is build output (not a tracked
|
|
4
5
|
* workspace file). Defaults to `["build"]`.
|
|
@@ -4,8 +4,13 @@ export interface Shebang {
|
|
|
4
4
|
/** Arguments that follow the interpreter on the shebang line. */
|
|
5
5
|
args: string[];
|
|
6
6
|
}
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Returns the first bytes of a script that starts with `#!`, or `null` when it
|
|
9
|
+
* has no shebang or cannot be read (e.g. a tracked broken symlink).
|
|
10
|
+
*/
|
|
11
|
+
export declare function readScriptHeader(absolutePath: string): string | null;
|
|
12
|
+
/** The shebang line of a header from `readScriptHeader`, without `#!`. */
|
|
13
|
+
export declare function getShebangLine(header: string): string;
|
|
9
14
|
/**
|
|
10
15
|
* Handles `/usr/bin/env node`, `/usr/bin/env -S node --flags`,
|
|
11
16
|
* `/usr/bin/env -S npx tsx` and direct paths such as `/usr/local/bin/node`.
|
|
@@ -1,29 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.readScriptHeader = readScriptHeader;
|
|
4
|
+
exports.getShebangLine = getShebangLine;
|
|
4
5
|
exports.parseShebang = parseShebang;
|
|
5
6
|
exports.stripBom = stripBom;
|
|
6
7
|
const node_fs_1 = require("node:fs");
|
|
7
8
|
const node_path_1 = require("node:path");
|
|
8
9
|
const SNIFF_BYTES = 512;
|
|
9
10
|
const PACKAGE_RUNNERS = new Set(['npx', 'pnpx', 'bunx']);
|
|
10
|
-
/**
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Returns the first bytes of a script that starts with `#!`, or `null` when it
|
|
13
|
+
* has no shebang or cannot be read (e.g. a tracked broken symlink).
|
|
14
|
+
*/
|
|
15
|
+
function readScriptHeader(absolutePath) {
|
|
12
16
|
const buffer = Buffer.alloc(SNIFF_BYTES);
|
|
13
|
-
const fd = (0, node_fs_1.openSync)(absolutePath, 'r');
|
|
14
17
|
let bytesRead;
|
|
15
18
|
try {
|
|
16
|
-
|
|
19
|
+
const fd = (0, node_fs_1.openSync)(absolutePath, 'r');
|
|
20
|
+
try {
|
|
21
|
+
bytesRead = (0, node_fs_1.readSync)(fd, buffer, 0, SNIFF_BYTES, 0);
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
(0, node_fs_1.closeSync)(fd);
|
|
25
|
+
}
|
|
17
26
|
}
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
20
29
|
}
|
|
21
|
-
|
|
30
|
+
let text = stripBom(buffer.toString('utf8', 0, bytesRead));
|
|
22
31
|
if (!text.startsWith('#!')) {
|
|
23
32
|
return null;
|
|
24
33
|
}
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
// Drop a line the sniff cut off, so a directive is never read truncated.
|
|
35
|
+
if (bytesRead === SNIFF_BYTES && text.includes('\n')) {
|
|
36
|
+
text = text.slice(0, text.lastIndexOf('\n'));
|
|
37
|
+
}
|
|
38
|
+
return text;
|
|
39
|
+
}
|
|
40
|
+
/** The shebang line of a header from `readScriptHeader`, without `#!`. */
|
|
41
|
+
function getShebangLine(header) {
|
|
42
|
+
const end = header.search(/\r?\n/);
|
|
43
|
+
return (end === -1 ? header : header.slice(0, end)).slice(2).trim();
|
|
27
44
|
}
|
|
28
45
|
/**
|
|
29
46
|
* Handles `/usr/bin/env node`, `/usr/bin/env -S node --flags`,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface TargetNameDirective {
|
|
2
|
+
name?: string;
|
|
3
|
+
warnings: string[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Reads `@nx-target-name: <name>` from the comment block that opens the
|
|
7
|
+
* script. Only the first directive counts; problems come back as warnings.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseTargetName(header: string): TargetNameDirective;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseTargetName = parseTargetName;
|
|
4
|
+
const internal_1 = require("@nx/devkit/internal");
|
|
5
|
+
/**
|
|
6
|
+
* Reads `@nx-target-name: <name>` from the comment block that opens the
|
|
7
|
+
* script. Only the first directive counts; problems come back as warnings.
|
|
8
|
+
*/
|
|
9
|
+
function parseTargetName(header) {
|
|
10
|
+
const [value, ...extra] = (0, internal_1.parseNxDirectives)(header).get('target-name') ?? [];
|
|
11
|
+
const warnings = extra.map((v) => `ignoring the extra "@nx-target-name: ${v}" directive`);
|
|
12
|
+
if (value === undefined) {
|
|
13
|
+
return { warnings };
|
|
14
|
+
}
|
|
15
|
+
if (!value) {
|
|
16
|
+
return { warnings: ['"@nx-target-name" is empty', ...warnings] };
|
|
17
|
+
}
|
|
18
|
+
return { name: value, warnings };
|
|
19
|
+
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
1
|
+
import { type ScriptPluginOptions } from './create-script-nodes';
|
|
2
|
+
export declare const createNodes: import("@nx/devkit").CreateNodes<ScriptPluginOptions>;
|
|
3
|
+
export declare const createNodesV2: import("@nx/devkit").CreateNodes<ScriptPluginOptions>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nx/js",
|
|
3
|
-
"version": "24.0.0-
|
|
3
|
+
"version": "24.0.0-canary.20261016-5ef7804",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"files": [
|
|
@@ -154,8 +154,8 @@
|
|
|
154
154
|
"@babel/preset-env": "^7.23.2",
|
|
155
155
|
"@babel/preset-typescript": "^7.22.5",
|
|
156
156
|
"@babel/runtime": "^7.22.6",
|
|
157
|
-
"@nx/devkit": "24.0.0-
|
|
158
|
-
"@nx/workspace": "24.0.0-
|
|
157
|
+
"@nx/devkit": "24.0.0-canary.20261016-5ef7804",
|
|
158
|
+
"@nx/workspace": "24.0.0-canary.20261016-5ef7804",
|
|
159
159
|
"@zkochan/js-yaml": "0.0.7",
|
|
160
160
|
"babel-plugin-const-enum": "^1.0.1",
|
|
161
161
|
"babel-plugin-macros": "^3.1.0",
|
|
@@ -175,14 +175,14 @@
|
|
|
175
175
|
"tslib": "^2.3.0"
|
|
176
176
|
},
|
|
177
177
|
"devDependencies": {
|
|
178
|
-
"@nx/esbuild": "24.0.0-
|
|
179
|
-
"@nx/eslint": "24.0.0-
|
|
180
|
-
"@nx/jest": "24.0.0-
|
|
181
|
-
"@nx/oxlint": "24.0.0-
|
|
182
|
-
"@nx/rollup": "24.0.0-
|
|
183
|
-
"@nx/vite": "24.0.0-
|
|
184
|
-
"@nx/vitest": "24.0.0-
|
|
185
|
-
"nx": "24.0.0-
|
|
178
|
+
"@nx/esbuild": "24.0.0-canary.20261016-5ef7804",
|
|
179
|
+
"@nx/eslint": "24.0.0-canary.20261016-5ef7804",
|
|
180
|
+
"@nx/jest": "24.0.0-canary.20261016-5ef7804",
|
|
181
|
+
"@nx/oxlint": "24.0.0-canary.20261016-5ef7804",
|
|
182
|
+
"@nx/rollup": "24.0.0-canary.20261016-5ef7804",
|
|
183
|
+
"@nx/vite": "24.0.0-canary.20261016-5ef7804",
|
|
184
|
+
"@nx/vitest": "24.0.0-canary.20261016-5ef7804",
|
|
185
|
+
"nx": "24.0.0-canary.20261016-5ef7804"
|
|
186
186
|
},
|
|
187
187
|
"peerDependencies": {
|
|
188
188
|
"@swc/cli": ">=0.6.0 <0.9.0",
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export type Frontmatter = Record<string, unknown>;
|
|
2
|
-
/**
|
|
3
|
-
* Reads the YAML in a `/** @nx ... *\/` block comment after the shebang. Any other
|
|
4
|
-
* first comment yields `{}`. Throws on malformed YAML or an unterminated `@nx` block.
|
|
5
|
-
*/
|
|
6
|
-
export declare function parseFrontmatter(source: string): Frontmatter;
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parseFrontmatter = parseFrontmatter;
|
|
4
|
-
const js_yaml_1 = require("@zkochan/js-yaml");
|
|
5
|
-
const shebang_1 = require("./shebang");
|
|
6
|
-
/**
|
|
7
|
-
* Reads the YAML in a `/** @nx ... *\/` block comment after the shebang. Any other
|
|
8
|
-
* first comment yields `{}`. Throws on malformed YAML or an unterminated `@nx` block.
|
|
9
|
-
*/
|
|
10
|
-
function parseFrontmatter(source) {
|
|
11
|
-
const lines = (0, shebang_1.stripBom)(source).split(/\r?\n/);
|
|
12
|
-
// lines[0] is the shebang; blank lines may follow it.
|
|
13
|
-
let start = 1;
|
|
14
|
-
while (start < lines.length && !lines[start].trim()) {
|
|
15
|
-
start++;
|
|
16
|
-
}
|
|
17
|
-
if (!lines[start]?.trim().startsWith('/*')) {
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
const block = [];
|
|
21
|
-
let terminated = false;
|
|
22
|
-
for (let i = start; i < lines.length; i++) {
|
|
23
|
-
let line = i === start ? lines[i].trim().replace(/^\/\*\*?/, '') : lines[i];
|
|
24
|
-
const end = line.indexOf('*/');
|
|
25
|
-
if (end !== -1) {
|
|
26
|
-
line = line.slice(0, end);
|
|
27
|
-
terminated = true;
|
|
28
|
-
}
|
|
29
|
-
// Tolerate JSDoc-style ` * ` line prefixes.
|
|
30
|
-
block.push(line.replace(/^\s*\* ?/, ''));
|
|
31
|
-
if (terminated) {
|
|
32
|
-
break;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
const firstContent = block.findIndex((line) => line.trim());
|
|
36
|
-
const marker = (block[firstContent] ?? '').match(/^\s*@nx(?=\s|$)(.*)$/);
|
|
37
|
-
if (!marker) {
|
|
38
|
-
return {};
|
|
39
|
-
}
|
|
40
|
-
if (!terminated) {
|
|
41
|
-
throw new Error('Unterminated "@nx" frontmatter block.');
|
|
42
|
-
}
|
|
43
|
-
const body = [marker[1].trim(), ...block.slice(firstContent + 1)];
|
|
44
|
-
const parsed = (0, js_yaml_1.load)(body.join('\n'));
|
|
45
|
-
if (parsed == null) {
|
|
46
|
-
return {};
|
|
47
|
-
}
|
|
48
|
-
if (typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
49
|
-
throw new Error('The "@nx" frontmatter block must be a YAML mapping.');
|
|
50
|
-
}
|
|
51
|
-
return parsed;
|
|
52
|
-
}
|