const projectDir = Deno.args[0]; if (!projectDir) { throw new Error("Usage: deno run --allow-read microapps/validator/validate_microapp_repo.ts "); } 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(); 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.`);