From 0459bff55635bb0af0e9b9da9d073e54bfa01327 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 3 May 2026 13:51:14 +0100 Subject: [PATCH] refactor: share package cleanup helpers --- scripts/copy-bundled-plugin-metadata.mjs | 87 +---------- scripts/lib/plugin-npm-package-manifest.mjs | 4 +- scripts/postinstall-bundled-plugins.mjs | 161 ++------------------ 3 files changed, 17 insertions(+), 235 deletions(-) diff --git a/scripts/copy-bundled-plugin-metadata.mjs b/scripts/copy-bundled-plugin-metadata.mjs index 2d7cb985517..a4f8dae480b 100644 --- a/scripts/copy-bundled-plugin-metadata.mjs +++ b/scripts/copy-bundled-plugin-metadata.mjs @@ -1,9 +1,12 @@ import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import JSON5 from "json5"; import { NON_PACKAGED_BUNDLED_PLUGIN_DIRS } from "./lib/bundled-plugin-build-entries.mjs"; import { shouldBuildBundledCluster } from "./lib/optional-bundled-clusters.mjs"; +import { + mergeGeneratedChannelConfigs, + readGeneratedBundledChannelConfigs, +} from "./lib/plugin-npm-package-manifest.mjs"; import { removeFileIfExists, removePathIfExists, @@ -11,8 +14,6 @@ import { } from "./runtime-postbuild-shared.mjs"; const GENERATED_BUNDLED_SKILLS_DIR = "bundled-skills"; -const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA_PATH = - "src/config/bundled-channel-config-metadata.generated.ts"; const TRANSIENT_COPY_ERROR_CODES = new Set(["EEXIST", "ENOENT", "ENOTEMPTY", "EBUSY"]); const COPY_RETRY_DELAYS_MS = [10, 25, 50]; @@ -220,86 +221,6 @@ function copyDeclaredPluginSkillPaths(params) { return copiedSkills; } -function readGeneratedBundledChannelConfigs(repoRoot) { - const metadataPath = path.join(repoRoot, GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA_PATH); - if (!fs.existsSync(metadataPath)) { - return new Map(); - } - const source = fs.readFileSync(metadataPath, "utf8"); - const match = source.match( - /export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = ([\s\S]*?) as const;/u, - ); - if (!match?.[1]) { - return new Map(); - } - let entries; - try { - entries = JSON5.parse(match[1]); - } catch { - return new Map(); - } - if (!Array.isArray(entries)) { - return new Map(); - } - const byPlugin = new Map(); - for (const entry of entries) { - if ( - !entry || - typeof entry !== "object" || - typeof entry.pluginId !== "string" || - typeof entry.channelId !== "string" || - !entry.schema || - typeof entry.schema !== "object" - ) { - continue; - } - const pluginConfigs = byPlugin.get(entry.pluginId) ?? {}; - pluginConfigs[entry.channelId] = { - schema: entry.schema, - ...(typeof entry.label === "string" && entry.label ? { label: entry.label } : {}), - ...(typeof entry.description === "string" && entry.description - ? { description: entry.description } - : {}), - ...(entry.uiHints && typeof entry.uiHints === "object" ? { uiHints: entry.uiHints } : {}), - }; - byPlugin.set(entry.pluginId, pluginConfigs); - } - return byPlugin; -} - -function mergeGeneratedChannelConfigs(manifest, generatedChannelConfigs) { - if (!generatedChannelConfigs || Object.keys(generatedChannelConfigs).length === 0) { - return manifest; - } - const existingChannelConfigs = - manifest.channelConfigs && typeof manifest.channelConfigs === "object" - ? manifest.channelConfigs - : {}; - const channelConfigs = { ...existingChannelConfigs }; - for (const [channelId, generated] of Object.entries(generatedChannelConfigs)) { - const existing = - existingChannelConfigs[channelId] && typeof existingChannelConfigs[channelId] === "object" - ? existingChannelConfigs[channelId] - : {}; - channelConfigs[channelId] = { - ...generated, - ...existing, - schema: generated.schema, - ...(generated.uiHints || existing.uiHints - ? { uiHints: { ...generated.uiHints, ...existing.uiHints } } - : {}), - ...(existing.label || generated.label ? { label: existing.label ?? generated.label } : {}), - ...(existing.description || generated.description - ? { description: existing.description ?? generated.description } - : {}), - }; - } - return { - ...manifest, - channelConfigs, - }; -} - /** * @param {{ * cwd?: string; diff --git a/scripts/lib/plugin-npm-package-manifest.mjs b/scripts/lib/plugin-npm-package-manifest.mjs index edf27f086c6..e5094485c93 100644 --- a/scripts/lib/plugin-npm-package-manifest.mjs +++ b/scripts/lib/plugin-npm-package-manifest.mjs @@ -96,7 +96,7 @@ export function resolveAugmentedPluginNpmPackageJson(params) { }; } -function readGeneratedBundledChannelConfigs(repoRoot) { +export function readGeneratedBundledChannelConfigs(repoRoot) { const metadataPath = path.join(repoRoot, GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA_PATH); if (!fs.existsSync(metadataPath)) { return new Map(); @@ -145,7 +145,7 @@ function readGeneratedBundledChannelConfigs(repoRoot) { return byPlugin; } -function mergeGeneratedChannelConfigs(manifest, generatedChannelConfigs) { +export function mergeGeneratedChannelConfigs(manifest, generatedChannelConfigs) { if (!generatedChannelConfigs || Object.keys(generatedChannelConfigs).length === 0) { return manifest; } diff --git a/scripts/postinstall-bundled-plugins.mjs b/scripts/postinstall-bundled-plugins.mjs index 8f18ed61dc2..6483295e617 100644 --- a/scripts/postinstall-bundled-plugins.mjs +++ b/scripts/postinstall-bundled-plugins.mjs @@ -19,16 +19,9 @@ import { writeFileSync, } from "node:fs"; import { homedir, tmpdir } from "node:os"; -import { - basename, - dirname, - isAbsolute, - join, - posix, - relative, - resolve as pathResolve, -} from "node:path"; +import { basename, dirname, isAbsolute, join, relative, resolve as pathResolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; +import { expandPackageDistImportClosure } from "./lib/package-dist-imports.mjs"; const __dirname = dirname(fileURLToPath(import.meta.url)); const DEFAULT_PACKAGE_ROOT = join(__dirname, ".."); @@ -400,145 +393,6 @@ export function pruneLegacyPluginRuntimeDepsState(params = {}) { return removed; } -const JS_DIST_FILE_RE = /^dist\/.*\.(?:cjs|js|mjs)$/u; - -function stripSpecifierSuffix(value) { - return value.replace(/[?#].*$/u, ""); -} - -function resolveDistImportPath(importerPath, specifier) { - if (!specifier.startsWith(".")) { - return null; - } - const stripped = stripSpecifierSuffix(specifier); - if (!stripped) { - return null; - } - return posix.normalize(posix.join(posix.dirname(importerPath), stripped)); -} - -function findStatementStart(source, index) { - return ( - Math.max( - source.lastIndexOf(";", index), - source.lastIndexOf("{", index), - source.lastIndexOf("}", index), - source.lastIndexOf("\n", index), - source.lastIndexOf("\r", index), - ) + 1 - ); -} - -function isImportSpecifierContext(source, index) { - const dynamicPrefix = source.slice(Math.max(0, index - 32), index); - if (/\bimport\s*\(\s*$/u.test(dynamicPrefix)) { - return true; - } - const statementPrefix = source.slice(findStatementStart(source, index), index).trimStart(); - return ( - /^(?:import|export)\b[\s\S]*\bfrom\s*$/u.test(statementPrefix) || - /^import\s*$/u.test(statementPrefix) - ); -} - -function collectImportSpecifiers(source) { - const specifiers = []; - let inBlockComment = false; - let inLineComment = false; - for (let index = 0; index < source.length; index += 1) { - if (inBlockComment) { - if (source[index] === "*" && source[index + 1] === "/") { - inBlockComment = false; - index += 1; - } - continue; - } - if (inLineComment) { - if (source[index] === "\n" || source[index] === "\r") { - inLineComment = false; - } - continue; - } - if (source[index] === "/" && source[index + 1] === "*") { - inBlockComment = true; - index += 1; - continue; - } - if (source[index] === "/" && source[index + 1] === "/") { - inLineComment = true; - index += 1; - continue; - } - - const quote = source[index]; - if (quote !== '"' && quote !== "'") { - continue; - } - - let cursor = index + 1; - let value = ""; - while (cursor < source.length) { - const char = source[cursor]; - if (char === "\\") { - value += source.slice(cursor, cursor + 2); - cursor += 2; - continue; - } - if (char === quote) { - break; - } - value += char; - cursor += 1; - } - if (cursor >= source.length) { - break; - } - - if (value.startsWith(".") && isImportSpecifierContext(source, index)) { - specifiers.push(value); - } - index = cursor; - } - return specifiers; -} - -function expandInstalledDistImportClosure(params) { - const files = [...new Set(params.files)]; - const fileSet = new Set(files); - const expectedSet = new Set(params.seedFiles); - let changed = true; - - while (changed) { - changed = false; - for (const importerPath of [...expectedSet] - .filter((file) => fileSet.has(file)) - .toSorted((left, right) => left.localeCompare(right))) { - if (!JS_DIST_FILE_RE.test(importerPath) || importerPath.includes("/node_modules/")) { - continue; - } - let source; - try { - source = params.readText(importerPath); - } catch (error) { - if (error?.code === "ENOENT") { - continue; - } - throw error; - } - for (const specifier of collectImportSpecifiers(source)) { - const importedPath = resolveDistImportPath(importerPath, specifier); - if (!importedPath || !fileSet.has(importedPath) || expectedSet.has(importedPath)) { - continue; - } - expectedSet.add(importedPath); - changed = true; - } - } - } - - return [...expectedSet].toSorted((left, right) => left.localeCompare(right)); -} - export function pruneInstalledPackageDist(params = {}) { const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT; const removeFile = params.unlinkSync ?? unlinkSync; @@ -569,11 +423,18 @@ export function pruneInstalledPackageDist(params = {}) { const installedFiles = listInstalledDistFiles(params); const readFile = params.readFileSync ?? readFileSync; expectedFiles = new Set( - expandInstalledDistImportClosure({ + expandPackageDistImportClosure({ files: installedFiles, seedFiles: [...expectedFiles], readText(relativePath) { - return readFile(join(packageRoot, relativePath), "utf8"); + try { + return readFile(join(packageRoot, relativePath), "utf8"); + } catch (error) { + if (error?.code === "ENOENT") { + return ""; + } + throw error; + } }, }), );