openclaw/scripts/lib/native-typescript-config.mts
Peter Steinberger 39373e69f8
chore(deps): refresh dependencies through September 19 cutoff (#159401)
Refresh application, plugin, native, build and container dependencies through the fixed 2026-09-19T16:27:11Z cutoff. Migrate native TypeScript snapshot/printer APIs while preserving compilation and filesystem contracts; retain existing patches and compatibility holds. Document offline container-image preparation.

Include the verified compiler process-census and loading-clock fixture repairs and deterministic warm-history regression. Adopt the canonical production history fixes from #159924 and #159955.

Land under the maintainer's explicit approval to treat proven pre-existing CI failures as non-blocking and repair main afterward. CI36365552098 failed an unchanged Android Compose fixture's asynchronous catalog projection assertion (3518 passed,1 failed); the Android/Gradle tree matches its main parent byte-for-byte. Security and dependency reviews passed. The final rebase preserves reviewed source changes and regenerates only the intentional Node-image documentation fingerprint. See PR159401 for complete validation and the follow-up repair obligation.
2026-09-27 19:33:08 -07:00

98 lines
3.3 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { API, type Diagnostic } from "typescript/unstable/sync";
import { createDeclarationFileSystem } from "./native-declaration-filesystem.mts";
import { formatNativeTypeScriptDiagnostics } from "./native-typescript-diagnostics.mts";
/** Read config roots/options without loading their semantic source graph. */
export function readNativeTypeScriptConfig(options: {
cwd: string;
configFileName: string;
readFile?: (file: string) => string | null;
assertInput?: (file: string) => string;
}) {
const cwd = path.resolve(options.cwd);
const configFileName = path.resolve(cwd, options.configFileName);
const contents = new Map<string, string>();
const readFile = options.readFile ?? ((file: string) => fs.readFileSync(file, "utf8"));
const view = options.assertInput
? createDeclarationFileSystem(cwd, options.assertInput, new Map())
: undefined;
let readFailure: Error | undefined;
const assertValid = () => {
view?.assertValid();
if (readFailure !== undefined) {
throw readFailure;
}
};
let parsing = true;
const api = new API({
cwd,
fs: {
...view?.filesystem,
readFile(file) {
if (view && !view.filesystem.fileExists(file)) {
return null;
}
const cached = contents.get(file);
if (cached !== undefined) {
return cached;
}
// Native config diagnostics require a project. Source bodies cannot affect
// this query; retaining real existence/discovery still preserves its roots.
if (!parsing && !file.endsWith(".json")) {
return "";
}
try {
const text = readFile(file);
if (text !== null) {
contents.set(file, text);
}
return text;
} catch (error) {
if (
error instanceof Error &&
"code" in error &&
(error.code === "ENOENT" || error.code === "ENOTDIR")
) {
return null;
}
readFailure ??= error instanceof Error ? error : new Error(String(error));
return null;
}
},
},
});
try {
const parsed = api.parseConfigFile(configFileName);
assertValid();
const syntax = api.readConfigFile(configFileName);
assertValid();
const configFiles = [...contents.keys()].toSorted();
let diagnostics: readonly Diagnostic[] = parsed.errors;
if (syntax.error) {
// Parsed options omit root JSON syntax errors; readConfigFile reports only
// the first. Open a project only for the complete malformed-config report.
parsing = false;
const snapshot = api.createSnapshot({ openProjects: [configFileName], ensurePrograms: true });
assertValid();
const project = snapshot.getConfiguredProject(configFileName);
if (!project) {
throw new Error(`Native TypeScript did not open config ${configFileName}`);
}
diagnostics = project.program.getConfigFileParsingDiagnostics();
assertValid();
}
if (diagnostics.length) {
throw new Error(
`Invalid TypeScript config ${configFileName}:\n${formatNativeTypeScriptDiagnostics(diagnostics)}`,
);
}
return { ...parsed, configFiles };
} catch (error) {
assertValid();
throw error;
} finally {
api.close();
}
}