Repair workspace authoring: typed RPC inputs, automatic preservation, and template releases
Use checked input contracts and protobuf JSON for CLI roundtrips; generate Web Studio platform inputs; reject invalid constructors before allocation and filter Createable eligibility. Preserve compatible storage without no-op migrations, report activation readiness, and validate migration coverage before maintenance. Follow authored package declarations during scaffold refresh and queue source capture. Add a real local TODO check/activate/create/place/edit acceptance, publish updated protocol and SDK dependencies, and make verified template default selection explicit.
This commit is contained in:
@@ -91,6 +91,36 @@ export const scaffoldRecipe = async (root: string, command: "package" | "functio
|
||||
} else {
|
||||
registry = await ownedJson<Registry>(root, prefix + "quixos.scaffold.json");
|
||||
catalog = await ownedJson<typeof catalog>(root, prefix + "quixos.migrations.json");
|
||||
// package.qx is authoritative. The registry remembers implementation paths,
|
||||
// not a second declaration list that can erase an author's new exports.
|
||||
const authored = await fs.readFile(path.join(root, prefix, "package.qx"), "utf8");
|
||||
const syntax = parseQx(authored);
|
||||
if (syntax.diagnostics.length) throw new Error("Cannot refresh an invalid package.qx; fix the reported syntax first");
|
||||
const declaration = [...walkSyntax(syntax.root)].find(node => node.kind === "packageResourceDecl");
|
||||
if (!declaration) throw new Error("Expected a package declaration");
|
||||
const text = (node: typeof declaration) => authored.slice(node.start, node.end);
|
||||
const literals = declaration.children.filter(node => node.kind === "stringLiteral");
|
||||
registry.name = text(declaration.children.find(node => node.kind === "identifier")!);
|
||||
registry.id = JSON.parse(text(literals[0]));
|
||||
registry.revision = JSON.parse(text(literals[1]));
|
||||
const previousExports = registry.exports;
|
||||
registry.exports = [];
|
||||
for (const node of walkSyntax(declaration)) {
|
||||
if (!["packageFunctionExport", "packageOperationExport", "packageConstructorExport"].includes(node.kind)) continue;
|
||||
const name = safeName(text(node.children.find(child => child.kind === "identifier")!));
|
||||
const id = JSON.parse(text(node.children.find(child => child.kind === "stringLiteral")!));
|
||||
if (registry.exports.some(entry => entry.id === id || entry.name === name)) throw new Error("Duplicate package export name or ID");
|
||||
const old = previousExports.find(entry => entry.id === id);
|
||||
const file = old?.file ?? `src/impl/${name}.ts`;
|
||||
if (!old) {
|
||||
const exists = await fs.access(path.join(root, prefix, file)).then(() => true, () => false);
|
||||
if (!exists) {
|
||||
const derived = [...walkSyntax(node)].some(child => child.kind === "eventClause");
|
||||
create(file, `import type {Implementation} from "../gen/qx.js";\nexport const handler: Implementation[${JSON.stringify(name)}] = ${derived ? '{kind: "derived", get: ' : ""}async (_context) => { throw new Error(${JSON.stringify(`Implement ${name}`)}); }${derived ? "}" : ""};\n`);
|
||||
}
|
||||
}
|
||||
registry.exports.push({...old, name, id, file});
|
||||
}
|
||||
if (command !== "refresh") {
|
||||
const name = safeName(spec.name);
|
||||
if (!spec.id || registry.exports.some((entry) => entry.id === spec.id || entry.name === name)) throw new Error("New export requires a unique name and ID");
|
||||
|
||||
Reference in New Issue
Block a user