@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
|
@@ -1,122 +1,274 @@
|
|
|
1
|
-
import { createHash } from
|
|
2
|
-
import { existsSync, readFileSync,
|
|
3
|
-
import { mkdir, readFile, writeFile } from
|
|
4
|
-
import path from
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
const PACKAGE_ICON =
|
|
8
|
-
const SKIPPED_ICON =
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
cwd: packageDir,
|
|
27
|
-
ignore: options.ignore ?? DEFAULT_IGNORE
|
|
28
|
-
});
|
|
29
|
-
const dependencies = ["package.json"];
|
|
30
|
-
const { changed, currentHash } = await detectBuildChanges(
|
|
31
|
-
packageDir,
|
|
32
|
-
[...entryPoints, ...dependencies],
|
|
33
|
-
outputConfig,
|
|
34
|
-
cacheFile
|
|
35
|
-
);
|
|
36
|
-
if (!changed) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
await build({
|
|
40
|
-
absWorkingDir: packageDir,
|
|
41
|
-
entryPoints,
|
|
42
|
-
outdir: options.outdir ?? DEFAULT_OUTDIR,
|
|
43
|
-
bundle: false,
|
|
44
|
-
sourcemap: false,
|
|
45
|
-
plugins: [rewriteTsExtensions(packageDir, aliases)],
|
|
46
|
-
...outputConfig
|
|
47
|
-
});
|
|
48
|
-
await writeBuildCache(packageDir, cacheFile, currentHash);
|
|
49
|
-
}
|
|
50
|
-
async function computeBuildHash(packageDir, files, outputConfig) {
|
|
51
|
-
const hash = createHash("sha256");
|
|
52
|
-
for (const file of [...files].sort()) {
|
|
53
|
-
hash.update(file);
|
|
54
|
-
hash.update("\0");
|
|
55
|
-
hash.update(await readFile(path.join(packageDir, file)));
|
|
56
|
-
}
|
|
57
|
-
hash.update(JSON.stringify(outputConfig));
|
|
58
|
-
return hash.digest("hex");
|
|
59
|
-
}
|
|
60
|
-
function resolveAliasImports(code, fileDir, aliasMap, packageDir) {
|
|
61
|
-
let result = code;
|
|
62
|
-
for (const [alias, targetDir] of Object.entries(aliasMap)) {
|
|
63
|
-
const escaped = alias.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
64
|
-
const regex = new RegExp(String.raw`(?<=from\s+['"])${escaped}([^'"]+)(?=['"])`, "g");
|
|
65
|
-
result = result.replace(regex, (_, subpath) => {
|
|
66
|
-
const absolute = path.resolve(packageDir, targetDir, subpath);
|
|
67
|
-
const relative = path.relative(fileDir, absolute);
|
|
68
|
-
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { glob } from 'glob';
|
|
6
|
+
import * as ts from 'typescript';
|
|
7
|
+
const PACKAGE_ICON = '📦';
|
|
8
|
+
const SKIPPED_ICON = '⏭️';
|
|
9
|
+
const DEFAULT_ENTRY_GLOBS = ['src/**/*.ts'];
|
|
10
|
+
const DEFAULT_IGNORE = ['**/__tests__/**'];
|
|
11
|
+
const DEFAULT_OUTDIR = 'dist/esm/';
|
|
12
|
+
const DEFAULT_CACHE_FILE = 'dist/esm/.cache';
|
|
13
|
+
const SOURCE_ROOT = 'src';
|
|
14
|
+
const MINIMUM_TYPESCRIPT_MAJOR = 5;
|
|
15
|
+
const MINIMUM_TYPESCRIPT_MINOR = 7;
|
|
16
|
+
const TS_EXTENSION = '.ts';
|
|
17
|
+
const JS_EXTENSION = '.js';
|
|
18
|
+
export async function buildPackage(packageDir, options = {}) {
|
|
19
|
+
assertSupportedTypeScript();
|
|
20
|
+
const cacheFile = options.cacheFile ?? DEFAULT_CACHE_FILE;
|
|
21
|
+
const outdir = options.outdir ?? DEFAULT_OUTDIR;
|
|
22
|
+
const emitConfig = { outdir, declaration: true, rewriteRelativeImportExtensions: true };
|
|
23
|
+
const entryPoints = await glob(options.entryGlobs ?? DEFAULT_ENTRY_GLOBS, {
|
|
24
|
+
cwd: packageDir,
|
|
25
|
+
ignore: options.ignore ?? DEFAULT_IGNORE,
|
|
69
26
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
27
|
+
const dependencies = ['package.json', ...resolveTsconfigChain(packageDir)];
|
|
28
|
+
const { changed, currentHash } = await detectBuildChanges(packageDir, [...entryPoints, ...dependencies], emitConfig, cacheFile);
|
|
29
|
+
if (!changed) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
emitPackage(packageDir, entryPoints, outdir);
|
|
33
|
+
await writeBuildCache(packageDir, cacheFile, currentHash);
|
|
34
|
+
}
|
|
35
|
+
export async function computeBuildHash(packageDir, files, emitConfig) {
|
|
36
|
+
const hash = createHash('sha256');
|
|
37
|
+
for (const file of [...files].sort()) {
|
|
38
|
+
hash.update(file);
|
|
39
|
+
hash.update('\0');
|
|
40
|
+
hash.update(await readFile(path.join(packageDir, file)));
|
|
41
|
+
}
|
|
42
|
+
hash.update(JSON.stringify(emitConfig));
|
|
43
|
+
return hash.digest('hex');
|
|
44
|
+
}
|
|
45
|
+
export function resolveTsconfigChain(packageDir, configFileName = 'tsconfig.json') {
|
|
46
|
+
const resolvedChain = [];
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
function walk(configPath) {
|
|
49
|
+
const normalized = path.resolve(configPath);
|
|
50
|
+
if (seen.has(normalized)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
seen.add(normalized);
|
|
54
|
+
resolvedChain.push(normalized);
|
|
55
|
+
const configFile = ts.readConfigFile(normalized, (fileName) => ts.sys.readFile(fileName));
|
|
56
|
+
if (configFile.error) {
|
|
57
|
+
throw new Error(`nmr-compile: failed to read ${normalized}.\n${formatDiagnostics([configFile.error])}`);
|
|
58
|
+
}
|
|
59
|
+
for (const entry of normalizeExtendsField(configFile.config)) {
|
|
60
|
+
const baseConfig = resolveExtendsTarget(entry, normalized);
|
|
61
|
+
if (baseConfig !== undefined) {
|
|
62
|
+
walk(baseConfig);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
walk(path.resolve(packageDir, configFileName));
|
|
67
|
+
return resolvedChain.map((absolute) => path.relative(packageDir, absolute));
|
|
68
|
+
}
|
|
69
|
+
function emitPackage(packageDir, entryPoints, outdir) {
|
|
70
|
+
const compilerOptions = synthesizeCompilerOptions(packageDir, outdir);
|
|
71
|
+
const rootNames = entryPoints.map((entry) => path.resolve(packageDir, entry));
|
|
72
|
+
const sourceRoot = path.resolve(packageDir, SOURCE_ROOT);
|
|
73
|
+
const program = ts.createProgram(rootNames, compilerOptions);
|
|
74
|
+
const emittedFiles = [];
|
|
75
|
+
const emitResult = program.emit(undefined, (fileName, text, writeByteOrderMark) => {
|
|
76
|
+
ts.sys.writeFile(fileName, text, writeByteOrderMark);
|
|
77
|
+
emittedFiles.push(fileName);
|
|
78
|
+
});
|
|
79
|
+
if (emitResult.emitSkipped) {
|
|
80
|
+
throw new Error(`nmr-compile: emit failed.\n${formatDiagnostics(emitResult.diagnostics)}`);
|
|
81
|
+
}
|
|
82
|
+
for (const file of emittedFiles) {
|
|
83
|
+
rewriteOutputSpecifiers(file, compilerOptions, sourceRoot);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function synthesizeCompilerOptions(packageDir, outdir) {
|
|
87
|
+
const configPath = path.join(packageDir, 'tsconfig.json');
|
|
88
|
+
const configFile = ts.readConfigFile(configPath, (fileName) => ts.sys.readFile(fileName));
|
|
89
|
+
if (configFile.error) {
|
|
90
|
+
throw new Error(`nmr-compile: failed to read ${configPath}.\n${formatDiagnostics([configFile.error])}`);
|
|
91
|
+
}
|
|
92
|
+
const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, packageDir);
|
|
93
|
+
if (parsed.errors.length > 0) {
|
|
94
|
+
throw new Error(`nmr-compile: failed to parse ${configPath}.\n${formatDiagnostics(parsed.errors)}`);
|
|
95
|
+
}
|
|
96
|
+
const resolvedOutDir = path.resolve(packageDir, outdir);
|
|
97
|
+
return {
|
|
98
|
+
...parsed.options,
|
|
99
|
+
noEmit: false,
|
|
100
|
+
emitDeclarationOnly: false,
|
|
101
|
+
declaration: true,
|
|
102
|
+
rewriteRelativeImportExtensions: true,
|
|
103
|
+
outDir: resolvedOutDir,
|
|
104
|
+
declarationDir: resolvedOutDir,
|
|
105
|
+
rootDir: path.resolve(packageDir, SOURCE_ROOT),
|
|
106
|
+
sourceMap: false,
|
|
107
|
+
declarationMap: false,
|
|
108
|
+
noEmitOnError: false,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function rewriteOutputSpecifiers(outputFile, compilerOptions, sourceRoot) {
|
|
112
|
+
if (!isRewritableOutput(outputFile)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const originalText = readFileSync(outputFile, 'utf8');
|
|
116
|
+
const sourceFile = ts.createSourceFile(outputFile, originalText, ts.ScriptTarget.Latest, false, scriptKindFor(outputFile));
|
|
117
|
+
const aliasPrefixes = collectAliasPrefixes(compilerOptions);
|
|
118
|
+
const sourceContainingFile = mapOutputToSource(outputFile, compilerOptions, sourceRoot);
|
|
119
|
+
const edits = [];
|
|
120
|
+
forEachModuleSpecifier(sourceFile, (literal) => {
|
|
121
|
+
const replacement = resolveSpecifierReplacement(literal.text, sourceContainingFile, compilerOptions, sourceRoot, aliasPrefixes);
|
|
122
|
+
if (replacement === undefined) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const start = literal.getStart(sourceFile);
|
|
126
|
+
const quote = originalText[start] ?? '"';
|
|
127
|
+
edits.push({ start, end: literal.getEnd(), text: `${quote}${replacement}${quote}` });
|
|
128
|
+
});
|
|
129
|
+
if (edits.length === 0) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const orderedEdits = [...edits].sort((a, b) => b.start - a.start);
|
|
133
|
+
let updatedText = originalText;
|
|
134
|
+
for (const edit of orderedEdits) {
|
|
135
|
+
updatedText = updatedText.slice(0, edit.start) + edit.text + updatedText.slice(edit.end);
|
|
136
|
+
}
|
|
137
|
+
writeFileSync(outputFile, updatedText);
|
|
138
|
+
}
|
|
139
|
+
function resolveSpecifierReplacement(specifier, sourceContainingFile, compilerOptions, sourceRoot, aliasPrefixes) {
|
|
140
|
+
if (isRelativeSpecifier(specifier)) {
|
|
141
|
+
const rewritten = swapTypeScriptExtension(specifier);
|
|
142
|
+
return rewritten === specifier ? undefined : rewritten;
|
|
143
|
+
}
|
|
144
|
+
if (!aliasPrefixes.some((prefix) => specifier === prefix || specifier.startsWith(prefix))) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const resolved = ts.resolveModuleName(specifier, sourceContainingFile, compilerOptions, ts.sys).resolvedModule;
|
|
148
|
+
if (!resolved) {
|
|
149
|
+
throw new Error(`nmr-compile: could not resolve aliased import '${specifier}' from ${sourceContainingFile}. ` +
|
|
150
|
+
`Verify the tsconfig 'paths' mapping and that the target file exists.`);
|
|
151
|
+
}
|
|
152
|
+
if (!isWithin(sourceRoot, resolved.resolvedFileName)) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
const relative = toRelativeSpecifier(path.dirname(sourceContainingFile), resolved.resolvedFileName);
|
|
156
|
+
return swapTypeScriptExtension(relative);
|
|
157
|
+
}
|
|
158
|
+
function forEachModuleSpecifier(sourceFile, visit) {
|
|
159
|
+
function walk(node) {
|
|
160
|
+
const specifier = getModuleSpecifier(node);
|
|
161
|
+
if (specifier !== undefined) {
|
|
162
|
+
visit(specifier);
|
|
163
|
+
}
|
|
164
|
+
ts.forEachChild(node, walk);
|
|
165
|
+
}
|
|
166
|
+
walk(sourceFile);
|
|
167
|
+
}
|
|
168
|
+
function getModuleSpecifier(node) {
|
|
169
|
+
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier !== undefined) {
|
|
170
|
+
return ts.isStringLiteralLike(node.moduleSpecifier) ? node.moduleSpecifier : undefined;
|
|
171
|
+
}
|
|
172
|
+
if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference)) {
|
|
173
|
+
return ts.isStringLiteralLike(node.moduleReference.expression) ? node.moduleReference.expression : undefined;
|
|
174
|
+
}
|
|
175
|
+
if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument)) {
|
|
176
|
+
return ts.isStringLiteralLike(node.argument.literal) ? node.argument.literal : undefined;
|
|
177
|
+
}
|
|
178
|
+
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
179
|
+
const [first] = node.arguments;
|
|
180
|
+
return first !== undefined && ts.isStringLiteralLike(first) ? first : undefined;
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
async function detectBuildChanges(packageDir, files, emitConfig, cacheFile) {
|
|
185
|
+
const packageName = path.basename(packageDir);
|
|
186
|
+
const cachePath = path.join(packageDir, cacheFile);
|
|
187
|
+
const previousHash = existsSync(cachePath) ? readFileSync(cachePath, 'utf8') : undefined;
|
|
188
|
+
const currentHash = await computeBuildHash(packageDir, files, emitConfig);
|
|
189
|
+
if (previousHash === currentHash) {
|
|
190
|
+
console.info(`${SKIPPED_ICON} ${packageName}: No changes detected. Skipping build.`);
|
|
191
|
+
return { changed: false, currentHash };
|
|
192
|
+
}
|
|
193
|
+
console.info(`${PACKAGE_ICON} ${packageName}: Changes detected.`);
|
|
194
|
+
return { changed: true, currentHash };
|
|
87
195
|
}
|
|
88
196
|
async function writeBuildCache(packageDir, cacheFile, hash) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
197
|
+
const cachePath = path.join(packageDir, cacheFile);
|
|
198
|
+
await mkdir(path.dirname(cachePath), { recursive: true });
|
|
199
|
+
await writeFile(cachePath, hash);
|
|
200
|
+
}
|
|
201
|
+
function assertSupportedTypeScript() {
|
|
202
|
+
const [majorPart, minorPart] = ts.versionMajorMinor.split('.');
|
|
203
|
+
const major = majorPart === undefined ? 0 : Number(majorPart);
|
|
204
|
+
const minor = minorPart === undefined ? 0 : Number(minorPart);
|
|
205
|
+
const tooOld = major < MINIMUM_TYPESCRIPT_MAJOR || (major === MINIMUM_TYPESCRIPT_MAJOR && minor < MINIMUM_TYPESCRIPT_MINOR);
|
|
206
|
+
if (tooOld) {
|
|
207
|
+
throw new Error(`nmr-compile requires TypeScript >=${MINIMUM_TYPESCRIPT_MAJOR}.${MINIMUM_TYPESCRIPT_MINOR} for ` +
|
|
208
|
+
`rewriteRelativeImportExtensions, but found ${ts.version}. Upgrade the 'typescript' peer dependency.`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function collectAliasPrefixes(compilerOptions) {
|
|
212
|
+
if (compilerOptions.paths === undefined) {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
return Object.keys(compilerOptions.paths).map((pattern) => pattern.replace(/\*$/, ''));
|
|
216
|
+
}
|
|
217
|
+
function formatDiagnostics(diagnostics) {
|
|
218
|
+
return ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
219
|
+
getCurrentDirectory: () => process.cwd(),
|
|
220
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
221
|
+
getNewLine: () => ts.sys.newLine,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function isRewritableOutput(file) {
|
|
225
|
+
return file.endsWith('.d.ts') || file.endsWith('.js');
|
|
226
|
+
}
|
|
227
|
+
function normalizeExtendsField(config) {
|
|
228
|
+
if (config === null || typeof config !== 'object' || !('extends' in config)) {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
const extendsField = config.extends;
|
|
232
|
+
if (typeof extendsField === 'string') {
|
|
233
|
+
return [extendsField];
|
|
234
|
+
}
|
|
235
|
+
if (Array.isArray(extendsField)) {
|
|
236
|
+
return extendsField.filter((entry) => typeof entry === 'string');
|
|
237
|
+
}
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
function resolveExtendsTarget(extendsEntry, fromConfigPath) {
|
|
241
|
+
if (isRelativeSpecifier(extendsEntry) || path.isAbsolute(extendsEntry)) {
|
|
242
|
+
const base = path.resolve(path.dirname(fromConfigPath), extendsEntry);
|
|
243
|
+
if (ts.sys.fileExists(base)) {
|
|
244
|
+
return base;
|
|
109
245
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
246
|
+
const withJsonExtension = `${base}.json`;
|
|
247
|
+
return ts.sys.fileExists(withJsonExtension) ? withJsonExtension : undefined;
|
|
248
|
+
}
|
|
249
|
+
const { resolvedModule } = ts.resolveModuleName(extendsEntry, fromConfigPath, { moduleResolution: ts.ModuleResolutionKind.NodeNext, resolveJsonModule: true }, ts.sys);
|
|
250
|
+
return resolvedModule?.resolvedFileName;
|
|
251
|
+
}
|
|
252
|
+
function isRelativeSpecifier(specifier) {
|
|
253
|
+
return specifier.startsWith('./') || specifier.startsWith('../');
|
|
254
|
+
}
|
|
255
|
+
function isWithin(parent, child) {
|
|
256
|
+
const relative = path.relative(parent, child);
|
|
257
|
+
return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
258
|
+
}
|
|
259
|
+
function mapOutputToSource(outputFile, compilerOptions, sourceRoot) {
|
|
260
|
+
const outDir = compilerOptions.outDir ?? path.dirname(outputFile);
|
|
261
|
+
const relativeFromOut = path.relative(outDir, outputFile);
|
|
262
|
+
const withoutExtension = relativeFromOut.replace(/\.d\.ts$|\.js$/, '');
|
|
263
|
+
return path.join(sourceRoot, `${withoutExtension}.ts`);
|
|
264
|
+
}
|
|
265
|
+
function toRelativeSpecifier(fromDir, targetFile) {
|
|
266
|
+
const relative = path.relative(fromDir, targetFile).split(path.sep).join('/');
|
|
267
|
+
return relative.startsWith('.') ? relative : `./${relative}`;
|
|
268
|
+
}
|
|
269
|
+
function scriptKindFor(file) {
|
|
270
|
+
return file.endsWith('.d.ts') ? ts.ScriptKind.TS : ts.ScriptKind.JS;
|
|
271
|
+
}
|
|
272
|
+
function swapTypeScriptExtension(specifier) {
|
|
273
|
+
return specifier.endsWith(TS_EXTENSION) ? `${specifier.slice(0, -TS_EXTENSION.length)}${JS_EXTENSION}` : specifier;
|
|
274
|
+
}
|
|
@@ -1,76 +1,73 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from
|
|
2
|
-
import path from
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
import { getWorkspacePackageDirs } from "../context.js";
|
|
4
4
|
import { readPackageJson } from "../helpers/package-json.js";
|
|
5
5
|
import { isObject } from "../helpers/type-guards.js";
|
|
6
|
-
const DEFAULT_HOOK =
|
|
7
|
-
function ensurePrepublishHooks(monorepoRoot, options) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
6
|
+
export const DEFAULT_HOOK = 'npm run build';
|
|
7
|
+
export function ensurePrepublishHooks(monorepoRoot, options) {
|
|
8
|
+
const hookCommand = options.command ?? DEFAULT_HOOK;
|
|
9
|
+
const packageDirs = getWorkspacePackageDirs(monorepoRoot);
|
|
10
|
+
const packages = [];
|
|
11
|
+
for (const packageDir of packageDirs) {
|
|
12
|
+
const pkg = readPackageJson(packageDir);
|
|
13
|
+
const packageName = pkg.name ?? path.basename(packageDir);
|
|
14
|
+
const isPrivate = pkg.private === true;
|
|
15
|
+
if (isPrivate) {
|
|
16
|
+
packages.push({
|
|
17
|
+
packageName,
|
|
18
|
+
packageDir,
|
|
19
|
+
isPrivate: true,
|
|
20
|
+
prepublishOnly: pkg.scripts?.prepublishOnly,
|
|
21
|
+
action: 'ok',
|
|
22
|
+
});
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const existing = pkg.scripts?.prepublishOnly;
|
|
26
|
+
if (existing) {
|
|
27
|
+
packages.push({
|
|
28
|
+
packageName,
|
|
29
|
+
packageDir,
|
|
30
|
+
isPrivate: false,
|
|
31
|
+
prepublishOnly: existing,
|
|
32
|
+
action: 'ok',
|
|
33
|
+
});
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (options.fix) {
|
|
37
|
+
const action = options.dryRun ? 'would-fix' : 'fixed';
|
|
38
|
+
if (!options.dryRun) {
|
|
39
|
+
addPrepublishOnly(packageDir, hookCommand);
|
|
40
|
+
}
|
|
41
|
+
packages.push({
|
|
42
|
+
packageName,
|
|
43
|
+
packageDir,
|
|
44
|
+
isPrivate: false,
|
|
45
|
+
prepublishOnly: undefined,
|
|
46
|
+
action,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
packages.push({
|
|
51
|
+
packageName,
|
|
52
|
+
packageDir,
|
|
53
|
+
isPrivate: false,
|
|
54
|
+
prepublishOnly: undefined,
|
|
55
|
+
action: 'missing',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
24
58
|
}
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
packages.push({
|
|
28
|
-
packageName,
|
|
29
|
-
packageDir,
|
|
30
|
-
isPrivate: false,
|
|
31
|
-
prepublishOnly: existing,
|
|
32
|
-
action: "ok"
|
|
33
|
-
});
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (options.fix) {
|
|
37
|
-
const action = options.dryRun ? "would-fix" : "fixed";
|
|
38
|
-
if (!options.dryRun) {
|
|
39
|
-
addPrepublishOnly(packageDir, hookCommand);
|
|
40
|
-
}
|
|
41
|
-
packages.push({
|
|
42
|
-
packageName,
|
|
43
|
-
packageDir,
|
|
44
|
-
isPrivate: false,
|
|
45
|
-
prepublishOnly: void 0,
|
|
46
|
-
action
|
|
47
|
-
});
|
|
48
|
-
} else {
|
|
49
|
-
packages.push({
|
|
50
|
-
packageName,
|
|
51
|
-
packageDir,
|
|
52
|
-
isPrivate: false,
|
|
53
|
-
prepublishOnly: void 0,
|
|
54
|
-
action: "missing"
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
const hasFailures = packages.some((p) => p.action === "missing");
|
|
59
|
-
return { packages, hasFailures };
|
|
59
|
+
const hasFailures = packages.some((p) => p.action === 'missing');
|
|
60
|
+
return { packages, hasFailures };
|
|
60
61
|
}
|
|
61
62
|
function addPrepublishOnly(packageDir, command) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
const filePath = path.join(packageDir, 'package.json');
|
|
64
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
65
|
+
const parsed = JSON.parse(raw);
|
|
66
|
+
if (!isObject(parsed)) {
|
|
67
|
+
throw new TypeError(`Invalid package.json in ${packageDir}: expected an object`);
|
|
68
|
+
}
|
|
69
|
+
const scripts = isObject(parsed.scripts) ? parsed.scripts : {};
|
|
70
|
+
scripts.prepublishOnly = command;
|
|
71
|
+
parsed.scripts = scripts;
|
|
72
|
+
writeFileSync(filePath, JSON.stringify(parsed, null, 2) + '\n', 'utf8');
|
|
72
73
|
}
|
|
73
|
-
export {
|
|
74
|
-
DEFAULT_HOOK,
|
|
75
|
-
ensurePrepublishHooks
|
|
76
|
-
};
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { getPnpmOverrides, readPackageJson } from "../helpers/package-json.js";
|
|
2
|
-
function reportOverrides(monorepoRoot) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
export function reportOverrides(monorepoRoot) {
|
|
3
|
+
const pkg = readPackageJson(monorepoRoot);
|
|
4
|
+
const overrides = getPnpmOverrides(pkg);
|
|
5
|
+
if (!overrides || Object.keys(overrides).length === 0) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
console.warn('🔒 WARN: pnpm overrides are active! Check whether these are still needed:');
|
|
9
|
+
for (const [name, version] of Object.entries(overrides)) {
|
|
10
|
+
console.warn(`- ${name} → ${version}`);
|
|
11
|
+
}
|
|
12
12
|
}
|
|
13
|
-
export {
|
|
14
|
-
reportOverrides
|
|
15
|
-
};
|