@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.
@@ -1,6 +1,10 @@
1
1
  import { type CreateNodes, type TargetConfiguration } from '@nx/devkit';
2
2
  import { type Shebang } from './shebang';
3
- export interface ScriptPluginDefinition<T> {
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 object>(definition: ScriptPluginDefinition<T>): CreateNodes<T>;
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
- // Frontmatter keys the plugin interprets; any other key merges into the target.
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 absolutePath = (0, node_path_1.join)(workspaceRoot, script);
98
- const line = (0, shebang_1.readShebangLine)(absolutePath);
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
- let frontmatter = {};
104
- try {
105
- frontmatter = (0, frontmatter_1.parseFrontmatter)((0, node_fs_1.readFileSync)(absolutePath, 'utf-8'));
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
- frontmatter,
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 = typeof script.frontmatter.name === 'string'
130
- ? script.frontmatter.name
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, frontmatter }) {
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(targetCwd, path)), shebang, watch === true),
147
- options: { ...options, cwd: targetCwd },
127
+ command: definition.command(quoteShellArg(node_path_1.posix.relative(projectRoot, path)), shebang, watch),
128
+ options: { cwd: projectRoot },
148
129
  cache: false,
149
- ...(watch === true && { continuous: true }),
130
+ ...(watch && { continuous: true }),
150
131
  metadata: {
151
132
  technologies: [definition.technology],
152
- description: description ?? `Runs ${relativePath}`,
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
- export interface NodeScriptsPluginOptions {
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
- /** Returns the shebang line without `#!`, or `null` when the file has none. */
8
- export declare function readShebangLine(absolutePath: string): string | null;
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.readShebangLine = readShebangLine;
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
- /** Returns the shebang line without `#!`, or `null` when the file has none. */
11
- function readShebangLine(absolutePath) {
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
- bytesRead = (0, node_fs_1.readSync)(fd, buffer, 0, SNIFF_BYTES, 0);
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
- finally {
19
- (0, node_fs_1.closeSync)(fd);
27
+ catch {
28
+ return null;
20
29
  }
21
- const text = stripBom(buffer.toString('utf8', 0, bytesRead));
30
+ let text = stripBom(buffer.toString('utf8', 0, bytesRead));
22
31
  if (!text.startsWith('#!')) {
23
32
  return null;
24
33
  }
25
- const end = text.search(/\r?\n/);
26
- return (end === -1 ? text : text.slice(0, end)).slice(2).trim();
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
- export declare const createNodes: import("@nx/devkit").CreateNodes<Record<string, never>>;
2
- export declare const createNodesV2: import("@nx/devkit").CreateNodes<Record<string, never>>;
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-beta.2",
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-beta.2",
158
- "@nx/workspace": "24.0.0-beta.2",
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-beta.2",
179
- "@nx/eslint": "24.0.0-beta.2",
180
- "@nx/jest": "24.0.0-beta.2",
181
- "@nx/oxlint": "24.0.0-beta.2",
182
- "@nx/rollup": "24.0.0-beta.2",
183
- "@nx/vite": "24.0.0-beta.2",
184
- "@nx/vitest": "24.0.0-beta.2",
185
- "nx": "24.0.0-beta.2"
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
- }