import nodePath from "node:path"; import process from "node:process"; import fs from "node:fs/promises"; import child_process from "node:child_process"; import { promisify } from "node:util"; import { Project } from "ts-morph"; const lexiconDir = nodePath.join(import.meta.dirname!, "..", "..", "lexicon"); export async function generate(): Promise { await Promise.all([ generateCLI("api").then(() => fixTS(`${lexiconDir}/api`)), generateCLI("server").then(() => fixTS(`${lexiconDir}/server`)), ]); await promisify(child_process.exec)(`npx prettier -w ${lexiconDir}`); } async function generateCLI(genType: "api" | "server"): Promise { let shell = process.env.SHELL ?? "sh"; await promisify(child_process.exec)( `${shell} -c "echo y | npx @atproto/lex-cli@0.7 -- gen-${genType} '${lexiconDir}/${genType}/' ${lexiconDir}/definitions/**/*.json"`, ); } // async function generateAPI(): Promise { // const cmd = new Deno.Command(Deno.execPath(), { // args: ["task", "lex-gen-api"], // cwd: import.meta.dirname, // }); // const { success, code: exitCode } = await cmd.output(); // if (!success) { // console.error( // "Error: task lex-gen-api returned with error exit code", // exitCode, // ); // process.exit(exitCode); // } // const errors = await denofyTsDir(apiOutDir); // if (errors) { // console.error( // `Errors occurred during file changes:\n${errors // .map((e) => e.message) // .join("\n")}`, // ); // process.exit(1); // } // console.log("done generating api"); // } // async function generateServer(): Promise { // const cmd = new Deno.Command(Deno.execPath(), { // args: ["task", "lex-gen-server"], // cwd: import.meta.dirname, // }); // const { success, code: exitCode } = await cmd.output(); // if (!success) { // console.error( // "Error: task lex-gen-api returned with error exit code", // exitCode, // ); // process.exit(exitCode); // } // const errors = await denofyTsDir(serverOutDir); // if (errors) { // console.error( // `Errors occurred during file changes:\n${errors // .map((e) => e.message) // .join("\n")}`, // ); // process.exit(1); // } // console.log("done generating server"); // } async function fixTS(root: string): Promise { const ps: Promise[] = []; for await (const fname of tsFiles(root)) { ps.push( (async () => { try { await fixTSFile(fname); } catch (e) { const msg = e instanceof Error ? e.message : `${e}`; throw new Error(`${fname}: ${msg}`); } })(), ); } const errors: Error[] = []; for (const r of await Promise.allSettled(ps)) { if (r.status === "rejected") { errors.push(r.reason as Error); } } console.log("root", root, "errors=", errors.length); return errors.length == 0 ? undefined : errors; } // async function denofyTsDir(root: string): Promise { // const ps: Promise[] = []; // for await (const fname of tsFiles(root)) { // ps.push( // (async () => { // try { // await denofyTsFile(fname); // } catch (e) { // const msg = e instanceof Error ? e.message : `${e}`; // throw new Error(`${fname}: ${msg}`); // } // })(), // ); // } // const errors: Error[] = []; // for (const r of await Promise.allSettled(ps)) { // if (r.status === "rejected") { // errors.push(r.reason as Error); // } // } // return errors.length == 0 ? undefined : errors; // } async function fixTSFile(filePath: string): Promise { const src = new Project({ skipFileDependencyResolution: true, skipLoadingLibFiles: true, skipAddingFilesFromTsConfig: true, }).addSourceFileAtPath(filePath); src.getImportDeclarations().forEach((imp) => { if (!imp.isModuleSpecifierRelative()) { // const current = imp.getModuleSpecifierValue(); // if ( // !current.startsWith("@atproto") && // !current.startsWith("multiformats") // ) { // imp.setModuleSpecifier("npm:" + current); // } return; } const current = imp.getModuleSpecifierValue(); let modified = current; if (current.endsWith(".js")) { modified = current.slice(0, current.length - ".js".length); } modified += ".ts"; imp.setModuleSpecifier(modified); console.log( "modified", modified, "in", src.getFilePath().split("/").at(-1), ); }); src.getExportDeclarations().forEach((exp) => { if (!exp.isModuleSpecifierRelative()) { return; } const current = exp.getModuleSpecifierValue(); if (!current) { throw new Error(`unexpected export: ${exp.getText()}`); } let modified = current; if (current.endsWith(".js")) { modified = current.slice(0, current.length - ".js".length); } modified += ".ts"; exp.setModuleSpecifier(modified); }); await src.save(); console.log("saved", src.getFilePath()); } // async function denofyTsFile(filePath: string): Promise { // const src = new Project({ // skipFileDependencyResolution: true, // skipLoadingLibFiles: true, // skipAddingFilesFromTsConfig: true, // }).addSourceFileAtPath(filePath); // src.getImportDeclarations().forEach((imp) => { // if (!imp.isModuleSpecifierRelative()) { // const current = imp.getModuleSpecifierValue(); // if ( // !current.startsWith("@atproto") && // !current.startsWith("multiformats") // ) { // imp.setModuleSpecifier("npm:" + current); // } // return; // } // const current = imp.getModuleSpecifierValue(); // let modified = current; // if (current.endsWith(".js")) { // modified = current.slice(0, current.length - ".js".length); // } // modified += ".ts"; // imp.setModuleSpecifier(modified); // }); // src.getExportDeclarations().forEach((exp) => { // if (!exp.isModuleSpecifierRelative()) { // return; // } // const current = exp.getModuleSpecifierValue(); // if (!current) { // throw new Error(`unexpected export: ${exp.getText()}`); // } // let modified = current; // if (current.endsWith(".js")) { // modified = current.slice(0, current.length - ".js".length); // } // modified += ".ts"; // exp.setModuleSpecifier(modified); // }); // await src.save(); // } async function* tsFiles(rootDir: string): AsyncIterable { for (const f of await fs.readdir(rootDir, { withFileTypes: true })) { const currentPath = nodePath.join(rootDir, f.name); if (f.isDirectory()) { yield* tsFiles(currentPath); } if (f.isFile() && f.name.endsWith(".ts")) { yield currentPath; } } }