Files
clientsflow/microapps/validator/validate_microapp_repo.ts
2026-03-23 11:47:14 +07:00

70 lines
1.8 KiB
TypeScript

const projectDir = Deno.args[0];
if (!projectDir) {
throw new Error("Usage: deno run --allow-read microapps/validator/validate_microapp_repo.ts <project-dir>");
}
const requiredFiles = new Set([
"app.config.ts",
"deno.json",
"main.ts",
"pocketbase/client.ts",
]);
const allowedTopLevelEntries = new Set([
"app.config.ts",
"deno.json",
"main.ts",
"pocketbase",
]);
const rootEntries = new Set<string>();
for await (const entry of Deno.readDir(projectDir)) {
rootEntries.add(entry.name);
}
for (const file of requiredFiles) {
const path = `${projectDir}/${file}`;
await Deno.stat(path);
}
for (const entry of rootEntries) {
if (!allowedTopLevelEntries.has(entry)) {
throw new Error(`Unexpected top-level entry: ${entry}`);
}
}
const configModule = await import(`file://${Deno.realPathSync(`${projectDir}/app.config.ts`)}`);
const config = configModule.default as {
manifestVersion?: number;
runtime?: string;
libraries?: string[];
permissions?: {
allowEnv?: string[];
allowNet?: string[];
};
};
if (config.manifestVersion !== 1) {
throw new Error("app.config.ts must export manifestVersion = 1");
}
if (config.runtime !== "deno-gvisor") {
throw new Error("app.config.ts must declare runtime = 'deno-gvisor'");
}
if (!Array.isArray(config.libraries) || config.libraries.length === 0) {
throw new Error("app.config.ts must declare at least one approved library");
}
if (!Array.isArray(config.permissions?.allowEnv) || config.permissions.allowEnv.length === 0) {
throw new Error("app.config.ts must declare allowEnv permissions");
}
if (!Array.isArray(config.permissions?.allowNet) || config.permissions.allowNet.length === 0) {
throw new Error("app.config.ts must declare allowNet permissions");
}
console.log(`Microapp repo at ${projectDir} matches the enforced template contract.`);