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.
- package/dist/api-contract.cjs +53 -15
- package/dist/api-contract.cjs.map +1 -1
- package/dist/api-contract.mjs +53 -15
- package/dist/api-contract.mjs.map +1 -1
- package/dist/cli/init.cjs +29 -3
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.d.cts.map +1 -1
- package/dist/cli/init.d.mts.map +1 -1
- package/dist/cli/init.mjs +29 -3
- package/dist/cli/init.mjs.map +1 -1
- package/dist/cli.cjs +27 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +27 -0
- package/dist/cli.mjs.map +1 -1
- package/dist/contract.cjs +20 -1
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +48 -3
- package/dist/contract.d.cts.map +1 -1
- package/dist/contract.d.mts +48 -3
- package/dist/contract.d.mts.map +1 -1
- package/dist/contract.meta.cjs +9 -0
- package/dist/contract.meta.cjs.map +1 -1
- package/dist/contract.meta.d.cts +13 -0
- package/dist/contract.meta.d.mts +13 -0
- package/dist/contract.meta.mjs +9 -0
- package/dist/contract.meta.mjs.map +1 -1
- package/dist/contract.mjs +19 -2
- package/dist/contract.mjs.map +1 -1
- package/dist/dev-session.cjs +3 -1
- package/dist/dev-session.cjs.map +1 -1
- package/dist/dev-session.mjs +3 -1
- package/dist/dev-session.mjs.map +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/orchestrator.cjs +6 -1
- package/dist/orchestrator.cjs.map +1 -1
- package/dist/orchestrator.d.cts.map +1 -1
- package/dist/orchestrator.d.mts.map +1 -1
- package/dist/orchestrator.mjs +6 -1
- package/dist/orchestrator.mjs.map +1 -1
- package/dist/plugin.cjs +80 -2
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +23 -2
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +23 -2
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +80 -2
- package/dist/plugin.mjs.map +1 -1
- package/dist/service-descriptor.d.cts +1 -1
- package/dist/service-descriptor.d.mts +1 -1
- package/dist/types.d.cts +2 -2
- package/dist/types.d.mts +2 -2
- package/package.json +2 -2
- package/skills/dev-workflow/SKILL.md +3 -3
- package/src/api-contract.ts +95 -22
- package/src/cli/init.ts +55 -6
- package/src/cli.ts +32 -0
- package/src/contract.meta.ts +9 -0
- package/src/contract.ts +21 -0
- package/src/dev-session.ts +5 -0
- package/src/orchestrator.ts +10 -21
- package/src/plugin.ts +116 -2
- 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.
|
|
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.
|
|
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 `
|
|
56
|
+
Plugin types are auto-generated from `bos.config.json` via `bos types gen`:
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
|
|
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 `
|
|
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
|
|
package/src/api-contract.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
283
|
-
if (opts.
|
|
284
|
-
|
|
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
|
-
|
|
402
|
+
scripts.postinstall = "bos types gen";
|
|
393
403
|
}
|
|
394
|
-
if (scripts.typecheck
|
|
395
|
-
scripts.typecheck = scripts.typecheck
|
|
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);
|
package/src/contract.meta.ts
CHANGED
|
@@ -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>;
|
package/src/dev-session.ts
CHANGED
|
@@ -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 = () => {
|
package/src/orchestrator.ts
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
260
|
-
|
|
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
|
-
});
|