@nx/devkit 24.0.0-pr.37405.acdaf31 → 24.0.0-rc.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.
- package/dist/internal.d.ts +7 -3
- package/dist/internal.js +26 -7
- package/dist/src/generators/add-ready-when-signal.d.ts +17 -0
- package/dist/src/generators/add-ready-when-signal.js +81 -0
- package/dist/src/generators/e2e-web-server-info-utils.d.ts +1 -1
- package/dist/src/generators/e2e-web-server-info-utils.js +1 -15
- package/dist/src/generators/plugin-migrations/conversion-warnings.d.ts +5 -0
- package/dist/src/generators/plugin-migrations/conversion-warnings.js +13 -0
- package/dist/src/generators/plugin-migrations/executor-to-plugin-migrator.d.ts +6 -0
- package/dist/src/generators/plugin-migrations/executor-to-plugin-migrator.js +58 -3
- package/dist/src/generators/plugin-migrations/remove-executors.d.ts +9 -0
- package/dist/src/generators/plugin-migrations/remove-executors.js +111 -0
- package/dist/src/utils/ignore-server-inputs-directive.d.ts +5 -0
- package/dist/src/utils/ignore-server-inputs-directive.js +12 -0
- package/dist/src/utils/nx-directives.d.ts +2 -1
- package/dist/src/utils/nx-directives.js +4 -3
- package/dist/src/utils/readiness.d.ts +71 -0
- package/dist/src/utils/readiness.js +259 -0
- package/dist/src/utils/split-shell-args.d.ts +7 -0
- package/dist/src/utils/split-shell-args.js +190 -0
- package/dist/src/utils/test-file-depends-on.d.ts +4 -4
- package/dist/src/utils/test-file-depends-on.js +6 -9
- package/dist/src/utils/test-file-directives.d.ts +7 -9
- package/dist/src/utils/test-file-directives.js +21 -38
- package/package.json +2 -2
- package/dist/src/utils/ultracache-directive.d.ts +0 -8
- package/dist/src/utils/ultracache-directive.js +0 -19
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `@nx-<name>: <value>` directives in the comment block that opens a file,
|
|
3
|
-
* by name, with their trimmed values in order.
|
|
3
|
+
* by name, with their trimmed values in order. A bare `@nx-<name>` has the
|
|
4
|
+
* value `''`. Reading stops at the first line
|
|
4
5
|
* that is not blank, a shebang or a comment.
|
|
5
6
|
*/
|
|
6
7
|
export declare function parseNxDirectives(content: string): Map<string, string[]>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseNxDirectives = parseNxDirectives;
|
|
4
|
-
const DIRECTIVE = /^@nx-([\w-]+)
|
|
4
|
+
const DIRECTIVE = /^@nx-([\w-]+)(?::(.*))?$/;
|
|
5
5
|
/**
|
|
6
6
|
* The `@nx-<name>: <value>` directives in the comment block that opens a file,
|
|
7
|
-
* by name, with their trimmed values in order.
|
|
7
|
+
* by name, with their trimmed values in order. A bare `@nx-<name>` has the
|
|
8
|
+
* value `''`. Reading stops at the first line
|
|
8
9
|
* that is not blank, a shebang or a comment.
|
|
9
10
|
*/
|
|
10
11
|
function parseNxDirectives(content) {
|
|
@@ -45,7 +46,7 @@ function parseNxDirectives(content) {
|
|
|
45
46
|
}
|
|
46
47
|
const match = line.trim().match(DIRECTIVE);
|
|
47
48
|
if (match) {
|
|
48
|
-
const [, name, value] = match;
|
|
49
|
+
const [, name, value = ''] = match;
|
|
49
50
|
const values = directives.get(name);
|
|
50
51
|
if (values) {
|
|
51
52
|
values.push(value.trim());
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type AddressInfo } from 'net';
|
|
2
|
+
/**
|
|
3
|
+
* The url of a server listening on `address`, for `emitReadySignal`. A
|
|
4
|
+
* wildcard address maps to localhost. Undefined for a socket or a pipe.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getListeningUrl(address: AddressInfo | string | null | undefined, https: boolean): string | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* A `readyWhen` that calls `waitForDevServer` from `module` when the task
|
|
9
|
+
* runs, with the config's file name, when given, and then `literals` as its
|
|
10
|
+
* arguments.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getReadyCheckReadyWhen(module: string, configFileName?: string, ...literals: string[]): {
|
|
13
|
+
command: string;
|
|
14
|
+
interval: number;
|
|
15
|
+
};
|
|
16
|
+
export type TaskOptions = Record<string, unknown> & {
|
|
17
|
+
cwd?: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The arguments run-commands passes the task's command, as the command
|
|
21
|
+
* receives them: built by run-commands' own code, then split the way the shell
|
|
22
|
+
* splits them. A `{args.*}` command gets them as if appended, which matches a
|
|
23
|
+
* command that passes each value to the flag of its name.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getForwardedArgs(options: TaskOptions): string[];
|
|
26
|
+
export interface CacArgsOptions {
|
|
27
|
+
/** Each short flag and the camel-case option it aliases, such as `{ c: 'config' }`. */
|
|
28
|
+
aliases?: Record<string, string>;
|
|
29
|
+
/** The flags that take no value, by any of their names. */
|
|
30
|
+
booleans?: string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The options a cac CLI, such as Rsbuild's or Rspack's, parses from `args`,
|
|
34
|
+
* before it applies option defaults and types. Ports cac's `mri()` step:
|
|
35
|
+
* - A flag takes the next argument as its value unless that starts with `-`.
|
|
36
|
+
* - A repeated flag collects its values in an array, and numeric values
|
|
37
|
+
* become numbers.
|
|
38
|
+
* - Every alias takes the value of the alias given first.
|
|
39
|
+
* - The kebab-case and camel-case spellings of an option take the value of
|
|
40
|
+
* the spelling that first appears last.
|
|
41
|
+
*
|
|
42
|
+
* Dotted names stay flat keys, where cac nests them.
|
|
43
|
+
*/
|
|
44
|
+
export declare function parseCacArgs(args: string[], { aliases, booleans }?: CacArgsOptions): Record<string, unknown>;
|
|
45
|
+
/**
|
|
46
|
+
* The options a commander 12 command, such as the Rsbuild 1.0 and 1.1 CLI's,
|
|
47
|
+
* parses from `args`, by attribute name, given its option flags such as
|
|
48
|
+
* `'-o --open [url]'`. The last value of an option wins, and no defaults or
|
|
49
|
+
* custom argument parsers apply. Throws where the command exits instead: on an
|
|
50
|
+
* unknown option, `--help` included, or a missing value.
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseCommanderArgs(args: string[], declarations: string[]): Record<string, string | true>;
|
|
53
|
+
export interface ServerAddress {
|
|
54
|
+
port: number;
|
|
55
|
+
host?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Where to probe a server, or null when its port is not a positive integer:
|
|
59
|
+
* a server that picks its port or listens on a socket can't be probed ahead.
|
|
60
|
+
*/
|
|
61
|
+
export declare function getServerAddress(port: unknown, host: unknown): ServerAddress | null;
|
|
62
|
+
/**
|
|
63
|
+
* Runs a task-time readiness check. Reads the options the task runs with,
|
|
64
|
+
* moves to its cwd, where the CLI resolves its config, and waits until every
|
|
65
|
+
* address `resolveAddresses` returns accepts a TCP connection. Probes nothing
|
|
66
|
+
* when it returns null.
|
|
67
|
+
*/
|
|
68
|
+
export declare function runReadyCheck(resolveAddresses: (task: {
|
|
69
|
+
options: TaskOptions;
|
|
70
|
+
root: string;
|
|
71
|
+
}) => Promise<ServerAddress[] | null>): Promise<void>;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getListeningUrl = getListeningUrl;
|
|
4
|
+
exports.getReadyCheckReadyWhen = getReadyCheckReadyWhen;
|
|
5
|
+
exports.getForwardedArgs = getForwardedArgs;
|
|
6
|
+
exports.parseCacArgs = parseCacArgs;
|
|
7
|
+
exports.parseCommanderArgs = parseCommanderArgs;
|
|
8
|
+
exports.getServerAddress = getServerAddress;
|
|
9
|
+
exports.runReadyCheck = runReadyCheck;
|
|
10
|
+
const net_1 = require("net");
|
|
11
|
+
const devkit_internals_1 = require("nx/src/devkit-internals");
|
|
12
|
+
const path_1 = require("path");
|
|
13
|
+
const split_shell_args_1 = require("./split-shell-args");
|
|
14
|
+
/**
|
|
15
|
+
* The url of a server listening on `address`, for `emitReadySignal`. A
|
|
16
|
+
* wildcard address maps to localhost. Undefined for a socket or a pipe.
|
|
17
|
+
*/
|
|
18
|
+
function getListeningUrl(address, https) {
|
|
19
|
+
if (!address || typeof address === 'string') {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const host = address.address === '::' || address.address === '0.0.0.0'
|
|
23
|
+
? 'localhost'
|
|
24
|
+
: address.family === 'IPv6'
|
|
25
|
+
? `[${address.address}]`
|
|
26
|
+
: address.address;
|
|
27
|
+
return `${https ? 'https' : 'http'}://${host}:${address.port}/`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A `readyWhen` that calls `waitForDevServer` from `module` when the task
|
|
31
|
+
* runs, with the config's file name, when given, and then `literals` as its
|
|
32
|
+
* arguments.
|
|
33
|
+
*/
|
|
34
|
+
function getReadyCheckReadyWhen(module, configFileName, ...literals) {
|
|
35
|
+
// Unquoted in the command, as no quoting works in every shell
|
|
36
|
+
if (configFileName !== undefined && !/^[\w.-]+$/.test(configFileName)) {
|
|
37
|
+
throw new Error(`Unsupported config file name: ${configFileName}`);
|
|
38
|
+
}
|
|
39
|
+
const args = [
|
|
40
|
+
...(configFileName === undefined ? [] : ['process.argv[1]']),
|
|
41
|
+
...literals.map((literal) => `'${literal}'`),
|
|
42
|
+
].join(', ');
|
|
43
|
+
return {
|
|
44
|
+
command: `node -e "require('${module}').waitForDevServer(${args})"${configFileName === undefined ? '' : ` ${configFileName}`}`,
|
|
45
|
+
// Each attempt starts node, and loads the config when there is one
|
|
46
|
+
interval: 1000,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The arguments run-commands passes the task's command, as the command
|
|
51
|
+
* receives them: built by run-commands' own code, then split the way the shell
|
|
52
|
+
* splits them. A `{args.*}` command gets them as if appended, which matches a
|
|
53
|
+
* command that passes each value to the flag of its name.
|
|
54
|
+
*/
|
|
55
|
+
function getForwardedArgs(options) {
|
|
56
|
+
const commands = options.command
|
|
57
|
+
? [{ command: [options.command].flat().join(' ') }]
|
|
58
|
+
: Array.isArray(options.commands)
|
|
59
|
+
? options.commands.map((c) => typeof c === 'string' ? { command: c } : c)
|
|
60
|
+
: [];
|
|
61
|
+
// Which of several commands starts the server is unknown
|
|
62
|
+
const server = commands.length === 1 ? commands[0] : {};
|
|
63
|
+
const template = server.command?.includes('{args}') && !server.command.includes('{args.')
|
|
64
|
+
? '{args}'
|
|
65
|
+
: '';
|
|
66
|
+
const forwarded = (0, devkit_internals_1.interpolateArgsIntoCommand)(template, (0, devkit_internals_1.normalizeRunCommandsArgs)({
|
|
67
|
+
...options,
|
|
68
|
+
__unparsed__: Array.isArray(options.__unparsed__)
|
|
69
|
+
? options.__unparsed__.map(String)
|
|
70
|
+
: [],
|
|
71
|
+
}), server.forwardAllArgs ?? options.forwardAllArgs ?? true);
|
|
72
|
+
return (0, split_shell_args_1.splitShellArgs)(forwarded);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The options a cac CLI, such as Rsbuild's or Rspack's, parses from `args`,
|
|
76
|
+
* before it applies option defaults and types. Ports cac's `mri()` step:
|
|
77
|
+
* - A flag takes the next argument as its value unless that starts with `-`.
|
|
78
|
+
* - A repeated flag collects its values in an array, and numeric values
|
|
79
|
+
* become numbers.
|
|
80
|
+
* - Every alias takes the value of the alias given first.
|
|
81
|
+
* - The kebab-case and camel-case spellings of an option take the value of
|
|
82
|
+
* the spelling that first appears last.
|
|
83
|
+
*
|
|
84
|
+
* Dotted names stay flat keys, where cac nests them.
|
|
85
|
+
*/
|
|
86
|
+
function parseCacArgs(args, { aliases = {}, booleans = [] } = {}) {
|
|
87
|
+
const aliasOf = new Map();
|
|
88
|
+
for (const [short, long] of Object.entries(aliases)) {
|
|
89
|
+
aliasOf.set(short, long).set(long, short);
|
|
90
|
+
}
|
|
91
|
+
const isBoolean = (name) => booleans.includes(name) || booleans.includes(aliasOf.get(name));
|
|
92
|
+
const parsed = {};
|
|
93
|
+
const add = (name, value) => {
|
|
94
|
+
const number = Number(value);
|
|
95
|
+
const typed = typeof value === 'boolean'
|
|
96
|
+
? value
|
|
97
|
+
: isBoolean(name)
|
|
98
|
+
? value !== 'false' && value !== ''
|
|
99
|
+
: Number.isFinite(number)
|
|
100
|
+
? number
|
|
101
|
+
: value;
|
|
102
|
+
parsed[name] =
|
|
103
|
+
parsed[name] === undefined ? typed : [parsed[name], typed].flat();
|
|
104
|
+
};
|
|
105
|
+
let i = 0;
|
|
106
|
+
for (; i < args.length && args[i] !== '--'; i++) {
|
|
107
|
+
const arg = args[i];
|
|
108
|
+
const dashes = arg.match(/^-*/)[0].length;
|
|
109
|
+
if (dashes === 0)
|
|
110
|
+
continue;
|
|
111
|
+
if (arg.startsWith('no-', dashes)) {
|
|
112
|
+
parsed[arg.slice(dashes + 3)] = false;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const equals = arg.indexOf('=', dashes + 1);
|
|
116
|
+
const name = arg.slice(dashes, equals === -1 ? undefined : equals);
|
|
117
|
+
const value = (equals === -1 ? '' : arg.slice(equals + 1)) ||
|
|
118
|
+
i + 1 === args.length ||
|
|
119
|
+
args[i + 1].startsWith('-') ||
|
|
120
|
+
args[++i];
|
|
121
|
+
// `-abc` sets `a` and `b` and gives its value to `c`
|
|
122
|
+
const names = dashes === 2 ? [name] : [...name];
|
|
123
|
+
names.forEach((n, index) => add(n, index + 1 < names.length || value));
|
|
124
|
+
}
|
|
125
|
+
const synced = new Set();
|
|
126
|
+
for (const name of Object.keys(parsed)) {
|
|
127
|
+
const alias = aliasOf.get(name);
|
|
128
|
+
if (alias !== undefined && !synced.has(name)) {
|
|
129
|
+
parsed[alias] = parsed[name];
|
|
130
|
+
synced.add(alias);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const options = { '--': args.slice(i + 1) };
|
|
134
|
+
for (const [name, value] of Object.entries(parsed)) {
|
|
135
|
+
options[name.replace(/([a-z])-([a-z])/g, (_, a, b) => a + b.toUpperCase())] = value;
|
|
136
|
+
}
|
|
137
|
+
return options;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The options a commander 12 command, such as the Rsbuild 1.0 and 1.1 CLI's,
|
|
141
|
+
* parses from `args`, by attribute name, given its option flags such as
|
|
142
|
+
* `'-o --open [url]'`. The last value of an option wins, and no defaults or
|
|
143
|
+
* custom argument parsers apply. Throws where the command exits instead: on an
|
|
144
|
+
* unknown option, `--help` included, or a missing value.
|
|
145
|
+
*/
|
|
146
|
+
function parseCommanderArgs(args, declarations) {
|
|
147
|
+
const options = declarations.map((flags) => {
|
|
148
|
+
const names = flags.split(/[ |,]+/).filter((part) => part.startsWith('-'));
|
|
149
|
+
return {
|
|
150
|
+
flags,
|
|
151
|
+
names,
|
|
152
|
+
key: names
|
|
153
|
+
.at(-1)
|
|
154
|
+
.replace(/^-+/, '')
|
|
155
|
+
.split('-')
|
|
156
|
+
.reduce((key, word) => key + word[0].toUpperCase() + word.slice(1)),
|
|
157
|
+
required: flags.includes('<'),
|
|
158
|
+
optional: flags.includes('['),
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
const find = (flag) => options.find((o) => o.names.includes(flag));
|
|
162
|
+
const isOption = (arg) => arg.length > 1 && arg[0] === '-';
|
|
163
|
+
const parsed = {};
|
|
164
|
+
const rest = [...args];
|
|
165
|
+
while (rest.length > 0 && rest[0] !== '--') {
|
|
166
|
+
const arg = rest.shift();
|
|
167
|
+
let option = find(arg);
|
|
168
|
+
if (option?.required) {
|
|
169
|
+
if (rest.length === 0) {
|
|
170
|
+
throw new Error(`option '${option.flags}' argument missing`);
|
|
171
|
+
}
|
|
172
|
+
parsed[option.key] = rest.shift();
|
|
173
|
+
}
|
|
174
|
+
else if (option) {
|
|
175
|
+
parsed[option.key] =
|
|
176
|
+
option.optional && rest.length > 0 && !isOption(rest[0])
|
|
177
|
+
? rest.shift()
|
|
178
|
+
: true;
|
|
179
|
+
}
|
|
180
|
+
else if (arg.length > 2 &&
|
|
181
|
+
arg[0] === '-' &&
|
|
182
|
+
arg[1] !== '-' &&
|
|
183
|
+
(option = find(arg.slice(0, 2)))) {
|
|
184
|
+
// `-abc` gives `a` the value `bc`, or sets it and goes on with `-bc`
|
|
185
|
+
if (option.required || option.optional) {
|
|
186
|
+
parsed[option.key] = arg.slice(2);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
parsed[option.key] = true;
|
|
190
|
+
rest.unshift(`-${arg.slice(2)}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else if (/^--[^=]+=/.test(arg) &&
|
|
194
|
+
(option = find(arg.slice(0, arg.indexOf('=')))) &&
|
|
195
|
+
(option.required || option.optional)) {
|
|
196
|
+
parsed[option.key] = arg.slice(arg.indexOf('=') + 1);
|
|
197
|
+
}
|
|
198
|
+
else if (isOption(arg)) {
|
|
199
|
+
throw new Error(`unknown option '${arg}'`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return parsed;
|
|
203
|
+
}
|
|
204
|
+
// The probe reaches these on loopback
|
|
205
|
+
const LOOPBACK_HOSTS = new Set(['', 'localhost', '0.0.0.0', '::']);
|
|
206
|
+
/**
|
|
207
|
+
* Where to probe a server, or null when its port is not a positive integer:
|
|
208
|
+
* a server that picks its port or listens on a socket can't be probed ahead.
|
|
209
|
+
*/
|
|
210
|
+
function getServerAddress(port, host) {
|
|
211
|
+
const number = Number(port);
|
|
212
|
+
if (!Number.isInteger(number) || number <= 0)
|
|
213
|
+
return null;
|
|
214
|
+
return typeof host === 'string' && !LOOPBACK_HOSTS.has(host)
|
|
215
|
+
? { port: number, host }
|
|
216
|
+
: { port: number };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Runs a task-time readiness check. Reads the options the task runs with,
|
|
220
|
+
* moves to its cwd, where the CLI resolves its config, and waits until every
|
|
221
|
+
* address `resolveAddresses` returns accepts a TCP connection. Probes nothing
|
|
222
|
+
* when it returns null.
|
|
223
|
+
*/
|
|
224
|
+
async function runReadyCheck(resolveAddresses) {
|
|
225
|
+
const options = readTaskOptions();
|
|
226
|
+
const root = (0, path_1.resolve)(options.cwd ?? '.');
|
|
227
|
+
process.chdir(root);
|
|
228
|
+
const addresses = await resolveAddresses({ options, root });
|
|
229
|
+
if (addresses) {
|
|
230
|
+
await Promise.all(addresses.map(waitForAddress));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function readTaskOptions() {
|
|
234
|
+
try {
|
|
235
|
+
return JSON.parse(process.env.NX_TASK_OPTIONS ?? '{}');
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return {};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function waitForAddress({ port, host }) {
|
|
242
|
+
const hosts = host ? [host] : ['127.0.0.1', '::1'];
|
|
243
|
+
while (!(await Promise.any(hosts.map((h) => connects(port, h))).catch(() => false))) {
|
|
244
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function connects(port, host) {
|
|
248
|
+
return new Promise((resolve, reject) => {
|
|
249
|
+
const socket = (0, net_1.createConnection)({ port, host });
|
|
250
|
+
socket.once('connect', () => {
|
|
251
|
+
socket.destroy();
|
|
252
|
+
resolve(true);
|
|
253
|
+
});
|
|
254
|
+
socket.once('error', (e) => {
|
|
255
|
+
socket.destroy();
|
|
256
|
+
reject(e);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits a string into the words a command gets when a shell runs it, as
|
|
3
|
+
* run-commands does: with POSIX sh rules, or on Windows with cmd.exe and the C
|
|
4
|
+
* runtime's argv rules. Variables expand from `process.env`; command
|
|
5
|
+
* substitution and globs stay literal.
|
|
6
|
+
*/
|
|
7
|
+
export declare function splitShellArgs(value: string): string[];
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.splitShellArgs = splitShellArgs;
|
|
4
|
+
/**
|
|
5
|
+
* Splits a string into the words a command gets when a shell runs it, as
|
|
6
|
+
* run-commands does: with POSIX sh rules, or on Windows with cmd.exe and the C
|
|
7
|
+
* runtime's argv rules. Variables expand from `process.env`; command
|
|
8
|
+
* substitution and globs stay literal.
|
|
9
|
+
*/
|
|
10
|
+
function splitShellArgs(value) {
|
|
11
|
+
return process.platform === 'win32'
|
|
12
|
+
? splitWindowsArgs(applyCmdRules(value))
|
|
13
|
+
: splitPosixArgs(value);
|
|
14
|
+
}
|
|
15
|
+
function splitPosixArgs(value) {
|
|
16
|
+
const words = [];
|
|
17
|
+
let word;
|
|
18
|
+
let quote;
|
|
19
|
+
// An unquoted expansion splits into words on whitespace
|
|
20
|
+
const appendUnquoted = (text) => text.split(/[ \t\n]+/).forEach((part, index) => {
|
|
21
|
+
if (index > 0 && word !== undefined) {
|
|
22
|
+
words.push(word);
|
|
23
|
+
word = undefined;
|
|
24
|
+
}
|
|
25
|
+
if (part)
|
|
26
|
+
word = (word ?? '') + part;
|
|
27
|
+
});
|
|
28
|
+
for (let i = 0; i < value.length; i++) {
|
|
29
|
+
const char = value[i];
|
|
30
|
+
if (quote === "'") {
|
|
31
|
+
if (char === "'")
|
|
32
|
+
quote = undefined;
|
|
33
|
+
else
|
|
34
|
+
word += char;
|
|
35
|
+
}
|
|
36
|
+
else if (char === '\\' && value[i + 1] === '\n') {
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
else if (char === '$' && expandPosixVariable(value, i)) {
|
|
40
|
+
const expansion = expandPosixVariable(value, i);
|
|
41
|
+
if (quote)
|
|
42
|
+
word += expansion.value;
|
|
43
|
+
else
|
|
44
|
+
appendUnquoted(expansion.value);
|
|
45
|
+
i = expansion.end;
|
|
46
|
+
}
|
|
47
|
+
else if (quote === '"') {
|
|
48
|
+
if (char === '"') {
|
|
49
|
+
quote = undefined;
|
|
50
|
+
}
|
|
51
|
+
else if (char === '\\' && '"\\$`'.includes(value[i + 1])) {
|
|
52
|
+
word += value[++i];
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
word += char;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else if (/[ \t\n]/.test(char)) {
|
|
59
|
+
if (word !== undefined)
|
|
60
|
+
words.push(word);
|
|
61
|
+
word = undefined;
|
|
62
|
+
}
|
|
63
|
+
else if (char === '"' || char === "'") {
|
|
64
|
+
quote = char;
|
|
65
|
+
word ??= '';
|
|
66
|
+
}
|
|
67
|
+
else if (char === '\\' && i + 1 < value.length) {
|
|
68
|
+
word = (word ?? '') + value[++i];
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
word = (word ?? '') + char;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (word !== undefined)
|
|
75
|
+
words.push(word);
|
|
76
|
+
return words;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Expands `$NAME`, `${NAME}`, `${NAME-default}` and `${NAME:-default}` at
|
|
80
|
+
* `start`, returning the value and the index of its last character. Returns
|
|
81
|
+
* undefined for anything else, which stays literal.
|
|
82
|
+
*/
|
|
83
|
+
function expandPosixVariable(value, start) {
|
|
84
|
+
const name = value.slice(start + 1).match(/^[A-Za-z_][A-Za-z0-9_]*/)?.[0];
|
|
85
|
+
if (name) {
|
|
86
|
+
return { value: process.env[name] ?? '', end: start + name.length };
|
|
87
|
+
}
|
|
88
|
+
if (value[start + 1] !== '{')
|
|
89
|
+
return undefined;
|
|
90
|
+
let depth = 0;
|
|
91
|
+
for (let i = start + 1; i < value.length; i++) {
|
|
92
|
+
if (value[i] === '{')
|
|
93
|
+
depth++;
|
|
94
|
+
else if (value[i] === '}' && --depth === 0) {
|
|
95
|
+
const match = value
|
|
96
|
+
.slice(start + 2, i)
|
|
97
|
+
.match(/^([A-Za-z_][A-Za-z0-9_]*)(?:(:?)-(.*))?$/s);
|
|
98
|
+
if (!match)
|
|
99
|
+
return undefined;
|
|
100
|
+
const [, braced, colon, fallback] = match;
|
|
101
|
+
const env = process.env[braced];
|
|
102
|
+
const useFallback = fallback !== undefined && (colon ? !env : env === undefined);
|
|
103
|
+
return {
|
|
104
|
+
value: useFallback ? expandPosixText(fallback) : (env ?? ''),
|
|
105
|
+
end: i,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
function expandPosixText(text) {
|
|
112
|
+
let result = '';
|
|
113
|
+
for (let i = 0; i < text.length; i++) {
|
|
114
|
+
const expansion = text[i] === '$' && expandPosixVariable(text, i);
|
|
115
|
+
if (expansion) {
|
|
116
|
+
result += expansion.value;
|
|
117
|
+
i = expansion.end;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
result += text[i];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
// cmd.exe expands defined %NAME% variables, then drops each ^ outside double
|
|
126
|
+
// quotes and keeps the character after it
|
|
127
|
+
function applyCmdRules(value) {
|
|
128
|
+
const expanded = value.replace(/%([^%\s]+)%/g, (match, name) => process.env[name] ?? match);
|
|
129
|
+
let result = '';
|
|
130
|
+
let quoted = false;
|
|
131
|
+
for (let i = 0; i < expanded.length; i++) {
|
|
132
|
+
const char = expanded[i];
|
|
133
|
+
if (char === '^' && !quoted && i + 1 < expanded.length) {
|
|
134
|
+
result += expanded[++i];
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
if (char === '"')
|
|
138
|
+
quoted = !quoted;
|
|
139
|
+
result += char;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
// Backslashes are literal unless they precede a double quote, and single
|
|
145
|
+
// quotes are literal
|
|
146
|
+
function splitWindowsArgs(value) {
|
|
147
|
+
const words = [];
|
|
148
|
+
let word;
|
|
149
|
+
let quoted = false;
|
|
150
|
+
for (let i = 0; i < value.length; i++) {
|
|
151
|
+
const char = value[i];
|
|
152
|
+
if (char === '\\') {
|
|
153
|
+
let count = 1;
|
|
154
|
+
while (value[i + count] === '\\')
|
|
155
|
+
count++;
|
|
156
|
+
i += count - 1;
|
|
157
|
+
if (value[i + 1] === '"') {
|
|
158
|
+
word = (word ?? '') + '\\'.repeat(Math.floor(count / 2));
|
|
159
|
+
if (count % 2 === 1) {
|
|
160
|
+
word += '"';
|
|
161
|
+
i++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
word = (word ?? '') + '\\'.repeat(count);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (char === '"') {
|
|
169
|
+
if (quoted && value[i + 1] === '"') {
|
|
170
|
+
word += '"';
|
|
171
|
+
i++;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
quoted = !quoted;
|
|
175
|
+
word ??= '';
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else if (!quoted && (char === ' ' || char === '\t')) {
|
|
179
|
+
if (word !== undefined)
|
|
180
|
+
words.push(word);
|
|
181
|
+
word = undefined;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
word = (word ?? '') + char;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (word !== undefined)
|
|
188
|
+
words.push(word);
|
|
189
|
+
return words;
|
|
190
|
+
}
|
|
@@ -3,9 +3,9 @@ import type { TargetConfiguration } from 'nx/src/devkit-exports';
|
|
|
3
3
|
export declare function dependsOnProjects(values: string[]): string[];
|
|
4
4
|
/**
|
|
5
5
|
* `target` narrowed to the projects a test file names: its dependency inputs
|
|
6
|
-
* (`^…`, `dependencies: true`) become
|
|
7
|
-
* first named input among them (else `default`) and kept
|
|
8
|
-
* (`always: true`). Its `dependsOn` entries naming projects,
|
|
9
|
-
* test talks to, get `inputs: false`.
|
|
6
|
+
* (`^…`, `dependencies: true`) become two inputs selecting `projects` and their
|
|
7
|
+
* dependencies, by the first named input among them (else `default`) and kept
|
|
8
|
+
* under ultracache (`always: true`). Its `dependsOn` entries naming projects,
|
|
9
|
+
* the servers the test talks to, get `inputs: false`.
|
|
10
10
|
*/
|
|
11
11
|
export declare function scopeTestTargetToProjects(target: TargetConfiguration, projects: string[]): TargetConfiguration;
|
|
@@ -18,10 +18,10 @@ function dependsOnProjects(values) {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* `target` narrowed to the projects a test file names: its dependency inputs
|
|
21
|
-
* (`^…`, `dependencies: true`) become
|
|
22
|
-
* first named input among them (else `default`) and kept
|
|
23
|
-
* (`always: true`). Its `dependsOn` entries naming projects,
|
|
24
|
-
* test talks to, get `inputs: false`.
|
|
21
|
+
* (`^…`, `dependencies: true`) become two inputs selecting `projects` and their
|
|
22
|
+
* dependencies, by the first named input among them (else `default`) and kept
|
|
23
|
+
* under ultracache (`always: true`). Its `dependsOn` entries naming projects,
|
|
24
|
+
* the servers the test talks to, get `inputs: false`.
|
|
25
25
|
*/
|
|
26
26
|
function scopeTestTargetToProjects(target, projects) {
|
|
27
27
|
return {
|
|
@@ -54,10 +54,7 @@ function scopeDependencyInputs(inputs, projects) {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
projects,
|
|
60
|
-
always: true,
|
|
61
|
-
});
|
|
57
|
+
const input = namedInput ?? 'default';
|
|
58
|
+
own.splice(position ?? own.length, 0, { input, projects, always: true }, { input, projects, dependencies: true, always: true });
|
|
62
59
|
return own;
|
|
63
60
|
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { TargetConfiguration } from 'nx/src/devkit-exports';
|
|
2
|
-
declare const TEST_FILE_DIRECTIVES: readonly ['depends-on', 'ultracache'];
|
|
3
|
-
/** An `@nx-<name>` directive a plugin can apply to a test file's target. */
|
|
4
|
-
type TestFileDirective = (typeof TEST_FILE_DIRECTIVES)[number];
|
|
5
2
|
/**
|
|
6
|
-
* `target` with the `@nx
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* warning.
|
|
3
|
+
* `target` with the `@nx-depends-on` and `@nx-ignore-server-inputs`
|
|
4
|
+
* directives of a workspace-relative test file applied. A directive that lists
|
|
5
|
+
* no projects, has a value it does not take, or is not supported
|
|
6
|
+
* (`ignoreServerInputs: false`) is skipped with a warning.
|
|
10
7
|
*/
|
|
11
|
-
export declare function applyTestFileDirectives(target: TargetConfiguration, workspaceRoot: string, file: string,
|
|
12
|
-
|
|
8
|
+
export declare function applyTestFileDirectives(target: TargetConfiguration, workspaceRoot: string, file: string, options: {
|
|
9
|
+
ignoreServerInputs: boolean;
|
|
10
|
+
}): TargetConfiguration;
|