@williamthorsen/nmr 0.16.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 +41 -44
- package/dist/esm/.cache +1 -1
- package/dist/esm/cli-build.js +5 -5
- package/dist/esm/cli-ensure-prepublish-hooks.js +33 -34
- package/dist/esm/cli-report-overrides.js +7 -6
- package/dist/esm/cli-sync-agent-files.js +20 -22
- package/dist/esm/cli-sync-pnpm-version.js +7 -6
- 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 -68
- package/dist/esm/help.d.ts +1 -1
- package/dist/esm/help.js +65 -67
- 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 +2 -2
- package/dist/esm/resolver.js +84 -86
- package/dist/esm/runCli.js +136 -144
- 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,167 +1,159 @@
|
|
|
1
|
-
import path from
|
|
2
|
-
import { readPackageVersion, reportError } 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 {
|
|
7
|
-
applyDevBin,
|
|
8
|
-
buildRootRegistry,
|
|
9
|
-
buildWorkspaceRegistry,
|
|
10
|
-
hasIntegrationTestConfig,
|
|
11
|
-
resolveScript
|
|
12
|
-
} from "./resolver.js";
|
|
6
|
+
import { applyDevBin, buildRootRegistry, buildWorkspaceRegistry, hasIntegrationTestConfig, resolveScript, } from "./resolver.js";
|
|
13
7
|
import { runCommand } from "./runner.js";
|
|
14
8
|
const VERSION = readPackageVersion(import.meta.url);
|
|
15
|
-
async function runCli(options) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
const { parsed } = parseResult;
|
|
23
|
-
if (parsed.version) {
|
|
24
|
-
stdout.write(`${VERSION}
|
|
25
|
-
`);
|
|
26
|
-
return { exitCode: 0 };
|
|
27
|
-
}
|
|
28
|
-
const context = await resolveContext(cwd);
|
|
29
|
-
const useRoot = parsed.workspaceRoot || context.isRoot;
|
|
30
|
-
const packageDir = useRoot ? context.monorepoRoot : context.packageDir ?? context.monorepoRoot;
|
|
31
|
-
if (parsed.help || !parsed.command) {
|
|
32
|
-
stdout.write(`${generateHelp(context.config, packageDir, useRoot)}
|
|
33
|
-
`);
|
|
34
|
-
return { exitCode: 0 };
|
|
35
|
-
}
|
|
36
|
-
const { command } = parsed;
|
|
37
|
-
const passthrough = parsed.passthrough.length > 0 ? " " + parsed.passthrough.map(shellQuote).join(" ") : "";
|
|
38
|
-
const runOptions = { quiet: parsed.quiet, stdout, stderr, env };
|
|
39
|
-
if (parsed.filter) {
|
|
40
|
-
const delegateCmd = `pnpm --filter ${shellQuote(parsed.filter)} exec nmr ${command}${passthrough}`;
|
|
41
|
-
const exitCode2 = runCommand(delegateCmd, context.monorepoRoot, runOptions);
|
|
42
|
-
return { exitCode: exitCode2 };
|
|
43
|
-
}
|
|
44
|
-
if (parsed.recursive) {
|
|
45
|
-
const childEnv = { ...env, NMR_RUN_IF_PRESENT: "1" };
|
|
46
|
-
const delegateCmd = `pnpm --recursive exec nmr ${command}${passthrough}`;
|
|
47
|
-
const exitCode2 = runCommand(delegateCmd, context.monorepoRoot, { ...runOptions, env: childEnv });
|
|
48
|
-
return { exitCode: exitCode2 };
|
|
49
|
-
}
|
|
50
|
-
const registry = useRoot ? buildRootRegistry(context.config) : buildWorkspaceRegistry(context.config, hasIntegrationTestConfig(packageDir));
|
|
51
|
-
const resolved = resolveScript(command, registry, packageDir, parsed.workspaceRoot);
|
|
52
|
-
if (!resolved) {
|
|
53
|
-
if (env.NMR_RUN_IF_PRESENT === "1") {
|
|
54
|
-
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 };
|
|
55
15
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
`);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const parsed = {
|
|
76
|
-
quiet: false,
|
|
77
|
-
recursive: false,
|
|
78
|
-
workspaceRoot: false,
|
|
79
|
-
help: false,
|
|
80
|
-
version: false,
|
|
81
|
-
passthrough: []
|
|
82
|
-
};
|
|
83
|
-
let i = 0;
|
|
84
|
-
while (i < args.length) {
|
|
85
|
-
const arg = args[i];
|
|
86
|
-
if (arg === void 0) break;
|
|
87
|
-
if (arg === "-F" || arg === "--filter") {
|
|
88
|
-
i++;
|
|
89
|
-
const filterValue = args[i];
|
|
90
|
-
if (filterValue === void 0) {
|
|
91
|
-
return { ok: false, error: "-F/--filter requires a pattern argument" };
|
|
92
|
-
}
|
|
93
|
-
parsed.filter = filterValue;
|
|
94
|
-
i++;
|
|
95
|
-
continue;
|
|
16
|
+
const { parsed } = parseResult;
|
|
17
|
+
if (parsed.version) {
|
|
18
|
+
stdout.write(`${VERSION}\n`);
|
|
19
|
+
return { exitCode: 0 };
|
|
20
|
+
}
|
|
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 };
|
|
27
|
+
}
|
|
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 };
|
|
96
35
|
}
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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 };
|
|
101
41
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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 };
|
|
106
52
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
continue;
|
|
53
|
+
const skipExitCode = handleSkipMessage(resolved.command, packageDir, parsed.quiet, stdout);
|
|
54
|
+
if (skipExitCode !== undefined) {
|
|
55
|
+
return { exitCode: skipExitCode };
|
|
111
56
|
}
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
i++;
|
|
115
|
-
continue;
|
|
57
|
+
if (resolved.source === 'package' && !parsed.quiet && registry[command] !== undefined) {
|
|
58
|
+
stdout.write(`📦 ${path.basename(packageDir)}: Using override script: ${resolved.command}\n`);
|
|
116
59
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
121
|
}
|
|
122
|
-
parsed
|
|
123
|
-
parsed.passthrough = args.slice(i + 1);
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
return { ok: true, parsed };
|
|
122
|
+
return { ok: true, parsed };
|
|
127
123
|
}
|
|
128
124
|
function handleSkipMessage(resolvedCommand, packageDir, quiet, stdout) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
125
|
+
if (resolvedCommand === '') {
|
|
126
|
+
if (!quiet) {
|
|
127
|
+
stdout.write(`⛔ ${path.basename(packageDir)}: Override script is defined but empty. Skipping.\n`);
|
|
128
|
+
}
|
|
129
|
+
return 0;
|
|
133
130
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
`);
|
|
131
|
+
if (resolvedCommand === ':') {
|
|
132
|
+
if (!quiet) {
|
|
133
|
+
stdout.write(`⛔ ${path.basename(packageDir)}: Override script is a no-op. Skipping.\n`);
|
|
134
|
+
}
|
|
135
|
+
return 0;
|
|
140
136
|
}
|
|
141
|
-
return
|
|
142
|
-
}
|
|
143
|
-
return void 0;
|
|
137
|
+
return undefined;
|
|
144
138
|
}
|
|
145
139
|
function wrapWithHooks(command, mainCommand, registry, packageDir, workspaceRoot) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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(' && ');
|
|
156
150
|
}
|
|
157
151
|
function hasRunnableHook(hookName, registry, packageDir, workspaceRoot) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
152
|
+
const resolved = resolveScript(hookName, registry, packageDir, workspaceRoot);
|
|
153
|
+
if (!resolved)
|
|
154
|
+
return false;
|
|
155
|
+
return resolved.command !== '' && resolved.command !== ':';
|
|
161
156
|
}
|
|
162
157
|
function shellQuote(arg) {
|
|
163
|
-
|
|
158
|
+
return "'" + arg.replace(/'/g, String.raw `'\''`) + "'";
|
|
164
159
|
}
|
|
165
|
-
export {
|
|
166
|
-
runCli
|
|
167
|
-
};
|
package/dist/esm/runner.js
CHANGED
|
@@ -1,40 +1,41 @@
|
|
|
1
|
-
import { spawnSync } from
|
|
2
|
-
import process from
|
|
3
|
-
import { reportError } from
|
|
4
|
-
function runCommand(command, cwd, options) {
|
|
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
|
-
};
|