@williamthorsen/nmr 0.15.0 → 0.17.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/AGENTS.md +1 -1
- package/README.md +43 -54
- package/dist/esm/.cache +1 -1
- package/dist/esm/cli-build.js +5 -4
- package/dist/esm/cli-ensure-prepublish-hooks.js +37 -56
- package/dist/esm/cli-report-overrides.js +7 -5
- package/dist/esm/cli-sync-agent-files.js +19 -28
- package/dist/esm/cli-sync-pnpm-version.js +7 -5
- package/dist/esm/cli.js +13 -13
- package/dist/esm/commands/build.d.ts +2 -8
- package/dist/esm/commands/build.js +270 -118
- package/dist/esm/commands/ensure-prepublish-hooks.js +66 -69
- package/dist/esm/commands/report-overrides.js +10 -13
- package/dist/esm/commands/sync-agent-files.js +46 -52
- package/dist/esm/commands/sync-pnpm-version.js +35 -42
- package/dist/esm/config.js +59 -57
- package/dist/esm/context.d.ts +1 -1
- package/dist/esm/context.js +70 -72
- package/dist/esm/default-scripts.js +63 -67
- package/dist/esm/help.d.ts +1 -1
- package/dist/esm/help.js +65 -68
- package/dist/esm/helpers/code-quality-pnpm-action.js +20 -24
- package/dist/esm/helpers/hook-name.js +2 -5
- package/dist/esm/helpers/package-json.js +46 -41
- package/dist/esm/helpers/type-guards.js +2 -5
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/index.js +1 -4
- package/dist/esm/resolve-scripts.d.ts +2 -2
- package/dist/esm/resolve-scripts.js +7 -11
- package/dist/esm/resolver.d.ts +3 -2
- package/dist/esm/resolver.js +85 -83
- package/dist/esm/runCli.js +135 -145
- package/dist/esm/runner.js +35 -34
- package/dist/esm/scripts.d.ts +2 -2
- package/dist/esm/scripts.js +1 -5
- package/dist/esm/tests/consistency.d.ts +1 -1
- package/dist/esm/tests/consistency.js +45 -54
- package/dist/esm/tests/helpers/get-runtime-version-from-asdf.js +11 -14
- package/dist/esm/tests/helpers/get-string-from-yaml-file.js +10 -13
- package/dist/esm/tests/helpers/get-value-at-path.js +30 -30
- package/package.json +5 -3
package/dist/esm/runCli.js
CHANGED
|
@@ -1,169 +1,159 @@
|
|
|
1
|
-
import path from
|
|
2
|
-
import { readPackageVersion } from
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readPackageVersion, reportError } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { resolveContext } from "./context.js";
|
|
4
4
|
import { generateHelp } from "./help.js";
|
|
5
5
|
import { isHookName } from "./helpers/hook-name.js";
|
|
6
|
-
import { applyDevBin, buildRootRegistry, buildWorkspaceRegistry, resolveScript } from "./resolver.js";
|
|
6
|
+
import { applyDevBin, buildRootRegistry, buildWorkspaceRegistry, hasIntegrationTestConfig, resolveScript, } from "./resolver.js";
|
|
7
7
|
import { runCommand } from "./runner.js";
|
|
8
8
|
const VERSION = readPackageVersion(import.meta.url);
|
|
9
|
-
async function runCli(options) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return { exitCode: 1 };
|
|
16
|
-
}
|
|
17
|
-
const { parsed } = parseResult;
|
|
18
|
-
if (parsed.version) {
|
|
19
|
-
stdout.write(`${VERSION}
|
|
20
|
-
`);
|
|
21
|
-
return { exitCode: 0 };
|
|
22
|
-
}
|
|
23
|
-
const context = await resolveContext(cwd);
|
|
24
|
-
const useRoot = parsed.workspaceRoot || context.isRoot;
|
|
25
|
-
const packageDir = useRoot ? context.monorepoRoot : context.packageDir ?? context.monorepoRoot;
|
|
26
|
-
if (parsed.help || !parsed.command) {
|
|
27
|
-
stdout.write(`${generateHelp(context.config, packageDir, useRoot)}
|
|
28
|
-
`);
|
|
29
|
-
return { exitCode: 0 };
|
|
30
|
-
}
|
|
31
|
-
const { command } = parsed;
|
|
32
|
-
const passthrough = parsed.passthrough.length > 0 ? " " + parsed.passthrough.map(shellQuote).join(" ") : "";
|
|
33
|
-
const runOptions = { quiet: parsed.quiet, stdout, stderr, env };
|
|
34
|
-
if (parsed.filter) {
|
|
35
|
-
const delegateCmd = `pnpm --filter ${shellQuote(parsed.filter)} exec nmr ${command}${passthrough}`;
|
|
36
|
-
const exitCode2 = runCommand(delegateCmd, context.monorepoRoot, runOptions);
|
|
37
|
-
return { exitCode: exitCode2 };
|
|
38
|
-
}
|
|
39
|
-
if (parsed.recursive) {
|
|
40
|
-
const childEnv = { ...env, NMR_RUN_IF_PRESENT: "1" };
|
|
41
|
-
const delegateCmd = `pnpm --recursive exec nmr ${command}${passthrough}`;
|
|
42
|
-
const exitCode2 = runCommand(delegateCmd, context.monorepoRoot, { ...runOptions, env: childEnv });
|
|
43
|
-
return { exitCode: exitCode2 };
|
|
44
|
-
}
|
|
45
|
-
const registry = useRoot ? buildRootRegistry(context.config) : buildWorkspaceRegistry(context.config, parsed.intTest);
|
|
46
|
-
const resolved = resolveScript(command, registry, packageDir, parsed.workspaceRoot);
|
|
47
|
-
if (!resolved) {
|
|
48
|
-
if (env.NMR_RUN_IF_PRESENT === "1") {
|
|
49
|
-
return { exitCode: 0 };
|
|
9
|
+
export async function runCli(options) {
|
|
10
|
+
const { args, cwd, env, stdout, stderr } = options;
|
|
11
|
+
const parseResult = parseArgs(args);
|
|
12
|
+
if (!parseResult.ok) {
|
|
13
|
+
reportError(parseResult.error, stderr);
|
|
14
|
+
return { exitCode: 1 };
|
|
50
15
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const skipExitCode = handleSkipMessage(resolved.command, packageDir, parsed.quiet, stdout);
|
|
56
|
-
if (skipExitCode !== void 0) {
|
|
57
|
-
return { exitCode: skipExitCode };
|
|
58
|
-
}
|
|
59
|
-
if (resolved.source === "package" && !parsed.quiet && registry[command] !== void 0) {
|
|
60
|
-
stdout.write(`\u{1F4E6} ${path.basename(packageDir)}: Using override script: ${resolved.command}
|
|
61
|
-
`);
|
|
62
|
-
}
|
|
63
|
-
const substitutedCommand = applyDevBin(resolved.command, context.config.devBin, context.monorepoRoot);
|
|
64
|
-
const mainCommand = substitutedCommand + passthrough;
|
|
65
|
-
const isHookInvocation = isHookName(command);
|
|
66
|
-
const fullCommand = isHookInvocation ? mainCommand : wrapWithHooks(command, mainCommand, registry, packageDir, parsed.workspaceRoot);
|
|
67
|
-
const exitCode = runCommand(fullCommand, cwd, runOptions);
|
|
68
|
-
return { exitCode };
|
|
69
|
-
}
|
|
70
|
-
function parseArgs(args) {
|
|
71
|
-
const parsed = {
|
|
72
|
-
quiet: false,
|
|
73
|
-
recursive: false,
|
|
74
|
-
workspaceRoot: false,
|
|
75
|
-
help: false,
|
|
76
|
-
version: false,
|
|
77
|
-
intTest: false,
|
|
78
|
-
passthrough: []
|
|
79
|
-
};
|
|
80
|
-
let i = 0;
|
|
81
|
-
while (i < args.length) {
|
|
82
|
-
const arg = args[i];
|
|
83
|
-
if (arg === void 0) break;
|
|
84
|
-
if (arg === "-F" || arg === "--filter") {
|
|
85
|
-
i++;
|
|
86
|
-
const filterValue = args[i];
|
|
87
|
-
if (filterValue === void 0) {
|
|
88
|
-
return { ok: false, error: "Error: -F/--filter requires a pattern argument" };
|
|
89
|
-
}
|
|
90
|
-
parsed.filter = filterValue;
|
|
91
|
-
i++;
|
|
92
|
-
continue;
|
|
16
|
+
const { parsed } = parseResult;
|
|
17
|
+
if (parsed.version) {
|
|
18
|
+
stdout.write(`${VERSION}\n`);
|
|
19
|
+
return { exitCode: 0 };
|
|
93
20
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
21
|
+
const context = await resolveContext(cwd);
|
|
22
|
+
const useRoot = parsed.workspaceRoot || context.isRoot;
|
|
23
|
+
const packageDir = useRoot ? context.monorepoRoot : (context.packageDir ?? context.monorepoRoot);
|
|
24
|
+
if (parsed.help || !parsed.command) {
|
|
25
|
+
stdout.write(`${generateHelp(context.config, packageDir, useRoot)}\n`);
|
|
26
|
+
return { exitCode: 0 };
|
|
98
27
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
28
|
+
const { command } = parsed;
|
|
29
|
+
const passthrough = parsed.passthrough.length > 0 ? ' ' + parsed.passthrough.map(shellQuote).join(' ') : '';
|
|
30
|
+
const runOptions = { quiet: parsed.quiet, stdout, stderr, env };
|
|
31
|
+
if (parsed.filter) {
|
|
32
|
+
const delegateCmd = `pnpm --filter ${shellQuote(parsed.filter)} exec nmr ${command}${passthrough}`;
|
|
33
|
+
const exitCode = runCommand(delegateCmd, context.monorepoRoot, runOptions);
|
|
34
|
+
return { exitCode };
|
|
103
35
|
}
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
36
|
+
if (parsed.recursive) {
|
|
37
|
+
const childEnv = { ...env, NMR_RUN_IF_PRESENT: '1' };
|
|
38
|
+
const delegateCmd = `pnpm --recursive exec nmr ${command}${passthrough}`;
|
|
39
|
+
const exitCode = runCommand(delegateCmd, context.monorepoRoot, { ...runOptions, env: childEnv });
|
|
40
|
+
return { exitCode };
|
|
108
41
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
42
|
+
const registry = useRoot
|
|
43
|
+
? buildRootRegistry(context.config)
|
|
44
|
+
: buildWorkspaceRegistry(context.config, hasIntegrationTestConfig(packageDir));
|
|
45
|
+
const resolved = resolveScript(command, registry, packageDir, parsed.workspaceRoot);
|
|
46
|
+
if (!resolved) {
|
|
47
|
+
if (env.NMR_RUN_IF_PRESENT === '1') {
|
|
48
|
+
return { exitCode: 0 };
|
|
49
|
+
}
|
|
50
|
+
reportError(`Unknown command: ${command}`, stderr);
|
|
51
|
+
return { exitCode: 1 };
|
|
113
52
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
continue;
|
|
53
|
+
const skipExitCode = handleSkipMessage(resolved.command, packageDir, parsed.quiet, stdout);
|
|
54
|
+
if (skipExitCode !== undefined) {
|
|
55
|
+
return { exitCode: skipExitCode };
|
|
118
56
|
}
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
i++;
|
|
122
|
-
continue;
|
|
57
|
+
if (resolved.source === 'package' && !parsed.quiet && registry[command] !== undefined) {
|
|
58
|
+
stdout.write(`📦 ${path.basename(packageDir)}: Using override script: ${resolved.command}\n`);
|
|
123
59
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
60
|
+
const substitutedCommand = applyDevBin(resolved.command, context.config.devBin, context.monorepoRoot);
|
|
61
|
+
const mainCommand = substitutedCommand + passthrough;
|
|
62
|
+
const isHookInvocation = isHookName(command);
|
|
63
|
+
const fullCommand = isHookInvocation
|
|
64
|
+
? mainCommand
|
|
65
|
+
: wrapWithHooks(command, mainCommand, registry, packageDir, parsed.workspaceRoot);
|
|
66
|
+
const exitCode = runCommand(fullCommand, cwd, runOptions);
|
|
67
|
+
return { exitCode };
|
|
68
|
+
}
|
|
69
|
+
function parseArgs(args) {
|
|
70
|
+
const parsed = {
|
|
71
|
+
quiet: false,
|
|
72
|
+
recursive: false,
|
|
73
|
+
workspaceRoot: false,
|
|
74
|
+
help: false,
|
|
75
|
+
version: false,
|
|
76
|
+
passthrough: [],
|
|
77
|
+
};
|
|
78
|
+
let i = 0;
|
|
79
|
+
while (i < args.length) {
|
|
80
|
+
const arg = args[i];
|
|
81
|
+
if (arg === undefined)
|
|
82
|
+
break;
|
|
83
|
+
if (arg === '-F' || arg === '--filter') {
|
|
84
|
+
i++;
|
|
85
|
+
const filterValue = args[i];
|
|
86
|
+
if (filterValue === undefined) {
|
|
87
|
+
return { ok: false, error: '-F/--filter requires a pattern argument' };
|
|
88
|
+
}
|
|
89
|
+
parsed.filter = filterValue;
|
|
90
|
+
i++;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (arg === '-R' || arg === '--recursive') {
|
|
94
|
+
parsed.recursive = true;
|
|
95
|
+
i++;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (arg === '-w' || arg === '--workspace-root') {
|
|
99
|
+
parsed.workspaceRoot = true;
|
|
100
|
+
i++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (arg === '-?' || arg === '--help') {
|
|
104
|
+
parsed.help = true;
|
|
105
|
+
i++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (arg === '-V' || arg === '--version') {
|
|
109
|
+
parsed.version = true;
|
|
110
|
+
i++;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (arg === '-q' || arg === '--quiet') {
|
|
114
|
+
parsed.quiet = true;
|
|
115
|
+
i++;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
parsed.command = arg;
|
|
119
|
+
parsed.passthrough = args.slice(i + 1);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
return { ok: true, parsed };
|
|
129
123
|
}
|
|
130
124
|
function handleSkipMessage(resolvedCommand, packageDir, quiet, stdout) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
125
|
+
if (resolvedCommand === '') {
|
|
126
|
+
if (!quiet) {
|
|
127
|
+
stdout.write(`⛔ ${path.basename(packageDir)}: Override script is defined but empty. Skipping.\n`);
|
|
128
|
+
}
|
|
129
|
+
return 0;
|
|
135
130
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
`);
|
|
131
|
+
if (resolvedCommand === ':') {
|
|
132
|
+
if (!quiet) {
|
|
133
|
+
stdout.write(`⛔ ${path.basename(packageDir)}: Override script is a no-op. Skipping.\n`);
|
|
134
|
+
}
|
|
135
|
+
return 0;
|
|
142
136
|
}
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
return void 0;
|
|
137
|
+
return undefined;
|
|
146
138
|
}
|
|
147
139
|
function wrapWithHooks(command, mainCommand, registry, packageDir, workspaceRoot) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
140
|
+
const segments = [];
|
|
141
|
+
const flag = workspaceRoot ? '-w ' : '';
|
|
142
|
+
if (hasRunnableHook(`${command}:pre`, registry, packageDir, workspaceRoot)) {
|
|
143
|
+
segments.push(`nmr ${flag}${command}:pre`);
|
|
144
|
+
}
|
|
145
|
+
segments.push(mainCommand);
|
|
146
|
+
if (hasRunnableHook(`${command}:post`, registry, packageDir, workspaceRoot)) {
|
|
147
|
+
segments.push(`nmr ${flag}${command}:post`);
|
|
148
|
+
}
|
|
149
|
+
return segments.join(' && ');
|
|
158
150
|
}
|
|
159
151
|
function hasRunnableHook(hookName, registry, packageDir, workspaceRoot) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
152
|
+
const resolved = resolveScript(hookName, registry, packageDir, workspaceRoot);
|
|
153
|
+
if (!resolved)
|
|
154
|
+
return false;
|
|
155
|
+
return resolved.command !== '' && resolved.command !== ':';
|
|
163
156
|
}
|
|
164
157
|
function shellQuote(arg) {
|
|
165
|
-
|
|
158
|
+
return "'" + arg.replace(/'/g, String.raw `'\''`) + "'";
|
|
166
159
|
}
|
|
167
|
-
export {
|
|
168
|
-
runCli
|
|
169
|
-
};
|
package/dist/esm/runner.js
CHANGED
|
@@ -1,40 +1,41 @@
|
|
|
1
|
-
import { spawnSync } from
|
|
2
|
-
import process from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
if (quiet) {
|
|
22
|
-
if (result.status !== 0) {
|
|
23
|
-
writeBuffer(result.stdout, stderr);
|
|
24
|
-
writeBuffer(result.stderr, stderr);
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { reportError } from '@williamthorsen/nmr-core';
|
|
4
|
+
export function runCommand(command, cwd, options) {
|
|
5
|
+
const quiet = options?.quiet === true;
|
|
6
|
+
const stdout = options?.stdout ?? process.stdout;
|
|
7
|
+
const stderr = options?.stderr ?? process.stderr;
|
|
8
|
+
const env = options?.env ?? process.env;
|
|
9
|
+
const stdoutChannel = quiet ? 'pipe' : streamFdOrPipe(stdout);
|
|
10
|
+
const stderrChannel = quiet ? 'pipe' : streamFdOrPipe(stderr);
|
|
11
|
+
const result = spawnSync(command, [], {
|
|
12
|
+
shell: true,
|
|
13
|
+
stdio: ['inherit', stdoutChannel, stderrChannel],
|
|
14
|
+
cwd,
|
|
15
|
+
env,
|
|
16
|
+
});
|
|
17
|
+
if (result.error) {
|
|
18
|
+
reportError(result.error.message, stderr);
|
|
19
|
+
return 1;
|
|
25
20
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
if (quiet) {
|
|
22
|
+
if (result.status !== 0) {
|
|
23
|
+
writeBuffer(result.stdout, stderr);
|
|
24
|
+
writeBuffer(result.stderr, stderr);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
if (stdoutChannel === 'pipe')
|
|
29
|
+
writeBuffer(result.stdout, stdout);
|
|
30
|
+
if (stderrChannel === 'pipe')
|
|
31
|
+
writeBuffer(result.stderr, stderr);
|
|
32
|
+
}
|
|
33
|
+
return result.status ?? 1;
|
|
31
34
|
}
|
|
32
35
|
function streamFdOrPipe(stream) {
|
|
33
|
-
|
|
36
|
+
return 'fd' in stream && typeof stream.fd === 'number' ? stream.fd : 'pipe';
|
|
34
37
|
}
|
|
35
38
|
function writeBuffer(buffer, dest) {
|
|
36
|
-
|
|
39
|
+
if (buffer.length > 0)
|
|
40
|
+
dest.write(buffer);
|
|
37
41
|
}
|
|
38
|
-
export {
|
|
39
|
-
runCommand
|
|
40
|
-
};
|
package/dist/esm/scripts.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { ScriptRegistry, ScriptValue } from './default-scripts.
|
|
2
|
-
export { getDefaultRootScripts, getDefaultWorkspaceScripts } from './resolve-scripts.
|
|
1
|
+
export type { ScriptRegistry, ScriptValue } from './default-scripts.js';
|
|
2
|
+
export { getDefaultRootScripts, getDefaultWorkspaceScripts } from './resolve-scripts.js';
|
package/dist/esm/scripts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { findMonorepoRoot } from '../context.
|
|
1
|
+
export { findMonorepoRoot } from '../context.js';
|
|
2
2
|
export declare function checkPnpmVersionConsistency(monorepoRoot: string): void;
|
|
3
3
|
export declare function checkNodeVersionConsistency(monorepoRoot: string): void;
|
|
4
4
|
export declare function getPnpmVersionFromAction(monorepoRoot: string): Promise<string>;
|
|
@@ -1,66 +1,57 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import { describe, expect, it } from
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
4
|
import { findMonorepoRoot } from "../context.js";
|
|
5
|
-
|
|
5
|
+
export { findMonorepoRoot } from "../context.js";
|
|
6
6
|
import { getRuntimeVersionFromAsdf } from "./helpers/get-runtime-version-from-asdf.js";
|
|
7
7
|
import { getStringFromYamlFile } from "./helpers/get-string-from-yaml-file.js";
|
|
8
8
|
import { getValueAtPathOrThrow } from "./helpers/get-value-at-path.js";
|
|
9
|
-
const GITHUB_ACTION_FILE_PATH =
|
|
10
|
-
function checkPnpmVersionConsistency(monorepoRoot) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
const GITHUB_ACTION_FILE_PATH = '.github/workflows/code-quality.yaml';
|
|
10
|
+
export function checkPnpmVersionConsistency(monorepoRoot) {
|
|
11
|
+
describe('pnpm version consistency', () => {
|
|
12
|
+
it('pnpm version is the same in GitHub action and package.json', async () => {
|
|
13
|
+
const actionVersion = await getPnpmVersionFromAction(monorepoRoot);
|
|
14
|
+
const packageJsonVersion = getPnpmVersionFromPackageJson(monorepoRoot);
|
|
15
|
+
expect(actionVersion).toBe(packageJsonVersion);
|
|
16
|
+
});
|
|
16
17
|
});
|
|
17
|
-
});
|
|
18
18
|
}
|
|
19
|
-
function checkNodeVersionConsistency(monorepoRoot) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
export function checkNodeVersionConsistency(monorepoRoot) {
|
|
20
|
+
describe('Node.js version consistency', () => {
|
|
21
|
+
it('version is the same in GitHub action and .tool-versions', async () => {
|
|
22
|
+
const toolVersion = await getRuntimeVersionFromAsdf('nodejs', monorepoRoot);
|
|
23
|
+
const actionVersion = await getNodeVersionFromAction(monorepoRoot);
|
|
24
|
+
expect(toolVersion).toBe(actionVersion);
|
|
25
|
+
});
|
|
25
26
|
});
|
|
26
|
-
});
|
|
27
27
|
}
|
|
28
|
-
async function getPnpmVersionFromAction(monorepoRoot) {
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
export async function getPnpmVersionFromAction(monorepoRoot) {
|
|
29
|
+
const actionPath = path.join(monorepoRoot, GITHUB_ACTION_FILE_PATH);
|
|
30
|
+
return getStringFromYamlFile(actionPath, 'jobs.code-quality.with.pnpm-version', 'pnpm version');
|
|
31
31
|
}
|
|
32
|
-
function getPnpmVersionFromPackageJson(monorepoRoot) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
export function getPnpmVersionFromPackageJson(monorepoRoot) {
|
|
33
|
+
const pkgPath = path.join(monorepoRoot, 'package.json');
|
|
34
|
+
const raw = fs.readFileSync(pkgPath, 'utf8');
|
|
35
|
+
const pkg = JSON.parse(raw);
|
|
36
|
+
const pm = getValueAtPathOrThrow(pkg, 'packageManager');
|
|
37
|
+
if (typeof pm !== 'string') {
|
|
38
|
+
throw new TypeError('"packageManager" field missing or not a string in package.json.');
|
|
39
|
+
}
|
|
40
|
+
const [name, version] = pm.split('@');
|
|
41
|
+
if (name !== 'pnpm') {
|
|
42
|
+
throw new Error('packageManager is not pnpm.');
|
|
43
|
+
}
|
|
44
|
+
if (!version) {
|
|
45
|
+
throw new Error('pnpm version missing in package.json.');
|
|
46
|
+
}
|
|
47
|
+
return version;
|
|
48
48
|
}
|
|
49
|
-
async function getNodeVersionFromAction(monorepoRoot) {
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
export async function getNodeVersionFromAction(monorepoRoot) {
|
|
50
|
+
const actionPath = path.join(monorepoRoot, GITHUB_ACTION_FILE_PATH);
|
|
51
|
+
return getStringFromYamlFile(actionPath, 'jobs.code-quality.with.node-version', 'Node.js version');
|
|
52
52
|
}
|
|
53
|
-
function runConsistencyChecks() {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
export function runConsistencyChecks() {
|
|
54
|
+
const monorepoRoot = findMonorepoRoot();
|
|
55
|
+
checkPnpmVersionConsistency(monorepoRoot);
|
|
56
|
+
checkNodeVersionConsistency(monorepoRoot);
|
|
57
57
|
}
|
|
58
|
-
export {
|
|
59
|
-
checkNodeVersionConsistency,
|
|
60
|
-
checkPnpmVersionConsistency,
|
|
61
|
-
findMonorepoRoot2 as findMonorepoRoot,
|
|
62
|
-
getNodeVersionFromAction,
|
|
63
|
-
getPnpmVersionFromAction,
|
|
64
|
-
getPnpmVersionFromPackageJson,
|
|
65
|
-
runConsistencyChecks
|
|
66
|
-
};
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import assert from
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
4
|
-
async function getRuntimeVersionFromAsdf(runtime, monorepoRoot) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export async function getRuntimeVersionFromAsdf(runtime, monorepoRoot) {
|
|
5
|
+
const toolVersionsPath = path.join(monorepoRoot, '.tool-versions');
|
|
6
|
+
const toolVersions = await fs.promises.readFile(toolVersionsPath, { encoding: 'utf8' });
|
|
7
|
+
const versionLine = toolVersions.split('\n').find((line) => line.trim().startsWith(runtime));
|
|
8
|
+
assert.ok(versionLine, `${runtime} not found in .tool-versions`);
|
|
9
|
+
const [, version] = versionLine.trim().split(/\s+/);
|
|
10
|
+
assert.ok(version, `${runtime} version missing in .tool-versions`);
|
|
11
|
+
return version;
|
|
12
12
|
}
|
|
13
|
-
export {
|
|
14
|
-
getRuntimeVersionFromAsdf
|
|
15
|
-
};
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import assert from
|
|
2
|
-
import fs from
|
|
3
|
-
import { parse } from
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { parse } from 'yaml';
|
|
4
4
|
import { getValueAtPathOrThrow } from "./get-value-at-path.js";
|
|
5
|
-
async function getStringFromYamlFile(filePath, keyPath, label) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
export async function getStringFromYamlFile(filePath, keyPath, label) {
|
|
6
|
+
const raw = await fs.promises.readFile(filePath, { encoding: 'utf8' });
|
|
7
|
+
const parsed = parse(raw);
|
|
8
|
+
assert.ok(parsed, `YAML content not found in ${filePath}`);
|
|
9
|
+
const value = getValueAtPathOrThrow(parsed, keyPath);
|
|
10
|
+
assert.ok(typeof value === 'string' && value.length > 0, `${label} not found at ${keyPath}`);
|
|
11
|
+
return value;
|
|
12
12
|
}
|
|
13
|
-
export {
|
|
14
|
-
getStringFromYamlFile
|
|
15
|
-
};
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
function isObject(value) {
|
|
2
|
-
|
|
2
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
3
|
}
|
|
4
|
-
function getValueAtPathOrThrow(obj, objPath) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
const keys = objPath.split(".");
|
|
9
|
-
let current = obj;
|
|
10
|
-
for (const key of keys) {
|
|
11
|
-
if (Array.isArray(current)) {
|
|
12
|
-
const index = Number(key);
|
|
13
|
-
if (Number.isNaN(index)) {
|
|
14
|
-
throw new TypeError(`Expected array index at segment "${key}" in path "${objPath}"`);
|
|
15
|
-
}
|
|
16
|
-
if (index >= 0 && index < current.length) {
|
|
17
|
-
current = current[index];
|
|
18
|
-
} else {
|
|
19
|
-
throw new Error(`Array index out of bounds: "${key}" in path "${objPath}"`);
|
|
20
|
-
}
|
|
21
|
-
} else if (isObject(current)) {
|
|
22
|
-
if (!(key in current)) {
|
|
23
|
-
throw new Error(`Missing key "${key}" in path "${objPath}"`);
|
|
24
|
-
}
|
|
25
|
-
current = current[key];
|
|
26
|
-
} else {
|
|
27
|
-
throw new Error(`Unexpected non-object/non-array at segment "${key}" in path "${objPath}"`);
|
|
4
|
+
export function getValueAtPathOrThrow(obj, objPath) {
|
|
5
|
+
if (!isObject(obj)) {
|
|
6
|
+
throw new Error('Expected an object as root value.');
|
|
28
7
|
}
|
|
29
|
-
|
|
30
|
-
|
|
8
|
+
const keys = objPath.split('.');
|
|
9
|
+
let current = obj;
|
|
10
|
+
for (const key of keys) {
|
|
11
|
+
if (Array.isArray(current)) {
|
|
12
|
+
const index = Number(key);
|
|
13
|
+
if (Number.isNaN(index)) {
|
|
14
|
+
throw new TypeError(`Expected array index at segment "${key}" in path "${objPath}"`);
|
|
15
|
+
}
|
|
16
|
+
if (index >= 0 && index < current.length) {
|
|
17
|
+
current = current[index];
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
throw new Error(`Array index out of bounds: "${key}" in path "${objPath}"`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else if (isObject(current)) {
|
|
24
|
+
if (!(key in current)) {
|
|
25
|
+
throw new Error(`Missing key "${key}" in path "${objPath}"`);
|
|
26
|
+
}
|
|
27
|
+
current = current[key];
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
throw new Error(`Unexpected non-object/non-array at segment "${key}" in path "${objPath}"`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return current;
|
|
31
34
|
}
|
|
32
|
-
export {
|
|
33
|
-
getValueAtPathOrThrow
|
|
34
|
-
};
|