Skip to content

Commit a820a30

Browse files
committed
fix(ci): keep postinstall script self-contained
1 parent 45f3074 commit a820a30

1 file changed

Lines changed: 133 additions & 3 deletions

File tree

scripts/postinstall-bundled-plugins.mjs

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ import {
2121
unlinkSync,
2222
writeFileSync,
2323
} from "node:fs";
24-
import { basename, dirname, isAbsolute, join, relative } from "node:path";
24+
import { basename, dirname, isAbsolute, join, posix, relative } from "node:path";
2525
import { fileURLToPath, pathToFileURL } from "node:url";
26-
import { expandPackageDistImportClosure } from "./lib/package-dist-imports.mjs";
2726
import { resolveNpmRunner } from "./npm-runner.mjs";
2827

2928
export const BUNDLED_PLUGIN_INSTALL_TARGETS = [];
@@ -272,6 +271,137 @@ function pruneEmptyDistDirectories(params = {}) {
272271
prune(distRoot.distDir);
273272
}
274273

274+
const JS_DIST_FILE_RE = /^dist\/.*\.(?:cjs|js|mjs)$/u;
275+
276+
function stripSpecifierSuffix(value) {
277+
return value.replace(/[?#].*$/u, "");
278+
}
279+
280+
function resolveDistImportPath(importerPath, specifier) {
281+
if (!specifier.startsWith(".")) {
282+
return null;
283+
}
284+
const stripped = stripSpecifierSuffix(specifier);
285+
if (!stripped) {
286+
return null;
287+
}
288+
return posix.normalize(posix.join(posix.dirname(importerPath), stripped));
289+
}
290+
291+
function findStatementStart(source, index) {
292+
return (
293+
Math.max(
294+
source.lastIndexOf(";", index),
295+
source.lastIndexOf("{", index),
296+
source.lastIndexOf("}", index),
297+
source.lastIndexOf("\n", index),
298+
source.lastIndexOf("\r", index),
299+
) + 1
300+
);
301+
}
302+
303+
function isImportSpecifierContext(source, index) {
304+
const dynamicPrefix = source.slice(Math.max(0, index - 32), index);
305+
if (/\bimport\s*\(\s*$/u.test(dynamicPrefix)) {
306+
return true;
307+
}
308+
const statementPrefix = source.slice(findStatementStart(source, index), index).trimStart();
309+
return (
310+
/^(?:import|export)\b[\s\S]*\bfrom\s*$/u.test(statementPrefix) ||
311+
/^import\s*$/u.test(statementPrefix)
312+
);
313+
}
314+
315+
function collectImportSpecifiers(source) {
316+
const specifiers = [];
317+
let inBlockComment = false;
318+
let inLineComment = false;
319+
for (let index = 0; index < source.length; index += 1) {
320+
if (inBlockComment) {
321+
if (source[index] === "*" && source[index + 1] === "/") {
322+
inBlockComment = false;
323+
index += 1;
324+
}
325+
continue;
326+
}
327+
if (inLineComment) {
328+
if (source[index] === "\n" || source[index] === "\r") {
329+
inLineComment = false;
330+
}
331+
continue;
332+
}
333+
if (source[index] === "/" && source[index + 1] === "*") {
334+
inBlockComment = true;
335+
index += 1;
336+
continue;
337+
}
338+
if (source[index] === "/" && source[index + 1] === "/") {
339+
inLineComment = true;
340+
index += 1;
341+
continue;
342+
}
343+
344+
const quote = source[index];
345+
if (quote !== '"' && quote !== "'") {
346+
continue;
347+
}
348+
349+
let cursor = index + 1;
350+
let value = "";
351+
while (cursor < source.length) {
352+
const char = source[cursor];
353+
if (char === "\\") {
354+
value += source.slice(cursor, cursor + 2);
355+
cursor += 2;
356+
continue;
357+
}
358+
if (char === quote) {
359+
break;
360+
}
361+
value += char;
362+
cursor += 1;
363+
}
364+
if (cursor >= source.length) {
365+
break;
366+
}
367+
368+
if (value.startsWith(".") && isImportSpecifierContext(source, index)) {
369+
specifiers.push(value);
370+
}
371+
index = cursor;
372+
}
373+
return specifiers;
374+
}
375+
376+
function expandInstalledDistImportClosure(params) {
377+
const files = [...new Set(params.files)];
378+
const fileSet = new Set(files);
379+
const expectedSet = new Set(params.seedFiles);
380+
let changed = true;
381+
382+
while (changed) {
383+
changed = false;
384+
for (const importerPath of [...expectedSet]
385+
.filter((file) => fileSet.has(file))
386+
.toSorted((left, right) => left.localeCompare(right))) {
387+
if (!JS_DIST_FILE_RE.test(importerPath) || importerPath.includes("/node_modules/")) {
388+
continue;
389+
}
390+
const source = params.readText(importerPath);
391+
for (const specifier of collectImportSpecifiers(source)) {
392+
const importedPath = resolveDistImportPath(importerPath, specifier);
393+
if (!importedPath || !fileSet.has(importedPath) || expectedSet.has(importedPath)) {
394+
continue;
395+
}
396+
expectedSet.add(importedPath);
397+
changed = true;
398+
}
399+
}
400+
}
401+
402+
return [...expectedSet].toSorted((left, right) => left.localeCompare(right));
403+
}
404+
275405
export function pruneInstalledPackageDist(params = {}) {
276406
const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT;
277407
const removeFile = params.unlinkSync ?? unlinkSync;
@@ -295,7 +425,7 @@ export function pruneInstalledPackageDist(params = {}) {
295425
const installedFiles = listInstalledDistFiles(params);
296426
const readFile = params.readFileSync ?? readFileSync;
297427
expectedFiles = new Set(
298-
expandPackageDistImportClosure({
428+
expandInstalledDistImportClosure({
299429
files: installedFiles,
300430
seedFiles: [...expectedFiles],
301431
readText(relativePath) {

0 commit comments

Comments
 (0)