Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
import fs from "node:fs/promises";
import path from "node:path";
import { NPM_UPDATE_COMPAT_SIDECAR_PATHS } from "./npm-update-compat-sidecars.js";
export const PACKAGE_DIST_INVENTORY_RELATIVE_PATH = "dist/postinstall-inventory.json";
const LEGACY_QA_CHANNEL_DIR = ["qa", "channel"].join("-");
const LEGACY_QA_LAB_DIR = ["qa", "lab"].join("-");
const LEGACY_VERIFIER_COMPAT_INVENTORY_PATHS = [
`dist/extensions/${LEGACY_QA_CHANNEL_DIR}/runtime-api.js`,
];
const OMITTED_QA_EXTENSION_PREFIXES = [
`dist/extensions/${LEGACY_QA_CHANNEL_DIR}/`,
`dist/extensions/${LEGACY_QA_LAB_DIR}/`,
"dist/extensions/qa-matrix/",
];
const OMITTED_PRIVATE_QA_PLUGIN_SDK_PREFIXES = [`dist/plugin-sdk/extensions/${LEGACY_QA_LAB_DIR}/`];
const OMITTED_PRIVATE_QA_PLUGIN_SDK_FILES = new Set([
`dist/plugin-sdk/${LEGACY_QA_LAB_DIR}.d.ts`,
`dist/plugin-sdk/${LEGACY_QA_LAB_DIR}.js`,
"dist/plugin-sdk/qa-runtime.d.ts",
"dist/plugin-sdk/qa-runtime.js",
`dist/plugin-sdk/src/plugin-sdk/${LEGACY_QA_LAB_DIR}.d.ts`,
"dist/plugin-sdk/src/plugin-sdk/qa-runtime.d.ts",
]);
const OMITTED_PRIVATE_QA_DIST_PREFIXES = ["dist/qa-runtime-"];
const OMITTED_DIST_SUBTREE_PATTERNS = [
/^dist\/extensions\/node_modules(?:\/|$)/u,
/^dist\/extensions\/[^/]+\/node_modules(?:\/|$)/u,
/^dist\/extensions\/[^/]+\/\.openclaw-runtime-deps-[^/]+(?:\/|$)/u,
/^dist\/extensions\/qa-matrix(?:\/|$)/u,
new RegExp(`^dist/plugin-sdk/extensions/${LEGACY_QA_LAB_DIR}(?:/|$)`, "u"),
] as const;
function normalizeRelativePath(value: string): string {
return value.replace(/\\/g, "/");
}
function isPackagedDistPath(relativePath: string): boolean {
if (!relativePath.startsWith("dist/")) {
return false;
}
if (relativePath === PACKAGE_DIST_INVENTORY_RELATIVE_PATH) {
return false;
}
if (relativePath.endsWith("/.openclaw-runtime-deps-stamp.json")) {
return false;
}
if (relativePath.endsWith(".map")) {
return false;
}
if (relativePath === "dist/plugin-sdk/.tsbuildinfo") {
return false;
}
if (LEGACY_VERIFIER_COMPAT_INVENTORY_PATHS.includes(relativePath)) {
return true;
}
if (
OMITTED_PRIVATE_QA_PLUGIN_SDK_PREFIXES.some((prefix) => relativePath.startsWith(prefix)) ||
OMITTED_PRIVATE_QA_PLUGIN_SDK_FILES.has(relativePath) ||
OMITTED_PRIVATE_QA_DIST_PREFIXES.some((prefix) => relativePath.startsWith(prefix))
) {
return false;
}
if (OMITTED_QA_EXTENSION_PREFIXES.some((prefix) => relativePath.startsWith(prefix))) {
return false;
}
return true;
}
function isOmittedDistSubtree(relativePath: string): boolean {
return OMITTED_DIST_SUBTREE_PATTERNS.some((pattern) => pattern.test(relativePath));
}
async function collectRelativeFiles(rootDir: string, baseDir: string): Promise<string[]> {
const rootRelativePath = normalizeRelativePath(path.relative(baseDir, rootDir));
if (rootRelativePath && isOmittedDistSubtree(rootRelativePath)) {
return [];
}
try {
const rootStats = await fs.lstat(rootDir);
if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) {
throw new Error(
`Unsafe package dist path: ${normalizeRelativePath(path.relative(baseDir, rootDir))}`,
);
}
const entries = await fs.readdir(rootDir, { withFileTypes: true });
const files = await Promise.all(
entries.map(async (entry) => {
const entryPath = path.join(rootDir, entry.name);
const relativePath = normalizeRelativePath(path.relative(baseDir, entryPath));
if (entry.isSymbolicLink()) {
throw new Error(`Unsafe package dist path: ${relativePath}`);
}
if (entry.isDirectory()) {
return await collectRelativeFiles(entryPath, baseDir);
}
if (entry.isFile()) {
return isPackagedDistPath(relativePath) ? [relativePath] : [];
}
return [];
}),
);
return files.flat().toSorted((left, right) => left.localeCompare(right));
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
return [];
}
throw error;
}
}
export async function collectPackageDistInventory(packageRoot: string): Promise<string[]> {
return await collectRelativeFiles(path.join(packageRoot, "dist"), packageRoot);
}
export async function writePackageDistInventory(packageRoot: string): Promise<string[]> {
const inventory = [
...new Set([
...(await collectPackageDistInventory(packageRoot)),
...LEGACY_VERIFIER_COMPAT_INVENTORY_PATHS,
]),
].toSorted((left, right) => left.localeCompare(right));
const inventoryPath = path.join(packageRoot, PACKAGE_DIST_INVENTORY_RELATIVE_PATH);
await fs.mkdir(path.dirname(inventoryPath), { recursive: true });
await fs.writeFile(inventoryPath, `${JSON.stringify(inventory, null, 2)}\n`, "utf8");
return inventory;
}
export async function readPackageDistInventory(packageRoot: string): Promise<string[]> {
const inventoryPath = path.join(packageRoot, PACKAGE_DIST_INVENTORY_RELATIVE_PATH);
const raw = await fs.readFile(inventoryPath, "utf8");
const parsed = JSON.parse(raw) as unknown;
if (!Array.isArray(parsed) || parsed.some((entry) => typeof entry !== "string")) {
throw new Error(`Invalid package dist inventory at ${PACKAGE_DIST_INVENTORY_RELATIVE_PATH}`);
}
return [...new Set(parsed.map(normalizeRelativePath))].toSorted((left, right) =>
left.localeCompare(right),
);
}
export async function readPackageDistInventoryIfPresent(
packageRoot: string,
): Promise<string[] | null> {
try {
return await readPackageDistInventory(packageRoot);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
return null;
}
throw error;
}
}
export async function collectPackageDistInventoryErrors(packageRoot: string): Promise<string[]> {
const expectedFiles = await readPackageDistInventoryIfPresent(packageRoot);
if (expectedFiles === null) {
return [`missing package dist inventory ${PACKAGE_DIST_INVENTORY_RELATIVE_PATH}`];
}
const actualFiles = await collectPackageDistInventory(packageRoot);
const expectedSet = new Set(expectedFiles);
const actualSet = new Set(actualFiles);
const errors: string[] = [];
for (const relativePath of expectedFiles) {
if (!actualSet.has(relativePath)) {
if (NPM_UPDATE_COMPAT_SIDECAR_PATHS.has(relativePath)) {
continue;
}
errors.push(`missing packaged dist file ${relativePath}`);
}
}
for (const relativePath of actualFiles) {
if (!expectedSet.has(relativePath)) {
errors.push(`unexpected packaged dist file ${relativePath}`);
}
}
return errors;
}
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland