my monorepo for atproto based applications
at main 233 lines 6.9 kB view raw
1import nodePath from "node:path"; 2import process from "node:process"; 3import fs from "node:fs/promises"; 4import child_process from "node:child_process"; 5import { promisify } from "node:util"; 6 7import { Project } from "ts-morph"; 8 9const lexiconDir = nodePath.join(import.meta.dirname!, "..", "..", "lexicon"); 10 11export async function generate(): Promise<void> { 12 await Promise.all([ 13 generateCLI("api").then(() => fixTS(`${lexiconDir}/api`)), 14 generateCLI("server").then(() => fixTS(`${lexiconDir}/server`)), 15 ]); 16 await promisify(child_process.exec)(`npx prettier -w ${lexiconDir}`); 17} 18 19async function generateCLI(genType: "api" | "server"): Promise<void> { 20 let shell = process.env.SHELL ?? "sh"; 21 22 await promisify(child_process.exec)( 23 `${shell} -c "echo y | npx @atproto/lex-cli@0.7 -- gen-${genType} '${lexiconDir}/${genType}/' ${lexiconDir}/definitions/**/*.json"`, 24 ); 25} 26 27// async function generateAPI(): Promise<void> { 28// const cmd = new Deno.Command(Deno.execPath(), { 29// args: ["task", "lex-gen-api"], 30// cwd: import.meta.dirname, 31// }); 32 33// const { success, code: exitCode } = await cmd.output(); 34// if (!success) { 35// console.error( 36// "Error: task lex-gen-api returned with error exit code", 37// exitCode, 38// ); 39// process.exit(exitCode); 40// } 41// const errors = await denofyTsDir(apiOutDir); 42// if (errors) { 43// console.error( 44// `Errors occurred during file changes:\n${errors 45// .map((e) => e.message) 46// .join("\n")}`, 47// ); 48// process.exit(1); 49// } 50// console.log("done generating api"); 51// } 52 53// async function generateServer(): Promise<void> { 54// const cmd = new Deno.Command(Deno.execPath(), { 55// args: ["task", "lex-gen-server"], 56// cwd: import.meta.dirname, 57// }); 58 59// const { success, code: exitCode } = await cmd.output(); 60// if (!success) { 61// console.error( 62// "Error: task lex-gen-api returned with error exit code", 63// exitCode, 64// ); 65// process.exit(exitCode); 66// } 67// const errors = await denofyTsDir(serverOutDir); 68// if (errors) { 69// console.error( 70// `Errors occurred during file changes:\n${errors 71// .map((e) => e.message) 72// .join("\n")}`, 73// ); 74// process.exit(1); 75// } 76// console.log("done generating server"); 77// } 78 79async function fixTS(root: string): Promise<undefined | Error[]> { 80 const ps: Promise<void>[] = []; 81 for await (const fname of tsFiles(root)) { 82 ps.push( 83 (async () => { 84 try { 85 await fixTSFile(fname); 86 } catch (e) { 87 const msg = e instanceof Error ? e.message : `${e}`; 88 throw new Error(`${fname}: ${msg}`); 89 } 90 })(), 91 ); 92 } 93 const errors: Error[] = []; 94 for (const r of await Promise.allSettled(ps)) { 95 if (r.status === "rejected") { 96 errors.push(r.reason as Error); 97 } 98 } 99 console.log("root", root, "errors=", errors.length); 100 return errors.length == 0 ? undefined : errors; 101} 102// async function denofyTsDir(root: string): Promise<undefined | Error[]> { 103// const ps: Promise<void>[] = []; 104// for await (const fname of tsFiles(root)) { 105// ps.push( 106// (async () => { 107// try { 108// await denofyTsFile(fname); 109// } catch (e) { 110// const msg = e instanceof Error ? e.message : `${e}`; 111// throw new Error(`${fname}: ${msg}`); 112// } 113// })(), 114// ); 115// } 116// const errors: Error[] = []; 117// for (const r of await Promise.allSettled(ps)) { 118// if (r.status === "rejected") { 119// errors.push(r.reason as Error); 120// } 121// } 122// return errors.length == 0 ? undefined : errors; 123// } 124 125async function fixTSFile(filePath: string): Promise<void> { 126 const src = new Project({ 127 skipFileDependencyResolution: true, 128 skipLoadingLibFiles: true, 129 skipAddingFilesFromTsConfig: true, 130 }).addSourceFileAtPath(filePath); 131 132 src.getImportDeclarations().forEach((imp) => { 133 if (!imp.isModuleSpecifierRelative()) { 134 // const current = imp.getModuleSpecifierValue(); 135 // if ( 136 // !current.startsWith("@atproto") && 137 // !current.startsWith("multiformats") 138 // ) { 139 // imp.setModuleSpecifier("npm:" + current); 140 // } 141 return; 142 } 143 const current = imp.getModuleSpecifierValue(); 144 let modified = current; 145 if (current.endsWith(".js")) { 146 modified = current.slice(0, current.length - ".js".length); 147 } 148 modified += ".ts"; 149 imp.setModuleSpecifier(modified); 150 console.log( 151 "modified", 152 modified, 153 "in", 154 src.getFilePath().split("/").at(-1), 155 ); 156 }); 157 158 src.getExportDeclarations().forEach((exp) => { 159 if (!exp.isModuleSpecifierRelative()) { 160 return; 161 } 162 const current = exp.getModuleSpecifierValue(); 163 if (!current) { 164 throw new Error(`unexpected export: ${exp.getText()}`); 165 } 166 let modified = current; 167 if (current.endsWith(".js")) { 168 modified = current.slice(0, current.length - ".js".length); 169 } 170 modified += ".ts"; 171 exp.setModuleSpecifier(modified); 172 }); 173 174 await src.save(); 175 console.log("saved", src.getFilePath()); 176} 177 178// async function denofyTsFile(filePath: string): Promise<void> { 179// const src = new Project({ 180// skipFileDependencyResolution: true, 181// skipLoadingLibFiles: true, 182// skipAddingFilesFromTsConfig: true, 183// }).addSourceFileAtPath(filePath); 184 185// src.getImportDeclarations().forEach((imp) => { 186// if (!imp.isModuleSpecifierRelative()) { 187// const current = imp.getModuleSpecifierValue(); 188// if ( 189// !current.startsWith("@atproto") && 190// !current.startsWith("multiformats") 191// ) { 192// imp.setModuleSpecifier("npm:" + current); 193// } 194// return; 195// } 196// const current = imp.getModuleSpecifierValue(); 197// let modified = current; 198// if (current.endsWith(".js")) { 199// modified = current.slice(0, current.length - ".js".length); 200// } 201// modified += ".ts"; 202// imp.setModuleSpecifier(modified); 203// }); 204 205// src.getExportDeclarations().forEach((exp) => { 206// if (!exp.isModuleSpecifierRelative()) { 207// return; 208// } 209// const current = exp.getModuleSpecifierValue(); 210// if (!current) { 211// throw new Error(`unexpected export: ${exp.getText()}`); 212// } 213// let modified = current; 214// if (current.endsWith(".js")) { 215// modified = current.slice(0, current.length - ".js".length); 216// } 217// modified += ".ts"; 218// exp.setModuleSpecifier(modified); 219// }); 220// await src.save(); 221// } 222 223async function* tsFiles(rootDir: string): AsyncIterable<string> { 224 for (const f of await fs.readdir(rootDir, { withFileTypes: true })) { 225 const currentPath = nodePath.join(rootDir, f.name); 226 if (f.isDirectory()) { 227 yield* tsFiles(currentPath); 228 } 229 if (f.isFile() && f.name.endsWith(".ts")) { 230 yield currentPath; 231 } 232 } 233}