everything-dev 1.27.0 → 1.28.1

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 (114) hide show
  1. package/dist/cli/infra.cjs +1 -1
  2. package/dist/cli/infra.mjs +1 -1
  3. package/dist/cli/init.cjs +34 -9
  4. package/dist/cli/init.cjs.map +1 -1
  5. package/dist/cli/init.d.cts +2 -1
  6. package/dist/cli/init.d.cts.map +1 -1
  7. package/dist/cli/init.d.mts +2 -1
  8. package/dist/cli/init.d.mts.map +1 -1
  9. package/dist/cli/init.mjs +34 -9
  10. package/dist/cli/init.mjs.map +1 -1
  11. package/dist/cli/prompts.cjs +28 -24
  12. package/dist/cli/prompts.cjs.map +1 -1
  13. package/dist/cli/prompts.mjs +27 -24
  14. package/dist/cli/prompts.mjs.map +1 -1
  15. package/dist/cli/sync.cjs +40 -3
  16. package/dist/cli/sync.cjs.map +1 -1
  17. package/dist/cli/sync.mjs +40 -3
  18. package/dist/cli/sync.mjs.map +1 -1
  19. package/dist/cli.cjs +187 -12
  20. package/dist/cli.cjs.map +1 -1
  21. package/dist/cli.mjs +186 -11
  22. package/dist/cli.mjs.map +1 -1
  23. package/dist/config.cjs +1 -0
  24. package/dist/config.cjs.map +1 -1
  25. package/dist/config.d.cts.map +1 -1
  26. package/dist/config.d.mts.map +1 -1
  27. package/dist/config.mjs +1 -0
  28. package/dist/config.mjs.map +1 -1
  29. package/dist/contract.cjs +1 -1
  30. package/dist/contract.cjs.map +1 -1
  31. package/dist/contract.d.cts +38 -34
  32. package/dist/contract.d.cts.map +1 -1
  33. package/dist/contract.d.mts +38 -34
  34. package/dist/contract.d.mts.map +1 -1
  35. package/dist/contract.mjs +1 -0
  36. package/dist/contract.mjs.map +1 -1
  37. package/dist/dev-session.cjs +0 -1
  38. package/dist/dev-session.mjs +1 -1
  39. package/dist/index.cjs +0 -2
  40. package/dist/index.d.cts +2 -2
  41. package/dist/index.d.mts +2 -2
  42. package/dist/index.mjs +0 -1
  43. package/dist/near-cli.cjs +1 -1
  44. package/dist/near-cli.mjs +1 -1
  45. package/dist/orchestrator.cjs +1 -1
  46. package/dist/orchestrator.mjs +1 -1
  47. package/dist/plugin.cjs +183 -151
  48. package/dist/plugin.cjs.map +1 -1
  49. package/dist/plugin.d.cts +67 -34
  50. package/dist/plugin.d.cts.map +1 -1
  51. package/dist/plugin.d.mts +66 -34
  52. package/dist/plugin.d.mts.map +1 -1
  53. package/dist/plugin.mjs +173 -142
  54. package/dist/plugin.mjs.map +1 -1
  55. package/dist/service-descriptor.d.cts +34 -0
  56. package/dist/service-descriptor.d.cts.map +1 -0
  57. package/dist/service-descriptor.d.mts +36 -0
  58. package/dist/service-descriptor.d.mts.map +1 -0
  59. package/dist/types.d.cts +2 -2
  60. package/dist/types.d.mts +2 -2
  61. package/dist/utils/run.cjs +9 -20
  62. package/dist/utils/run.cjs.map +1 -1
  63. package/dist/utils/run.mjs +9 -20
  64. package/dist/utils/run.mjs.map +1 -1
  65. package/package.json +2 -2
  66. package/src/api-contract.ts +0 -623
  67. package/src/app.ts +0 -193
  68. package/src/cli/catalog.ts +0 -49
  69. package/src/cli/framework-version.ts +0 -61
  70. package/src/cli/help.ts +0 -13
  71. package/src/cli/infra.ts +0 -190
  72. package/src/cli/init.ts +0 -1145
  73. package/src/cli/parse.ts +0 -147
  74. package/src/cli/prompts.ts +0 -135
  75. package/src/cli/snapshot.ts +0 -46
  76. package/src/cli/status.ts +0 -99
  77. package/src/cli/sync.ts +0 -429
  78. package/src/cli/timing.ts +0 -63
  79. package/src/cli/upgrade.ts +0 -869
  80. package/src/cli.ts +0 -516
  81. package/src/components/dev-view.tsx +0 -352
  82. package/src/components/streaming-view.ts +0 -177
  83. package/src/config.ts +0 -893
  84. package/src/contract.meta.ts +0 -140
  85. package/src/contract.ts +0 -326
  86. package/src/dev-logs.ts +0 -92
  87. package/src/dev-session.ts +0 -283
  88. package/src/fastkv.ts +0 -181
  89. package/src/index.ts +0 -8
  90. package/src/integrity.ts +0 -138
  91. package/src/internal/manifest-normalizer.ts +0 -290
  92. package/src/merge.ts +0 -187
  93. package/src/mf.ts +0 -147
  94. package/src/near-cli.ts +0 -259
  95. package/src/network.ts +0 -3
  96. package/src/orchestrator.ts +0 -493
  97. package/src/plugin.ts +0 -1799
  98. package/src/sdk.ts +0 -14
  99. package/src/service-descriptor.ts +0 -281
  100. package/src/shared.ts +0 -249
  101. package/src/sidebar.ts +0 -140
  102. package/src/types.ts +0 -330
  103. package/src/ui/head.ts +0 -83
  104. package/src/ui/index.ts +0 -5
  105. package/src/ui/metadata.ts +0 -95
  106. package/src/ui/router.ts +0 -88
  107. package/src/ui/runtime.ts +0 -42
  108. package/src/ui/types.ts +0 -65
  109. package/src/utils/banner.ts +0 -21
  110. package/src/utils/linkify.ts +0 -11
  111. package/src/utils/path-match.ts +0 -16
  112. package/src/utils/run.ts +0 -31
  113. package/src/utils/save-config.ts +0 -20
  114. package/src/utils/theme.ts +0 -39
package/src/plugin.ts DELETED
@@ -1,1799 +0,0 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import { basename, dirname, join, resolve } from "node:path";
3
- import process from "node:process";
4
- import * as p from "@clack/prompts";
5
- import { Effect } from "effect";
6
- import { syncApiContractBridge } from "./api-contract";
7
- import { buildRuntimeConfig, detectLocalPackages, prepareDevelopmentRuntimeConfig } from "./app";
8
- import { ensureEnvFile, writeGeneratedInfra } from "./cli/infra";
9
- import {
10
- buildInitPatterns,
11
- copyFilteredFiles,
12
- detectGitRemoteUrl,
13
- fetchParentConfig,
14
- generateDatabaseMigrations,
15
- personalizeConfig,
16
- removeInitLockfile,
17
- resolveSourceDir,
18
- runBunInstall,
19
- runDockerComposeUp,
20
- runTypesGen,
21
- scaffoldMinimalProject,
22
- stripOrphanedWorkspacesFromLockfile,
23
- writeInitSnapshot,
24
- } from "./cli/init";
25
- import { promptInitOptions } from "./cli/prompts";
26
- import { getStatus } from "./cli/status";
27
- import { syncTemplate } from "./cli/sync";
28
- import { timePhase } from "./cli/timing";
29
- import { upgradeTemplate } from "./cli/upgrade";
30
- import {
31
- buildRuntimePluginsForConfig,
32
- findConfigPath,
33
- getHostDevelopmentPort,
34
- getProjectRoot,
35
- loadConfig,
36
- resolveLocalDevelopmentPath,
37
- writeResolvedConfig,
38
- } from "./config";
39
- import {
40
- type BosConfigResult,
41
- bosContract,
42
- type OverrideSection,
43
- type PhaseTiming,
44
- type PluginListResult,
45
- } from "./contract";
46
- import {
47
- buildRegistryConfigUrl,
48
- buildRegistryConfigUrlForNetwork,
49
- fetchBosConfigFromFastKv,
50
- fetchRemotePluginManifest,
51
- getRegistryNamespaceForAccount,
52
- getRegistryNamespaceForNetwork,
53
- type PluginManifest,
54
- } from "./fastkv";
55
- import { computeSriHashForUrl } from "./integrity";
56
- import type { BosEnv } from "./merge";
57
- import { addFunctionCallAccessKey, ensureNearCli, executeTransaction } from "./near-cli";
58
- import { getNetworkIdForAccount } from "./network";
59
- import { createPlugin, z } from "./sdk";
60
- import {
61
- type AppOrchestrator,
62
- buildDescription,
63
- buildServiceDescriptorMap,
64
- } from "./service-descriptor";
65
- import { syncAndGenerateSharedUi } from "./shared";
66
- import { writePluginSidebarGen } from "./sidebar";
67
- import type { BosConfig, BosConfigInput, BosPluginRef, RuntimeConfig, SourceMode } from "./types";
68
- import { run } from "./utils/run";
69
- import { saveBosConfig } from "./utils/save-config";
70
- import { colors } from "./utils/theme";
71
-
72
- async function loadDevSession() {
73
- return import("./dev-session");
74
- }
75
-
76
- const buildCommands: Record<string, { cmd: string; args: string[] }> = {
77
- host: { cmd: "bun", args: ["run", "build"] },
78
- ui: { cmd: "bun", args: ["run", "build"] },
79
- api: { cmd: "bun", args: ["run", "build"] },
80
- };
81
-
82
- const PUBLISH_FUNCTION_NAMES = ["__fastdata_kv"];
83
-
84
- type BosDeps = {
85
- bosConfig: BosConfig | null;
86
- runtimeConfig: RuntimeConfig | null;
87
- configDir: string;
88
- };
89
-
90
- type PluginAttachmentConfig = NonNullable<BosConfig["plugins"]>[string];
91
-
92
- function getPluginRef(entry: string | BosPluginRef | undefined | null): BosPluginRef | null {
93
- if (!entry || typeof entry === "string") return null;
94
- return entry;
95
- }
96
-
97
- function parseSourceMode(value: string | undefined, defaultValue: SourceMode): SourceMode {
98
- if (value === "local" || value === "remote") return value;
99
- return defaultValue;
100
- }
101
-
102
- function buildConfigResult(bosConfig: BosConfig | null): BosConfigResult {
103
- const packages = bosConfig ? Object.keys(bosConfig.app) : [];
104
- const remotes = packages.filter((name) => name !== "host");
105
-
106
- return {
107
- config: bosConfig,
108
- packages,
109
- remotes,
110
- };
111
- }
112
-
113
- type WorkspaceTarget = {
114
- key: string;
115
- kind: "app" | "plugin";
116
- path: string;
117
- };
118
-
119
- function resolveWorkspaceTarget(
120
- key: string,
121
- bosConfig: BosConfig | null,
122
- runtimeConfig: RuntimeConfig | null,
123
- configDir: string,
124
- ): WorkspaceTarget | null {
125
- if (bosConfig?.app && key in bosConfig.app) {
126
- const appEntry = (bosConfig.app as Record<string, { development?: string }>)[key];
127
- const devPath = resolveLocalDevelopmentPath(appEntry?.development, configDir);
128
- if (devPath) {
129
- return {
130
- key,
131
- kind: "app",
132
- path: devPath,
133
- };
134
- }
135
- return {
136
- key,
137
- kind: "app",
138
- path: `${configDir}/${key}`,
139
- };
140
- }
141
-
142
- const runtimePlugin = runtimeConfig?.plugins?.[key];
143
- const pluginPath =
144
- runtimePlugin?.localPath ??
145
- resolveLocalDevelopmentPath(getPluginRef(bosConfig?.plugins?.[key])?.development, configDir);
146
- if (pluginPath) {
147
- return {
148
- key,
149
- kind: "plugin",
150
- path: pluginPath,
151
- };
152
- }
153
-
154
- return null;
155
- }
156
-
157
- function isValidProxyUrl(url: string): boolean {
158
- try {
159
- const parsed = new URL(url);
160
- return parsed.protocol === "http:" || parsed.protocol === "https:";
161
- } catch {
162
- return false;
163
- }
164
- }
165
-
166
- function resolveProxyUrl(bosConfig: BosConfig | null): string | null {
167
- if (!bosConfig) return null;
168
- const apiConfig = bosConfig.app.api;
169
- if (!apiConfig) return null;
170
- if (apiConfig.proxy && isValidProxyUrl(apiConfig.proxy)) return apiConfig.proxy;
171
- if (apiConfig.production && isValidProxyUrl(apiConfig.production)) return apiConfig.production;
172
- return null;
173
- }
174
-
175
- function sanitizePluginKey(value: string): string {
176
- return value
177
- .replace(/[^A-Za-z0-9/_-]/g, "-")
178
- .replace(/\/+/g, "/")
179
- .split("/")
180
- .filter(Boolean)
181
- .map((segment) => segment.replace(/[^A-Za-z0-9_-]/g, "-"))
182
- .join("/")
183
- .replace(/^\/+|\/+$/g, "");
184
- }
185
-
186
- function defaultPluginKey(source: string): string {
187
- const normalized = source.replace(/^local:/, "").replace(/\/$/, "");
188
- if (source.startsWith("local:")) {
189
- return sanitizePluginKey(basename(normalized)) || "plugin";
190
- }
191
-
192
- try {
193
- const url = new URL(source);
194
- return sanitizePluginKey(basename(url.pathname) || url.hostname) || "plugin";
195
- } catch {
196
- return sanitizePluginKey(source) || "plugin";
197
- }
198
- }
199
-
200
- function pluginLocalPath(configDir: string, attachment: PluginAttachmentConfig): string | null {
201
- const ref = getPluginRef(attachment);
202
- const source = ref?.development ?? ref?.production;
203
- if (!source?.startsWith("local:")) {
204
- return null;
205
- }
206
-
207
- return join(configDir, source.slice("local:".length));
208
- }
209
-
210
- function listPluginAttachments(config: BosConfig | null) {
211
- return (Object.entries(config?.plugins ?? {}) as Array<[string, PluginAttachmentConfig]>)
212
- .map(([key, attachment]) => {
213
- const ref = getPluginRef(attachment);
214
- return {
215
- key,
216
- development: ref?.development,
217
- production: ref?.production,
218
- localPath: ref?.development?.startsWith("local:")
219
- ? ref.development.slice("local:".length)
220
- : undefined,
221
- source: ref?.development?.startsWith("local:") ? ("local" as const) : ("remote" as const),
222
- integrity: ref?.integrity,
223
- version: ref?.version,
224
- name: ref?.name,
225
- };
226
- })
227
- .sort((a, b) => a.key.localeCompare(b.key));
228
- }
229
-
230
- interface GeneratedArtifacts {
231
- sidebarPath: string;
232
- resolvedConfigPath?: string;
233
- contractBridgePath: string;
234
- }
235
-
236
- async function generateCodeArtifacts(
237
- configDir: string,
238
- config: BosConfig,
239
- opts?: {
240
- env?: BosEnv;
241
- extendsChain?: string[];
242
- runtimeConfig?: RuntimeConfig;
243
- },
244
- ): Promise<GeneratedArtifacts | null> {
245
- if (opts?.env) {
246
- writeResolvedConfig(configDir, config, opts.env, opts.extendsChain);
247
- }
248
-
249
- const runtimeConfig = opts?.runtimeConfig ?? (await loadConfig({ cwd: configDir }))?.runtime;
250
- if (!runtimeConfig) return null;
251
-
252
- writePluginSidebarGen(configDir, runtimeConfig);
253
-
254
- const bridge = await syncApiContractBridge({
255
- configDir,
256
- runtimeConfig,
257
- apiBaseUrl: runtimeConfig.api.url,
258
- });
259
-
260
- return {
261
- sidebarPath: join(configDir, "ui/src/lib/plugin-sidebar.gen.ts"),
262
- resolvedConfigPath: opts?.env ? join(configDir, ".bos/bos.resolved-config.json") : undefined,
263
- contractBridgePath: bridge.bridgePath,
264
- };
265
- }
266
-
267
- function extractPublishedUrl(output: string): string | null {
268
- const match = output.match(/https?:\/\/[^\s"'<>]+/g);
269
- if (!match || match.length === 0) return null;
270
- return match[match.length - 1] ?? null;
271
- }
272
-
273
- async function buildEveryPluginQuietly(cwd: string) {
274
- const packageDir = `${cwd}/packages/every-plugin`;
275
- const packageExists = await Bun.file(`${packageDir}/package.json`).exists();
276
- if (!packageExists) {
277
- return;
278
- }
279
-
280
- const distPath = `${cwd}/packages/every-plugin/dist/build/rspack/plugin.mjs`;
281
- const distExists = await Bun.file(distPath).exists();
282
-
283
- if (distExists) {
284
- return;
285
- }
286
-
287
- const result = (await run("bun", ["run", "--cwd", "packages/every-plugin", "build"], {
288
- cwd,
289
- capture: true,
290
- })) as { stdout: string; stderr: string; exitCode: number };
291
-
292
- if (result.exitCode === 0) {
293
- console.log("[build:ssr] build succeeded");
294
- return;
295
- }
296
-
297
- if (result.stdout.trim()) {
298
- process.stdout.write(result.stdout);
299
- }
300
-
301
- if (result.stderr.trim()) {
302
- process.stderr.write(result.stderr);
303
- }
304
-
305
- throw new Error(
306
- `bun run --cwd packages/every-plugin build failed with exit code ${result.exitCode}`,
307
- );
308
- }
309
-
310
- async function buildEverythingDevQuietly(cwd: string) {
311
- const packageDir = `${cwd}/packages/everything-dev`;
312
- const packageExists = await Bun.file(`${packageDir}/package.json`).exists();
313
- if (!packageExists) {
314
- return;
315
- }
316
-
317
- const distPath = `${cwd}/packages/everything-dev/dist/index.mjs`;
318
- const distExists = await Bun.file(distPath).exists();
319
-
320
- if (distExists) {
321
- return;
322
- }
323
-
324
- const result = (await run("bun", ["run", "--cwd", "packages/everything-dev", "build"], {
325
- cwd,
326
- capture: true,
327
- })) as { stdout: string; stderr: string; exitCode: number };
328
-
329
- if (result.exitCode === 0) {
330
- console.log("[everything-dev] build succeeded");
331
- return;
332
- }
333
-
334
- if (result.stdout.trim()) {
335
- process.stdout.write(result.stdout);
336
- }
337
-
338
- if (result.stderr.trim()) {
339
- process.stderr.write(result.stderr);
340
- }
341
-
342
- throw new Error(
343
- `bun run --cwd packages/everything-dev build failed with exit code ${result.exitCode}`,
344
- );
345
- }
346
-
347
- async function fetchPublishedConfig(
348
- accountId: string,
349
- gatewayId: string,
350
- ): Promise<BosConfig | null> {
351
- try {
352
- return await fetchBosConfigFromFastKv<BosConfig>(`bos://${accountId}/${gatewayId}`);
353
- } catch {
354
- return null;
355
- }
356
- }
357
-
358
- function selectWorkspaceTargets(packages: string, bosConfig: BosConfig | null): string[] {
359
- const allPackages = [
360
- ...Object.keys(bosConfig?.app ?? {}),
361
- ...Object.keys(bosConfig?.plugins ?? {}),
362
- ];
363
- if (packages === "all") {
364
- return allPackages;
365
- }
366
-
367
- return packages
368
- .split(",")
369
- .map((pkg) => pkg.trim())
370
- .filter((pkg) => allPackages.includes(pkg));
371
- }
372
-
373
- async function buildWorkspaceTargets(opts: {
374
- configDir: string;
375
- bosConfig: BosConfig | null;
376
- runtimeConfig: RuntimeConfig | null;
377
- targets: string[];
378
- deploy: boolean;
379
- }): Promise<{ built: string[]; skipped: string[] }> {
380
- const existing: WorkspaceTarget[] = [];
381
- const skipped: string[] = [];
382
-
383
- for (const target of opts.targets) {
384
- const resolved = resolveWorkspaceTarget(
385
- target,
386
- opts.bosConfig,
387
- opts.runtimeConfig,
388
- opts.configDir,
389
- );
390
- if (!resolved) {
391
- skipped.push(target);
392
- continue;
393
- }
394
-
395
- const exists = await Bun.file(`${resolved.path}/package.json`).exists();
396
- if (exists) existing.push(resolved);
397
- else skipped.push(target);
398
- }
399
-
400
- if (existing.length === 0) {
401
- return { built: [], skipped };
402
- }
403
-
404
- const sharedSync = await syncAndGenerateSharedUi({
405
- configDir: opts.configDir,
406
- hostMode: "local",
407
- bosConfig: opts.bosConfig ?? undefined,
408
- extendsChain: [],
409
- });
410
- if (sharedSync.catalogChanged) {
411
- await run("bun", ["install"], { cwd: opts.configDir });
412
- }
413
-
414
- if (existing.some((entry) => entry.key === "api")) {
415
- await buildEveryPluginQuietly(opts.configDir);
416
- }
417
-
418
- await buildEverythingDevQuietly(opts.configDir);
419
-
420
- const env: Record<string, string> = {
421
- ...process.env,
422
- NODE_ENV: opts.deploy ? "production" : "development",
423
- };
424
- if (opts.deploy) {
425
- env.DEPLOY = "true";
426
- } else {
427
- delete env.DEPLOY;
428
- }
429
-
430
- const orderedExisting = opts.deploy
431
- ? [
432
- ...existing.filter((entry) => entry.kind === "app" && entry.key !== "host"),
433
- ...existing.filter((entry) => entry.kind === "plugin"),
434
- ...existing.filter((entry) => entry.kind === "app" && entry.key === "host"),
435
- ]
436
- : existing;
437
- const built: string[] = [];
438
-
439
- for (const resolved of orderedExisting) {
440
- const pkgJson = JSON.parse(await Bun.file(`${resolved.path}/package.json`).text()) as {
441
- scripts?: Record<string, string>;
442
- };
443
- const shouldDeployScript = opts.deploy && pkgJson.scripts?.deploy;
444
- const buildConfig = shouldDeployScript
445
- ? { cmd: "bun", args: ["run", "deploy"] }
446
- : (buildCommands[resolved.key] ?? { cmd: "bun", args: ["run", "build"] });
447
-
448
- await run(buildConfig.cmd, buildConfig.args, {
449
- cwd: resolved.path,
450
- env,
451
- });
452
- built.push(resolved.key);
453
- }
454
-
455
- return { built, skipped };
456
- }
457
-
458
- export default createPlugin({
459
- variables: z.object({
460
- configPath: z.string().optional(),
461
- }),
462
- secrets: z.object({}),
463
- contract: bosContract,
464
- initialize: (config) =>
465
- Effect.promise(async () => {
466
- const configResult = await loadConfig({ path: config.variables.configPath });
467
- return {
468
- bosConfig: configResult?.config ?? null,
469
- runtimeConfig: configResult?.runtime ?? null,
470
- configDir: getProjectRoot(),
471
- } satisfies BosDeps;
472
- }),
473
- shutdown: () => Effect.void,
474
- createRouter: (deps, builder) => ({
475
- config: builder.config.handler(async () => buildConfigResult(deps.bosConfig)),
476
-
477
- pluginAdd: builder.pluginAdd.handler(async ({ input }) => {
478
- if (!deps.bosConfig) {
479
- return {
480
- status: "error" as const,
481
- key: "",
482
- error: "No bos.config.json found",
483
- };
484
- }
485
-
486
- const isBosRef = input.source.startsWith("bos://");
487
- const isLocal = input.source.startsWith("local:");
488
- const key = sanitizePluginKey(
489
- input.as ??
490
- (isBosRef ? (input.source.split("/").pop() ?? "plugin") : defaultPluginKey(input.source)),
491
- );
492
- const existing = deps.bosConfig.plugins?.[key];
493
- const existingEntry = existing && typeof existing === "object" ? existing : {};
494
- const nextPlugins = { ...(deps.bosConfig.plugins ?? {}) };
495
-
496
- if (isBosRef) {
497
- nextPlugins[key] = {
498
- ...existingEntry,
499
- extends: input.source,
500
- };
501
- } else if (isLocal) {
502
- nextPlugins[key] = {
503
- ...existingEntry,
504
- development: input.source,
505
- ...(existingEntry.extends ? {} : {}),
506
- };
507
- } else {
508
- nextPlugins[key] = {
509
- ...existingEntry,
510
- production: input.production ?? input.source,
511
- };
512
- }
513
-
514
- deps.bosConfig = {
515
- ...deps.bosConfig,
516
- plugins: nextPlugins,
517
- };
518
-
519
- await saveBosConfig(deps.configDir, deps.bosConfig);
520
- await generateCodeArtifacts(deps.configDir, deps.bosConfig);
521
-
522
- const stored = deps.bosConfig.plugins?.[key];
523
- const storedObj = stored && typeof stored === "object" ? stored : {};
524
-
525
- return {
526
- status: "added" as const,
527
- key,
528
- development: storedObj.development,
529
- production: storedObj.production,
530
- integrity: storedObj.integrity,
531
- version: storedObj.version,
532
- };
533
- }),
534
-
535
- pluginRemove: builder.pluginRemove.handler(async ({ input }) => {
536
- if (!deps.bosConfig) {
537
- return {
538
- status: "error" as const,
539
- key: input.key,
540
- error: "No bos.config.json found",
541
- };
542
- }
543
-
544
- if (!deps.bosConfig.plugins?.[input.key]) {
545
- return {
546
- status: "error" as const,
547
- key: input.key,
548
- error: `Plugin '${input.key}' is not configured`,
549
- };
550
- }
551
-
552
- const nextPlugins = { ...(deps.bosConfig.plugins ?? {}) };
553
- delete nextPlugins[input.key];
554
- deps.bosConfig = {
555
- ...deps.bosConfig,
556
- plugins: Object.keys(nextPlugins).length > 0 ? nextPlugins : undefined,
557
- };
558
-
559
- await saveBosConfig(deps.configDir, deps.bosConfig);
560
- await generateCodeArtifacts(deps.configDir, deps.bosConfig);
561
-
562
- return {
563
- status: "removed" as const,
564
- key: input.key,
565
- };
566
- }),
567
-
568
- pluginList: builder.pluginList.handler(async () => {
569
- const plugins: PluginListResult["plugins"] = listPluginAttachments(deps.bosConfig);
570
- return {
571
- status: "listed" as const,
572
- plugins,
573
- };
574
- }),
575
-
576
- pluginPublish: builder.pluginPublish.handler(async ({ input }) => {
577
- if (!deps.bosConfig) {
578
- return {
579
- status: "error" as const,
580
- key: input.key,
581
- error: "No bos.config.json found",
582
- };
583
- }
584
-
585
- const attachment = deps.bosConfig.plugins?.[input.key];
586
- if (!attachment) {
587
- return {
588
- status: "error" as const,
589
- key: input.key,
590
- error: `Plugin '${input.key}' is not configured`,
591
- };
592
- }
593
-
594
- const attachmentRef = getPluginRef(attachment);
595
-
596
- const localPath = pluginLocalPath(deps.configDir, attachment);
597
- if (!localPath) {
598
- return {
599
- status: "error" as const,
600
- key: input.key,
601
- error: `Plugin '${input.key}' does not have a local development path`,
602
- };
603
- }
604
-
605
- const pkgPath = join(localPath, "package.json");
606
- if (!(await Bun.file(pkgPath).exists())) {
607
- return {
608
- status: "error" as const,
609
- key: input.key,
610
- error: `Missing package.json at ${localPath}`,
611
- };
612
- }
613
-
614
- const pkgJson = (await Bun.file(pkgPath).json()) as {
615
- scripts?: Record<string, string>;
616
- name?: string;
617
- version?: string;
618
- };
619
- const script = pkgJson.scripts?.deploy ? "deploy" : "build";
620
-
621
- const { stdout, stderr, exitCode } = (await run("bun", ["run", script], {
622
- cwd: localPath,
623
- capture: true,
624
- })) as { stdout: string; stderr: string; exitCode: number };
625
-
626
- if (exitCode !== 0) {
627
- if (stdout.trim()) process.stdout.write(stdout);
628
- if (stderr.trim()) process.stderr.write(stderr);
629
- return {
630
- status: "error" as const,
631
- key: input.key,
632
- error: `Publish failed with exit code ${exitCode}`,
633
- };
634
- }
635
-
636
- if (stdout.trim()) process.stdout.write(stdout);
637
- if (stderr.trim()) process.stderr.write(stderr);
638
-
639
- let publishedUrl = extractPublishedUrl(`${stdout}\n${stderr}`);
640
-
641
- let manifest: PluginManifest | null = null;
642
- if (publishedUrl) {
643
- manifest = await fetchRemotePluginManifest(publishedUrl);
644
- } else if (attachmentRef?.production) {
645
- manifest = await fetchRemotePluginManifest(attachmentRef.production);
646
- if (manifest) {
647
- publishedUrl = attachmentRef.production;
648
- }
649
- }
650
-
651
- const integrity = publishedUrl ? await computeSriHashForUrl(publishedUrl) : null;
652
- const version = manifest?.plugin.version ?? pkgJson.version;
653
-
654
- if (publishedUrl) {
655
- const rootConfigPath = join(deps.configDir, "bos.config.json");
656
- try {
657
- const rootConfig = JSON.parse(readFileSync(rootConfigPath, "utf-8")) as Record<
658
- string,
659
- unknown
660
- >;
661
- if (!rootConfig.plugins || typeof rootConfig.plugins !== "object") {
662
- rootConfig.plugins = {};
663
- }
664
- const plugins = rootConfig.plugins as Record<string, unknown>;
665
- if (!plugins[input.key] || typeof plugins[input.key] !== "object") {
666
- plugins[input.key] = {};
667
- }
668
- const entry = plugins[input.key] as Record<string, unknown>;
669
- entry.production = publishedUrl;
670
- if (integrity) {
671
- entry.integrity = integrity;
672
- } else {
673
- delete entry.integrity;
674
- }
675
- writeFileSync(rootConfigPath, `${JSON.stringify(rootConfig, null, 2)}\n`);
676
- console.log(` ✅ Updated bos.config.json: plugins.${input.key}.production`);
677
- } catch (err) {
678
- console.error(
679
- ` ❌ Failed to update bos.config.json:`,
680
- err instanceof Error ? err.message : err,
681
- );
682
- }
683
-
684
- await generateCodeArtifacts(deps.configDir, deps.bosConfig);
685
- }
686
-
687
- return {
688
- status: "published" as const,
689
- key: input.key,
690
- path: localPath,
691
- script,
692
- production: publishedUrl ?? attachmentRef?.production,
693
- integrity: integrity ?? undefined,
694
- version: version ?? undefined,
695
- };
696
- }),
697
-
698
- dev: builder.dev.handler(async ({ input }) => {
699
- ensureEnvFile(deps.configDir);
700
-
701
- const localPackages = detectLocalPackages(
702
- deps.bosConfig ?? undefined,
703
- deps.runtimeConfig ?? undefined,
704
- );
705
-
706
- const hostSource: SourceMode = localPackages.includes("host")
707
- ? parseSourceMode(input.host, "local")
708
- : "remote";
709
- const uiSource: SourceMode = localPackages.includes("ui")
710
- ? parseSourceMode(input.ui, "local")
711
- : "remote";
712
- const apiSource: SourceMode = localPackages.includes("api")
713
- ? parseSourceMode(input.api, "local")
714
- : "remote";
715
- const authSource: SourceMode = localPackages.includes("auth")
716
- ? parseSourceMode(input.auth, "local")
717
- : "remote";
718
- const ssr = input.ssr ?? false;
719
- const proxy = input.proxy ?? false;
720
-
721
- const sharedSync = await syncAndGenerateSharedUi({
722
- configDir: deps.configDir,
723
- hostMode: hostSource,
724
- bosConfig: deps.bosConfig ?? undefined,
725
- extendsChain: [],
726
- });
727
- if (sharedSync.catalogChanged) {
728
- await run("bun", ["install"], { cwd: deps.configDir });
729
- }
730
- if (
731
- (apiSource === "local" && !proxy) ||
732
- localPackages.some((pkg) => pkg.startsWith("plugin:"))
733
- ) {
734
- await buildEveryPluginQuietly(deps.configDir);
735
- }
736
-
737
- await buildEverythingDevQuietly(deps.configDir);
738
-
739
- const refreshed = await loadConfig({ cwd: deps.configDir });
740
- deps.bosConfig = refreshed?.config ?? deps.bosConfig;
741
- deps.runtimeConfig = refreshed?.runtime ?? deps.runtimeConfig;
742
-
743
- if (!deps.bosConfig) {
744
- return {
745
- status: "error" as const,
746
- description: "No bos.config.json found",
747
- processes: [],
748
- };
749
- }
750
-
751
- if (proxy && !resolveProxyUrl(deps.bosConfig)) {
752
- return {
753
- status: "error" as const,
754
- description: "No valid proxy URL configured in bos.config.json",
755
- processes: [],
756
- };
757
- }
758
-
759
- const hostPort = input.port ?? getHostDevelopmentPort(deps.bosConfig.app.host.development);
760
- const developmentRuntime = buildRuntimeConfig(deps.bosConfig, {
761
- uiSource,
762
- apiSource,
763
- authSource,
764
- hostSource,
765
- env: "development",
766
- plugins: deps.runtimeConfig?.plugins,
767
- });
768
- const runtimeConfig = await prepareDevelopmentRuntimeConfig(developmentRuntime, {
769
- hostPort,
770
- ssr,
771
- });
772
-
773
- await generateCodeArtifacts(deps.configDir, deps.bosConfig, {
774
- env: "development",
775
- extendsChain: refreshed?.source.extended,
776
- runtimeConfig,
777
- });
778
-
779
- const services = buildServiceDescriptorMap(runtimeConfig, { ssr, proxy });
780
- const packages = [...services.keys()];
781
- const displayEnv: Record<string, string> = {};
782
- const apiDescriptor = services.get("api");
783
- if (apiDescriptor?.proxy) {
784
- const proxyUrl = resolveProxyUrl(deps.bosConfig);
785
- if (proxyUrl) displayEnv.API_PROXY = proxyUrl;
786
- }
787
-
788
- const orchestrator: AppOrchestrator = {
789
- packages,
790
- env: displayEnv,
791
- description: buildDescription(services),
792
- port: runtimeConfig.host.port,
793
- interactive: input.interactive,
794
- };
795
-
796
- const { devApp } = await loadDevSession();
797
- devApp(orchestrator, services, runtimeConfig);
798
-
799
- return {
800
- status: "started" as const,
801
- description: orchestrator.description,
802
- processes: packages,
803
- };
804
- }),
805
-
806
- start: builder.start.handler(async ({ input }) => {
807
- ensureEnvFile(deps.configDir);
808
-
809
- const account = input.account ?? process.env.BOS_ACCOUNT;
810
- const domain = input.domain ?? process.env.BOS_GATEWAY;
811
-
812
- let config: BosConfig | null = null;
813
- let remoteConfig: BosConfig | null = null;
814
-
815
- if (account && domain) {
816
- remoteConfig = await fetchPublishedConfig(account, domain);
817
- if (remoteConfig) {
818
- config = remoteConfig;
819
- } else {
820
- console.warn(
821
- `[Start] Failed to fetch remote config for ${account}/${domain}, falling back to local bos.config.json`,
822
- );
823
- }
824
- }
825
-
826
- if (!config) {
827
- config = deps.bosConfig;
828
- }
829
-
830
- if (!config) {
831
- return {
832
- status: "error" as const,
833
- url: "",
834
- error:
835
- "No configuration found. Set BOS_ACCOUNT and BOS_GATEWAY environment variables, or provide a local bos.config.json.",
836
- };
837
- }
838
-
839
- // Apply runtime overrides from CLI flags / env vars
840
- if (account) {
841
- config = { ...config, account };
842
- }
843
- if (domain) {
844
- config = { ...config, domain };
845
- }
846
-
847
- const port = input.port ?? getHostDevelopmentPort(config.app.host.development);
848
- const isStaging = input.env === "staging";
849
- const runtimePlugins = await buildRuntimePluginsForConfig(
850
- config,
851
- deps.configDir,
852
- "production",
853
- );
854
- const runtimeConfig = buildRuntimeConfig(config, {
855
- uiSource: "remote",
856
- apiSource: "remote",
857
- authSource: "remote",
858
- hostSource: "remote",
859
- env: "production",
860
- plugins: runtimePlugins,
861
- });
862
-
863
- await generateCodeArtifacts(deps.configDir, config, {
864
- env: "production",
865
- runtimeConfig,
866
- });
867
-
868
- // ── Production Readiness Validation ──
869
- const productionEnv: Record<string, string> = {};
870
- const warnings: string[] = [];
871
-
872
- // Default CORS_ORIGIN to the configured domain if not set
873
- if (!process.env.CORS_ORIGIN && config.domain) {
874
- const defaultOrigin = `https://${config.domain}`;
875
- productionEnv.CORS_ORIGIN = defaultOrigin;
876
- warnings.push(`CORS_ORIGIN defaulting to ${defaultOrigin}`);
877
- }
878
-
879
- // Validate required secrets
880
- const requiredSecrets = new Set<string>();
881
- const missingSecrets: string[] = [];
882
-
883
- if (runtimeConfig.auth?.secrets) {
884
- for (const s of runtimeConfig.auth.secrets) requiredSecrets.add(s);
885
- }
886
- if (runtimeConfig.api?.secrets) {
887
- for (const s of runtimeConfig.api.secrets) requiredSecrets.add(s);
888
- }
889
- for (const plugin of Object.values(runtimeConfig.plugins ?? {})) {
890
- if (plugin.secrets) {
891
- for (const s of plugin.secrets) requiredSecrets.add(s);
892
- }
893
- }
894
-
895
- for (const secret of requiredSecrets) {
896
- const value = process.env[secret];
897
- if (!value || value.length === 0) {
898
- missingSecrets.push(secret);
899
- }
900
- }
901
-
902
- if (missingSecrets.length > 0) {
903
- warnings.push(`Missing ${missingSecrets.length} secret(s): ${missingSecrets.join(", ")}`);
904
- }
905
-
906
- const services = buildServiceDescriptorMap(runtimeConfig);
907
-
908
- const stagingEnvVars: Record<string, string> = isStaging
909
- ? { BOS_GATEWAY: config.staging?.domain ?? config.domain ?? "" }
910
- : {};
911
-
912
- const configSource = remoteConfig
913
- ? `bos://${account}/${domain}`
914
- : (findConfigPath() ?? "bos.config.json");
915
-
916
- const configSourceHttp =
917
- remoteConfig && account && domain ? buildRegistryConfigUrl(account, domain) : undefined;
918
-
919
- const summaryLines: string[] = ["", ` ${colors.dim("Config Source:")} ${configSource}`];
920
- if (configSourceHttp) {
921
- summaryLines.push(` ${colors.dim(configSourceHttp)}`);
922
- }
923
- summaryLines.push(
924
- ` ${colors.dim("Account:")} ${config.account}`,
925
- ` ${colors.dim("Domain:")} ${config.domain ?? "not configured"}`,
926
- "",
927
- ` ${colors.dim("Modules:")}`,
928
- ` ${colors.dim("HOST")} → ${runtimeConfig.host.remoteUrl ?? runtimeConfig.host.url ?? "local"}`,
929
- ` ${colors.dim("UI")} → ${runtimeConfig.ui.url ?? "local"}`,
930
- ` ${colors.dim("API")} → ${runtimeConfig.api.url ?? "local"}`,
931
- );
932
- if (runtimeConfig.auth) {
933
- summaryLines.push(` ${colors.dim("AUTH")} → ${runtimeConfig.auth.url ?? "local"}`);
934
- }
935
- if (warnings.length > 0) {
936
- summaryLines.push("");
937
- for (const w of warnings) {
938
- summaryLines.push(` ${colors.yellow(w)}`);
939
- }
940
- }
941
- summaryLines.push("");
942
- console.log(summaryLines.join("\n"));
943
-
944
- const orchestrator: AppOrchestrator = {
945
- packages: ["host"],
946
- env: {
947
- NODE_ENV: "production",
948
- ...productionEnv,
949
- ...stagingEnvVars,
950
- },
951
- description: `${isStaging ? "Staging" : "Production"} Mode (${config.account})`,
952
- port,
953
- interactive: input.interactive,
954
- noLogs: true,
955
- };
956
-
957
- const { startApp } = await loadDevSession();
958
- startApp(orchestrator, services, runtimeConfig);
959
- return {
960
- status: "running" as const,
961
- url: `http://localhost:${port}`,
962
- };
963
- }),
964
-
965
- build: builder.build.handler(async ({ input }) => {
966
- if (!deps.bosConfig) {
967
- return {
968
- status: "error" as const,
969
- built: [],
970
- skipped: [],
971
- };
972
- }
973
-
974
- const buildEnv: BosEnv = input.deploy ? "production" : "development";
975
-
976
- const targets = selectWorkspaceTargets(input.packages, deps.bosConfig);
977
- if (targets.length === 0) {
978
- return {
979
- status: "error" as const,
980
- built: [],
981
- skipped: [],
982
- };
983
- }
984
-
985
- const runtimeConfig = buildRuntimeConfig(deps.bosConfig, {
986
- uiSource: deps.bosConfig.app.ui?.development ? "local" : "remote",
987
- apiSource: deps.bosConfig.app.api?.development ? "local" : "remote",
988
- authSource: deps.bosConfig.app.auth?.development ? "local" : "remote",
989
- hostSource: deps.bosConfig.app.host?.development ? "local" : "remote",
990
- env: buildEnv,
991
- plugins: deps.runtimeConfig?.plugins,
992
- });
993
-
994
- await generateCodeArtifacts(deps.configDir, deps.bosConfig, {
995
- env: buildEnv,
996
- runtimeConfig,
997
- });
998
-
999
- const { built, skipped } = await buildWorkspaceTargets({
1000
- configDir: deps.configDir,
1001
- bosConfig: deps.bosConfig,
1002
- runtimeConfig: runtimeConfig,
1003
- targets,
1004
- deploy: input.deploy,
1005
- });
1006
-
1007
- if (built.length === 0) {
1008
- return {
1009
- status: "error" as const,
1010
- built: [],
1011
- skipped,
1012
- };
1013
- }
1014
-
1015
- return {
1016
- status: "success" as const,
1017
- built,
1018
- skipped,
1019
- deployed: input.deploy,
1020
- };
1021
- }),
1022
-
1023
- publish: builder.publish.handler(async ({ input }) => {
1024
- if (!deps.bosConfig) {
1025
- return {
1026
- status: "error" as const,
1027
- registryUrl: "",
1028
- error: "No bos.config.json found",
1029
- };
1030
- }
1031
-
1032
- const account = deps.bosConfig.account;
1033
- const gateway = deps.bosConfig.domain;
1034
- if (!gateway) {
1035
- return {
1036
- status: "error" as const,
1037
- registryUrl: "",
1038
- error: "bos.config.json must define domain to publish",
1039
- };
1040
- }
1041
-
1042
- const network = input.network ?? getNetworkIdForAccount(account);
1043
- const bosUrl = `bos://${account}/${gateway}`;
1044
- const registryUrl = buildRegistryConfigUrlForNetwork(network, account, gateway);
1045
- const targets = selectWorkspaceTargets(input.packages, deps.bosConfig);
1046
-
1047
- let publishConfig = deps.bosConfig;
1048
- let built: string[] | undefined;
1049
- let skipped: string[] | undefined;
1050
-
1051
- if (input.dryRun) {
1052
- return {
1053
- status: "dry-run" as const,
1054
- registryUrl,
1055
- built,
1056
- skipped,
1057
- };
1058
- }
1059
-
1060
- if (input.deploy) {
1061
- await generateCodeArtifacts(deps.configDir, deps.bosConfig, {
1062
- env: "production",
1063
- runtimeConfig: deps.runtimeConfig ?? undefined,
1064
- });
1065
-
1066
- const result = await buildWorkspaceTargets({
1067
- configDir: deps.configDir,
1068
- bosConfig: deps.bosConfig,
1069
- runtimeConfig: deps.runtimeConfig,
1070
- targets,
1071
- deploy: true,
1072
- });
1073
- built = result.built;
1074
- skipped = result.skipped;
1075
-
1076
- const refreshed = await loadConfig({ cwd: deps.configDir });
1077
- if (refreshed?.config) {
1078
- deps.bosConfig = refreshed.config;
1079
- deps.runtimeConfig = refreshed.runtime;
1080
- publishConfig = refreshed.config;
1081
- }
1082
- }
1083
-
1084
- const registryEntries: Record<string, string> = {
1085
- [`apps/${account}/${gateway}/bos.config.json`]: JSON.stringify(publishConfig),
1086
- };
1087
-
1088
- const payload = JSON.stringify(registryEntries);
1089
- const argsBase64 = Buffer.from(payload).toString("base64");
1090
- const privateKey =
1091
- input.privateKey || process.env.NEAR_PRIVATE_KEY || process.env.BOS_NEAR_PRIVATE_KEY;
1092
-
1093
- try {
1094
- await Effect.runPromise(ensureNearCli);
1095
- let txHash: string | undefined;
1096
-
1097
- try {
1098
- const tx = await Effect.runPromise(
1099
- executeTransaction({
1100
- account,
1101
- contract: getRegistryNamespaceForNetwork(network),
1102
- method: "__fastdata_kv",
1103
- argsBase64,
1104
- network,
1105
- privateKey,
1106
- gas: "300Tgas",
1107
- deposit: "0NEAR",
1108
- }),
1109
- );
1110
- txHash = tx.txHash;
1111
- } catch (error) {
1112
- txHash = extractTransactionHash(error);
1113
-
1114
- if (!txHash) {
1115
- throw error;
1116
- }
1117
-
1118
- try {
1119
- const verifiedConfig = await fetchBosConfigFromFastKv<BosConfig>(bosUrl);
1120
- if (JSON.stringify(verifiedConfig) !== JSON.stringify(publishConfig)) {
1121
- throw error;
1122
- }
1123
- } catch {
1124
- // Config may not exist yet on first publish or propagation delay;
1125
- // a valid txHash is sufficient proof the transaction was submitted.
1126
- }
1127
- }
1128
-
1129
- return {
1130
- status: "published" as const,
1131
- registryUrl,
1132
- txHash,
1133
- built,
1134
- skipped,
1135
- };
1136
- } catch (error) {
1137
- return {
1138
- status: "error" as const,
1139
- registryUrl,
1140
- error: error instanceof Error ? error.message : "Unknown error",
1141
- built,
1142
- skipped,
1143
- };
1144
- }
1145
- }),
1146
-
1147
- keyPublish: builder.keyPublish.handler(async ({ input }) => {
1148
- if (!deps.bosConfig) {
1149
- return {
1150
- status: "error" as const,
1151
- account: "",
1152
- network: "mainnet" as const,
1153
- contract: "",
1154
- allowance: input.allowance,
1155
- functionNames: PUBLISH_FUNCTION_NAMES,
1156
- error: "No bos.config.json found",
1157
- };
1158
- }
1159
-
1160
- const account = deps.bosConfig.account;
1161
- const network = getNetworkIdForAccount(account);
1162
- const contract = getRegistryNamespaceForAccount(account);
1163
- try {
1164
- await Effect.runPromise(ensureNearCli);
1165
- const keyPair = await addFunctionCallAccessKey({
1166
- account,
1167
- contract,
1168
- allowance: input.allowance,
1169
- functionNames: PUBLISH_FUNCTION_NAMES,
1170
- network,
1171
- });
1172
-
1173
- return {
1174
- status: "published" as const,
1175
- account,
1176
- network,
1177
- contract,
1178
- allowance: input.allowance,
1179
- functionNames: PUBLISH_FUNCTION_NAMES,
1180
- publicKey: keyPair.publicKey,
1181
- privateKey: keyPair.privateKey,
1182
- };
1183
- } catch (error) {
1184
- return {
1185
- status: "error" as const,
1186
- account,
1187
- network,
1188
- contract,
1189
- allowance: input.allowance,
1190
- functionNames: PUBLISH_FUNCTION_NAMES,
1191
- error: error instanceof Error ? error.message : "Unknown error",
1192
- };
1193
- }
1194
- }),
1195
-
1196
- init: builder.init.handler(async ({ input }) => {
1197
- try {
1198
- const timings: PhaseTiming[] = [];
1199
- let extendsAccount = "";
1200
- let extendsGateway = "";
1201
- let directory = input.directory;
1202
- let account = input.account;
1203
- let domain = input.domain;
1204
- let overrides = input.overrides as OverrideSection[] | undefined;
1205
- let plugins = input.plugins;
1206
-
1207
- if (input.extends) {
1208
- const normalized = input.extends.startsWith("bos://")
1209
- ? input.extends
1210
- : `bos://${input.extends}`;
1211
- const match = normalized.match(/^bos:\/\/([^/]+)\/(.+)$/);
1212
- if (match) {
1213
- extendsAccount = match[1];
1214
- extendsGateway = match[2];
1215
- }
1216
- }
1217
-
1218
- extendsAccount = extendsAccount || "dev.everything.near";
1219
- extendsGateway = extendsGateway || "everything.dev";
1220
-
1221
- const s = p.spinner();
1222
- s.start("Initializing project");
1223
-
1224
- let parentPluginKeys: string[] = [];
1225
- let parentConfig: BosConfig | null = null;
1226
- try {
1227
- parentConfig = await timePhase(
1228
- timings,
1229
- "parent config",
1230
- () => fetchParentConfig(extendsAccount, extendsGateway),
1231
- s,
1232
- );
1233
- if (parentConfig?.plugins && typeof parentConfig.plugins === "object") {
1234
- parentPluginKeys = Object.keys(parentConfig.plugins);
1235
- }
1236
- } catch {}
1237
-
1238
- if (!input.noInteractive) {
1239
- s.stop("Config fetched");
1240
- const initialExtendsAccount = extendsAccount;
1241
- const initialExtendsGateway = extendsGateway;
1242
- const prompted = await promptInitOptions({
1243
- extends: `bos://${extendsAccount}/${extendsGateway}`,
1244
- directory,
1245
- account,
1246
- domain,
1247
- plugins,
1248
- overrides,
1249
- parentPluginKeys,
1250
- });
1251
- extendsAccount = prompted.extendsAccount;
1252
- extendsGateway = prompted.extendsGateway;
1253
- directory = prompted.directory;
1254
- account = prompted.account;
1255
- domain = prompted.domain;
1256
- plugins = prompted.plugins;
1257
- overrides = prompted.overrides;
1258
-
1259
- if (
1260
- !parentConfig ||
1261
- prompted.extendsAccount !== initialExtendsAccount ||
1262
- prompted.extendsGateway !== initialExtendsGateway
1263
- ) {
1264
- try {
1265
- parentConfig = await timePhase(
1266
- timings,
1267
- "parent config",
1268
- () => fetchParentConfig(prompted.extendsAccount, prompted.extendsGateway),
1269
- s,
1270
- );
1271
- if (parentConfig?.plugins && typeof parentConfig.plugins === "object") {
1272
- parentPluginKeys = Object.keys(parentConfig.plugins);
1273
- } else {
1274
- parentPluginKeys = [];
1275
- }
1276
- } catch {
1277
- return {
1278
- status: "error" as const,
1279
- directory,
1280
- extendsRef: `bos://${prompted.extendsAccount}/${prompted.extendsGateway}`,
1281
- account,
1282
- domain,
1283
- extends: `bos://${prompted.extendsAccount}/${prompted.extendsGateway}`,
1284
- plugins,
1285
- overrides,
1286
- filesCopied: 0,
1287
- timings,
1288
- error: `No config found at bos://${prompted.extendsAccount}/${prompted.extendsGateway} — are you sure this is the right parent?`,
1289
- };
1290
- }
1291
- s.stop("Config fetched");
1292
- }
1293
-
1294
- if (
1295
- typeof parentConfig?.title === "string" &&
1296
- parentConfig.title.trim() &&
1297
- typeof parentConfig.description === "string" &&
1298
- parentConfig.description.trim()
1299
- ) {
1300
- const shouldContinue = await p.confirm({
1301
- message: `You will be extending ${parentConfig.title} - ${parentConfig.description}. Continue?`,
1302
- initialValue: true,
1303
- });
1304
-
1305
- if (p.isCancel(shouldContinue) || !shouldContinue) {
1306
- process.exit(0);
1307
- }
1308
- }
1309
-
1310
- s.start("Setting up project");
1311
- }
1312
-
1313
- overrides = overrides?.length ? overrides : (["ui", "api"] as OverrideSection[]);
1314
- if (overrides.includes("plugins") && !plugins?.length) {
1315
- plugins = parentPluginKeys;
1316
- }
1317
- plugins = plugins ?? [];
1318
-
1319
- directory = directory || domain || extendsGateway;
1320
- const targetDir = resolve(directory);
1321
- const extendsRef = `bos://${extendsAccount}/${extendsGateway}`;
1322
-
1323
- if (overrides.includes("plugins") && !plugins.length) {
1324
- // explicitly selected plugins override with none selected — back out of all inherited plugins
1325
- }
1326
-
1327
- const repository =
1328
- (await detectGitRemoteUrl(process.cwd()).catch(() => undefined)) ??
1329
- parentConfig?.repository;
1330
-
1331
- if (!parentConfig) {
1332
- try {
1333
- parentConfig = await timePhase(
1334
- timings,
1335
- "parent config",
1336
- () => fetchParentConfig(extendsAccount, extendsGateway),
1337
- s,
1338
- );
1339
- } catch {
1340
- s.stop("Failed");
1341
- return {
1342
- status: "error" as const,
1343
- directory,
1344
- extendsRef,
1345
- account,
1346
- domain,
1347
- extends: extendsRef,
1348
- plugins,
1349
- overrides,
1350
- filesCopied: 0,
1351
- timings,
1352
- error: `No config found at ${extendsRef} — are you sure this is the right parent?`,
1353
- };
1354
- }
1355
- }
1356
-
1357
- const {
1358
- sourceDir,
1359
- parentConfig: resolvedParentConfig,
1360
- cleanup,
1361
- } = await timePhase(
1362
- timings,
1363
- "template source",
1364
- () =>
1365
- resolveSourceDir({
1366
- extendsAccount,
1367
- extendsGateway,
1368
- source: input.source,
1369
- }),
1370
- s,
1371
- );
1372
-
1373
- parentConfig = resolvedParentConfig;
1374
-
1375
- const isMinimalScaffold = sourceDir === "";
1376
-
1377
- try {
1378
- let filesCopied: number;
1379
-
1380
- if (isMinimalScaffold) {
1381
- filesCopied = await timePhase(
1382
- timings,
1383
- "scaffold project",
1384
- () =>
1385
- scaffoldMinimalProject(targetDir, parentConfig as unknown as BosConfigInput, {
1386
- extendsAccount,
1387
- extendsGateway,
1388
- account: account || extendsAccount,
1389
- domain,
1390
- plugins,
1391
- overrides,
1392
- repository,
1393
- title: parentConfig?.title,
1394
- description: parentConfig?.description,
1395
- }),
1396
- s,
1397
- );
1398
-
1399
- await timePhase(
1400
- timings,
1401
- "personalize config",
1402
- () =>
1403
- personalizeConfig(targetDir, {
1404
- extendsAccount,
1405
- extendsGateway,
1406
- account: account || extendsAccount,
1407
- domain: domain || extendsGateway,
1408
- plugins,
1409
- overrides,
1410
- mode: "init",
1411
- repository,
1412
- title: parentConfig?.title,
1413
- description: parentConfig?.description,
1414
- testnet: parentConfig?.testnet,
1415
- staging: parentConfig?.staging,
1416
- }),
1417
- s,
1418
- );
1419
- } else {
1420
- const patterns = buildInitPatterns(overrides, plugins);
1421
-
1422
- filesCopied = await timePhase(
1423
- timings,
1424
- "copy files",
1425
- () =>
1426
- copyFilteredFiles(sourceDir, targetDir, patterns, {
1427
- overrides,
1428
- plugins,
1429
- }),
1430
- s,
1431
- );
1432
-
1433
- await timePhase(
1434
- timings,
1435
- "personalize config",
1436
- () =>
1437
- personalizeConfig(targetDir, {
1438
- extendsAccount,
1439
- extendsGateway,
1440
- account: account || extendsAccount,
1441
- domain: domain || extendsGateway,
1442
- plugins,
1443
- overrides,
1444
- workspaceOpts: { sourceDir },
1445
- repository,
1446
- title: parentConfig?.title,
1447
- description: parentConfig?.description,
1448
- testnet: parentConfig?.testnet,
1449
- staging: parentConfig?.staging,
1450
- }),
1451
- s,
1452
- );
1453
-
1454
- await timePhase(
1455
- timings,
1456
- "write snapshot",
1457
- () =>
1458
- writeInitSnapshot(targetDir, extendsAccount, extendsGateway, sourceDir, patterns, {
1459
- overrides,
1460
- plugins,
1461
- }),
1462
- s,
1463
- );
1464
- }
1465
-
1466
- const lockfilePath = join(targetDir, "bun.lock");
1467
- const allowedWorkspaces = computeAllowedWorkspaces(overrides, plugins);
1468
- stripOrphanedWorkspacesFromLockfile(lockfilePath, allowedWorkspaces);
1469
- removeInitLockfile(lockfilePath);
1470
-
1471
- const initConfig = await timePhase(
1472
- timings,
1473
- "resolve config",
1474
- () => loadConfig({ cwd: targetDir }),
1475
- s,
1476
- );
1477
- if (initConfig?.runtime) {
1478
- await timePhase(
1479
- timings,
1480
- "generate env/docker",
1481
- async () => {
1482
- writeGeneratedInfra(targetDir, initConfig.runtime);
1483
- },
1484
- s,
1485
- );
1486
- }
1487
- await timePhase(
1488
- timings,
1489
- "create env file",
1490
- async () => {
1491
- ensureEnvFile(targetDir);
1492
- },
1493
- s,
1494
- );
1495
-
1496
- if (!input.noInstall) {
1497
- await timePhase(timings, "install dependencies", () =>
1498
- runBunInstall(targetDir, s ?? undefined),
1499
- );
1500
- await timePhase(timings, "generate types", () =>
1501
- runTypesGen(targetDir, s ?? undefined),
1502
- );
1503
- await timePhase(
1504
- timings,
1505
- "generate migrations",
1506
- () => generateDatabaseMigrations(targetDir),
1507
- s,
1508
- );
1509
- }
1510
-
1511
- if (input.noInstall && initConfig?.config) {
1512
- await timePhase(
1513
- timings,
1514
- "generate code artifacts",
1515
- () => generateCodeArtifacts(targetDir, initConfig.config),
1516
- s,
1517
- );
1518
- }
1519
-
1520
- s.stop("Project initialized");
1521
-
1522
- if (!input.noInteractive) {
1523
- const shouldStartDocker = await p.confirm({
1524
- message: "Run docker compose up -d --wait?",
1525
- initialValue: true,
1526
- });
1527
-
1528
- if (shouldStartDocker === true) {
1529
- const dockerSpinner = p.spinner();
1530
- dockerSpinner.start("Starting Docker services");
1531
- try {
1532
- await timePhase(timings, "docker compose up", () => runDockerComposeUp(targetDir));
1533
- dockerSpinner.stop("Docker services ready");
1534
- } catch (error) {
1535
- dockerSpinner.stop("Docker services not started");
1536
- p.log.warn(
1537
- `docker compose up -d --wait failed: ${error instanceof Error ? error.message : error}`,
1538
- );
1539
- }
1540
- }
1541
- }
1542
-
1543
- return {
1544
- status: "initialized" as const,
1545
- directory,
1546
- extendsRef,
1547
- account,
1548
- domain,
1549
- extends: extendsRef,
1550
- plugins,
1551
- overrides,
1552
- filesCopied,
1553
- timings,
1554
- };
1555
- } finally {
1556
- await cleanup();
1557
- }
1558
- } catch (error) {
1559
- const extendsRef = input.extends
1560
- ? input.extends.startsWith("bos://")
1561
- ? input.extends
1562
- : `bos://${input.extends}`
1563
- : "bos://dev.everything.near/everything.dev";
1564
- return {
1565
- status: "error" as const,
1566
- directory: input.directory ?? "",
1567
- extendsRef,
1568
- account: input.account,
1569
- domain: input.domain,
1570
- extends: extendsRef,
1571
- plugins: input.plugins ?? [],
1572
- overrides: input.overrides,
1573
- filesCopied: 0,
1574
- timings: [],
1575
- error: error instanceof Error ? error.message : "Unknown error",
1576
- };
1577
- }
1578
- }),
1579
-
1580
- sync: builder.sync.handler(async ({ input }) => {
1581
- try {
1582
- const configPath = findConfigPath();
1583
- if (!configPath) {
1584
- return {
1585
- status: "error" as const,
1586
- updated: [],
1587
- skipped: [],
1588
- added: [],
1589
- error: "No bos.config.json found in current directory",
1590
- };
1591
- }
1592
-
1593
- const projectDir = resolve(dirname(configPath));
1594
- const result = await syncTemplate(projectDir, input);
1595
-
1596
- if (result.status === "synced" || result.status === "dry-run") {
1597
- const syncedConfig = await loadConfig({ cwd: projectDir });
1598
- if (syncedConfig?.config) {
1599
- await generateCodeArtifacts(projectDir, syncedConfig.config);
1600
- }
1601
- }
1602
-
1603
- return result;
1604
- } catch (error) {
1605
- return {
1606
- status: "error" as const,
1607
- updated: [],
1608
- skipped: [],
1609
- added: [],
1610
- error: error instanceof Error ? error.message : "Unknown error",
1611
- };
1612
- }
1613
- }),
1614
-
1615
- upgrade: builder.upgrade.handler(async ({ input }) => {
1616
- try {
1617
- const configPath = findConfigPath();
1618
- if (!configPath) {
1619
- return {
1620
- status: "error" as const,
1621
- packages: [],
1622
- error: "No bos.config.json found in current directory",
1623
- };
1624
- }
1625
-
1626
- const projectDir = resolve(dirname(configPath));
1627
- return await upgradeTemplate(projectDir, input);
1628
- } catch (error) {
1629
- return {
1630
- status: "error" as const,
1631
- packages: [],
1632
- error: error instanceof Error ? error.message : "Unknown error",
1633
- };
1634
- }
1635
- }),
1636
-
1637
- typesGen: builder.typesGen.handler(async ({ input }) => {
1638
- try {
1639
- const configPath = findConfigPath();
1640
- if (!configPath) {
1641
- return {
1642
- status: "error" as const,
1643
- generated: [],
1644
- fetched: [],
1645
- skipped: [],
1646
- failed: [],
1647
- error: "No bos.config.json found in current directory",
1648
- };
1649
- }
1650
-
1651
- const projectDir = resolve(dirname(configPath));
1652
- const env =
1653
- input.env ?? (process.env.NODE_ENV === "production" ? "production" : "development");
1654
-
1655
- const refreshed = await loadConfig({ cwd: projectDir, env });
1656
- if (!refreshed) {
1657
- return {
1658
- status: "error" as const,
1659
- generated: [],
1660
- fetched: [],
1661
- skipped: [],
1662
- failed: [],
1663
- error: "Failed to load bos.config.json",
1664
- };
1665
- }
1666
-
1667
- if (input.dryRun) {
1668
- const pluginEntries = Object.entries(refreshed.runtime.plugins ?? {});
1669
- const fetched: string[] = [];
1670
- const skipped: string[] = [];
1671
-
1672
- if (refreshed.runtime.api.source !== "local") {
1673
- fetched.push(refreshed.runtime.api.url);
1674
- } else {
1675
- skipped.push("api (local)");
1676
- }
1677
-
1678
- if (refreshed.runtime.auth) {
1679
- if (refreshed.runtime.auth.source !== "local") {
1680
- fetched.push(refreshed.runtime.auth.url);
1681
- } else {
1682
- skipped.push("auth (local)");
1683
- }
1684
- }
1685
-
1686
- for (const [key, plugin] of pluginEntries) {
1687
- if (plugin.url && plugin.source !== "local") {
1688
- fetched.push(plugin.url);
1689
- } else if (plugin.localPath) {
1690
- skipped.push(`${key} (local)`);
1691
- }
1692
- }
1693
-
1694
- const generated = [
1695
- "ui/src/lib/api-types.gen.ts",
1696
- "ui/src/lib/auth-types.gen.ts",
1697
- "api/src/lib/plugins-types.gen.ts",
1698
- "api/src/lib/auth-types.gen.ts",
1699
- ];
1700
- if (existsSync(join(projectDir, "host", "src"))) {
1701
- generated.push("host/src/lib/auth-types.gen.ts");
1702
- }
1703
-
1704
- return {
1705
- status: "success" as const,
1706
- generated,
1707
- fetched,
1708
- skipped,
1709
- failed: [],
1710
- source: refreshed.runtime.api.source,
1711
- };
1712
- }
1713
-
1714
- await generateCodeArtifacts(projectDir, refreshed.config, {
1715
- runtimeConfig: refreshed.runtime,
1716
- });
1717
-
1718
- const generated = [
1719
- "ui/src/lib/plugin-sidebar.gen.ts",
1720
- "ui/src/lib/api-types.gen.ts",
1721
- "api/src/lib/plugins-types.gen.ts",
1722
- "api/src/lib/auth-types.gen.ts",
1723
- ];
1724
- if (
1725
- refreshed.runtime.auth &&
1726
- (refreshed.runtime.auth.source !== "local" || refreshed.runtime.auth.localPath)
1727
- ) {
1728
- generated.push("ui/src/lib/auth-types.gen.ts");
1729
- }
1730
- if (existsSync(join(projectDir, "host", "src"))) {
1731
- generated.push("host/src/lib/auth-types.gen.ts");
1732
- }
1733
-
1734
- return {
1735
- status: "success" as const,
1736
- generated,
1737
- fetched: refreshed.runtime.api.source === "remote" ? [refreshed.runtime.api.url] : [],
1738
- skipped: refreshed.runtime.api.source === "local" ? ["api (local)"] : [],
1739
- failed: [],
1740
- source: refreshed.runtime.api.source,
1741
- };
1742
- } catch (error) {
1743
- return {
1744
- status: "error" as const,
1745
- generated: [],
1746
- fetched: [],
1747
- skipped: [],
1748
- failed: [],
1749
- error: error instanceof Error ? error.message : "Unknown error",
1750
- };
1751
- }
1752
- }),
1753
-
1754
- status: builder.status.handler(async () => {
1755
- try {
1756
- const configPath = findConfigPath();
1757
- if (!configPath) {
1758
- return {
1759
- status: "error" as const,
1760
- packages: [],
1761
- envFile: "missing" as const,
1762
- error: "No bos.config.json found in current directory",
1763
- };
1764
- }
1765
-
1766
- const projectDir = resolve(dirname(configPath));
1767
- return await getStatus(projectDir);
1768
- } catch (error) {
1769
- return {
1770
- status: "error" as const,
1771
- packages: [],
1772
- envFile: "missing" as const,
1773
- error: error instanceof Error ? error.message : "Unknown error",
1774
- };
1775
- }
1776
- }),
1777
- }),
1778
- });
1779
-
1780
- function extractTransactionHash(error: unknown) {
1781
- const message = error instanceof Error ? error.message : String(error);
1782
- const match = message.match(/Transaction ID:\s*([A-Za-z0-9]+)/i);
1783
- return match?.[1];
1784
- }
1785
-
1786
- function computeAllowedWorkspaces(overrides: string[], plugins?: string[]): string[] {
1787
- const workspaces: string[] = [];
1788
- for (const section of overrides) {
1789
- if (section === "host") workspaces.push("host");
1790
- if (section === "ui") workspaces.push("ui");
1791
- if (section === "api") workspaces.push("api");
1792
- }
1793
- if (plugins) {
1794
- for (const plugin of plugins) {
1795
- workspaces.push(`plugins/${plugin}`);
1796
- }
1797
- }
1798
- return workspaces;
1799
- }