appflare 0.2.53 โ†’ 0.2.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/generate.ts CHANGED
@@ -17,6 +17,7 @@ import { ensureRelativeImportPath } from "./utils/path-utils";
17
17
  import {
18
18
  packageSearchDirs,
19
19
  resolvePackageBin,
20
+ resolvePackageModule,
20
21
  } from "./utils/resolve-package";
21
22
 
22
23
  function extractRolesFromConfig(config: LoadedAppflareConfig["config"]): string[] {
@@ -108,11 +109,9 @@ export async function generateArtifacts(
108
109
 
109
110
  const moduleDir = dirname(fileURLToPath(import.meta.url));
110
111
  const searchDirs = packageSearchDirs(moduleDir, [configDir]);
111
- const tscBin = resolvePackageBin(
112
- "typescript",
113
- "bin/tsc",
114
- searchDirs,
115
- );
112
+ const tscBin = resolvePackageBin("typescript", "bin/tsc", searchDirs);
113
+ const esbuildModulePath = resolvePackageModule("esbuild", searchDirs);
114
+ const typescriptModulePath = resolvePackageModule("typescript", searchDirs);
116
115
  const betterAuthBin = resolvePackageBin(
117
116
  "@better-auth/cli",
118
117
  "dist/index.mjs",
@@ -253,52 +252,85 @@ export async function generateArtifacts(
253
252
  process.stdout.write(`๐Ÿ”ง Patched role type (${(performance.now() - t0).toFixed(0)}ms)\n`);
254
253
  }
255
254
 
256
- function generatedTsconfig(overrides: Record<string, unknown> = {}) {
257
- return {
258
- compilerOptions: {
259
- lib: ["es2024"],
260
- types: ["@types/node", "@cloudflare/workers-types/2023-07-01"],
261
- module: "es2022",
262
- target: "es2024",
263
- moduleResolution: "bundler",
264
- strictNullChecks: true,
265
- skipLibCheck: true,
266
- declaration: true,
267
- emitDeclarationOnly: true,
268
- noCheck: true,
269
- jsx: "react-jsx",
270
- paths: {
271
- "_generated/*": ["./*"],
272
- },
273
- ...overrides,
274
- },
275
- include: ["./*.ts", "./client/*.ts"],
276
- };
255
+ // Collect all .ts entries written so far (includes schema-compiler + better-auth outputs)
256
+ const tsEntries: string[] = [];
257
+ async function collectTsEntries(dir: string): Promise<void> {
258
+ const entries = await readdir(dir, { withFileTypes: true });
259
+ for (const entry of entries) {
260
+ const fullPath = join(dir, entry.name);
261
+ if (entry.isDirectory()) {
262
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
263
+ await collectTsEntries(fullPath);
264
+ } else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
265
+ tsEntries.push(fullPath);
266
+ }
267
+ }
277
268
  }
269
+ await collectTsEntries(outDirAbs);
278
270
 
279
- const jsTsconfigPath = resolve(outDirAbs, "tsconfig.js.json");
280
-
281
- await Promise.all([
282
- Bun.write(
283
- jsTsconfigPath,
284
- `${JSON.stringify(generatedTsconfig({ declaration:true,emitDeclarationOnly: false }), null, 2)}\n`,
285
- ),
286
- ]);
287
- process.stdout.write(`๐Ÿ“„ TS configs (${(performance.now() - t0).toFixed(0)}ms)\n`);
271
+ // Transpile .ts โ†’ .js in-process with esbuild. Per-file syntax transform with
272
+ // no bundling keeps output strictly inside _generated and preserves extensionless
273
+ // relative imports exactly as Wrangler's bundler expects at runtime.
274
+ process.stdout.write(`โš™๏ธ Transpiling (esbuild)... (${(performance.now() - t0).toFixed(0)}ms)\n`);
275
+ const esbuild = (await import(esbuildModulePath)) as typeof import("esbuild");
276
+ await Promise.all(
277
+ tsEntries.map(async (tsPath) => {
278
+ const source = await Bun.file(tsPath).text();
279
+ const result = await esbuild.transform(source, {
280
+ loader: tsPath.endsWith(".tsx") ? "tsx" : "ts",
281
+ format: "esm",
282
+ target: "es2024",
283
+ jsx: "automatic",
284
+ sourcefile: tsPath,
285
+ });
286
+ await Bun.write(tsPath.replace(/\.tsx?$/, ".js"), result.code);
287
+ }),
288
+ );
289
+ process.stdout.write(`โœ… JS files (${(performance.now() - t0).toFixed(0)}ms)\n`);
288
290
 
289
- async function runTsc(configPath: string, label: string): Promise<void> {
290
- process.stdout.write(`โš™๏ธ ${label}... (${(performance.now() - t0).toFixed(0)}ms)\n`);
291
- const p = Bun.spawn([process.execPath, tscBin, "-p", configPath], {
292
- cwd: outDirAbs,
293
- stdout: "inherit",
294
- stderr: "inherit",
295
- });
296
- const code = await p.exited;
297
- if (code !== 0) throw new Error(`tsc ${label} failed with exit code ${code}`);
291
+ // Emit .d.ts in-process with the TypeScript compiler API.
292
+ // .tsbuildinfo persists parsed lib + node_modules type info between rebuilds so
293
+ // the declaration pass only re-resolves what changed (~60% faster on warm builds).
294
+ process.stdout.write(`โš™๏ธ Declarations (tsc)... (${(performance.now() - t0).toFixed(0)}ms)\n`);
295
+ const ts = (await import(typescriptModulePath)) as { default?: typeof import("typescript") } & typeof import("typescript");
296
+ const tsApi = ts.default ?? ts;
297
+ const tsBuildInfoPath = resolve(outDirAbs, ".tsbuildinfo");
298
+ const declOptions: import("typescript").CompilerOptions = {
299
+ lib: ["lib.es2024.d.ts"],
300
+ types: ["@types/node", "@cloudflare/workers-types/2023-07-01"],
301
+ module: tsApi.ModuleKind.ES2022,
302
+ target: tsApi.ScriptTarget.ES2024,
303
+ moduleResolution: tsApi.ModuleResolutionKind.Bundler,
304
+ strictNullChecks: true,
305
+ skipLibCheck: true,
306
+ declaration: true,
307
+ emitDeclarationOnly: true,
308
+ noCheck: true,
309
+ incremental: true,
310
+ tsBuildInfoFile: tsBuildInfoPath,
311
+ jsx: tsApi.JsxEmit.ReactJSX,
312
+ rootDir: outDirAbs,
313
+ outDir: outDirAbs,
314
+ paths: { "_generated/*": ["./*"] },
315
+ baseUrl: outDirAbs,
316
+ };
317
+ const program = tsApi.createIncrementalProgram({ rootNames: tsEntries, options: declOptions });
318
+ const emitResult = program.emit();
319
+ if (emitResult.emitSkipped) {
320
+ const diagnostics = tsApi
321
+ .getPreEmitDiagnostics(program.getProgram())
322
+ .concat(emitResult.diagnostics);
323
+ throw new Error(
324
+ `tsc declaration emit failed:\n${tsApi.formatDiagnostics(diagnostics, {
325
+ getCanonicalFileName: (f) => f,
326
+ getCurrentDirectory: () => outDirAbs,
327
+ getNewLine: () => "\n",
328
+ })}`,
329
+ );
298
330
  }
331
+ process.stdout.write(`โœ… Declarations (${(performance.now() - t0).toFixed(0)}ms)\n`);
299
332
 
300
- await runTsc(jsTsconfigPath, "JS files");
301
- process.stdout.write(`โœ… JS + declarations (${(performance.now() - t0).toFixed(0)}ms)\n`);
333
+ // Remove .ts source files from _generated (keep .js + .d.ts + .tsbuildinfo)
302
334
  async function removeTsFiles(dir: string): Promise<void> {
303
335
  const entries = await readdir(dir, { withFileTypes: true });
304
336
  for (const entry of entries) {
@@ -312,34 +344,30 @@ export async function generateArtifacts(
312
344
  }
313
345
  }
314
346
  await removeTsFiles(outDirAbs);
315
- process.stdout.write(`๐Ÿ“ฆ Cleaned .ts files (${(performance.now() - t0).toFixed(0)}ms)\n`);
316
-
347
+ // Remove stale config files written by earlier versions of the generator.
348
+ await Promise.all([
349
+ rm(resolve(outDirAbs, "vite.generated.config.mjs"), { force: true }),
350
+ rm(resolve(outDirAbs, "tsconfig.dts.json"), { force: true }),
351
+ rm(resolve(outDirAbs, "tsconfig.js.json"), { force: true }),
352
+ ]);
353
+
354
+ // esbuild's per-file transform never emits outside _generated. tsc declaration
355
+ // emit follows ../imports into user source, so only .d.ts need cleaning outside.
317
356
  const generatedDirPrefix = outDirAbs + "/";
318
- async function cleanDtsOutside(dir: string): Promise<void> {
357
+ async function cleanStrayDts(dir: string): Promise<void> {
319
358
  const entries = await readdir(dir, { withFileTypes: true });
320
359
  for (const entry of entries) {
321
360
  const fullPath = join(dir, entry.name);
322
361
  if (entry.isDirectory()) {
323
- if (
324
- entry.name === "node_modules" ||
325
- entry.name.startsWith(".") ||
326
- fullPath === outDirAbs
327
- ) {
328
- continue;
329
- }
330
- await cleanDtsOutside(fullPath);
331
- } else if (
332
- !fullPath.startsWith(generatedDirPrefix) && (
333
- entry.name.endsWith(".d.ts") || (
334
- entry.name.endsWith(".js"))
335
- )
336
- ) {
362
+ if (entry.name === "node_modules" || entry.name.startsWith(".") || fullPath === outDirAbs) continue;
363
+ await cleanStrayDts(fullPath);
364
+ } else if (!fullPath.startsWith(generatedDirPrefix) && entry.name.endsWith(".d.ts")) {
337
365
  await rm(fullPath);
338
366
  }
339
367
  }
340
368
  }
341
- await cleanDtsOutside(configDir);
342
- process.stdout.write(`๐Ÿงน Cleanup (${(performance.now() - t0).toFixed(0)}ms)\n`);
369
+ await cleanStrayDts(configDir);
370
+ process.stdout.write(`๐Ÿงน Cleaned up (${(performance.now() - t0).toFixed(0)}ms)\n`);
343
371
 
344
372
 
345
373
  const tsconfigPath = resolve(configDir, "tsconfig.json");
@@ -57,3 +57,24 @@ export function packageSearchDirs(
57
57
  ];
58
58
  return [...new Set(dirs)];
59
59
  }
60
+
61
+ /**
62
+ * Resolves the importable module entry for a package so it can be loaded
63
+ * in-process via dynamic `import()`, searching the user's install dirs first.
64
+ */
65
+ export function resolvePackageModule(
66
+ packageName: string,
67
+ searchDirs: string[],
68
+ ): string {
69
+ for (const dir of searchDirs) {
70
+ try {
71
+ return Bun.resolveSync(packageName, dir);
72
+ } catch {
73
+ continue;
74
+ }
75
+ }
76
+
77
+ throw new Error(
78
+ `Could not resolve "${packageName}". Install it in your project (see appflare peerDependencies).`,
79
+ );
80
+ }