everything-dev 1.8.12 → 1.9.0

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.
Files changed (65) hide show
  1. package/dist/api-contract.cjs +53 -15
  2. package/dist/api-contract.cjs.map +1 -1
  3. package/dist/api-contract.mjs +53 -15
  4. package/dist/api-contract.mjs.map +1 -1
  5. package/dist/cli/init.cjs +29 -3
  6. package/dist/cli/init.cjs.map +1 -1
  7. package/dist/cli/init.d.cts.map +1 -1
  8. package/dist/cli/init.d.mts.map +1 -1
  9. package/dist/cli/init.mjs +29 -3
  10. package/dist/cli/init.mjs.map +1 -1
  11. package/dist/cli.cjs +27 -0
  12. package/dist/cli.cjs.map +1 -1
  13. package/dist/cli.mjs +27 -0
  14. package/dist/cli.mjs.map +1 -1
  15. package/dist/contract.cjs +20 -1
  16. package/dist/contract.cjs.map +1 -1
  17. package/dist/contract.d.cts +48 -3
  18. package/dist/contract.d.cts.map +1 -1
  19. package/dist/contract.d.mts +48 -3
  20. package/dist/contract.d.mts.map +1 -1
  21. package/dist/contract.meta.cjs +9 -0
  22. package/dist/contract.meta.cjs.map +1 -1
  23. package/dist/contract.meta.d.cts +13 -0
  24. package/dist/contract.meta.d.mts +13 -0
  25. package/dist/contract.meta.mjs +9 -0
  26. package/dist/contract.meta.mjs.map +1 -1
  27. package/dist/contract.mjs +19 -2
  28. package/dist/contract.mjs.map +1 -1
  29. package/dist/dev-session.cjs +3 -1
  30. package/dist/dev-session.cjs.map +1 -1
  31. package/dist/dev-session.mjs +3 -1
  32. package/dist/dev-session.mjs.map +1 -1
  33. package/dist/index.cjs +2 -0
  34. package/dist/index.d.cts +2 -2
  35. package/dist/index.d.mts +2 -2
  36. package/dist/index.mjs +2 -2
  37. package/dist/orchestrator.cjs +6 -1
  38. package/dist/orchestrator.cjs.map +1 -1
  39. package/dist/orchestrator.d.cts.map +1 -1
  40. package/dist/orchestrator.d.mts.map +1 -1
  41. package/dist/orchestrator.mjs +6 -1
  42. package/dist/orchestrator.mjs.map +1 -1
  43. package/dist/plugin.cjs +80 -2
  44. package/dist/plugin.cjs.map +1 -1
  45. package/dist/plugin.d.cts +23 -2
  46. package/dist/plugin.d.cts.map +1 -1
  47. package/dist/plugin.d.mts +23 -2
  48. package/dist/plugin.d.mts.map +1 -1
  49. package/dist/plugin.mjs +80 -2
  50. package/dist/plugin.mjs.map +1 -1
  51. package/dist/service-descriptor.d.cts +1 -1
  52. package/dist/service-descriptor.d.mts +1 -1
  53. package/dist/types.d.cts +2 -2
  54. package/dist/types.d.mts +2 -2
  55. package/package.json +2 -2
  56. package/skills/dev-workflow/SKILL.md +3 -3
  57. package/src/api-contract.ts +95 -22
  58. package/src/cli/init.ts +55 -6
  59. package/src/cli.ts +32 -0
  60. package/src/contract.meta.ts +9 -0
  61. package/src/contract.ts +21 -0
  62. package/src/dev-session.ts +5 -0
  63. package/src/orchestrator.ts +10 -21
  64. package/src/plugin.ts +116 -2
  65. package/src/scripts/sync-api-contract.ts +0 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "everything-dev",
3
- "version": "1.8.12",
3
+ "version": "1.9.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -155,7 +155,7 @@
155
155
  "@orpc/zod": "^1.13.4",
156
156
  "chalk": "^5.6.2",
157
157
  "effect": "^3.21.0",
158
- "every-plugin": "^2.5.3",
158
+ "every-plugin": "^2.5.4",
159
159
  "glob": "^13.0.6",
160
160
  "gradient-string": "^3.0.0",
161
161
  "hono": "^4.7.11",
@@ -53,10 +53,10 @@ The orchestrator:
53
53
 
54
54
  ## Contract Sync & Type Generation
55
55
 
56
- Plugin types are auto-generated from `bos.config.json` via `bun run sync:api-contract`:
56
+ Plugin types are auto-generated from `bos.config.json` via `bos types gen`:
57
57
 
58
58
  ```bash
59
- bun run sync:api-contract # Regenerate ui/src/api-contract.gen.ts and api/src/plugins-client.gen.ts
59
+ bos types gen # Regenerate ui/src/api-contract.gen.ts and api/src/plugins-client.gen.ts
60
60
  ```
61
61
 
62
62
  **When it auto-runs:**
@@ -73,7 +73,7 @@ bun run sync:api-contract # Regenerate ui/src/api-contract.gen.ts and api/src/
73
73
 
74
74
  **Source of truth:** `bos.config.json`. If a plugin is listed there, its routes appear on `ApiContract`. If removed, TypeScript catches stale usage.
75
75
 
76
- **After hand-editing `bos.config.json`:** Run `bun run sync:api-contract` or restart `bos dev` to pick up changes.
76
+ **After hand-editing `bos.config.json`:** Run `bos types gen` or restart `bos dev` to pick up changes.
77
77
 
78
78
  ## Runtime Config Loading
79
79
 
@@ -22,6 +22,11 @@ export interface ApiPluginManifest {
22
22
  sha256?: string;
23
23
  };
24
24
  };
25
+ additionalExports?: Array<{
26
+ path: string;
27
+ exports: string[];
28
+ sha256?: string;
29
+ }>;
25
30
  }
26
31
 
27
32
  interface ContractSource {
@@ -136,6 +141,51 @@ async function remoteContractSource(opts: {
136
141
  };
137
142
  }
138
143
 
144
+ async function fetchAuthExportTypes(opts: {
145
+ baseUrl: string;
146
+ runtimeDir: string;
147
+ manifest: ApiPluginManifest;
148
+ }): Promise<string | null> {
149
+ if (!opts.manifest.additionalExports || opts.manifest.additionalExports.length === 0) {
150
+ return null;
151
+ }
152
+
153
+ const authExportEntry = opts.manifest.additionalExports.find(
154
+ (entry) => entry.path.includes("auth-export") || entry.path.endsWith("auth-export.d.ts"),
155
+ );
156
+
157
+ if (!authExportEntry) {
158
+ return null;
159
+ }
160
+
161
+ const exportUrl = `${trimTrailingSlash(opts.baseUrl)}/${authExportEntry.path.replace(/^\.\//, "")}`;
162
+ const response = await fetch(exportUrl);
163
+ if (!response.ok) {
164
+ console.warn(`[API Contract] Failed to fetch auth export types: ${response.status}`);
165
+ return null;
166
+ }
167
+
168
+ const content = await response.text();
169
+ if (authExportEntry.sha256 && authExportEntry.sha256 !== sha256(content)) {
170
+ console.warn("[API Contract] Auth export types checksum mismatch");
171
+ return null;
172
+ }
173
+
174
+ const generatedPath = join(opts.runtimeDir, "auth", "auth-export.d.ts");
175
+ mkdirSync(dirname(generatedPath), { recursive: true });
176
+ writeFileIfChanged(generatedPath, content);
177
+
178
+ return generatedPath;
179
+ }
180
+
181
+ function writeAuthTypesGen(configDir: string, authExportPath: string) {
182
+ const authTypesPath = join(configDir, "ui", "src", "auth-types.gen.ts");
183
+ const importPath = toImportPath(authTypesPath, authExportPath);
184
+ const content = `export type { createAuthInstance } from "${importPath}";\n`;
185
+ mkdirSync(dirname(authTypesPath), { recursive: true });
186
+ writeFileIfChanged(authTypesPath, content);
187
+ }
188
+
139
189
  async function resolveContractSource(opts: {
140
190
  configDir: string;
141
191
  runtimeDir: string;
@@ -202,6 +252,7 @@ function writeGeneratedFiles(opts: {
202
252
  sources: ContractSource[];
203
253
  pluginKeys: string[];
204
254
  authSource: ContractSource | null;
255
+ authExportPath?: string | null;
205
256
  }) {
206
257
  const baseSource = opts.sources.find((source) => source.key === "api");
207
258
  const pluginSources = opts.pluginKeys
@@ -245,6 +296,7 @@ function writeGeneratedFiles(opts: {
245
296
  writeFileIfChanged(uiContractPath, `${uiLines.join("\n")}\n`);
246
297
 
247
298
  // --- Generate api/src/plugins-client.gen.ts ---
299
+ // Includes both plugin contracts AND auth as a unified PluginsClient type
248
300
  const pluginsClientPath = join(opts.configDir, "api", "src", "plugins-client.gen.ts");
249
301
  const pluginsClientLines: string[] = [];
250
302
 
@@ -255,6 +307,13 @@ function writeGeneratedFiles(opts: {
255
307
  );
256
308
  }
257
309
 
310
+ if (opts.authSource) {
311
+ const authImportPath = toImportPath(pluginsClientPath, opts.authSource.sourceFilePath);
312
+ pluginsClientLines.push(
313
+ `import type { ContractType as ${opts.authSource.importName} } from "${authImportPath}";`,
314
+ );
315
+ }
316
+
258
317
  pluginsClientLines.push(
259
318
  'import type { ContractRouterClient, AnyContractRouter } from "@orpc/contract";',
260
319
  );
@@ -263,11 +322,16 @@ function writeGeneratedFiles(opts: {
263
322
  );
264
323
  pluginsClientLines.push("");
265
324
 
266
- if (pluginSources.length === 0) {
325
+ const allPluginSources = [...pluginSources];
326
+ if (opts.authSource) {
327
+ allPluginSources.push({ ...opts.authSource, key: "auth" });
328
+ }
329
+
330
+ if (allPluginSources.length === 0) {
267
331
  pluginsClientLines.push("export type PluginsClient = Record<string, never>;");
268
332
  } else {
269
333
  pluginsClientLines.push("export type PluginsClient = {");
270
- for (const source of pluginSources) {
334
+ for (const source of allPluginSources) {
271
335
  const key = /^[$A-Z_][0-9A-Z_$]*$/i.test(source.key)
272
336
  ? source.key
273
337
  : JSON.stringify(source.key);
@@ -279,26 +343,9 @@ function writeGeneratedFiles(opts: {
279
343
  mkdirSync(dirname(pluginsClientPath), { recursive: true });
280
344
  writeFileIfChanged(pluginsClientPath, `${pluginsClientLines.join("\n")}\n`);
281
345
 
282
- // --- Generate api/src/auth-client.gen.ts ---
283
- if (opts.authSource) {
284
- const authClientPath = join(opts.configDir, "api", "src", "auth-client.gen.ts");
285
- const authClientLines: string[] = [];
286
-
287
- const importPath = toImportPath(authClientPath, opts.authSource.sourceFilePath);
288
- authClientLines.push(
289
- `import type { ContractType as ${opts.authSource.importName} } from "${importPath}";`,
290
- );
291
- authClientLines.push(
292
- 'import type { ContractRouterClient, AnyContractRouter } from "@orpc/contract";',
293
- );
294
- authClientLines.push(
295
- "type ClientFactory<C extends AnyContractRouter> = (context?: Record<string, unknown>) => ContractRouterClient<C>;",
296
- );
297
- authClientLines.push("");
298
- authClientLines.push(`export type AuthClient = ClientFactory<${opts.authSource.importName}>;`);
299
-
300
- mkdirSync(dirname(authClientPath), { recursive: true });
301
- writeFileIfChanged(authClientPath, `${authClientLines.join("\n")}\n`);
346
+ // --- Generate ui/src/auth-types.gen.ts ---
347
+ if (opts.authExportPath) {
348
+ writeAuthTypesGen(opts.configDir, opts.authExportPath);
302
349
  }
303
350
 
304
351
  return uiContractPath;
@@ -322,6 +369,7 @@ export async function syncApiContractBridge(opts: {
322
369
  let manifest: ApiPluginManifest | null = null;
323
370
  let generatedPath: string | null = null;
324
371
  let authSource: ContractSource | null = null;
372
+ let authExportPath: string | null = null;
325
373
 
326
374
  const baseSource = await resolveContractSource({
327
375
  configDir: opts.configDir,
@@ -347,6 +395,30 @@ export async function syncApiContractBridge(opts: {
347
395
  if (authSource.generatedPath) {
348
396
  generatedPath = authSource.generatedPath;
349
397
  }
398
+
399
+ // Fetch auth additional exports (auth-export.d.ts) for remote auth
400
+ if (opts.runtimeConfig.auth.url && opts.runtimeConfig.auth.source !== "local") {
401
+ try {
402
+ const authManifest = await fetchApiPluginManifest(opts.runtimeConfig.auth.url);
403
+ const fetchedAuthExportPath = await fetchAuthExportTypes({
404
+ baseUrl: opts.runtimeConfig.auth.url,
405
+ runtimeDir,
406
+ manifest: authManifest,
407
+ });
408
+ if (fetchedAuthExportPath) {
409
+ authExportPath = fetchedAuthExportPath;
410
+ }
411
+ } catch (error) {
412
+ console.warn(
413
+ `[API Contract] Failed to fetch auth additional exports: ${error instanceof Error ? error.message : String(error)}`,
414
+ );
415
+ }
416
+ }
417
+
418
+ // Fallback to local auth export source if remote fetch failed or auth is local
419
+ if (!authExportPath) {
420
+ authExportPath = join(opts.configDir, "plugins", "auth", "src", "auth-export.ts");
421
+ }
350
422
  }
351
423
 
352
424
  for (const [key, plugin] of pluginEntries) {
@@ -377,6 +449,7 @@ export async function syncApiContractBridge(opts: {
377
449
  sources,
378
450
  pluginKeys: allPluginKeys,
379
451
  authSource,
452
+ authExportPath,
380
453
  });
381
454
 
382
455
  if (opts.runtimeConfig.api.source !== "local") {
package/src/cli/init.ts CHANGED
@@ -309,6 +309,19 @@ export async function personalizeConfig(
309
309
 
310
310
  if (isInit && config.app && typeof config.app === "object") {
311
311
  const app = config.app as Record<string, unknown>;
312
+
313
+ const authClientPath = join(destination, "ui", "src", "lib", "auth-client.ts");
314
+ if (existsSync(authClientPath)) {
315
+ const authClientContent = readFileSync(authClientPath, "utf-8")
316
+ .split("\n")
317
+ .filter(
318
+ (line) =>
319
+ !line.includes("inferAdditionalFields") && !line.includes("createAuthInstance"),
320
+ )
321
+ .join("\n");
322
+ writeFileSync(authClientPath, authClientContent);
323
+ }
324
+
312
325
  for (const entryKey of Object.keys(app)) {
313
326
  const entry = app[entryKey];
314
327
  if (entry && typeof entry === "object") {
@@ -385,14 +398,16 @@ export async function personalizeConfig(
385
398
  rewrite("publish", "packages/everything-dev/cli.js", "node_modules/.bin/bos");
386
399
  rewrite("start", "packages/everything-dev/cli.js", "node_modules/.bin/bos");
387
400
 
388
- if (scripts["sync:api-contract"]) {
389
- delete scripts["sync:api-contract"];
390
- }
391
401
  if (scripts.postinstall) {
392
- delete scripts.postinstall;
402
+ scripts.postinstall = "bos types gen";
393
403
  }
394
- if (scripts.typecheck?.includes("sync:api-contract")) {
395
- scripts.typecheck = scripts.typecheck.replace("bun run sync:api-contract && ", "");
404
+ if (scripts.typecheck) {
405
+ scripts.typecheck = scripts.typecheck
406
+ .replace("bun run types:gen && ", "")
407
+ .replace(/bun run --cwd packages\/everything-dev typecheck & ?/, "");
408
+ if (!opts.withHost) {
409
+ scripts.typecheck = scripts.typecheck.replace(/bun run --cwd host tsc --noEmit & ?/, "");
410
+ }
396
411
  }
397
412
  }
398
413
 
@@ -414,6 +429,25 @@ export async function personalizeConfig(
414
429
  writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
415
430
  }
416
431
 
432
+ const apiTsConfigPath = join(destination, "api", "tsconfig.json");
433
+ if (existsSync(apiTsConfigPath)) {
434
+ const apiTsConfig = JSON.parse(readFileSync(apiTsConfigPath, "utf-8")) as {
435
+ files?: string[];
436
+ [key: string]: unknown;
437
+ };
438
+ if (apiTsConfig.files) {
439
+ const validFiles = apiTsConfig.files.filter((f) => existsSync(join(destination, "api", f)));
440
+ if (validFiles.length !== apiTsConfig.files.length) {
441
+ if (validFiles.length === 0) {
442
+ delete apiTsConfig.files;
443
+ } else {
444
+ apiTsConfig.files = validFiles;
445
+ }
446
+ writeFileSync(apiTsConfigPath, `${JSON.stringify(apiTsConfig, null, 2)}\n`);
447
+ }
448
+ }
449
+ }
450
+
417
451
  await resolveWorkspaceRefs(destination, opts.workspaceOpts);
418
452
 
419
453
  const genContractPath = join(destination, "ui", "src", "api-contract.gen.ts");
@@ -421,6 +455,21 @@ export async function personalizeConfig(
421
455
  mkdirSync(dirname(genContractPath), { recursive: true });
422
456
  writeFileSync(genContractPath, `export type ApiContract = Record<string, never>;\n`);
423
457
  }
458
+
459
+ const pluginsClientGenPath = join(destination, "api", "src", "plugins-client.gen.ts");
460
+ if (!existsSync(pluginsClientGenPath)) {
461
+ mkdirSync(dirname(pluginsClientGenPath), { recursive: true });
462
+ writeFileSync(
463
+ pluginsClientGenPath,
464
+ `import type { ContractRouterClient, AnyContractRouter } from "@orpc/contract";\ntype ClientFactory<C extends AnyContractRouter> = (context?: Record<string, unknown>) => ContractRouterClient<C>;\nexport type PluginsClient = Record<string, never>;\n`,
465
+ );
466
+ }
467
+
468
+ const authTypesGenPath = join(destination, "ui", "src", "auth-types.gen.ts");
469
+ if (!existsSync(authTypesGenPath)) {
470
+ mkdirSync(dirname(authTypesGenPath), { recursive: true });
471
+ writeFileSync(authTypesGenPath, `export type { createAuthInstance } from "better-auth";\n`);
472
+ }
424
473
  }
425
474
 
426
475
  export async function runBunInstall(destination: string): Promise<void> {
package/src/cli.ts CHANGED
@@ -310,6 +310,38 @@ async function main() {
310
310
  return;
311
311
  }
312
312
 
313
+ if (descriptor.key === "typesGen") {
314
+ console.log();
315
+ if (result.status === "error") {
316
+ console.error(`[CLI] ${result.error || "Unknown error"}`);
317
+ process.exit(1);
318
+ }
319
+ console.log(colors.green(`${icons.ok} Types generated`));
320
+ if (result.source) {
321
+ console.log(
322
+ ` ${colors.dim("Mode:")} ${result.source === "remote" ? colors.cyan("remote") : colors.dim("local")}`,
323
+ );
324
+ }
325
+ if (result.generated.length > 0) {
326
+ console.log(` ${colors.dim("Generated:")}`);
327
+ for (const f of result.generated) console.log(` ${colors.dim(f)}`);
328
+ }
329
+ if (result.fetched.length > 0) {
330
+ console.log(` ${colors.dim("Fetched from remote:")}`);
331
+ for (const url of result.fetched) console.log(` ${colors.dim(url)}`);
332
+ }
333
+ if (result.skipped.length > 0) {
334
+ console.log(` ${colors.dim("Skipped (local):")}`);
335
+ for (const s of result.skipped) console.log(` ${colors.dim(s)}`);
336
+ }
337
+ if (result.failed.length > 0) {
338
+ console.log(` ${colors.yellow("Failed:")}`);
339
+ for (const f of result.failed) console.log(` ${colors.error(f)}`);
340
+ }
341
+ console.log();
342
+ return;
343
+ }
344
+
313
345
  if (result?.status === "error") {
314
346
  console.error(`[CLI] ${result.error || "Unknown error"}`);
315
347
  process.exit(1);
@@ -121,6 +121,15 @@ export const cliCommandMeta = {
121
121
  noSync: { description: "Only upgrade packages, skip template sync" },
122
122
  },
123
123
  },
124
+ typesGen: {
125
+ commandPath: ["types", "gen"],
126
+ summary: "Generate type definitions from configured API and plugin contracts",
127
+ interactive: false,
128
+ fields: {
129
+ env: { description: "Environment: development (default) or production" },
130
+ dryRun: { description: "Preview what would be fetched without writing files" },
131
+ },
132
+ },
124
133
  status: {
125
134
  commandPath: ["status"],
126
135
  summary: "Show project health, versions, and update availability",
package/src/contract.ts CHANGED
@@ -221,6 +221,21 @@ export const StatusResultSchema = z.object({
221
221
  error: z.string().optional(),
222
222
  });
223
223
 
224
+ export const TypesGenOptionsSchema = z.object({
225
+ env: z.enum(["development", "production"]).optional(),
226
+ dryRun: z.boolean().default(false),
227
+ });
228
+
229
+ export const TypesGenResultSchema = z.object({
230
+ status: z.enum(["success", "error"]),
231
+ generated: z.array(z.string()),
232
+ fetched: z.array(z.string()),
233
+ skipped: z.array(z.string()),
234
+ failed: z.array(z.string()),
235
+ source: z.enum(["local", "remote"]).optional(),
236
+ error: z.string().optional(),
237
+ });
238
+
224
239
  export const bosContract = oc.router({
225
240
  dev: oc.route({ method: "POST", path: "/dev" }).input(DevOptionsSchema).output(DevResultSchema),
226
241
  start: oc
@@ -266,6 +281,10 @@ export const bosContract = oc.router({
266
281
  .input(UpgradeOptionsSchema)
267
282
  .output(UpgradeResultSchema),
268
283
  status: oc.route({ method: "GET", path: "/status" }).output(StatusResultSchema),
284
+ typesGen: oc
285
+ .route({ method: "POST", path: "/types/gen" })
286
+ .input(TypesGenOptionsSchema)
287
+ .output(TypesGenResultSchema),
269
288
  });
270
289
 
271
290
  export type DevOptions = z.infer<typeof DevOptionsSchema>;
@@ -289,3 +308,5 @@ export type SyncResult = z.infer<typeof SyncResultSchema>;
289
308
  export type UpgradeOptions = z.infer<typeof UpgradeOptionsSchema>;
290
309
  export type UpgradeResult = z.infer<typeof UpgradeResultSchema>;
291
310
  export type StatusResult = z.infer<typeof StatusResultSchema>;
311
+ export type TypesGenOptions = z.infer<typeof TypesGenOptionsSchema>;
312
+ export type TypesGenResult = z.infer<typeof TypesGenResultSchema>;
@@ -251,6 +251,11 @@ const runApp = (
251
251
  Effect.provide(ServiceDescriptorMapLive(services)),
252
252
  Effect.provide(DevRuntimeConfigLive(runtimeConfig)),
253
253
  Effect.provide(NodeContext.layer),
254
+ Effect.catchAllDefect((defect) =>
255
+ Effect.sync(() => {
256
+ console.error("[Dev] Unhandled defect in orchestrator:", defect);
257
+ }),
258
+ ),
254
259
  );
255
260
 
256
261
  const handleSignal = () => {
@@ -1,4 +1,3 @@
1
- import { createConnection } from "node:net";
2
1
  import { Command } from "@effect/platform";
3
2
  import type { ExitCode } from "@effect/platform/CommandExecutor";
4
3
  import { Deferred, Effect, Option, Ref, Stream } from "effect";
@@ -10,6 +9,14 @@ import {
10
9
  } from "./service-descriptor";
11
10
  import type { RuntimeConfig } from "./types";
12
11
 
12
+ process.on("unhandledRejection", (reason) => {
13
+ console.error("[Orchestrator] Unhandled rejection:", reason);
14
+ });
15
+
16
+ process.on("uncaughtException", (err) => {
17
+ console.error("[Orchestrator] Uncaught exception:", err);
18
+ });
19
+
13
20
  export interface ProcessCallbacks {
14
21
  onStatus: (name: string, status: ProcessStatus, message?: string) => void;
15
22
  onLog: (name: string, line: string, isError?: boolean) => void;
@@ -55,26 +62,8 @@ const probeHttpOk = (url: string, timeoutMs = 400) =>
55
62
  clearTimeout(timer);
56
63
  }
57
64
  },
58
- catch: () => false,
59
- });
60
-
61
- const probeTcpOpen = (port: number, timeoutMs = 250) =>
62
- Effect.async<boolean>((resume) => {
63
- const socket = createConnection({ host: "127.0.0.1", port });
64
- const timer = setTimeout(() => {
65
- socket.destroy();
66
- resume(Effect.succeed(false));
67
- }, timeoutMs);
68
- socket.once("connect", () => {
69
- clearTimeout(timer);
70
- socket.destroy();
71
- resume(Effect.succeed(true));
72
- });
73
- socket.once("error", () => {
74
- clearTimeout(timer);
75
- resume(Effect.succeed(false));
76
- });
77
- });
65
+ catch: () => false,
66
+ });
78
67
 
79
68
  const detectStatus = (
80
69
  line: string,
package/src/plugin.ts CHANGED
@@ -40,6 +40,7 @@ import {
40
40
  type PublishOptions,
41
41
  type StartOptions,
42
42
  type SyncOptions,
43
+ type TypesGenOptions,
43
44
  type UpgradeOptions,
44
45
  } from "./contract";
45
46
  import { devApp, startApp } from "./dev-session";
@@ -256,8 +257,11 @@ function listPluginAttachments(config: BosConfig | null) {
256
257
  .sort((a, b) => a.key.localeCompare(b.key));
257
258
  }
258
259
 
259
- async function refreshApiContractBridge(configDir: string): Promise<void> {
260
- const refreshed = await loadConfig({ cwd: configDir, env: "development" });
260
+ async function refreshApiContractBridge(
261
+ configDir: string,
262
+ env: "development" | "production" = "development",
263
+ ): Promise<void> {
264
+ const refreshed = await loadConfig({ cwd: configDir, env });
261
265
  if (!refreshed) return;
262
266
 
263
267
  await syncApiContractBridge({
@@ -1467,6 +1471,116 @@ export default createPlugin({
1467
1471
  }
1468
1472
  }),
1469
1473
 
1474
+ typesGen: builder.typesGen.handler(async ({ input }: { input: TypesGenOptions }) => {
1475
+ try {
1476
+ const configPath = findConfigPath();
1477
+ if (!configPath) {
1478
+ return {
1479
+ status: "error" as const,
1480
+ generated: [],
1481
+ fetched: [],
1482
+ skipped: [],
1483
+ failed: [],
1484
+ error: "No bos.config.json found in current directory",
1485
+ };
1486
+ }
1487
+
1488
+ const projectDir = resolve(dirname(configPath));
1489
+ const env =
1490
+ input.env ?? (process.env.NODE_ENV === "production" ? "production" : "development");
1491
+
1492
+ const refreshed = await loadConfig({ cwd: projectDir, env });
1493
+ if (!refreshed) {
1494
+ return {
1495
+ status: "error" as const,
1496
+ generated: [],
1497
+ fetched: [],
1498
+ skipped: [],
1499
+ failed: [],
1500
+ error: "Failed to load bos.config.json",
1501
+ };
1502
+ }
1503
+
1504
+ if (input.dryRun) {
1505
+ const pluginEntries = Object.entries(refreshed.runtime.plugins ?? {});
1506
+ const fetched: string[] = [];
1507
+ const skipped: string[] = [];
1508
+
1509
+ if (refreshed.runtime.api.source !== "local") {
1510
+ fetched.push(refreshed.runtime.api.url);
1511
+ } else {
1512
+ skipped.push("api (local)");
1513
+ }
1514
+
1515
+ if (refreshed.runtime.auth) {
1516
+ if (refreshed.runtime.auth.source !== "local") {
1517
+ fetched.push(refreshed.runtime.auth.url);
1518
+ } else {
1519
+ skipped.push("auth (local)");
1520
+ }
1521
+ }
1522
+
1523
+ for (const [key, plugin] of pluginEntries) {
1524
+ if (plugin.url && plugin.source !== "local") {
1525
+ fetched.push(plugin.url);
1526
+ } else if (plugin.localPath) {
1527
+ skipped.push(`${key} (local)`);
1528
+ }
1529
+ }
1530
+
1531
+ return {
1532
+ status: "success" as const,
1533
+ generated: [
1534
+ "ui/src/api-contract.gen.ts",
1535
+ "ui/src/auth-types.gen.ts",
1536
+ "api/src/plugins-client.gen.ts",
1537
+ "api/src/auth-client.gen.ts",
1538
+ ],
1539
+ fetched,
1540
+ skipped,
1541
+ failed: [],
1542
+ source: refreshed.runtime.api.source,
1543
+ };
1544
+ }
1545
+
1546
+ const result = await syncApiContractBridge({
1547
+ configDir: projectDir,
1548
+ runtimeConfig: refreshed.runtime,
1549
+ apiBaseUrl: refreshed.runtime.api.url,
1550
+ });
1551
+
1552
+ const generated = [
1553
+ "ui/src/api-contract.gen.ts",
1554
+ "api/src/plugins-client.gen.ts",
1555
+ "api/src/auth-client.gen.ts",
1556
+ ];
1557
+ if (
1558
+ refreshed.runtime.auth &&
1559
+ (refreshed.runtime.auth.source !== "local" || refreshed.runtime.auth.localPath)
1560
+ ) {
1561
+ generated.push("ui/src/auth-types.gen.ts");
1562
+ }
1563
+
1564
+ return {
1565
+ status: "success" as const,
1566
+ generated,
1567
+ fetched: result.source === "remote" ? [refreshed.runtime.api.url] : [],
1568
+ skipped: result.source === "local" ? ["api (local)"] : [],
1569
+ failed: [],
1570
+ source: result.source,
1571
+ };
1572
+ } catch (error) {
1573
+ return {
1574
+ status: "error" as const,
1575
+ generated: [],
1576
+ fetched: [],
1577
+ skipped: [],
1578
+ failed: [],
1579
+ error: error instanceof Error ? error.message : "Unknown error",
1580
+ };
1581
+ }
1582
+ }),
1583
+
1470
1584
  status: builder.status.handler(async () => {
1471
1585
  try {
1472
1586
  const configPath = findConfigPath();
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import { dirname } from "node:path";
4
- import { syncApiContractBridge } from "../api-contract";
5
- import { loadConfig } from "../config";
6
-
7
- async function main() {
8
- const result = await loadConfig({ cwd: process.cwd(), env: "development" });
9
- if (!result) {
10
- throw new Error("No bos.config.json found");
11
- }
12
-
13
- const configDir = dirname(result.source.path);
14
- await syncApiContractBridge({
15
- configDir,
16
- runtimeConfig: result.runtime,
17
- apiBaseUrl: result.runtime.api.url,
18
- });
19
- }
20
-
21
- main().catch((error) => {
22
- console.error("[sync-api-contract]", error instanceof Error ? error.message : String(error));
23
- process.exit(1);
24
- });