everything-dev 0.3.2 → 1.3.2
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/README.md +64 -0
- package/cli.js +10 -0
- package/dist/_virtual/_rolldown/runtime.cjs +29 -0
- package/dist/api-contract.cjs +172 -0
- package/dist/api-contract.cjs.map +1 -0
- package/dist/api-contract.mjs +171 -0
- package/dist/api-contract.mjs.map +1 -0
- package/dist/api.cjs +124 -0
- package/dist/api.cjs.map +1 -0
- package/dist/api.d.cts +36 -0
- package/dist/api.d.cts.map +1 -0
- package/dist/api.d.mts +36 -0
- package/dist/api.d.mts.map +1 -0
- package/dist/api.mjs +119 -0
- package/dist/api.mjs.map +1 -0
- package/dist/app.cjs +156 -0
- package/dist/app.cjs.map +1 -0
- package/dist/app.mjs +153 -0
- package/dist/app.mjs.map +1 -0
- package/dist/cli/catalog.cjs +30 -0
- package/dist/cli/catalog.cjs.map +1 -0
- package/dist/cli/catalog.mjs +29 -0
- package/dist/cli/catalog.mjs.map +1 -0
- package/dist/cli/help.cjs +16 -0
- package/dist/cli/help.cjs.map +1 -0
- package/dist/cli/help.mjs +16 -0
- package/dist/cli/help.mjs.map +1 -0
- package/dist/cli/init.cjs +317 -0
- package/dist/cli/init.cjs.map +1 -0
- package/dist/cli/init.d.cts +36 -0
- package/dist/cli/init.d.cts.map +1 -0
- package/dist/cli/init.d.mts +36 -0
- package/dist/cli/init.d.mts.map +1 -0
- package/dist/cli/init.mjs +309 -0
- package/dist/cli/init.mjs.map +1 -0
- package/dist/cli/parse.cjs +96 -0
- package/dist/cli/parse.cjs.map +1 -0
- package/dist/cli/parse.mjs +95 -0
- package/dist/cli/parse.mjs.map +1 -0
- package/dist/cli/prompts.cjs +42 -0
- package/dist/cli/prompts.cjs.map +1 -0
- package/dist/cli/prompts.mjs +41 -0
- package/dist/cli/prompts.mjs.map +1 -0
- package/dist/cli.cjs +167 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +166 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/components/dev-view.cjs +307 -0
- package/dist/components/dev-view.cjs.map +1 -0
- package/dist/components/dev-view.mjs +306 -0
- package/dist/components/dev-view.mjs.map +1 -0
- package/dist/components/streaming-view.cjs +146 -0
- package/dist/components/streaming-view.cjs.map +1 -0
- package/dist/components/streaming-view.mjs +144 -0
- package/dist/components/streaming-view.mjs.map +1 -0
- package/dist/config.cjs +280 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +35 -0
- package/dist/config.d.cts.map +1 -0
- package/dist/config.d.mts +35 -0
- package/dist/config.d.mts.map +1 -0
- package/dist/config.mjs +266 -0
- package/dist/config.mjs.map +1 -0
- package/dist/contract.cjs +209 -0
- package/dist/contract.cjs.map +1 -0
- package/dist/contract.d.cts +490 -0
- package/dist/contract.d.cts.map +1 -0
- package/dist/contract.d.mts +490 -0
- package/dist/contract.d.mts.map +1 -0
- package/dist/contract.meta.cjs +104 -0
- package/dist/contract.meta.cjs.map +1 -0
- package/dist/contract.meta.d.cts +141 -0
- package/dist/contract.meta.d.cts.map +1 -0
- package/dist/contract.meta.d.mts +141 -0
- package/dist/contract.meta.d.mts.map +1 -0
- package/dist/contract.meta.mjs +102 -0
- package/dist/contract.meta.mjs.map +1 -0
- package/dist/contract.mjs +186 -0
- package/dist/contract.mjs.map +1 -0
- package/dist/dev-logs.cjs +53 -0
- package/dist/dev-logs.cjs.map +1 -0
- package/dist/dev-logs.mjs +51 -0
- package/dist/dev-logs.mjs.map +1 -0
- package/dist/dev-session.cjs +195 -0
- package/dist/dev-session.cjs.map +1 -0
- package/dist/dev-session.mjs +194 -0
- package/dist/dev-session.mjs.map +1 -0
- package/dist/fastkv.cjs +89 -0
- package/dist/fastkv.cjs.map +1 -0
- package/dist/fastkv.d.cts +11 -0
- package/dist/fastkv.d.cts.map +1 -0
- package/dist/fastkv.d.mts +11 -0
- package/dist/fastkv.d.mts.map +1 -0
- package/dist/fastkv.mjs +82 -0
- package/dist/fastkv.mjs.map +1 -0
- package/dist/federation.server.cjs +27 -0
- package/dist/federation.server.cjs.map +1 -0
- package/dist/federation.server.mjs +27 -0
- package/dist/federation.server.mjs.map +1 -0
- package/dist/host.cjs +367 -0
- package/dist/host.cjs.map +1 -0
- package/dist/host.d.cts +22 -0
- package/dist/host.d.cts.map +1 -0
- package/dist/host.d.mts +22 -0
- package/dist/host.d.mts.map +1 -0
- package/dist/host.mjs +364 -0
- package/dist/host.mjs.map +1 -0
- package/dist/index.cjs +122 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +9 -0
- package/dist/integrity.cjs +39 -0
- package/dist/integrity.cjs.map +1 -0
- package/dist/integrity.d.cts +7 -0
- package/dist/integrity.d.cts.map +1 -0
- package/dist/integrity.d.mts +7 -0
- package/dist/integrity.d.mts.map +1 -0
- package/dist/integrity.mjs +35 -0
- package/dist/integrity.mjs.map +1 -0
- package/dist/mf.cjs +77 -0
- package/dist/mf.cjs.map +1 -0
- package/dist/mf.d.cts +19 -0
- package/dist/mf.d.cts.map +1 -0
- package/dist/mf.d.mts +19 -0
- package/dist/mf.d.mts.map +1 -0
- package/dist/mf.mjs +71 -0
- package/dist/mf.mjs.map +1 -0
- package/dist/near-cli.cjs +196 -0
- package/dist/near-cli.cjs.map +1 -0
- package/dist/near-cli.mjs +193 -0
- package/dist/near-cli.mjs.map +1 -0
- package/dist/network.cjs +9 -0
- package/dist/network.cjs.map +1 -0
- package/dist/network.mjs +8 -0
- package/dist/network.mjs.map +1 -0
- package/dist/orchestrator.cjs +441 -0
- package/dist/orchestrator.cjs.map +1 -0
- package/dist/orchestrator.d.cts +40 -0
- package/dist/orchestrator.d.cts.map +1 -0
- package/dist/orchestrator.d.mts +40 -0
- package/dist/orchestrator.d.mts.map +1 -0
- package/dist/orchestrator.mjs +436 -0
- package/dist/orchestrator.mjs.map +1 -0
- package/dist/plugin.cjs +825 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +347 -0
- package/dist/plugin.d.cts.map +1 -0
- package/dist/plugin.d.mts +348 -0
- package/dist/plugin.d.mts.map +1 -0
- package/dist/plugin.mjs +822 -0
- package/dist/plugin.mjs.map +1 -0
- package/dist/process-registry.cjs +120 -0
- package/dist/process-registry.cjs.map +1 -0
- package/dist/process-registry.d.cts +25 -0
- package/dist/process-registry.d.cts.map +1 -0
- package/dist/process-registry.d.mts +25 -0
- package/dist/process-registry.d.mts.map +1 -0
- package/dist/process-registry.mjs +119 -0
- package/dist/process-registry.mjs.map +1 -0
- package/dist/sdk.cjs +61 -0
- package/dist/sdk.d.cts +5 -0
- package/dist/sdk.d.mts +5 -0
- package/dist/sdk.mjs +6 -0
- package/dist/shared.cjs +143 -0
- package/dist/shared.cjs.map +1 -0
- package/dist/shared.d.cts +33 -0
- package/dist/shared.d.cts.map +1 -0
- package/dist/shared.d.mts +33 -0
- package/dist/shared.d.mts.map +1 -0
- package/dist/shared.mjs +140 -0
- package/dist/shared.mjs.map +1 -0
- package/dist/types.cjs +160 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +269 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +269 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +144 -0
- package/dist/types.mjs.map +1 -0
- package/dist/ui/head.cjs +67 -0
- package/dist/ui/head.cjs.map +1 -0
- package/dist/ui/head.d.cts +19 -0
- package/dist/ui/head.d.cts.map +1 -0
- package/dist/ui/head.d.mts +19 -0
- package/dist/ui/head.d.mts.map +1 -0
- package/dist/ui/head.mjs +61 -0
- package/dist/ui/head.mjs.map +1 -0
- package/dist/ui/index.cjs +32 -0
- package/dist/ui/index.d.cts +7 -0
- package/dist/ui/index.d.mts +7 -0
- package/dist/ui/index.mjs +6 -0
- package/dist/ui/metadata.cjs +106 -0
- package/dist/ui/metadata.cjs.map +1 -0
- package/dist/ui/metadata.d.cts +35 -0
- package/dist/ui/metadata.d.cts.map +1 -0
- package/dist/ui/metadata.d.mts +35 -0
- package/dist/ui/metadata.d.mts.map +1 -0
- package/dist/ui/metadata.mjs +100 -0
- package/dist/ui/metadata.mjs.map +1 -0
- package/dist/ui/router.cjs +56 -0
- package/dist/ui/router.cjs.map +1 -0
- package/dist/ui/router.d.cts +11 -0
- package/dist/ui/router.d.cts.map +1 -0
- package/dist/ui/router.d.mts +11 -0
- package/dist/ui/router.d.mts.map +1 -0
- package/dist/ui/router.mjs +51 -0
- package/dist/ui/router.mjs.map +1 -0
- package/dist/ui/runtime.cjs +65 -0
- package/dist/ui/runtime.cjs.map +1 -0
- package/dist/ui/runtime.d.cts +29 -0
- package/dist/ui/runtime.d.cts.map +1 -0
- package/dist/ui/runtime.d.mts +29 -0
- package/dist/ui/runtime.d.mts.map +1 -0
- package/dist/ui/runtime.mjs +53 -0
- package/dist/ui/runtime.mjs.map +1 -0
- package/dist/ui/types.cjs +0 -0
- package/dist/ui/types.d.cts +52 -0
- package/dist/ui/types.d.cts.map +1 -0
- package/dist/ui/types.d.mts +52 -0
- package/dist/ui/types.d.mts.map +1 -0
- package/dist/ui/types.mjs +1 -0
- package/dist/utils/banner.cjs +24 -0
- package/dist/utils/banner.cjs.map +1 -0
- package/dist/utils/banner.mjs +23 -0
- package/dist/utils/banner.mjs.map +1 -0
- package/dist/utils/linkify.cjs +15 -0
- package/dist/utils/linkify.cjs.map +1 -0
- package/dist/utils/linkify.mjs +14 -0
- package/dist/utils/linkify.mjs.map +1 -0
- package/dist/utils/run.cjs +40 -0
- package/dist/utils/run.cjs.map +1 -0
- package/dist/utils/run.mjs +39 -0
- package/dist/utils/run.mjs.map +1 -0
- package/dist/utils/theme.cjs +44 -0
- package/dist/utils/theme.cjs.map +1 -0
- package/dist/utils/theme.mjs +37 -0
- package/dist/utils/theme.mjs.map +1 -0
- package/package.json +269 -80
- package/src/api-contract.ts +309 -0
- package/src/api.ts +181 -0
- package/src/app.ts +346 -0
- package/src/cli/catalog.ts +49 -0
- package/src/cli/help.ts +13 -0
- package/src/cli/init.ts +415 -0
- package/src/cli/parse.ts +130 -0
- package/src/cli/prompts.ts +64 -0
- package/src/cli.ts +203 -1507
- package/src/components/dev-view.tsx +104 -41
- package/src/components/streaming-view.ts +89 -22
- package/src/config.ts +462 -532
- package/src/contract.meta.ts +96 -0
- package/src/contract.ts +164 -561
- package/src/dev-logs.ts +85 -0
- package/src/dev-session.ts +318 -0
- package/src/fastkv.ts +153 -0
- package/src/federation.server.ts +43 -0
- package/src/host.ts +526 -0
- package/src/index.ts +6 -3
- package/src/integrity.ts +54 -0
- package/src/mf.ts +105 -0
- package/src/near-cli.ts +284 -0
- package/src/network.ts +3 -0
- package/src/orchestrator.ts +648 -0
- package/src/plugin.ts +1116 -2303
- package/src/process-registry.ts +154 -0
- package/src/scripts/sync-api-contract.ts +24 -0
- package/src/sdk.ts +14 -0
- package/src/shared.ts +206 -0
- package/src/types.ts +152 -206
- package/src/ui/head.ts +34 -27
- package/src/ui/index.ts +3 -3
- package/src/ui/metadata.ts +95 -0
- package/src/ui/router.ts +22 -6
- package/src/ui/runtime.ts +55 -6
- package/src/ui/types.ts +24 -11
- package/src/utils/banner.ts +10 -6
- package/src/utils/run.ts +26 -27
- package/src/utils/theme.ts +3 -66
- package/src/components/monitor-view.tsx +0 -475
- package/src/components/status-view.tsx +0 -173
- package/src/lib/env.ts +0 -109
- package/src/lib/near-cli.ts +0 -289
- package/src/lib/nova.ts +0 -266
- package/src/lib/orchestrator.ts +0 -276
- package/src/lib/process-registry.ts +0 -166
- package/src/lib/process.ts +0 -549
- package/src/lib/resource-monitor/assertions.ts +0 -234
- package/src/lib/resource-monitor/command.ts +0 -283
- package/src/lib/resource-monitor/diff.ts +0 -157
- package/src/lib/resource-monitor/errors.ts +0 -127
- package/src/lib/resource-monitor/index.ts +0 -305
- package/src/lib/resource-monitor/platform/darwin.ts +0 -306
- package/src/lib/resource-monitor/platform/index.ts +0 -35
- package/src/lib/resource-monitor/platform/linux.ts +0 -332
- package/src/lib/resource-monitor/platform/windows.ts +0 -298
- package/src/lib/resource-monitor/snapshot.ts +0 -217
- package/src/lib/resource-monitor/types.ts +0 -74
- package/src/lib/session-recorder/errors.ts +0 -102
- package/src/lib/session-recorder/flows/login.ts +0 -210
- package/src/lib/session-recorder/index.ts +0 -361
- package/src/lib/session-recorder/playwright.ts +0 -257
- package/src/lib/session-recorder/report.ts +0 -353
- package/src/lib/session-recorder/server.ts +0 -267
- package/src/lib/session-recorder/types.ts +0 -115
- package/src/lib/sync.ts +0 -1
- package/src/ui/files.ts +0 -134
package/src/plugin.ts
CHANGED
|
@@ -1,2350 +1,1163 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import { runMonitorCli } from "./components/monitor-view";
|
|
7
|
-
import {
|
|
8
|
-
type AppConfig,
|
|
9
|
-
type BosConfig as BosConfigType,
|
|
10
|
-
DEFAULT_DEV_CONFIG,
|
|
11
|
-
getProjectRoot,
|
|
12
|
-
loadConfig,
|
|
13
|
-
parsePort,
|
|
14
|
-
type RemoteConfig,
|
|
15
|
-
resolvePackages,
|
|
16
|
-
type SourceMode,
|
|
17
|
-
} from "./config";
|
|
18
|
-
import { bosContract } from "./contract";
|
|
19
|
-
import {
|
|
20
|
-
getBuildEnv,
|
|
21
|
-
hasZephyrConfig,
|
|
22
|
-
loadBosEnv,
|
|
23
|
-
ZEPHYR_DOCS_URL,
|
|
24
|
-
} from "./lib/env";
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, join, resolve } from "node:path";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { syncApiContractBridge } from "./api-contract";
|
|
5
|
+
import { buildRuntimeConfig, detectLocalPackages, prepareDevelopmentRuntimeConfig } from "./app";
|
|
25
6
|
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
7
|
+
copyFilteredFiles,
|
|
8
|
+
personalizeConfig,
|
|
9
|
+
readTemplatekeep,
|
|
10
|
+
resolveSourceDir,
|
|
11
|
+
runBunInstall,
|
|
12
|
+
} from "./cli/init";
|
|
13
|
+
import { promptInitOptions } from "./cli/prompts";
|
|
30
14
|
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
uploadSecrets,
|
|
40
|
-
verifyNovaCredentials,
|
|
41
|
-
} from "./lib/nova";
|
|
42
|
-
import { type AppOrchestrator, startApp } from "./lib/orchestrator";
|
|
43
|
-
import { createProcessRegistry } from "./lib/process-registry";
|
|
15
|
+
buildRuntimePluginsForConfig,
|
|
16
|
+
getHostDevelopmentPort,
|
|
17
|
+
getProjectRoot,
|
|
18
|
+
loadConfig,
|
|
19
|
+
parsePort,
|
|
20
|
+
resolveDevelopmentHostUrl,
|
|
21
|
+
resolveLocalDevelopmentPath,
|
|
22
|
+
} from "./config";
|
|
44
23
|
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
24
|
+
type BosConfigResult,
|
|
25
|
+
type BuildOptions,
|
|
26
|
+
bosContract,
|
|
27
|
+
type DevOptions,
|
|
28
|
+
type InitOptions,
|
|
29
|
+
type KeyPublishOptions,
|
|
30
|
+
type PluginAddOptions,
|
|
31
|
+
type PluginListResult,
|
|
32
|
+
type PluginPublishOptions,
|
|
33
|
+
type PluginRemoveOptions,
|
|
34
|
+
type PublishOptions,
|
|
35
|
+
type StartOptions,
|
|
36
|
+
} from "./contract";
|
|
37
|
+
import { type AppConfig, type AppOrchestrator, startApp } from "./dev-session";
|
|
49
38
|
import {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} from "./
|
|
56
|
-
import {
|
|
39
|
+
buildRegistryConfigUrlForNetwork,
|
|
40
|
+
fetchBosConfigFromFastKv,
|
|
41
|
+
getRegistryNamespaceForAccount,
|
|
42
|
+
getRegistryNamespaceForNetwork,
|
|
43
|
+
} from "./fastkv";
|
|
44
|
+
import { addFunctionCallAccessKey, ensureNearCli, executeTransaction } from "./near-cli";
|
|
45
|
+
import { getNetworkIdForAccount } from "./network";
|
|
46
|
+
import { createPlugin, z } from "./sdk";
|
|
47
|
+
import { syncAndGenerateSharedUi } from "./shared";
|
|
48
|
+
import type { BosConfig, RuntimeConfig, SourceMode } from "./types";
|
|
57
49
|
import { run } from "./utils/run";
|
|
58
|
-
import { colors, icons } from "./utils/theme";
|
|
59
50
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
51
|
+
const DEFAULT_DEV_CONFIG: AppConfig = {
|
|
52
|
+
host: "local",
|
|
53
|
+
ui: "local",
|
|
54
|
+
api: "local",
|
|
55
|
+
ssr: false,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const buildCommands: Record<string, { cmd: string; args: string[] }> = {
|
|
59
|
+
host: { cmd: "bun", args: ["run", "build"] },
|
|
60
|
+
ui: { cmd: "bun", args: ["run", "build"] },
|
|
61
|
+
api: { cmd: "bun", args: ["run", "build"] },
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const PUBLISH_FUNCTION_NAMES = ["__fastdata_kv"];
|
|
65
|
+
|
|
66
|
+
type BosDeps = {
|
|
67
|
+
bosConfig: BosConfig | null;
|
|
68
|
+
runtimeConfig: RuntimeConfig | null;
|
|
69
|
+
configDir: string;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type PluginAttachmentConfig = NonNullable<BosConfig["plugins"]>[string];
|
|
73
|
+
|
|
74
|
+
function parseSourceMode(value: string | undefined, defaultValue: SourceMode): SourceMode {
|
|
75
|
+
if (value === "local" || value === "remote") return value;
|
|
76
|
+
return defaultValue;
|
|
64
77
|
}
|
|
65
78
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
function buildAppConfig(options: {
|
|
80
|
+
host?: string;
|
|
81
|
+
ui?: string;
|
|
82
|
+
api?: string;
|
|
83
|
+
proxy?: boolean;
|
|
84
|
+
ssr?: boolean;
|
|
85
|
+
}): AppConfig {
|
|
86
|
+
return {
|
|
87
|
+
host: parseSourceMode(options.host, DEFAULT_DEV_CONFIG.host),
|
|
88
|
+
ui: parseSourceMode(options.ui, DEFAULT_DEV_CONFIG.ui),
|
|
89
|
+
api: parseSourceMode(options.api, DEFAULT_DEV_CONFIG.api),
|
|
90
|
+
proxy: options.proxy,
|
|
91
|
+
ssr: options.ssr ?? DEFAULT_DEV_CONFIG.ssr,
|
|
92
|
+
};
|
|
78
93
|
}
|
|
79
94
|
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
return config.account;
|
|
95
|
+
function buildDescription(config: AppConfig): string {
|
|
96
|
+
if (config.host === "local" && config.ui === "local" && config.api === "local" && !config.proxy) {
|
|
97
|
+
return "Full Local Development";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const parts: string[] = [];
|
|
101
|
+
parts.push(config.host === "remote" ? "Remote Host" : "Local Host");
|
|
102
|
+
if (config.ui === "remote") parts.push("Remote UI");
|
|
103
|
+
if (config.proxy) parts.push("Proxy API → Production");
|
|
104
|
+
else if (config.api === "remote") parts.push("Remote API");
|
|
105
|
+
return parts.join(" + ");
|
|
93
106
|
}
|
|
94
107
|
|
|
95
|
-
function
|
|
96
|
-
|
|
108
|
+
function buildConfigResult(bosConfig: BosConfig | null): BosConfigResult {
|
|
109
|
+
const packages = bosConfig ? Object.keys(bosConfig.app) : [];
|
|
110
|
+
const remotes = packages.filter((name) => name !== "host");
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
config: bosConfig,
|
|
114
|
+
packages,
|
|
115
|
+
remotes,
|
|
116
|
+
};
|
|
97
117
|
}
|
|
98
118
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
119
|
+
type WorkspaceTarget = {
|
|
120
|
+
key: string;
|
|
121
|
+
kind: "app" | "plugin";
|
|
122
|
+
path: string;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
function resolveWorkspaceTarget(
|
|
126
|
+
key: string,
|
|
127
|
+
bosConfig: BosConfig | null,
|
|
128
|
+
runtimeConfig: RuntimeConfig | null,
|
|
129
|
+
configDir: string,
|
|
130
|
+
): WorkspaceTarget | null {
|
|
131
|
+
if (bosConfig?.app && key in bosConfig.app) {
|
|
132
|
+
return {
|
|
133
|
+
key,
|
|
134
|
+
kind: "app",
|
|
135
|
+
path: `${configDir}/${key}`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const runtimePlugin = runtimeConfig?.plugins?.[key];
|
|
140
|
+
const pluginPath =
|
|
141
|
+
runtimePlugin?.localPath ??
|
|
142
|
+
resolveLocalDevelopmentPath(bosConfig?.plugins?.[key]?.development, configDir);
|
|
143
|
+
if (pluginPath) {
|
|
144
|
+
return {
|
|
145
|
+
key,
|
|
146
|
+
kind: "plugin",
|
|
147
|
+
path: pluginPath,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return null;
|
|
106
152
|
}
|
|
107
153
|
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
):
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
154
|
+
function determineProcesses(
|
|
155
|
+
config: AppConfig,
|
|
156
|
+
localPackages: string[],
|
|
157
|
+
runtimeConfig?: RuntimeConfig | null,
|
|
158
|
+
): string[] {
|
|
159
|
+
const processes: string[] = [];
|
|
160
|
+
if (config.ssr && config.ui === "local") processes.push("ui-ssr");
|
|
161
|
+
if (config.ui === "local") processes.push("ui");
|
|
162
|
+
if (config.api === "local" && !config.proxy) processes.push("api");
|
|
163
|
+
for (const pkg of localPackages) {
|
|
164
|
+
if (pkg.startsWith("plugin:")) {
|
|
165
|
+
const pluginId = pkg.slice("plugin:".length);
|
|
166
|
+
if (runtimeConfig?.plugins?.[pluginId]?.source === "local") {
|
|
167
|
+
processes.push(pkg);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
processes.push("host");
|
|
172
|
+
return processes;
|
|
126
173
|
}
|
|
127
174
|
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
175
|
+
function isValidProxyUrl(url: string): boolean {
|
|
176
|
+
try {
|
|
177
|
+
const parsed = new URL(url);
|
|
178
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
134
182
|
}
|
|
135
183
|
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
host: parseSourceMode(options.host, DEFAULT_DEV_CONFIG.host),
|
|
144
|
-
ui: parseSourceMode(options.ui, DEFAULT_DEV_CONFIG.ui),
|
|
145
|
-
api: parseSourceMode(options.api, DEFAULT_DEV_CONFIG.api),
|
|
146
|
-
proxy: options.proxy,
|
|
147
|
-
};
|
|
184
|
+
function resolveProxyUrl(bosConfig: BosConfig | null): string | null {
|
|
185
|
+
if (!bosConfig) return null;
|
|
186
|
+
const apiConfig = bosConfig.app.api;
|
|
187
|
+
if (!apiConfig) return null;
|
|
188
|
+
if (apiConfig.proxy && isValidProxyUrl(apiConfig.proxy)) return apiConfig.proxy;
|
|
189
|
+
if (apiConfig.production && isValidProxyUrl(apiConfig.production)) return apiConfig.production;
|
|
190
|
+
return null;
|
|
148
191
|
}
|
|
149
192
|
|
|
150
|
-
function
|
|
151
|
-
|
|
193
|
+
function sanitizePluginKey(value: string): string {
|
|
194
|
+
return value
|
|
195
|
+
.replace(/[^A-Za-z0-9/_-]/g, "-")
|
|
196
|
+
.replace(/\/+/g, "/")
|
|
197
|
+
.split("/")
|
|
198
|
+
.filter(Boolean)
|
|
199
|
+
.map((segment) => segment.replace(/[^A-Za-z0-9_-]/g, "-"))
|
|
200
|
+
.join("/")
|
|
201
|
+
.replace(/^\/+|\/+$/g, "");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function defaultPluginKey(source: string): string {
|
|
205
|
+
const normalized = source.replace(/^local:/, "").replace(/\/$/, "");
|
|
206
|
+
if (source.startsWith("local:")) {
|
|
207
|
+
return sanitizePluginKey(basename(normalized)) || "plugin";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const url = new URL(source);
|
|
212
|
+
return sanitizePluginKey(basename(url.pathname) || url.hostname) || "plugin";
|
|
213
|
+
} catch {
|
|
214
|
+
return sanitizePluginKey(source) || "plugin";
|
|
215
|
+
}
|
|
216
|
+
}
|
|
152
217
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
) {
|
|
159
|
-
return "Full Local Development";
|
|
160
|
-
}
|
|
218
|
+
function pluginLocalPath(configDir: string, attachment: PluginAttachmentConfig): string | null {
|
|
219
|
+
const source = attachment.development ?? attachment.production;
|
|
220
|
+
if (!source?.startsWith("local:")) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
161
223
|
|
|
162
|
-
|
|
163
|
-
|
|
224
|
+
return join(configDir, source.slice("local:".length));
|
|
225
|
+
}
|
|
164
226
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
227
|
+
async function saveBosConfig(configDir: string, config: BosConfig): Promise<void> {
|
|
228
|
+
const filePath = join(configDir, "bos.config.json");
|
|
229
|
+
const next = `${JSON.stringify(config, null, 2)}\n`;
|
|
230
|
+
try {
|
|
231
|
+
if (readFileSync(filePath, "utf8") === next) return;
|
|
232
|
+
} catch {
|
|
233
|
+
// file does not exist yet
|
|
234
|
+
}
|
|
168
235
|
|
|
169
|
-
|
|
236
|
+
writeFileSync(filePath, next);
|
|
170
237
|
}
|
|
171
238
|
|
|
172
|
-
function
|
|
173
|
-
|
|
239
|
+
function listPluginAttachments(config: BosConfig | null) {
|
|
240
|
+
return (Object.entries(config?.plugins ?? {}) as Array<[string, PluginAttachmentConfig]>)
|
|
241
|
+
.map(([key, attachment]) => ({
|
|
242
|
+
key,
|
|
243
|
+
development: attachment.development,
|
|
244
|
+
production: attachment.production,
|
|
245
|
+
localPath: attachment.development?.startsWith("local:")
|
|
246
|
+
? attachment.development.slice("local:".length)
|
|
247
|
+
: undefined,
|
|
248
|
+
source: attachment.development?.startsWith("local:")
|
|
249
|
+
? ("local" as const)
|
|
250
|
+
: ("remote" as const),
|
|
251
|
+
}))
|
|
252
|
+
.sort((a, b) => a.key.localeCompare(b.key));
|
|
253
|
+
}
|
|
174
254
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
255
|
+
async function refreshApiContractBridge(configDir: string): Promise<void> {
|
|
256
|
+
const refreshed = await loadConfig({ cwd: configDir, env: "development" });
|
|
257
|
+
if (!refreshed) return;
|
|
179
258
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
259
|
+
await syncApiContractBridge({
|
|
260
|
+
configDir,
|
|
261
|
+
runtimeConfig: refreshed.runtime,
|
|
262
|
+
apiBaseUrl: refreshed.runtime.api.url,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
183
265
|
|
|
184
|
-
|
|
266
|
+
function extractPublishedUrl(output: string): string | null {
|
|
267
|
+
const match = output.match(/https?:\/\/[^\s"'<>]+/g);
|
|
268
|
+
if (!match || match.length === 0) return null;
|
|
269
|
+
return match[match.length - 1] ?? null;
|
|
270
|
+
}
|
|
185
271
|
|
|
186
|
-
|
|
272
|
+
async function buildEnvVars(
|
|
273
|
+
config: AppConfig,
|
|
274
|
+
bosConfig?: BosConfig | null,
|
|
275
|
+
): Promise<Record<string, string>> {
|
|
276
|
+
const env: Record<string, string> = {
|
|
277
|
+
HOST_SOURCE: config.host,
|
|
278
|
+
UI_SOURCE: config.ui,
|
|
279
|
+
API_SOURCE: config.api,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
if (config.host === "remote") {
|
|
283
|
+
const remoteUrl = bosConfig?.app.host.production;
|
|
284
|
+
if (remoteUrl) env.HOST_REMOTE_URL = remoteUrl;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (config.ui === "remote") {
|
|
288
|
+
const remoteUrl = bosConfig?.app.ui.production;
|
|
289
|
+
if (remoteUrl) env.UI_REMOTE_URL = remoteUrl;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (config.api === "remote") {
|
|
293
|
+
const remoteUrl = bosConfig?.app.api.production;
|
|
294
|
+
if (remoteUrl) env.API_REMOTE_URL = remoteUrl;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (config.proxy && bosConfig) {
|
|
298
|
+
const proxyUrl = resolveProxyUrl(bosConfig);
|
|
299
|
+
if (proxyUrl) env.API_PROXY = proxyUrl;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return env;
|
|
187
303
|
}
|
|
188
304
|
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
305
|
+
async function buildEveryPluginQuietly(cwd: string) {
|
|
306
|
+
const distPath = `${cwd}/packages/every-plugin/dist/build/rspack/plugin.mjs`;
|
|
307
|
+
const distExists = await Bun.file(distPath).exists();
|
|
308
|
+
|
|
309
|
+
if (distExists) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const result = (await run("bun", ["run", "--cwd", "packages/every-plugin", "build"], {
|
|
314
|
+
cwd,
|
|
315
|
+
capture: true,
|
|
316
|
+
})) as { stdout: string; stderr: string; exitCode: number };
|
|
317
|
+
|
|
318
|
+
if (result.exitCode === 0) {
|
|
319
|
+
console.log("[build:ssr] build succeeded");
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (result.stdout.trim()) {
|
|
324
|
+
process.stdout.write(result.stdout);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (result.stderr.trim()) {
|
|
328
|
+
process.stderr.write(result.stderr);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
throw new Error(
|
|
332
|
+
`bun run --cwd packages/every-plugin build failed with exit code ${result.exitCode}`,
|
|
333
|
+
);
|
|
196
334
|
}
|
|
197
335
|
|
|
198
|
-
function
|
|
199
|
-
|
|
336
|
+
async function buildEverythingDevQuietly(cwd: string) {
|
|
337
|
+
const distPath = `${cwd}/packages/everything-dev/dist/index.mjs`;
|
|
338
|
+
const distExists = await Bun.file(distPath).exists();
|
|
339
|
+
|
|
340
|
+
if (distExists) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
200
343
|
|
|
201
|
-
|
|
202
|
-
|
|
344
|
+
const result = (await run("bun", ["run", "--cwd", "packages/everything-dev", "build"], {
|
|
345
|
+
cwd,
|
|
346
|
+
capture: true,
|
|
347
|
+
})) as { stdout: string; stderr: string; exitCode: number };
|
|
203
348
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
349
|
+
if (result.exitCode === 0) {
|
|
350
|
+
console.log("[everything-dev] build succeeded");
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
207
353
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
354
|
+
if (result.stdout.trim()) {
|
|
355
|
+
process.stdout.write(result.stdout);
|
|
356
|
+
}
|
|
211
357
|
|
|
212
|
-
|
|
358
|
+
if (result.stderr.trim()) {
|
|
359
|
+
process.stderr.write(result.stderr);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
throw new Error(
|
|
363
|
+
`bun run --cwd packages/everything-dev build failed with exit code ${result.exitCode}`,
|
|
364
|
+
);
|
|
213
365
|
}
|
|
214
366
|
|
|
215
|
-
async function
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
): Promise<
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (config.host === "remote") {
|
|
226
|
-
const remoteUrl = bosConfig?.app.host.production;
|
|
227
|
-
if (remoteUrl) {
|
|
228
|
-
env.HOST_REMOTE_URL = remoteUrl;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (config.proxy && bosConfig) {
|
|
233
|
-
const proxyUrl = resolveProxyUrl(bosConfig);
|
|
234
|
-
if (proxyUrl) {
|
|
235
|
-
env.API_PROXY = proxyUrl;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return env;
|
|
367
|
+
async function fetchPublishedConfig(
|
|
368
|
+
accountId: string,
|
|
369
|
+
gatewayId: string,
|
|
370
|
+
): Promise<BosConfig | null> {
|
|
371
|
+
try {
|
|
372
|
+
return await fetchBosConfigFromFastKv<BosConfig>(`bos://${accountId}/${gatewayId}`);
|
|
373
|
+
} catch {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
240
376
|
}
|
|
241
377
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
378
|
+
function selectWorkspaceTargets(packages: string, bosConfig: BosConfig | null): string[] {
|
|
379
|
+
const allPackages = [
|
|
380
|
+
...Object.keys(bosConfig?.app ?? {}),
|
|
381
|
+
...Object.keys(bosConfig?.plugins ?? {}),
|
|
382
|
+
];
|
|
383
|
+
if (packages === "all") {
|
|
384
|
+
return allPackages;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return packages
|
|
388
|
+
.split(",")
|
|
389
|
+
.map((pkg) => pkg.trim())
|
|
390
|
+
.filter((pkg) => allPackages.includes(pkg));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function buildWorkspaceTargets(opts: {
|
|
394
|
+
configDir: string;
|
|
395
|
+
bosConfig: BosConfig | null;
|
|
396
|
+
runtimeConfig: RuntimeConfig | null;
|
|
397
|
+
targets: string[];
|
|
398
|
+
deploy: boolean;
|
|
399
|
+
}): Promise<{ built: string[]; skipped: string[] }> {
|
|
400
|
+
const existing: WorkspaceTarget[] = [];
|
|
401
|
+
const skipped: string[] = [];
|
|
402
|
+
|
|
403
|
+
for (const target of opts.targets) {
|
|
404
|
+
const resolved = resolveWorkspaceTarget(
|
|
405
|
+
target,
|
|
406
|
+
opts.bosConfig,
|
|
407
|
+
opts.runtimeConfig,
|
|
408
|
+
opts.configDir,
|
|
409
|
+
);
|
|
410
|
+
if (!resolved) {
|
|
411
|
+
skipped.push(target);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const exists = await Bun.file(`${resolved.path}/package.json`).exists();
|
|
416
|
+
if (exists) existing.push(resolved);
|
|
417
|
+
else skipped.push(target);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (existing.length === 0) {
|
|
421
|
+
return { built: [], skipped };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const sharedSync = await syncAndGenerateSharedUi({
|
|
425
|
+
configDir: opts.configDir,
|
|
426
|
+
hostMode: "local",
|
|
427
|
+
bosConfig: opts.bosConfig ?? undefined,
|
|
428
|
+
});
|
|
429
|
+
if (sharedSync.catalogChanged) {
|
|
430
|
+
await run("bun", ["install"], { cwd: opts.configDir });
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (existing.some((entry) => entry.key === "api")) {
|
|
434
|
+
await buildEveryPluginQuietly(opts.configDir);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
await buildEverythingDevQuietly(opts.configDir);
|
|
438
|
+
|
|
439
|
+
const env: Record<string, string> = {
|
|
440
|
+
...process.env,
|
|
441
|
+
NODE_ENV: opts.deploy ? "production" : "development",
|
|
442
|
+
};
|
|
443
|
+
if (opts.deploy) {
|
|
444
|
+
env.DEPLOY = "true";
|
|
445
|
+
} else {
|
|
446
|
+
delete env.DEPLOY;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const orderedExisting = opts.deploy
|
|
450
|
+
? [
|
|
451
|
+
...existing.filter((entry) => entry.kind === "app" && entry.key !== "host"),
|
|
452
|
+
...existing.filter((entry) => entry.kind === "plugin"),
|
|
453
|
+
...existing.filter((entry) => entry.kind === "app" && entry.key === "host"),
|
|
454
|
+
]
|
|
455
|
+
: existing;
|
|
456
|
+
const built: string[] = [];
|
|
457
|
+
|
|
458
|
+
for (const resolved of orderedExisting) {
|
|
459
|
+
const pkgJson = JSON.parse(await Bun.file(`${resolved.path}/package.json`).text()) as {
|
|
460
|
+
scripts?: Record<string, string>;
|
|
461
|
+
};
|
|
462
|
+
const shouldDeployScript = opts.deploy && pkgJson.scripts?.deploy;
|
|
463
|
+
const buildConfig = shouldDeployScript
|
|
464
|
+
? { cmd: "bun", args: ["run", "deploy"] }
|
|
465
|
+
: (buildCommands[resolved.key] ?? { cmd: "bun", args: ["run", "build"] });
|
|
466
|
+
|
|
467
|
+
await run(buildConfig.cmd, buildConfig.args, {
|
|
468
|
+
cwd: resolved.path,
|
|
469
|
+
env,
|
|
470
|
+
});
|
|
471
|
+
built.push(resolved.key);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return { built, skipped };
|
|
475
|
+
}
|
|
247
476
|
|
|
248
477
|
export default createPlugin({
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
// Clone template using sparse checkout
|
|
927
|
-
await cloneTemplate(gitTemplate, destDir, packagesToClone);
|
|
928
|
-
|
|
929
|
-
// Remove excluded files/directories
|
|
930
|
-
const excludedPaths = [
|
|
931
|
-
"database.db",
|
|
932
|
-
".bos",
|
|
933
|
-
".turbo",
|
|
934
|
-
".env",
|
|
935
|
-
".env.bos",
|
|
936
|
-
".env.bos.example",
|
|
937
|
-
];
|
|
938
|
-
|
|
939
|
-
// Add host/gateway to exclusions if not requested
|
|
940
|
-
if (!includeHost) excludedPaths.push("host");
|
|
941
|
-
if (!includeGateway) excludedPaths.push("gateway");
|
|
942
|
-
|
|
943
|
-
for (const excluded of excludedPaths) {
|
|
944
|
-
try {
|
|
945
|
-
await rm(join(destDir, excluded), { recursive: true, force: true });
|
|
946
|
-
} catch {
|
|
947
|
-
// Ignore errors
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
// Generate bos.config.json
|
|
952
|
-
const bosConfig: Record<string, unknown> = {
|
|
953
|
-
extends: templateUrl,
|
|
954
|
-
account: account,
|
|
955
|
-
app: {
|
|
956
|
-
ui: {
|
|
957
|
-
name: "ui",
|
|
958
|
-
development: "http://localhost:3002",
|
|
959
|
-
},
|
|
960
|
-
api: {
|
|
961
|
-
name: "api",
|
|
962
|
-
development: "http://localhost:3014",
|
|
963
|
-
variables: {},
|
|
964
|
-
secrets: [],
|
|
965
|
-
},
|
|
966
|
-
},
|
|
967
|
-
};
|
|
968
|
-
|
|
969
|
-
// Only add testnet if provided
|
|
970
|
-
if (testnet) {
|
|
971
|
-
bosConfig.testnet = testnet;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
await writeFile(
|
|
975
|
-
join(destDir, "bos.config.json"),
|
|
976
|
-
JSON.stringify(bosConfig, null, 2),
|
|
977
|
-
);
|
|
978
|
-
|
|
979
|
-
// Generate .env.example with secrets from parent config
|
|
980
|
-
const envExample = await generateEnvExample(parentConfig);
|
|
981
|
-
await writeFile(join(destDir, ".env.example"), envExample);
|
|
982
|
-
|
|
983
|
-
// Modify package.json to update workspaces
|
|
984
|
-
try {
|
|
985
|
-
const pkgJsonPath = join(destDir, "package.json");
|
|
986
|
-
const pkgJsonContent = await Bun.file(pkgJsonPath).text();
|
|
987
|
-
const pkgJson = JSON.parse(pkgJsonContent);
|
|
988
|
-
|
|
989
|
-
// Update workspaces to only include cloned packages
|
|
990
|
-
pkgJson.workspaces.packages = packagesToClone;
|
|
991
|
-
|
|
992
|
-
// Remove host/gateway scripts if not included
|
|
993
|
-
if (!includeHost) {
|
|
994
|
-
delete pkgJson.scripts["dev:host"];
|
|
995
|
-
delete pkgJson.scripts["build:host"];
|
|
996
|
-
}
|
|
997
|
-
if (!includeGateway) {
|
|
998
|
-
delete pkgJson.scripts["docker:build"];
|
|
999
|
-
delete pkgJson.scripts["docker:run"];
|
|
1000
|
-
delete pkgJson.scripts["docker:stop"];
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
1004
|
-
} catch {
|
|
1005
|
-
// Package.json might not exist, skip
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
// Run sync to update dependencies
|
|
1009
|
-
try {
|
|
1010
|
-
const { syncFiles } = await import("./lib/sync");
|
|
1011
|
-
const catalog = parentConfig.shared?.ui || {};
|
|
1012
|
-
await syncFiles({
|
|
1013
|
-
configDir: destDir,
|
|
1014
|
-
packages: packagesToClone,
|
|
1015
|
-
bosConfig: parentConfig,
|
|
1016
|
-
catalog,
|
|
1017
|
-
});
|
|
1018
|
-
} catch {
|
|
1019
|
-
// Sync might fail, continue anyway
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
return {
|
|
1023
|
-
status: "created" as const,
|
|
1024
|
-
path: projectName,
|
|
1025
|
-
};
|
|
1026
|
-
} catch (error) {
|
|
1027
|
-
// Cleanup on error
|
|
1028
|
-
try {
|
|
1029
|
-
await rm(destDir, { recursive: true, force: true });
|
|
1030
|
-
} catch {
|
|
1031
|
-
// Ignore cleanup errors
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
return {
|
|
1035
|
-
status: "error" as const,
|
|
1036
|
-
path: projectName,
|
|
1037
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
}),
|
|
1041
|
-
|
|
1042
|
-
info: builder.info.handler(async () => {
|
|
1043
|
-
const config = deps.bosConfig;
|
|
1044
|
-
const packages = config ? Object.keys(config.app) : [];
|
|
1045
|
-
const remotes = packages.filter((k) => k !== "host");
|
|
1046
|
-
|
|
1047
|
-
return {
|
|
1048
|
-
config: config as any,
|
|
1049
|
-
packages,
|
|
1050
|
-
remotes,
|
|
1051
|
-
};
|
|
1052
|
-
}),
|
|
1053
|
-
|
|
1054
|
-
status: builder.status.handler(async (input) => {
|
|
1055
|
-
const config = deps.bosConfig;
|
|
1056
|
-
|
|
1057
|
-
if (!config) {
|
|
1058
|
-
return { endpoints: [] };
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
const host = config.app.host;
|
|
1062
|
-
const remotes = Object.keys(config.app).filter((k) => k !== "host");
|
|
1063
|
-
const env = input.env;
|
|
1064
|
-
|
|
1065
|
-
interface Endpoint {
|
|
1066
|
-
name: string;
|
|
1067
|
-
url: string;
|
|
1068
|
-
type: "host" | "remote" | "ssr";
|
|
1069
|
-
healthy: boolean;
|
|
1070
|
-
latency?: number;
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
const endpoints: Endpoint[] = [];
|
|
1074
|
-
|
|
1075
|
-
const checkHealth = async (
|
|
1076
|
-
url: string,
|
|
1077
|
-
): Promise<{ healthy: boolean; latency?: number }> => {
|
|
1078
|
-
const start = Date.now();
|
|
1079
|
-
try {
|
|
1080
|
-
const response = await fetch(url, { method: "HEAD" });
|
|
1081
|
-
return {
|
|
1082
|
-
healthy: response.ok,
|
|
1083
|
-
latency: Date.now() - start,
|
|
1084
|
-
};
|
|
1085
|
-
} catch {
|
|
1086
|
-
return { healthy: false };
|
|
1087
|
-
}
|
|
1088
|
-
};
|
|
1089
|
-
|
|
1090
|
-
const hostHealth = await checkHealth(host[env]);
|
|
1091
|
-
endpoints.push({
|
|
1092
|
-
name: "host",
|
|
1093
|
-
url: host[env],
|
|
1094
|
-
type: "host",
|
|
1095
|
-
...hostHealth,
|
|
1096
|
-
});
|
|
1097
|
-
|
|
1098
|
-
for (const name of remotes) {
|
|
1099
|
-
const remote = config.app[name];
|
|
1100
|
-
if (!remote || !("name" in remote)) continue;
|
|
1101
|
-
|
|
1102
|
-
const remoteHealth = await checkHealth(remote[env]);
|
|
1103
|
-
endpoints.push({
|
|
1104
|
-
name,
|
|
1105
|
-
url: remote[env],
|
|
1106
|
-
type: "remote",
|
|
1107
|
-
...remoteHealth,
|
|
1108
|
-
});
|
|
1109
|
-
|
|
1110
|
-
if ((remote as any).ssr && env === "production") {
|
|
1111
|
-
const ssrHealth = await checkHealth((remote as any).ssr);
|
|
1112
|
-
endpoints.push({
|
|
1113
|
-
name: `${name}/ssr`,
|
|
1114
|
-
url: (remote as any).ssr,
|
|
1115
|
-
type: "ssr",
|
|
1116
|
-
...ssrHealth,
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
return { endpoints };
|
|
1122
|
-
}),
|
|
1123
|
-
|
|
1124
|
-
clean: builder.clean.handler(async () => {
|
|
1125
|
-
const { configDir } = deps;
|
|
1126
|
-
const packages = deps.bosConfig ? Object.keys(deps.bosConfig.app) : [];
|
|
1127
|
-
const removed: string[] = [];
|
|
1128
|
-
|
|
1129
|
-
for (const pkg of packages) {
|
|
1130
|
-
const distPath = `${configDir}/${pkg}/dist`;
|
|
1131
|
-
try {
|
|
1132
|
-
await Bun.spawn(["rm", "-rf", distPath]).exited;
|
|
1133
|
-
removed.push(`${pkg}/dist`);
|
|
1134
|
-
} catch {}
|
|
1135
|
-
|
|
1136
|
-
const nodeModulesPath = `${configDir}/${pkg}/node_modules`;
|
|
1137
|
-
try {
|
|
1138
|
-
await Bun.spawn(["rm", "-rf", nodeModulesPath]).exited;
|
|
1139
|
-
removed.push(`${pkg}/node_modules`);
|
|
1140
|
-
} catch {}
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
return {
|
|
1144
|
-
status: "cleaned" as const,
|
|
1145
|
-
removed,
|
|
1146
|
-
};
|
|
1147
|
-
}),
|
|
1148
|
-
|
|
1149
|
-
register: builder.register.handler(async (input) => {
|
|
1150
|
-
const { bosConfig } = deps;
|
|
1151
|
-
|
|
1152
|
-
if (!bosConfig) {
|
|
1153
|
-
return {
|
|
1154
|
-
status: "error" as const,
|
|
1155
|
-
account: input.name,
|
|
1156
|
-
error: "No bos.config.json found. Run from a BOS project directory.",
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
const network = input.network;
|
|
1161
|
-
|
|
1162
|
-
try {
|
|
1163
|
-
const parentAccount = getAccountForNetwork(bosConfig, network);
|
|
1164
|
-
const fullAccount = `${input.name}.${parentAccount}`;
|
|
1165
|
-
|
|
1166
|
-
const registerEffect = Effect.gen(function* () {
|
|
1167
|
-
yield* ensureNearCli;
|
|
1168
|
-
|
|
1169
|
-
const bosEnv = yield* loadBosEnv;
|
|
1170
|
-
const gatewayPrivateKey = bosEnv.GATEWAY_PRIVATE_KEY;
|
|
1171
|
-
|
|
1172
|
-
yield* createSubaccount({
|
|
1173
|
-
newAccount: fullAccount,
|
|
1174
|
-
parentAccount,
|
|
1175
|
-
initialBalance: "0.1NEAR",
|
|
1176
|
-
network,
|
|
1177
|
-
privateKey: gatewayPrivateKey,
|
|
1178
|
-
});
|
|
1179
|
-
|
|
1180
|
-
const novaConfig = yield* getNovaConfig;
|
|
1181
|
-
const nova = createNovaClient(novaConfig);
|
|
1182
|
-
|
|
1183
|
-
const gatewayNovaAccount = bosConfig.gateway?.nova?.account;
|
|
1184
|
-
if (!gatewayNovaAccount) {
|
|
1185
|
-
return yield* Effect.fail(
|
|
1186
|
-
new Error(
|
|
1187
|
-
"gateway.nova.account is required for secrets registration",
|
|
1188
|
-
),
|
|
1189
|
-
);
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
yield* registerSecretsGroup(nova, fullAccount, gatewayNovaAccount);
|
|
1193
|
-
|
|
1194
|
-
return {
|
|
1195
|
-
status: "registered" as const,
|
|
1196
|
-
account: fullAccount,
|
|
1197
|
-
novaGroup: getSecretsGroupId(fullAccount),
|
|
1198
|
-
};
|
|
1199
|
-
});
|
|
1200
|
-
|
|
1201
|
-
return await Effect.runPromise(registerEffect);
|
|
1202
|
-
} catch (error) {
|
|
1203
|
-
const parentAccount =
|
|
1204
|
-
network === "testnet" ? bosConfig.testnet : bosConfig.account;
|
|
1205
|
-
return {
|
|
1206
|
-
status: "error" as const,
|
|
1207
|
-
account: `${input.name}.${parentAccount || bosConfig.account}`,
|
|
1208
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1209
|
-
};
|
|
1210
|
-
}
|
|
1211
|
-
}),
|
|
1212
|
-
|
|
1213
|
-
secretsSync: builder.secretsSync.handler(async (input) => {
|
|
1214
|
-
const { bosConfig } = deps;
|
|
1215
|
-
|
|
1216
|
-
if (!bosConfig) {
|
|
1217
|
-
return {
|
|
1218
|
-
status: "error" as const,
|
|
1219
|
-
count: 0,
|
|
1220
|
-
error: "No bos.config.json found. Run from a BOS project directory.",
|
|
1221
|
-
};
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
const syncEffect = Effect.gen(function* () {
|
|
1225
|
-
const novaConfig = yield* getNovaConfig;
|
|
1226
|
-
const nova = createNovaClient(novaConfig);
|
|
1227
|
-
const groupId = getSecretsGroupId(bosConfig.account);
|
|
1228
|
-
|
|
1229
|
-
const envContent = yield* Effect.tryPromise({
|
|
1230
|
-
try: () => Bun.file(input.envPath).text(),
|
|
1231
|
-
catch: (e) => new Error(`Failed to read env file: ${e}`),
|
|
1232
|
-
});
|
|
1233
|
-
|
|
1234
|
-
const secrets = parseEnvFile(envContent);
|
|
1235
|
-
const result = yield* uploadSecrets(nova, groupId, secrets);
|
|
1236
|
-
|
|
1237
|
-
return {
|
|
1238
|
-
status: "synced" as const,
|
|
1239
|
-
count: Object.keys(secrets).length,
|
|
1240
|
-
cid: result.cid,
|
|
1241
|
-
};
|
|
1242
|
-
});
|
|
1243
|
-
|
|
1244
|
-
try {
|
|
1245
|
-
return await Effect.runPromise(syncEffect);
|
|
1246
|
-
} catch (error) {
|
|
1247
|
-
return {
|
|
1248
|
-
status: "error" as const,
|
|
1249
|
-
count: 0,
|
|
1250
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1253
|
-
}),
|
|
1254
|
-
|
|
1255
|
-
secretsSet: builder.secretsSet.handler(async (input) => {
|
|
1256
|
-
const { bosConfig } = deps;
|
|
1257
|
-
|
|
1258
|
-
if (!bosConfig) {
|
|
1259
|
-
return {
|
|
1260
|
-
status: "error" as const,
|
|
1261
|
-
error: "No bos.config.json found. Run from a BOS project directory.",
|
|
1262
|
-
};
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
const setEffect = Effect.gen(function* () {
|
|
1266
|
-
const novaConfig = yield* getNovaConfig;
|
|
1267
|
-
const nova = createNovaClient(novaConfig);
|
|
1268
|
-
const groupId = getSecretsGroupId(bosConfig.account);
|
|
1269
|
-
|
|
1270
|
-
const result = yield* uploadSecrets(nova, groupId, {
|
|
1271
|
-
[input.key]: input.value,
|
|
1272
|
-
});
|
|
1273
|
-
|
|
1274
|
-
return {
|
|
1275
|
-
status: "set" as const,
|
|
1276
|
-
cid: result.cid,
|
|
1277
|
-
};
|
|
1278
|
-
});
|
|
1279
|
-
|
|
1280
|
-
try {
|
|
1281
|
-
return await Effect.runPromise(setEffect);
|
|
1282
|
-
} catch (error) {
|
|
1283
|
-
return {
|
|
1284
|
-
status: "error" as const,
|
|
1285
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1286
|
-
};
|
|
1287
|
-
}
|
|
1288
|
-
}),
|
|
1289
|
-
|
|
1290
|
-
secretsList: builder.secretsList.handler(async () => {
|
|
1291
|
-
const { bosConfig } = deps;
|
|
1292
|
-
|
|
1293
|
-
if (!bosConfig) {
|
|
1294
|
-
return {
|
|
1295
|
-
status: "error" as const,
|
|
1296
|
-
keys: [],
|
|
1297
|
-
error: "No bos.config.json found. Run from a BOS project directory.",
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
const listEffect = Effect.gen(function* () {
|
|
1302
|
-
const novaConfig = yield* getNovaConfig;
|
|
1303
|
-
const nova = createNovaClient(novaConfig);
|
|
1304
|
-
const groupId = getSecretsGroupId(bosConfig.account);
|
|
1305
|
-
|
|
1306
|
-
const bosEnv = yield* loadBosEnv;
|
|
1307
|
-
const cid = bosEnv.NOVA_SECRETS_CID;
|
|
1308
|
-
|
|
1309
|
-
if (!cid) {
|
|
1310
|
-
return {
|
|
1311
|
-
status: "listed" as const,
|
|
1312
|
-
keys: [] as string[],
|
|
1313
|
-
};
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
const secretsData = yield* retrieveSecrets(nova, groupId, cid);
|
|
1317
|
-
|
|
1318
|
-
return {
|
|
1319
|
-
status: "listed" as const,
|
|
1320
|
-
keys: Object.keys(secretsData.secrets),
|
|
1321
|
-
};
|
|
1322
|
-
});
|
|
1323
|
-
|
|
1324
|
-
try {
|
|
1325
|
-
return await Effect.runPromise(listEffect);
|
|
1326
|
-
} catch (error) {
|
|
1327
|
-
return {
|
|
1328
|
-
status: "error" as const,
|
|
1329
|
-
keys: [],
|
|
1330
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1331
|
-
};
|
|
1332
|
-
}
|
|
1333
|
-
}),
|
|
1334
|
-
|
|
1335
|
-
secretsDelete: builder.secretsDelete.handler(async (input) => {
|
|
1336
|
-
const { bosConfig } = deps;
|
|
1337
|
-
|
|
1338
|
-
if (!bosConfig) {
|
|
1339
|
-
return {
|
|
1340
|
-
status: "error" as const,
|
|
1341
|
-
error: "No bos.config.json found. Run from a BOS project directory.",
|
|
1342
|
-
};
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
const deleteEffect = Effect.gen(function* () {
|
|
1346
|
-
const novaConfig = yield* getNovaConfig;
|
|
1347
|
-
const nova = createNovaClient(novaConfig);
|
|
1348
|
-
const groupId = getSecretsGroupId(bosConfig.account);
|
|
1349
|
-
|
|
1350
|
-
const bosEnv = yield* loadBosEnv;
|
|
1351
|
-
const cid = bosEnv.NOVA_SECRETS_CID;
|
|
1352
|
-
|
|
1353
|
-
if (!cid) {
|
|
1354
|
-
return yield* Effect.fail(
|
|
1355
|
-
new Error("No secrets found to delete from"),
|
|
1356
|
-
);
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
const secretsData = yield* retrieveSecrets(nova, groupId, cid);
|
|
1360
|
-
const { [input.key]: _, ...remainingSecrets } = secretsData.secrets;
|
|
1361
|
-
|
|
1362
|
-
const result = yield* uploadSecrets(nova, groupId, remainingSecrets);
|
|
1363
|
-
|
|
1364
|
-
return {
|
|
1365
|
-
status: "deleted" as const,
|
|
1366
|
-
cid: result.cid,
|
|
1367
|
-
};
|
|
1368
|
-
});
|
|
1369
|
-
|
|
1370
|
-
try {
|
|
1371
|
-
return await Effect.runPromise(deleteEffect);
|
|
1372
|
-
} catch (error) {
|
|
1373
|
-
return {
|
|
1374
|
-
status: "error" as const,
|
|
1375
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1376
|
-
};
|
|
1377
|
-
}
|
|
1378
|
-
}),
|
|
1379
|
-
|
|
1380
|
-
login: builder.login.handler(async (input) => {
|
|
1381
|
-
const loginEffect = Effect.gen(function* () {
|
|
1382
|
-
const { token, accountId } = input;
|
|
1383
|
-
|
|
1384
|
-
if (!token || !accountId) {
|
|
1385
|
-
return yield* Effect.fail(
|
|
1386
|
-
new Error("Both token and accountId are required"),
|
|
1387
|
-
);
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
yield* verifyNovaCredentials(accountId, token);
|
|
1391
|
-
yield* saveNovaCredentials(accountId, token);
|
|
1392
|
-
|
|
1393
|
-
return {
|
|
1394
|
-
status: "logged-in" as const,
|
|
1395
|
-
accountId,
|
|
1396
|
-
};
|
|
1397
|
-
});
|
|
1398
|
-
|
|
1399
|
-
try {
|
|
1400
|
-
return await Effect.runPromise(loginEffect);
|
|
1401
|
-
} catch (error) {
|
|
1402
|
-
let message = "Unknown error";
|
|
1403
|
-
if (error instanceof Error) {
|
|
1404
|
-
message = error.message;
|
|
1405
|
-
} else if (typeof error === "object" && error !== null) {
|
|
1406
|
-
if ("message" in error) {
|
|
1407
|
-
message = String(error.message);
|
|
1408
|
-
} else if ("_tag" in error && "error" in error) {
|
|
1409
|
-
const inner = (error as { error: unknown }).error;
|
|
1410
|
-
message = inner instanceof Error ? inner.message : String(inner);
|
|
1411
|
-
} else {
|
|
1412
|
-
message = JSON.stringify(error);
|
|
1413
|
-
}
|
|
1414
|
-
} else {
|
|
1415
|
-
message = String(error);
|
|
1416
|
-
}
|
|
1417
|
-
console.error("Login error details:", error);
|
|
1418
|
-
return {
|
|
1419
|
-
status: "error" as const,
|
|
1420
|
-
error: message,
|
|
1421
|
-
};
|
|
1422
|
-
}
|
|
1423
|
-
}),
|
|
1424
|
-
|
|
1425
|
-
logout: builder.logout.handler(async () => {
|
|
1426
|
-
const logoutEffect = Effect.gen(function* () {
|
|
1427
|
-
yield* removeNovaCredentials;
|
|
1428
|
-
|
|
1429
|
-
return {
|
|
1430
|
-
status: "logged-out" as const,
|
|
1431
|
-
};
|
|
1432
|
-
});
|
|
1433
|
-
|
|
1434
|
-
try {
|
|
1435
|
-
return await Effect.runPromise(logoutEffect);
|
|
1436
|
-
} catch (error) {
|
|
1437
|
-
return {
|
|
1438
|
-
status: "error" as const,
|
|
1439
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1440
|
-
};
|
|
1441
|
-
}
|
|
1442
|
-
}),
|
|
1443
|
-
|
|
1444
|
-
gatewayDev: builder.gatewayDev.handler(async () => {
|
|
1445
|
-
const { configDir } = deps;
|
|
1446
|
-
const gatewayDir = `${configDir}/gateway`;
|
|
1447
|
-
|
|
1448
|
-
const devEffect = Effect.gen(function* () {
|
|
1449
|
-
const { execa } = yield* Effect.tryPromise({
|
|
1450
|
-
try: () => import("execa"),
|
|
1451
|
-
catch: (e) => new Error(`Failed to import execa: ${e}`),
|
|
1452
|
-
});
|
|
1453
|
-
|
|
1454
|
-
const subprocess = execa("npx", ["wrangler", "dev"], {
|
|
1455
|
-
cwd: gatewayDir,
|
|
1456
|
-
stdio: "inherit",
|
|
1457
|
-
});
|
|
1458
|
-
|
|
1459
|
-
subprocess.catch(() => {});
|
|
1460
|
-
|
|
1461
|
-
return {
|
|
1462
|
-
status: "started" as const,
|
|
1463
|
-
url: "http://localhost:8787",
|
|
1464
|
-
};
|
|
1465
|
-
});
|
|
1466
|
-
|
|
1467
|
-
try {
|
|
1468
|
-
return await Effect.runPromise(devEffect);
|
|
1469
|
-
} catch (error) {
|
|
1470
|
-
return {
|
|
1471
|
-
status: "error" as const,
|
|
1472
|
-
url: "",
|
|
1473
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1474
|
-
};
|
|
1475
|
-
}
|
|
1476
|
-
}),
|
|
1477
|
-
|
|
1478
|
-
gatewayDeploy: builder.gatewayDeploy.handler(async (input) => {
|
|
1479
|
-
const { configDir, bosConfig } = deps;
|
|
1480
|
-
|
|
1481
|
-
if (!bosConfig) {
|
|
1482
|
-
return {
|
|
1483
|
-
status: "error" as const,
|
|
1484
|
-
url: "",
|
|
1485
|
-
error: "No bos.config.json found. Run from a BOS project directory.",
|
|
1486
|
-
};
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
const gatewayDir = `${configDir}/gateway`;
|
|
1490
|
-
|
|
1491
|
-
const deployEffect = Effect.gen(function* () {
|
|
1492
|
-
const { execa } = yield* Effect.tryPromise({
|
|
1493
|
-
try: () => import("execa"),
|
|
1494
|
-
catch: (e) => new Error(`Failed to import execa: ${e}`),
|
|
1495
|
-
});
|
|
1496
|
-
|
|
1497
|
-
const args = ["wrangler", "deploy"];
|
|
1498
|
-
if (input.env) {
|
|
1499
|
-
args.push("--env", input.env);
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
yield* Effect.tryPromise({
|
|
1503
|
-
try: () =>
|
|
1504
|
-
execa("npx", args, {
|
|
1505
|
-
cwd: gatewayDir,
|
|
1506
|
-
stdio: "inherit",
|
|
1507
|
-
}),
|
|
1508
|
-
catch: (e) => new Error(`Deploy failed: ${e}`),
|
|
1509
|
-
});
|
|
1510
|
-
|
|
1511
|
-
const gatewayDomain = getGatewayDomain(bosConfig);
|
|
1512
|
-
const domain =
|
|
1513
|
-
input.env === "staging" ? `staging.${gatewayDomain}` : gatewayDomain;
|
|
1514
|
-
|
|
1515
|
-
return {
|
|
1516
|
-
status: "deployed" as const,
|
|
1517
|
-
url: `https://${domain}`,
|
|
1518
|
-
};
|
|
1519
|
-
});
|
|
1520
|
-
|
|
1521
|
-
try {
|
|
1522
|
-
return await Effect.runPromise(deployEffect);
|
|
1523
|
-
} catch (error) {
|
|
1524
|
-
return {
|
|
1525
|
-
status: "error" as const,
|
|
1526
|
-
url: "",
|
|
1527
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1528
|
-
};
|
|
1529
|
-
}
|
|
1530
|
-
}),
|
|
1531
|
-
|
|
1532
|
-
gatewaySync: builder.gatewaySync.handler(async () => {
|
|
1533
|
-
const { configDir, bosConfig } = deps;
|
|
1534
|
-
|
|
1535
|
-
if (!bosConfig) {
|
|
1536
|
-
return {
|
|
1537
|
-
status: "error" as const,
|
|
1538
|
-
error: "No bos.config.json found. Run from a BOS project directory.",
|
|
1539
|
-
};
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
const wranglerPath = `${configDir}/gateway/wrangler.toml`;
|
|
1543
|
-
|
|
1544
|
-
try {
|
|
1545
|
-
const gatewayDomain = getGatewayDomain(bosConfig);
|
|
1546
|
-
const gatewayAccount = bosConfig.gateway?.account || bosConfig.account;
|
|
1547
|
-
|
|
1548
|
-
const wranglerContent = await Bun.file(wranglerPath).text();
|
|
1549
|
-
|
|
1550
|
-
let updatedContent = wranglerContent.replace(
|
|
1551
|
-
/GATEWAY_DOMAIN\s*=\s*"[^"]*"/g,
|
|
1552
|
-
`GATEWAY_DOMAIN = "${gatewayDomain}"`,
|
|
1553
|
-
);
|
|
1554
|
-
updatedContent = updatedContent.replace(
|
|
1555
|
-
/GATEWAY_ACCOUNT\s*=\s*"[^"]*"/g,
|
|
1556
|
-
`GATEWAY_ACCOUNT = "${gatewayAccount}"`,
|
|
1557
|
-
);
|
|
1558
|
-
|
|
1559
|
-
await Bun.write(wranglerPath, updatedContent);
|
|
1560
|
-
|
|
1561
|
-
return {
|
|
1562
|
-
status: "synced" as const,
|
|
1563
|
-
gatewayDomain,
|
|
1564
|
-
gatewayAccount,
|
|
1565
|
-
};
|
|
1566
|
-
} catch (error) {
|
|
1567
|
-
return {
|
|
1568
|
-
status: "error" as const,
|
|
1569
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1570
|
-
};
|
|
1571
|
-
}
|
|
1572
|
-
}),
|
|
1573
|
-
|
|
1574
|
-
depsUpdate: builder.depsUpdate.handler(async (input) => {
|
|
1575
|
-
const { configDir, bosConfig } = deps;
|
|
1576
|
-
|
|
1577
|
-
if (!bosConfig) {
|
|
1578
|
-
return {
|
|
1579
|
-
status: "error" as const,
|
|
1580
|
-
updated: [],
|
|
1581
|
-
error: "No bos.config.json found. Run from a BOS project directory.",
|
|
1582
|
-
};
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
const category = input.category;
|
|
1586
|
-
const sharedDeps = bosConfig.shared?.[category];
|
|
1587
|
-
|
|
1588
|
-
if (!sharedDeps || Object.keys(sharedDeps).length === 0) {
|
|
1589
|
-
return {
|
|
1590
|
-
status: "error" as const,
|
|
1591
|
-
updated: [],
|
|
1592
|
-
error: `No shared.${category} dependencies found in bos.config.json`,
|
|
1593
|
-
};
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
const { mkdtemp, rm } = await import("fs/promises");
|
|
1597
|
-
const { tmpdir } = await import("os");
|
|
1598
|
-
const { join } = await import("path");
|
|
1599
|
-
const { execa } = await import("execa");
|
|
1600
|
-
|
|
1601
|
-
const tempDir = await mkdtemp(join(tmpdir(), "bos-deps-"));
|
|
1602
|
-
|
|
1603
|
-
try {
|
|
1604
|
-
const tempDeps: Record<string, string> = {};
|
|
1605
|
-
for (const [name, config] of Object.entries(sharedDeps)) {
|
|
1606
|
-
const version =
|
|
1607
|
-
(config as { requiredVersion?: string }).requiredVersion || "*";
|
|
1608
|
-
tempDeps[name] = version.replace(/^[\^~]/, "");
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
const tempPkg = {
|
|
1612
|
-
name: "bos-deps-update",
|
|
1613
|
-
private: true,
|
|
1614
|
-
dependencies: tempDeps,
|
|
1615
|
-
};
|
|
1616
|
-
|
|
1617
|
-
await Bun.write(
|
|
1618
|
-
join(tempDir, "package.json"),
|
|
1619
|
-
JSON.stringify(tempPkg, null, 2),
|
|
1620
|
-
);
|
|
1621
|
-
|
|
1622
|
-
await execa("bun", ["install"], {
|
|
1623
|
-
cwd: tempDir,
|
|
1624
|
-
stdio: "inherit",
|
|
1625
|
-
});
|
|
1626
|
-
|
|
1627
|
-
await execa("bun", ["update", "-i"], {
|
|
1628
|
-
cwd: tempDir,
|
|
1629
|
-
stdio: "inherit",
|
|
1630
|
-
});
|
|
1631
|
-
|
|
1632
|
-
const updatedPkg = (await Bun.file(
|
|
1633
|
-
join(tempDir, "package.json"),
|
|
1634
|
-
).json()) as {
|
|
1635
|
-
dependencies: Record<string, string>;
|
|
1636
|
-
};
|
|
1637
|
-
|
|
1638
|
-
const updated: { name: string; from: string; to: string }[] = [];
|
|
1639
|
-
const updatedConfig = { ...bosConfig };
|
|
1640
|
-
|
|
1641
|
-
if (!updatedConfig.shared) {
|
|
1642
|
-
updatedConfig.shared = {};
|
|
1643
|
-
}
|
|
1644
|
-
if (!updatedConfig.shared[category]) {
|
|
1645
|
-
updatedConfig.shared[category] = {};
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
for (const [name, newVersion] of Object.entries(
|
|
1649
|
-
updatedPkg.dependencies,
|
|
1650
|
-
)) {
|
|
1651
|
-
const oldVersion =
|
|
1652
|
-
(sharedDeps[name] as { requiredVersion?: string })
|
|
1653
|
-
?.requiredVersion || "";
|
|
1654
|
-
if (newVersion !== oldVersion) {
|
|
1655
|
-
updated.push({ name, from: oldVersion, to: newVersion });
|
|
1656
|
-
updatedConfig.shared[category][name] = {
|
|
1657
|
-
...(sharedDeps[name] as object),
|
|
1658
|
-
requiredVersion: newVersion,
|
|
1659
|
-
};
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
if (updated.length > 0) {
|
|
1664
|
-
const bosConfigPath = `${configDir}/bos.config.json`;
|
|
1665
|
-
await Bun.write(
|
|
1666
|
-
bosConfigPath,
|
|
1667
|
-
JSON.stringify(updatedConfig, null, 2),
|
|
1668
|
-
);
|
|
1669
|
-
|
|
1670
|
-
const rootPkgPath = `${configDir}/package.json`;
|
|
1671
|
-
const rootPkg = (await Bun.file(rootPkgPath).json()) as {
|
|
1672
|
-
workspaces?: { catalog?: Record<string, string> };
|
|
1673
|
-
};
|
|
1674
|
-
|
|
1675
|
-
if (rootPkg.workspaces?.catalog) {
|
|
1676
|
-
for (const { name, to } of updated) {
|
|
1677
|
-
rootPkg.workspaces.catalog[name] = to;
|
|
1678
|
-
}
|
|
1679
|
-
await Bun.write(rootPkgPath, JSON.stringify(rootPkg, null, 2));
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
await execa("bun", ["install"], {
|
|
1683
|
-
cwd: configDir,
|
|
1684
|
-
stdio: "inherit",
|
|
1685
|
-
});
|
|
1686
|
-
|
|
1687
|
-
return {
|
|
1688
|
-
status: "updated" as const,
|
|
1689
|
-
updated,
|
|
1690
|
-
syncStatus: "synced" as const,
|
|
1691
|
-
};
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
return {
|
|
1695
|
-
status: "cancelled" as const,
|
|
1696
|
-
updated: [],
|
|
1697
|
-
};
|
|
1698
|
-
} catch (error) {
|
|
1699
|
-
return {
|
|
1700
|
-
status: "error" as const,
|
|
1701
|
-
updated: [],
|
|
1702
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1703
|
-
};
|
|
1704
|
-
} finally {
|
|
1705
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
1706
|
-
}
|
|
1707
|
-
}),
|
|
1708
|
-
|
|
1709
|
-
filesSync: builder.filesSync.handler(async (input) => {
|
|
1710
|
-
const { configDir, bosConfig } = deps;
|
|
1711
|
-
|
|
1712
|
-
if (!bosConfig) {
|
|
1713
|
-
return {
|
|
1714
|
-
status: "error" as const,
|
|
1715
|
-
synced: [],
|
|
1716
|
-
error: "No bos.config.json found. Run from a BOS project directory.",
|
|
1717
|
-
};
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
const rootPkgPath = `${configDir}/package.json`;
|
|
1721
|
-
const rootPkg = (await Bun.file(rootPkgPath).json()) as {
|
|
1722
|
-
workspaces?: { catalog?: Record<string, string> };
|
|
1723
|
-
};
|
|
1724
|
-
const catalog = rootPkg.workspaces?.catalog ?? {};
|
|
1725
|
-
|
|
1726
|
-
const packages = input.packages || Object.keys(bosConfig.app);
|
|
1727
|
-
|
|
1728
|
-
const synced = await syncFiles({
|
|
1729
|
-
configDir,
|
|
1730
|
-
packages,
|
|
1731
|
-
bosConfig,
|
|
1732
|
-
catalog,
|
|
1733
|
-
force: input.force,
|
|
1734
|
-
});
|
|
1735
|
-
|
|
1736
|
-
return {
|
|
1737
|
-
status: "synced" as const,
|
|
1738
|
-
synced,
|
|
1739
|
-
};
|
|
1740
|
-
}),
|
|
1741
|
-
|
|
1742
|
-
update: builder.update.handler(async (input) => {
|
|
1743
|
-
const { configDir, bosConfig } = deps;
|
|
1744
|
-
|
|
1745
|
-
const DEFAULT_ACCOUNT = "every.near";
|
|
1746
|
-
|
|
1747
|
-
const account = input.account || bosConfig?.account || DEFAULT_ACCOUNT;
|
|
1748
|
-
const gateway = input.gateway || getGatewayDomain(bosConfig);
|
|
1749
|
-
const socialUrl = `https://near.social/mob.near/widget/State.Inspector?key=${account}/bos/gateways/${gateway}`;
|
|
1750
|
-
|
|
1751
|
-
if (!bosConfig) {
|
|
1752
|
-
return {
|
|
1753
|
-
status: "error" as const,
|
|
1754
|
-
account,
|
|
1755
|
-
gateway,
|
|
1756
|
-
socialUrl,
|
|
1757
|
-
hostUrl: "",
|
|
1758
|
-
catalogUpdated: false,
|
|
1759
|
-
packagesUpdated: [],
|
|
1760
|
-
error: "No bos.config.json found. Run from a BOS project directory.",
|
|
1761
|
-
};
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
try {
|
|
1765
|
-
const graph = new Graph();
|
|
1766
|
-
const configPath = `${account}/bos/gateways/${gateway}/bos.config.json`;
|
|
1767
|
-
|
|
1768
|
-
let remoteConfig: BosConfigType | null = null;
|
|
1769
|
-
|
|
1770
|
-
const data = await graph.get({ keys: [configPath] });
|
|
1771
|
-
if (data) {
|
|
1772
|
-
const parts = configPath.split("/");
|
|
1773
|
-
let current: unknown = data;
|
|
1774
|
-
for (const part of parts) {
|
|
1775
|
-
if (current && typeof current === "object" && part in current) {
|
|
1776
|
-
current = (current as Record<string, unknown>)[part];
|
|
1777
|
-
} else {
|
|
1778
|
-
current = null;
|
|
1779
|
-
break;
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
if (typeof current === "string") {
|
|
1783
|
-
remoteConfig = JSON.parse(current) as BosConfigType;
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
if (!remoteConfig) {
|
|
1788
|
-
return {
|
|
1789
|
-
status: "error" as const,
|
|
1790
|
-
account,
|
|
1791
|
-
gateway,
|
|
1792
|
-
hostUrl: "",
|
|
1793
|
-
catalogUpdated: false,
|
|
1794
|
-
packagesUpdated: [],
|
|
1795
|
-
error: `No config found at ${configPath} on Near Social. Run 'bos publish' first.`,
|
|
1796
|
-
};
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
const hostUrl = remoteConfig.app?.host?.production;
|
|
1800
|
-
if (!hostUrl) {
|
|
1801
|
-
return {
|
|
1802
|
-
status: "error" as const,
|
|
1803
|
-
account,
|
|
1804
|
-
gateway,
|
|
1805
|
-
hostUrl: "",
|
|
1806
|
-
catalogUpdated: false,
|
|
1807
|
-
packagesUpdated: [],
|
|
1808
|
-
error: `Published config is missing 'app.host.production'. Republish with updated bos.config.json.`,
|
|
1809
|
-
};
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
const mergeAppConfig = (
|
|
1813
|
-
localApp: Record<string, unknown>,
|
|
1814
|
-
remoteApp: Record<string, unknown>,
|
|
1815
|
-
): Record<string, unknown> => {
|
|
1816
|
-
const merged: Record<string, unknown> = {};
|
|
1817
|
-
|
|
1818
|
-
for (const key of Object.keys(remoteApp)) {
|
|
1819
|
-
const local = localApp[key] as Record<string, unknown> | undefined;
|
|
1820
|
-
const remote = remoteApp[key] as Record<string, unknown>;
|
|
1821
|
-
|
|
1822
|
-
if (!local) {
|
|
1823
|
-
merged[key] = remote;
|
|
1824
|
-
continue;
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
|
-
merged[key] = {
|
|
1828
|
-
...remote,
|
|
1829
|
-
development: local.development,
|
|
1830
|
-
secrets: [
|
|
1831
|
-
...new Set([
|
|
1832
|
-
...((remote.secrets as string[]) || []),
|
|
1833
|
-
...((local.secrets as string[]) || []),
|
|
1834
|
-
]),
|
|
1835
|
-
],
|
|
1836
|
-
variables: {
|
|
1837
|
-
...((remote.variables as Record<string, unknown>) || {}),
|
|
1838
|
-
...((local.variables as Record<string, unknown>) || {}),
|
|
1839
|
-
},
|
|
1840
|
-
};
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
return merged;
|
|
1844
|
-
};
|
|
1845
|
-
|
|
1846
|
-
const updatedBosConfig: BosConfigType = {
|
|
1847
|
-
account: bosConfig.account,
|
|
1848
|
-
testnet: bosConfig.testnet,
|
|
1849
|
-
template: remoteConfig.template,
|
|
1850
|
-
shared: remoteConfig.shared,
|
|
1851
|
-
gateway: remoteConfig.gateway,
|
|
1852
|
-
app: mergeAppConfig(
|
|
1853
|
-
bosConfig.app as Record<string, unknown>,
|
|
1854
|
-
remoteConfig.app as Record<string, unknown>,
|
|
1855
|
-
) as BosConfigType["app"],
|
|
1856
|
-
};
|
|
1857
|
-
|
|
1858
|
-
const bosConfigPath = `${configDir}/bos.config.json`;
|
|
1859
|
-
await Bun.write(
|
|
1860
|
-
bosConfigPath,
|
|
1861
|
-
JSON.stringify(updatedBosConfig, null, 2),
|
|
1862
|
-
);
|
|
1863
|
-
// Config is written to disk, subsequent code uses updatedBosConfig directly
|
|
1864
|
-
// Cache will be refreshed on next loadConfig() call
|
|
1865
|
-
|
|
1866
|
-
const sharedUiDeps: Record<string, string> = {};
|
|
1867
|
-
const sharedUi = updatedBosConfig.shared?.ui as
|
|
1868
|
-
| Record<string, { requiredVersion?: string }>
|
|
1869
|
-
| undefined;
|
|
1870
|
-
if (sharedUi) {
|
|
1871
|
-
for (const [name, config] of Object.entries(sharedUi)) {
|
|
1872
|
-
if (config.requiredVersion) {
|
|
1873
|
-
sharedUiDeps[name] = config.requiredVersion;
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
const rootPkgPath = `${configDir}/package.json`;
|
|
1879
|
-
const rootPkg = (await Bun.file(rootPkgPath).json()) as {
|
|
1880
|
-
workspaces: { packages: string[]; catalog: Record<string, string> };
|
|
1881
|
-
[key: string]: unknown;
|
|
1882
|
-
};
|
|
1883
|
-
|
|
1884
|
-
rootPkg.workspaces.catalog = {
|
|
1885
|
-
...rootPkg.workspaces.catalog,
|
|
1886
|
-
...sharedUiDeps,
|
|
1887
|
-
};
|
|
1888
|
-
await Bun.write(rootPkgPath, JSON.stringify(rootPkg, null, 2));
|
|
1889
|
-
|
|
1890
|
-
const packages = ["host", "ui", "api"];
|
|
1891
|
-
const packagesUpdated: string[] = [];
|
|
1892
|
-
|
|
1893
|
-
for (const pkg of packages) {
|
|
1894
|
-
const pkgDir = `${configDir}/${pkg}`;
|
|
1895
|
-
const pkgDirExists = await Bun.file(
|
|
1896
|
-
`${pkgDir}/package.json`,
|
|
1897
|
-
).exists();
|
|
1898
|
-
if (!pkgDirExists) continue;
|
|
1899
|
-
|
|
1900
|
-
const pkgPath = `${pkgDir}/package.json`;
|
|
1901
|
-
const pkgFile = Bun.file(pkgPath);
|
|
1902
|
-
|
|
1903
|
-
const pkgJson = (await pkgFile.json()) as {
|
|
1904
|
-
dependencies?: Record<string, string>;
|
|
1905
|
-
devDependencies?: Record<string, string>;
|
|
1906
|
-
peerDependencies?: Record<string, string>;
|
|
1907
|
-
};
|
|
1908
|
-
|
|
1909
|
-
let updated = false;
|
|
1910
|
-
|
|
1911
|
-
for (const depType of [
|
|
1912
|
-
"dependencies",
|
|
1913
|
-
"devDependencies",
|
|
1914
|
-
"peerDependencies",
|
|
1915
|
-
] as const) {
|
|
1916
|
-
const deps = pkgJson[depType];
|
|
1917
|
-
if (!deps) continue;
|
|
1918
|
-
|
|
1919
|
-
for (const [name, version] of Object.entries(deps)) {
|
|
1920
|
-
if (
|
|
1921
|
-
name in rootPkg.workspaces.catalog &&
|
|
1922
|
-
version !== "catalog:"
|
|
1923
|
-
) {
|
|
1924
|
-
deps[name] = "catalog:";
|
|
1925
|
-
updated = true;
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
if (updated || input.force) {
|
|
1931
|
-
await Bun.write(pkgPath, JSON.stringify(pkgJson, null, 2));
|
|
1932
|
-
packagesUpdated.push(pkg);
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
const results = await syncFiles({
|
|
1937
|
-
configDir,
|
|
1938
|
-
packages: Object.keys(updatedBosConfig.app),
|
|
1939
|
-
bosConfig: updatedBosConfig,
|
|
1940
|
-
catalog: rootPkg.workspaces?.catalog ?? {},
|
|
1941
|
-
force: input.force,
|
|
1942
|
-
});
|
|
1943
|
-
|
|
1944
|
-
const filesSynced =
|
|
1945
|
-
results.length > 0
|
|
1946
|
-
? results.map((r) => ({ package: r.package, files: r.files }))
|
|
1947
|
-
: undefined;
|
|
1948
|
-
|
|
1949
|
-
return {
|
|
1950
|
-
status: "updated" as const,
|
|
1951
|
-
account,
|
|
1952
|
-
gateway,
|
|
1953
|
-
socialUrl,
|
|
1954
|
-
hostUrl,
|
|
1955
|
-
catalogUpdated: true,
|
|
1956
|
-
packagesUpdated,
|
|
1957
|
-
filesSynced,
|
|
1958
|
-
};
|
|
1959
|
-
} catch (error) {
|
|
1960
|
-
return {
|
|
1961
|
-
status: "error" as const,
|
|
1962
|
-
account,
|
|
1963
|
-
gateway,
|
|
1964
|
-
hostUrl: "",
|
|
1965
|
-
catalogUpdated: false,
|
|
1966
|
-
packagesUpdated: [],
|
|
1967
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1968
|
-
};
|
|
1969
|
-
}
|
|
1970
|
-
}),
|
|
1971
|
-
|
|
1972
|
-
kill: builder.kill.handler(async (input) => {
|
|
1973
|
-
const killEffect = Effect.gen(function* () {
|
|
1974
|
-
const registry = yield* createProcessRegistry();
|
|
1975
|
-
const result = yield* registry.killAll(input.force);
|
|
1976
|
-
return {
|
|
1977
|
-
status: "killed" as const,
|
|
1978
|
-
killed: result.killed,
|
|
1979
|
-
failed: result.failed,
|
|
1980
|
-
};
|
|
1981
|
-
});
|
|
1982
|
-
|
|
1983
|
-
try {
|
|
1984
|
-
return await Effect.runPromise(killEffect);
|
|
1985
|
-
} catch (error) {
|
|
1986
|
-
return {
|
|
1987
|
-
status: "error" as const,
|
|
1988
|
-
killed: [],
|
|
1989
|
-
failed: [],
|
|
1990
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1991
|
-
};
|
|
1992
|
-
}
|
|
1993
|
-
}),
|
|
1994
|
-
|
|
1995
|
-
ps: builder.ps.handler(async () => {
|
|
1996
|
-
const psEffect = Effect.gen(function* () {
|
|
1997
|
-
const registry = yield* createProcessRegistry();
|
|
1998
|
-
const processes = yield* registry.getAll();
|
|
1999
|
-
return {
|
|
2000
|
-
status: "listed" as const,
|
|
2001
|
-
processes,
|
|
2002
|
-
};
|
|
2003
|
-
});
|
|
2004
|
-
|
|
2005
|
-
try {
|
|
2006
|
-
return await Effect.runPromise(psEffect);
|
|
2007
|
-
} catch (error) {
|
|
2008
|
-
return {
|
|
2009
|
-
status: "error" as const,
|
|
2010
|
-
processes: [],
|
|
2011
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
2012
|
-
};
|
|
2013
|
-
}
|
|
2014
|
-
}),
|
|
2015
|
-
|
|
2016
|
-
dockerBuild: builder.dockerBuild.handler(async (input) => {
|
|
2017
|
-
const { configDir, bosConfig } = deps;
|
|
2018
|
-
|
|
2019
|
-
const dockerEffect = Effect.gen(function* () {
|
|
2020
|
-
const { execa } = yield* Effect.tryPromise({
|
|
2021
|
-
try: () => import("execa"),
|
|
2022
|
-
catch: (e) => new Error(`Failed to import execa: ${e}`),
|
|
2023
|
-
});
|
|
2024
|
-
|
|
2025
|
-
const dockerfile =
|
|
2026
|
-
input.target === "development" ? "Dockerfile.dev" : "Dockerfile";
|
|
2027
|
-
const imageName = bosConfig?.account?.replace(/\./g, "-") || "bos-app";
|
|
2028
|
-
const tag =
|
|
2029
|
-
input.tag || (input.target === "development" ? "dev" : "latest");
|
|
2030
|
-
const fullTag = `${imageName}:${tag}`;
|
|
2031
|
-
|
|
2032
|
-
const args = ["build", "-f", dockerfile, "-t", fullTag];
|
|
2033
|
-
if (input.noCache) {
|
|
2034
|
-
args.push("--no-cache");
|
|
2035
|
-
}
|
|
2036
|
-
args.push(".");
|
|
2037
|
-
|
|
2038
|
-
yield* Effect.tryPromise({
|
|
2039
|
-
try: () =>
|
|
2040
|
-
execa("docker", args, {
|
|
2041
|
-
cwd: configDir,
|
|
2042
|
-
stdio: "inherit",
|
|
2043
|
-
}),
|
|
2044
|
-
catch: (e) => new Error(`Docker build failed: ${e}`),
|
|
2045
|
-
});
|
|
2046
|
-
|
|
2047
|
-
return {
|
|
2048
|
-
status: "built" as const,
|
|
2049
|
-
image: imageName,
|
|
2050
|
-
tag: fullTag,
|
|
2051
|
-
};
|
|
2052
|
-
});
|
|
2053
|
-
|
|
2054
|
-
try {
|
|
2055
|
-
return await Effect.runPromise(dockerEffect);
|
|
2056
|
-
} catch (error) {
|
|
2057
|
-
return {
|
|
2058
|
-
status: "error" as const,
|
|
2059
|
-
image: "",
|
|
2060
|
-
tag: "",
|
|
2061
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
2062
|
-
};
|
|
2063
|
-
}
|
|
2064
|
-
}),
|
|
2065
|
-
|
|
2066
|
-
dockerRun: builder.dockerRun.handler(async (input) => {
|
|
2067
|
-
const { bosConfig } = deps;
|
|
2068
|
-
|
|
2069
|
-
const dockerEffect = Effect.gen(function* () {
|
|
2070
|
-
const { execa } = yield* Effect.tryPromise({
|
|
2071
|
-
try: () => import("execa"),
|
|
2072
|
-
catch: (e) => new Error(`Failed to import execa: ${e}`),
|
|
2073
|
-
});
|
|
2074
|
-
|
|
2075
|
-
const imageName = bosConfig?.account?.replace(/\./g, "-") || "bos-app";
|
|
2076
|
-
const tag = input.target === "development" ? "dev" : "latest";
|
|
2077
|
-
const fullTag = `${imageName}:${tag}`;
|
|
2078
|
-
const port =
|
|
2079
|
-
input.port || (input.target === "development" ? 4000 : 3000);
|
|
2080
|
-
|
|
2081
|
-
const args = ["run"];
|
|
2082
|
-
|
|
2083
|
-
if (input.detach) {
|
|
2084
|
-
args.push("-d");
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
|
-
args.push("-p", `${port}:${port}`);
|
|
2088
|
-
args.push("-e", `PORT=${port}`);
|
|
2089
|
-
|
|
2090
|
-
if (input.target === "development") {
|
|
2091
|
-
args.push("-e", `MODE=${input.mode}`);
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
if (input.env) {
|
|
2095
|
-
for (const [key, value] of Object.entries(input.env)) {
|
|
2096
|
-
args.push("-e", `${key}=${value}`);
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
if (bosConfig) {
|
|
2101
|
-
args.push("-e", `BOS_ACCOUNT=${bosConfig.account}`);
|
|
2102
|
-
const gateway = bosConfig.gateway as
|
|
2103
|
-
| { production?: string }
|
|
2104
|
-
| string
|
|
2105
|
-
| undefined;
|
|
2106
|
-
if (gateway) {
|
|
2107
|
-
const domain =
|
|
2108
|
-
typeof gateway === "string"
|
|
2109
|
-
? gateway
|
|
2110
|
-
: gateway.production?.replace(/^https?:\/\//, "") || "";
|
|
2111
|
-
if (domain) {
|
|
2112
|
-
args.push("-e", `GATEWAY_DOMAIN=${domain}`);
|
|
2113
|
-
}
|
|
2114
|
-
}
|
|
2115
|
-
}
|
|
2116
|
-
|
|
2117
|
-
args.push(fullTag);
|
|
2118
|
-
|
|
2119
|
-
const result = yield* Effect.tryPromise({
|
|
2120
|
-
try: () =>
|
|
2121
|
-
execa("docker", args, {
|
|
2122
|
-
stdio: input.detach ? "pipe" : "inherit",
|
|
2123
|
-
}),
|
|
2124
|
-
catch: (e) => new Error(`Docker run failed: ${e}`),
|
|
2125
|
-
});
|
|
2126
|
-
|
|
2127
|
-
const containerId =
|
|
2128
|
-
input.detach && result.stdout
|
|
2129
|
-
? result.stdout.trim().slice(0, 12)
|
|
2130
|
-
: "attached";
|
|
2131
|
-
|
|
2132
|
-
return {
|
|
2133
|
-
status: "running" as const,
|
|
2134
|
-
containerId,
|
|
2135
|
-
url: `http://localhost:${port}`,
|
|
2136
|
-
};
|
|
2137
|
-
});
|
|
2138
|
-
|
|
2139
|
-
try {
|
|
2140
|
-
return await Effect.runPromise(dockerEffect);
|
|
2141
|
-
} catch (error) {
|
|
2142
|
-
return {
|
|
2143
|
-
status: "error" as const,
|
|
2144
|
-
containerId: "",
|
|
2145
|
-
url: "",
|
|
2146
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
2147
|
-
};
|
|
2148
|
-
}
|
|
2149
|
-
}),
|
|
2150
|
-
|
|
2151
|
-
dockerStop: builder.dockerStop.handler(async (input) => {
|
|
2152
|
-
const { bosConfig } = deps;
|
|
2153
|
-
|
|
2154
|
-
const dockerEffect = Effect.gen(function* () {
|
|
2155
|
-
const { execa } = yield* Effect.tryPromise({
|
|
2156
|
-
try: () => import("execa"),
|
|
2157
|
-
catch: (e) => new Error(`Failed to import execa: ${e}`),
|
|
2158
|
-
});
|
|
2159
|
-
|
|
2160
|
-
const stopped: string[] = [];
|
|
2161
|
-
|
|
2162
|
-
if (input.containerId) {
|
|
2163
|
-
yield* Effect.tryPromise({
|
|
2164
|
-
try: () => execa("docker", ["stop", input.containerId!]),
|
|
2165
|
-
catch: (e) => new Error(`Failed to stop container: ${e}`),
|
|
2166
|
-
});
|
|
2167
|
-
stopped.push(input.containerId!);
|
|
2168
|
-
} else if (input.all) {
|
|
2169
|
-
const imageName =
|
|
2170
|
-
bosConfig?.account?.replace(/\./g, "-") || "bos-app";
|
|
2171
|
-
|
|
2172
|
-
const psResult = yield* Effect.tryPromise({
|
|
2173
|
-
try: () =>
|
|
2174
|
-
execa("docker", [
|
|
2175
|
-
"ps",
|
|
2176
|
-
"-q",
|
|
2177
|
-
"--filter",
|
|
2178
|
-
`ancestor=${imageName}`,
|
|
2179
|
-
]),
|
|
2180
|
-
catch: () => new Error("Failed to list containers"),
|
|
2181
|
-
});
|
|
2182
|
-
|
|
2183
|
-
const containerIds = psResult.stdout
|
|
2184
|
-
.trim()
|
|
2185
|
-
.split("\n")
|
|
2186
|
-
.filter(Boolean);
|
|
2187
|
-
|
|
2188
|
-
for (const id of containerIds) {
|
|
2189
|
-
yield* Effect.tryPromise({
|
|
2190
|
-
try: () => execa("docker", ["stop", id]),
|
|
2191
|
-
catch: () => new Error(`Failed to stop container ${id}`),
|
|
2192
|
-
}).pipe(Effect.catchAll(() => Effect.void));
|
|
2193
|
-
stopped.push(id);
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
return {
|
|
2198
|
-
status: "stopped" as const,
|
|
2199
|
-
stopped,
|
|
2200
|
-
};
|
|
2201
|
-
});
|
|
2202
|
-
|
|
2203
|
-
try {
|
|
2204
|
-
return await Effect.runPromise(dockerEffect);
|
|
2205
|
-
} catch (error) {
|
|
2206
|
-
return {
|
|
2207
|
-
status: "error" as const,
|
|
2208
|
-
stopped: [],
|
|
2209
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
2210
|
-
};
|
|
2211
|
-
}
|
|
2212
|
-
}),
|
|
2213
|
-
|
|
2214
|
-
monitor: builder.monitor.handler(async (input) => {
|
|
2215
|
-
try {
|
|
2216
|
-
if (input.json) {
|
|
2217
|
-
const snapshot = await runWithInfo(
|
|
2218
|
-
createSnapshotWithPlatform(
|
|
2219
|
-
input.ports ? { ports: input.ports } : undefined,
|
|
2220
|
-
),
|
|
2221
|
-
);
|
|
2222
|
-
return {
|
|
2223
|
-
status: "snapshot" as const,
|
|
2224
|
-
snapshot: snapshot as any,
|
|
2225
|
-
};
|
|
2226
|
-
}
|
|
2227
|
-
|
|
2228
|
-
if (input.watch) {
|
|
2229
|
-
runMonitorCli({ ports: input.ports, json: false });
|
|
2230
|
-
return {
|
|
2231
|
-
status: "watching" as const,
|
|
2232
|
-
};
|
|
2233
|
-
}
|
|
2234
|
-
|
|
2235
|
-
const snapshot = await runWithInfo(
|
|
2236
|
-
createSnapshotWithPlatform(
|
|
2237
|
-
input.ports ? { ports: input.ports } : undefined,
|
|
2238
|
-
),
|
|
2239
|
-
);
|
|
2240
|
-
console.log(formatSnapshotSummary(snapshot));
|
|
2241
|
-
|
|
2242
|
-
return {
|
|
2243
|
-
status: "snapshot" as const,
|
|
2244
|
-
snapshot: snapshot as any,
|
|
2245
|
-
};
|
|
2246
|
-
} catch (error) {
|
|
2247
|
-
return {
|
|
2248
|
-
status: "error" as const,
|
|
2249
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
2250
|
-
};
|
|
2251
|
-
}
|
|
2252
|
-
}),
|
|
2253
|
-
|
|
2254
|
-
session: builder.session.handler(async (input) => {
|
|
2255
|
-
const sessionEffect = Effect.gen(function* () {
|
|
2256
|
-
const recorder = yield* SessionRecorder.create({
|
|
2257
|
-
ports: [3000],
|
|
2258
|
-
snapshotIntervalMs: input.snapshotInterval,
|
|
2259
|
-
headless: input.headless,
|
|
2260
|
-
baseUrl: "http://localhost:3000",
|
|
2261
|
-
timeout: input.timeout,
|
|
2262
|
-
});
|
|
2263
|
-
|
|
2264
|
-
try {
|
|
2265
|
-
yield* recorder.startServers("start");
|
|
2266
|
-
|
|
2267
|
-
yield* recorder.startRecording();
|
|
2268
|
-
|
|
2269
|
-
const browser = yield* recorder.launchBrowser();
|
|
2270
|
-
|
|
2271
|
-
if (input.flow === "login") {
|
|
2272
|
-
yield* runLoginFlow(
|
|
2273
|
-
browser,
|
|
2274
|
-
{
|
|
2275
|
-
recordEvent: (type, label, metadata) =>
|
|
2276
|
-
recorder.recordEvent(type, label, metadata).pipe(
|
|
2277
|
-
Effect.asVoid,
|
|
2278
|
-
Effect.catchAll(() => Effect.void),
|
|
2279
|
-
),
|
|
2280
|
-
},
|
|
2281
|
-
{
|
|
2282
|
-
baseUrl: "http://localhost:3000",
|
|
2283
|
-
headless: input.headless,
|
|
2284
|
-
stubWallet: input.headless,
|
|
2285
|
-
timeout: 30000,
|
|
2286
|
-
},
|
|
2287
|
-
);
|
|
2288
|
-
} else if (input.flow === "navigation" && input.routes) {
|
|
2289
|
-
yield* runNavigationFlow(
|
|
2290
|
-
browser,
|
|
2291
|
-
{
|
|
2292
|
-
recordEvent: (type, label, metadata) =>
|
|
2293
|
-
recorder.recordEvent(type, label, metadata).pipe(
|
|
2294
|
-
Effect.asVoid,
|
|
2295
|
-
Effect.catchAll(() => Effect.void),
|
|
2296
|
-
),
|
|
2297
|
-
},
|
|
2298
|
-
input.routes,
|
|
2299
|
-
"http://localhost:3000",
|
|
2300
|
-
);
|
|
2301
|
-
} else {
|
|
2302
|
-
yield* navigateTo(browser.page, "http://localhost:3000");
|
|
2303
|
-
yield* Effect.sleep("5 seconds");
|
|
2304
|
-
}
|
|
2305
|
-
|
|
2306
|
-
yield* recorder.cleanup();
|
|
2307
|
-
|
|
2308
|
-
const report = yield* recorder.stopRecording();
|
|
2309
|
-
|
|
2310
|
-
yield* recorder.exportReport(input.output, input.format);
|
|
2311
|
-
|
|
2312
|
-
console.log(formatReportSummary(report));
|
|
2313
|
-
|
|
2314
|
-
return {
|
|
2315
|
-
status: report.summary.hasLeaks
|
|
2316
|
-
? ("leaks_detected" as const)
|
|
2317
|
-
: ("completed" as const),
|
|
2318
|
-
sessionId: recorder.getSessionId(),
|
|
2319
|
-
reportPath: input.output,
|
|
2320
|
-
summary: {
|
|
2321
|
-
totalMemoryDeltaMb: report.summary.totalMemoryDeltaMb,
|
|
2322
|
-
peakMemoryMb: report.summary.peakMemoryMb,
|
|
2323
|
-
averageMemoryMb: report.summary.averageMemoryMb,
|
|
2324
|
-
processesSpawned: report.summary.processesSpawned,
|
|
2325
|
-
processesKilled: report.summary.processesKilled,
|
|
2326
|
-
orphanedProcesses: report.summary.orphanedProcesses,
|
|
2327
|
-
portsUsed: report.summary.portsUsed,
|
|
2328
|
-
portsLeaked: report.summary.portsLeaked,
|
|
2329
|
-
hasLeaks: report.summary.hasLeaks,
|
|
2330
|
-
eventCount: report.summary.eventCount,
|
|
2331
|
-
duration: report.summary.duration,
|
|
2332
|
-
},
|
|
2333
|
-
};
|
|
2334
|
-
} catch (error) {
|
|
2335
|
-
yield* recorder.cleanup();
|
|
2336
|
-
throw error;
|
|
2337
|
-
}
|
|
2338
|
-
});
|
|
2339
|
-
|
|
2340
|
-
try {
|
|
2341
|
-
return await Effect.runPromise(sessionEffect);
|
|
2342
|
-
} catch (error) {
|
|
2343
|
-
return {
|
|
2344
|
-
status: "error" as const,
|
|
2345
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
2346
|
-
};
|
|
2347
|
-
}
|
|
2348
|
-
}),
|
|
2349
|
-
}),
|
|
478
|
+
variables: z.object({
|
|
479
|
+
configPath: z.string().optional(),
|
|
480
|
+
}),
|
|
481
|
+
secrets: z.object({}),
|
|
482
|
+
contract: bosContract,
|
|
483
|
+
initialize: (config: any) =>
|
|
484
|
+
Effect.promise(async () => {
|
|
485
|
+
const configResult = await loadConfig({ path: config.variables.configPath });
|
|
486
|
+
return {
|
|
487
|
+
bosConfig: configResult?.config ?? null,
|
|
488
|
+
runtimeConfig: configResult?.runtime ?? null,
|
|
489
|
+
configDir: getProjectRoot(),
|
|
490
|
+
} satisfies BosDeps;
|
|
491
|
+
}),
|
|
492
|
+
shutdown: () => Effect.void,
|
|
493
|
+
createRouter: (deps: BosDeps, builder: any) => ({
|
|
494
|
+
config: builder.config.handler(async () => buildConfigResult(deps.bosConfig)),
|
|
495
|
+
|
|
496
|
+
pluginAdd: builder.pluginAdd.handler(async ({ input }: { input: PluginAddOptions }) => {
|
|
497
|
+
if (!deps.bosConfig) {
|
|
498
|
+
return {
|
|
499
|
+
status: "error" as const,
|
|
500
|
+
key: "",
|
|
501
|
+
error: "No bos.config.json found",
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const key = sanitizePluginKey(input.as ?? defaultPluginKey(input.source));
|
|
506
|
+
const existing = deps.bosConfig.plugins?.[key];
|
|
507
|
+
const nextPlugins = { ...(deps.bosConfig.plugins ?? {}) };
|
|
508
|
+
|
|
509
|
+
nextPlugins[key] = input.source.startsWith("local:")
|
|
510
|
+
? {
|
|
511
|
+
...(existing ?? {}),
|
|
512
|
+
development: input.source,
|
|
513
|
+
production: input.production ?? existing?.production,
|
|
514
|
+
}
|
|
515
|
+
: {
|
|
516
|
+
...(existing ?? {}),
|
|
517
|
+
production: input.production ?? input.source,
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
deps.bosConfig = {
|
|
521
|
+
...deps.bosConfig,
|
|
522
|
+
plugins: nextPlugins,
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
await saveBosConfig(deps.configDir, deps.bosConfig);
|
|
526
|
+
await refreshApiContractBridge(deps.configDir);
|
|
527
|
+
|
|
528
|
+
return {
|
|
529
|
+
status: "added" as const,
|
|
530
|
+
key,
|
|
531
|
+
development: deps.bosConfig.plugins?.[key]?.development,
|
|
532
|
+
production: deps.bosConfig.plugins?.[key]?.production,
|
|
533
|
+
};
|
|
534
|
+
}),
|
|
535
|
+
|
|
536
|
+
pluginRemove: builder.pluginRemove.handler(
|
|
537
|
+
async ({ input }: { input: PluginRemoveOptions }) => {
|
|
538
|
+
if (!deps.bosConfig) {
|
|
539
|
+
return {
|
|
540
|
+
status: "error" as const,
|
|
541
|
+
key: input.key,
|
|
542
|
+
error: "No bos.config.json found",
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (!deps.bosConfig.plugins?.[input.key]) {
|
|
547
|
+
return {
|
|
548
|
+
status: "error" as const,
|
|
549
|
+
key: input.key,
|
|
550
|
+
error: `Plugin '${input.key}' is not configured`,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const nextPlugins = { ...(deps.bosConfig.plugins ?? {}) };
|
|
555
|
+
delete nextPlugins[input.key];
|
|
556
|
+
deps.bosConfig = {
|
|
557
|
+
...deps.bosConfig,
|
|
558
|
+
plugins: Object.keys(nextPlugins).length > 0 ? nextPlugins : undefined,
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
await saveBosConfig(deps.configDir, deps.bosConfig);
|
|
562
|
+
await refreshApiContractBridge(deps.configDir);
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
status: "removed" as const,
|
|
566
|
+
key: input.key,
|
|
567
|
+
};
|
|
568
|
+
},
|
|
569
|
+
),
|
|
570
|
+
|
|
571
|
+
pluginList: builder.pluginList.handler(async () => {
|
|
572
|
+
const plugins: PluginListResult["plugins"] = listPluginAttachments(deps.bosConfig);
|
|
573
|
+
return {
|
|
574
|
+
status: "listed" as const,
|
|
575
|
+
plugins,
|
|
576
|
+
};
|
|
577
|
+
}),
|
|
578
|
+
|
|
579
|
+
pluginPublish: builder.pluginPublish.handler(
|
|
580
|
+
async ({ input }: { input: PluginPublishOptions }) => {
|
|
581
|
+
if (!deps.bosConfig) {
|
|
582
|
+
return {
|
|
583
|
+
status: "error" as const,
|
|
584
|
+
key: input.key,
|
|
585
|
+
error: "No bos.config.json found",
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const attachment = deps.bosConfig.plugins?.[input.key];
|
|
590
|
+
if (!attachment) {
|
|
591
|
+
return {
|
|
592
|
+
status: "error" as const,
|
|
593
|
+
key: input.key,
|
|
594
|
+
error: `Plugin '${input.key}' is not configured`,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const localPath = pluginLocalPath(deps.configDir, attachment);
|
|
599
|
+
if (!localPath) {
|
|
600
|
+
return {
|
|
601
|
+
status: "error" as const,
|
|
602
|
+
key: input.key,
|
|
603
|
+
error: `Plugin '${input.key}' does not have a local development path`,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const pkgPath = join(localPath, "package.json");
|
|
608
|
+
if (!(await Bun.file(pkgPath).exists())) {
|
|
609
|
+
return {
|
|
610
|
+
status: "error" as const,
|
|
611
|
+
key: input.key,
|
|
612
|
+
error: `Missing package.json at ${localPath}`,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const pkgJson = (await Bun.file(pkgPath).json()) as { scripts?: Record<string, string> };
|
|
617
|
+
const script = pkgJson.scripts?.deploy ? "deploy" : "build";
|
|
618
|
+
|
|
619
|
+
const { stdout, stderr, exitCode } = (await run("bun", ["run", script], {
|
|
620
|
+
cwd: localPath,
|
|
621
|
+
capture: true,
|
|
622
|
+
})) as { stdout: string; stderr: string; exitCode: number };
|
|
623
|
+
|
|
624
|
+
if (exitCode !== 0) {
|
|
625
|
+
if (stdout.trim()) process.stdout.write(stdout);
|
|
626
|
+
if (stderr.trim()) process.stderr.write(stderr);
|
|
627
|
+
return {
|
|
628
|
+
status: "error" as const,
|
|
629
|
+
key: input.key,
|
|
630
|
+
error: `Publish failed with exit code ${exitCode}`,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (stdout.trim()) process.stdout.write(stdout);
|
|
635
|
+
if (stderr.trim()) process.stderr.write(stderr);
|
|
636
|
+
|
|
637
|
+
const publishedUrl = extractPublishedUrl(`${stdout}\n${stderr}`);
|
|
638
|
+
if (publishedUrl) {
|
|
639
|
+
deps.bosConfig = {
|
|
640
|
+
...deps.bosConfig,
|
|
641
|
+
plugins: {
|
|
642
|
+
...(deps.bosConfig.plugins ?? {}),
|
|
643
|
+
[input.key]: {
|
|
644
|
+
...(deps.bosConfig.plugins?.[input.key] ?? {}),
|
|
645
|
+
production: publishedUrl,
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
};
|
|
649
|
+
await saveBosConfig(deps.configDir, deps.bosConfig);
|
|
650
|
+
await refreshApiContractBridge(deps.configDir);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return {
|
|
654
|
+
status: "published" as const,
|
|
655
|
+
key: input.key,
|
|
656
|
+
path: localPath,
|
|
657
|
+
script,
|
|
658
|
+
production: publishedUrl ?? attachment.production,
|
|
659
|
+
};
|
|
660
|
+
},
|
|
661
|
+
),
|
|
662
|
+
|
|
663
|
+
dev: builder.dev.handler(async ({ input }: { input: DevOptions }) => {
|
|
664
|
+
const localPackages = detectLocalPackages(
|
|
665
|
+
deps.bosConfig ?? undefined,
|
|
666
|
+
deps.runtimeConfig ?? undefined,
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
const appConfig = buildAppConfig({
|
|
670
|
+
host: localPackages.includes("host") ? (input.host as string) : "remote",
|
|
671
|
+
ui: localPackages.includes("ui") ? (input.ui as string) : "remote",
|
|
672
|
+
api: localPackages.includes("api") ? (input.api as string) : "remote",
|
|
673
|
+
proxy: input.proxy,
|
|
674
|
+
ssr: input.ssr,
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
const sharedSync = await syncAndGenerateSharedUi({
|
|
678
|
+
configDir: deps.configDir,
|
|
679
|
+
hostMode: appConfig.host,
|
|
680
|
+
bosConfig: deps.bosConfig ?? undefined,
|
|
681
|
+
});
|
|
682
|
+
if (sharedSync.catalogChanged) {
|
|
683
|
+
await run("bun", ["install"], { cwd: deps.configDir });
|
|
684
|
+
}
|
|
685
|
+
if (
|
|
686
|
+
(appConfig.api === "local" && !appConfig.proxy) ||
|
|
687
|
+
localPackages.some((pkg) => pkg.startsWith("plugin:"))
|
|
688
|
+
) {
|
|
689
|
+
await buildEveryPluginQuietly(deps.configDir);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
await buildEverythingDevQuietly(deps.configDir);
|
|
693
|
+
|
|
694
|
+
const refreshed = await loadConfig({ cwd: deps.configDir });
|
|
695
|
+
deps.bosConfig = refreshed?.config ?? deps.bosConfig;
|
|
696
|
+
deps.runtimeConfig = refreshed?.runtime ?? deps.runtimeConfig;
|
|
697
|
+
|
|
698
|
+
if (!deps.bosConfig) {
|
|
699
|
+
return {
|
|
700
|
+
status: "error" as const,
|
|
701
|
+
description: "No bos.config.json found",
|
|
702
|
+
processes: [],
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (appConfig.proxy && !resolveProxyUrl(deps.bosConfig)) {
|
|
707
|
+
return {
|
|
708
|
+
status: "error" as const,
|
|
709
|
+
description: "No valid proxy URL configured in bos.config.json",
|
|
710
|
+
processes: [],
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const refreshedLocalPackages = detectLocalPackages(
|
|
715
|
+
deps.bosConfig ?? undefined,
|
|
716
|
+
deps.runtimeConfig ?? undefined,
|
|
717
|
+
);
|
|
718
|
+
const processes = determineProcesses(appConfig, refreshedLocalPackages, deps.runtimeConfig);
|
|
719
|
+
const env = await buildEnvVars(appConfig, deps.bosConfig);
|
|
720
|
+
const hostPort = input.port ?? getHostDevelopmentPort(deps.bosConfig.app.host.development);
|
|
721
|
+
const developmentRuntime = buildRuntimeConfig(deps.bosConfig, {
|
|
722
|
+
uiSource: appConfig.ui,
|
|
723
|
+
apiSource: appConfig.api,
|
|
724
|
+
hostUrl: `http://localhost:${hostPort}`,
|
|
725
|
+
proxy: env.API_PROXY,
|
|
726
|
+
env: "development",
|
|
727
|
+
plugins: deps.runtimeConfig?.plugins,
|
|
728
|
+
});
|
|
729
|
+
const runtimeConfig = await prepareDevelopmentRuntimeConfig(developmentRuntime, {
|
|
730
|
+
hostPort,
|
|
731
|
+
ssr: appConfig.ssr,
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
await syncApiContractBridge({
|
|
735
|
+
configDir: deps.configDir,
|
|
736
|
+
runtimeConfig: runtimeConfig,
|
|
737
|
+
apiBaseUrl: runtimeConfig.api.url,
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
const orchestrator: AppOrchestrator = {
|
|
741
|
+
packages: processes,
|
|
742
|
+
env,
|
|
743
|
+
description: buildDescription(appConfig),
|
|
744
|
+
appConfig,
|
|
745
|
+
bosConfig: deps.bosConfig,
|
|
746
|
+
runtimeConfig,
|
|
747
|
+
port: parsePort(runtimeConfig.hostUrl),
|
|
748
|
+
interactive: input.interactive,
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
startApp(orchestrator);
|
|
752
|
+
|
|
753
|
+
return {
|
|
754
|
+
status: "started" as const,
|
|
755
|
+
description: orchestrator.description,
|
|
756
|
+
processes,
|
|
757
|
+
};
|
|
758
|
+
}),
|
|
759
|
+
|
|
760
|
+
start: builder.start.handler(async ({ input }: { input: StartOptions }) => {
|
|
761
|
+
let remoteConfig: BosConfig | null = null;
|
|
762
|
+
|
|
763
|
+
if (input.account && input.domain) {
|
|
764
|
+
remoteConfig = await fetchPublishedConfig(input.account, input.domain);
|
|
765
|
+
if (!remoteConfig) {
|
|
766
|
+
return {
|
|
767
|
+
status: "error" as const,
|
|
768
|
+
url: "",
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const config = remoteConfig || deps.bosConfig;
|
|
774
|
+
if (!config) {
|
|
775
|
+
return {
|
|
776
|
+
status: "error" as const,
|
|
777
|
+
url: "",
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const port = input.port ?? getHostDevelopmentPort(config.app.host.development);
|
|
782
|
+
const appConfig: AppConfig = { host: "remote", ui: "remote", api: "remote" };
|
|
783
|
+
const env = await buildEnvVars(appConfig, config);
|
|
784
|
+
const isStaging = input.env === "staging";
|
|
785
|
+
const runtimePlugins = remoteConfig
|
|
786
|
+
? await buildRuntimePluginsForConfig(config, deps.configDir, "production")
|
|
787
|
+
: deps.runtimeConfig?.plugins;
|
|
788
|
+
const runtimeConfig = buildRuntimeConfig(config, {
|
|
789
|
+
uiSource: "remote",
|
|
790
|
+
apiSource: "remote",
|
|
791
|
+
hostUrl: `http://localhost:${port}`,
|
|
792
|
+
env: "production",
|
|
793
|
+
plugins: runtimePlugins,
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
await syncApiContractBridge({
|
|
797
|
+
configDir: deps.configDir,
|
|
798
|
+
runtimeConfig: runtimeConfig,
|
|
799
|
+
apiBaseUrl: runtimeConfig.api.url,
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
const stagingEnvVars: Record<string, string> = isStaging
|
|
803
|
+
? { GATEWAY_DOMAIN: config.staging?.domain ?? config.domain ?? "" }
|
|
804
|
+
: {};
|
|
805
|
+
|
|
806
|
+
const orchestrator: AppOrchestrator = {
|
|
807
|
+
packages: ["host"],
|
|
808
|
+
env: {
|
|
809
|
+
NODE_ENV: "production",
|
|
810
|
+
...env,
|
|
811
|
+
...stagingEnvVars,
|
|
812
|
+
},
|
|
813
|
+
description: `${isStaging ? "Staging" : "Production"} Mode (${config.account})`,
|
|
814
|
+
appConfig,
|
|
815
|
+
bosConfig: config,
|
|
816
|
+
runtimeConfig,
|
|
817
|
+
port,
|
|
818
|
+
interactive: input.interactive,
|
|
819
|
+
noLogs: true,
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
startApp(orchestrator);
|
|
823
|
+
return {
|
|
824
|
+
status: "running" as const,
|
|
825
|
+
url: `http://localhost:${port}`,
|
|
826
|
+
};
|
|
827
|
+
}),
|
|
828
|
+
|
|
829
|
+
build: builder.build.handler(async ({ input }: { input: BuildOptions }) => {
|
|
830
|
+
if (!deps.bosConfig) {
|
|
831
|
+
return {
|
|
832
|
+
status: "error" as const,
|
|
833
|
+
built: [],
|
|
834
|
+
skipped: [],
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const targets = selectWorkspaceTargets(input.packages, deps.bosConfig);
|
|
839
|
+
if (targets.length === 0) {
|
|
840
|
+
return {
|
|
841
|
+
status: "error" as const,
|
|
842
|
+
built: [],
|
|
843
|
+
skipped: [],
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const runtimeConfig = buildRuntimeConfig(deps.bosConfig, {
|
|
848
|
+
uiSource: deps.bosConfig.app.ui?.development ? "local" : "remote",
|
|
849
|
+
apiSource: deps.bosConfig.app.api?.development ? "local" : "remote",
|
|
850
|
+
hostUrl: resolveDevelopmentHostUrl(deps.bosConfig.app.host.development),
|
|
851
|
+
env: "development",
|
|
852
|
+
plugins: deps.runtimeConfig?.plugins,
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
await syncApiContractBridge({
|
|
856
|
+
configDir: deps.configDir,
|
|
857
|
+
runtimeConfig,
|
|
858
|
+
apiBaseUrl: runtimeConfig.api.url,
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
const { built, skipped } = await buildWorkspaceTargets({
|
|
862
|
+
configDir: deps.configDir,
|
|
863
|
+
bosConfig: deps.bosConfig,
|
|
864
|
+
runtimeConfig: runtimeConfig,
|
|
865
|
+
targets,
|
|
866
|
+
deploy: input.deploy,
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
if (built.length === 0) {
|
|
870
|
+
return {
|
|
871
|
+
status: "error" as const,
|
|
872
|
+
built: [],
|
|
873
|
+
skipped,
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
return {
|
|
878
|
+
status: "success" as const,
|
|
879
|
+
built,
|
|
880
|
+
skipped,
|
|
881
|
+
deployed: input.deploy,
|
|
882
|
+
};
|
|
883
|
+
}),
|
|
884
|
+
|
|
885
|
+
publish: builder.publish.handler(async ({ input }: { input: PublishOptions }) => {
|
|
886
|
+
if (!deps.bosConfig) {
|
|
887
|
+
return {
|
|
888
|
+
status: "error" as const,
|
|
889
|
+
registryUrl: "",
|
|
890
|
+
error: "No bos.config.json found",
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const account = deps.bosConfig.account;
|
|
895
|
+
const gateway = deps.bosConfig.domain;
|
|
896
|
+
if (!gateway) {
|
|
897
|
+
return {
|
|
898
|
+
status: "error" as const,
|
|
899
|
+
registryUrl: "",
|
|
900
|
+
error: "bos.config.json must define domain to publish",
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const network = input.network ?? getNetworkIdForAccount(account);
|
|
905
|
+
const bosUrl = `bos://${account}/${gateway}`;
|
|
906
|
+
const registryUrl = buildRegistryConfigUrlForNetwork(network, account, gateway);
|
|
907
|
+
const targets = selectWorkspaceTargets(input.packages, deps.bosConfig);
|
|
908
|
+
|
|
909
|
+
let publishConfig = deps.bosConfig;
|
|
910
|
+
let built: string[] | undefined;
|
|
911
|
+
let skipped: string[] | undefined;
|
|
912
|
+
|
|
913
|
+
if (input.dryRun) {
|
|
914
|
+
return {
|
|
915
|
+
status: "dry-run" as const,
|
|
916
|
+
registryUrl,
|
|
917
|
+
built,
|
|
918
|
+
skipped,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if (input.deploy) {
|
|
923
|
+
const result = await buildWorkspaceTargets({
|
|
924
|
+
configDir: deps.configDir,
|
|
925
|
+
bosConfig: deps.bosConfig,
|
|
926
|
+
runtimeConfig: deps.runtimeConfig,
|
|
927
|
+
targets,
|
|
928
|
+
deploy: true,
|
|
929
|
+
});
|
|
930
|
+
built = result.built;
|
|
931
|
+
skipped = result.skipped;
|
|
932
|
+
|
|
933
|
+
const refreshed = await loadConfig({ cwd: deps.configDir });
|
|
934
|
+
if (refreshed?.config) {
|
|
935
|
+
deps.bosConfig = refreshed.config;
|
|
936
|
+
deps.runtimeConfig = refreshed.runtime;
|
|
937
|
+
publishConfig = refreshed.config;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const payload = JSON.stringify({
|
|
942
|
+
[`apps/${account}/${gateway}/bos.config.json`]: JSON.stringify(publishConfig),
|
|
943
|
+
});
|
|
944
|
+
const argsBase64 = Buffer.from(payload).toString("base64");
|
|
945
|
+
const privateKey =
|
|
946
|
+
input.privateKey || process.env.NEAR_PRIVATE_KEY || process.env.BOS_NEAR_PRIVATE_KEY;
|
|
947
|
+
|
|
948
|
+
try {
|
|
949
|
+
await Effect.runPromise(ensureNearCli);
|
|
950
|
+
let txHash: string | undefined;
|
|
951
|
+
|
|
952
|
+
try {
|
|
953
|
+
const tx = await Effect.runPromise(
|
|
954
|
+
executeTransaction({
|
|
955
|
+
account,
|
|
956
|
+
contract: getRegistryNamespaceForNetwork(network),
|
|
957
|
+
method: "__fastdata_kv",
|
|
958
|
+
argsBase64,
|
|
959
|
+
network,
|
|
960
|
+
privateKey,
|
|
961
|
+
gas: "300Tgas",
|
|
962
|
+
deposit: "0NEAR",
|
|
963
|
+
}),
|
|
964
|
+
);
|
|
965
|
+
txHash = tx.txHash;
|
|
966
|
+
} catch (error) {
|
|
967
|
+
txHash = extractTransactionHash(error);
|
|
968
|
+
|
|
969
|
+
if (!txHash) {
|
|
970
|
+
throw error;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
const verifiedConfig = await fetchBosConfigFromFastKv<BosConfig>(bosUrl);
|
|
974
|
+
if (JSON.stringify(verifiedConfig) !== JSON.stringify(publishConfig)) {
|
|
975
|
+
throw error;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
return {
|
|
980
|
+
status: "published" as const,
|
|
981
|
+
registryUrl,
|
|
982
|
+
txHash,
|
|
983
|
+
built,
|
|
984
|
+
skipped,
|
|
985
|
+
};
|
|
986
|
+
} catch (error) {
|
|
987
|
+
return {
|
|
988
|
+
status: "error" as const,
|
|
989
|
+
registryUrl,
|
|
990
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
991
|
+
built,
|
|
992
|
+
skipped,
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
}),
|
|
996
|
+
|
|
997
|
+
keyPublish: builder.keyPublish.handler(async ({ input }: { input: KeyPublishOptions }) => {
|
|
998
|
+
if (!deps.bosConfig) {
|
|
999
|
+
return {
|
|
1000
|
+
status: "error" as const,
|
|
1001
|
+
account: "",
|
|
1002
|
+
network: "mainnet" as const,
|
|
1003
|
+
contract: "",
|
|
1004
|
+
allowance: input.allowance,
|
|
1005
|
+
functionNames: PUBLISH_FUNCTION_NAMES,
|
|
1006
|
+
error: "No bos.config.json found",
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const account = deps.bosConfig.account;
|
|
1011
|
+
const network = getNetworkIdForAccount(account);
|
|
1012
|
+
const contract = getRegistryNamespaceForAccount(account);
|
|
1013
|
+
try {
|
|
1014
|
+
await Effect.runPromise(ensureNearCli);
|
|
1015
|
+
const keyPair = await addFunctionCallAccessKey({
|
|
1016
|
+
account,
|
|
1017
|
+
contract,
|
|
1018
|
+
allowance: input.allowance,
|
|
1019
|
+
functionNames: PUBLISH_FUNCTION_NAMES,
|
|
1020
|
+
network,
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
return {
|
|
1024
|
+
status: "published" as const,
|
|
1025
|
+
account,
|
|
1026
|
+
network,
|
|
1027
|
+
contract,
|
|
1028
|
+
allowance: input.allowance,
|
|
1029
|
+
functionNames: PUBLISH_FUNCTION_NAMES,
|
|
1030
|
+
publicKey: keyPair.publicKey,
|
|
1031
|
+
privateKey: keyPair.privateKey,
|
|
1032
|
+
};
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
return {
|
|
1035
|
+
status: "error" as const,
|
|
1036
|
+
account,
|
|
1037
|
+
network,
|
|
1038
|
+
contract,
|
|
1039
|
+
allowance: input.allowance,
|
|
1040
|
+
functionNames: PUBLISH_FUNCTION_NAMES,
|
|
1041
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
}),
|
|
1045
|
+
|
|
1046
|
+
init: builder.init.handler(async ({ input }: { input: InitOptions }) => {
|
|
1047
|
+
try {
|
|
1048
|
+
let account = input.account;
|
|
1049
|
+
let gateway = input.gateway;
|
|
1050
|
+
let destination = input.destination;
|
|
1051
|
+
let name = input.name;
|
|
1052
|
+
let domain = input.domain;
|
|
1053
|
+
let withHost = input.withHost;
|
|
1054
|
+
|
|
1055
|
+
if (!account || !gateway) {
|
|
1056
|
+
if (input.noInteractive) {
|
|
1057
|
+
return {
|
|
1058
|
+
status: "error" as const,
|
|
1059
|
+
destination: "",
|
|
1060
|
+
parentAccount: account ?? "",
|
|
1061
|
+
parentGateway: gateway ?? "",
|
|
1062
|
+
name: input.name,
|
|
1063
|
+
domain: input.domain,
|
|
1064
|
+
extends: account && gateway ? `bos://${account}/${gateway}` : "",
|
|
1065
|
+
filesCopied: 0,
|
|
1066
|
+
error:
|
|
1067
|
+
"account and gateway are required (use --no-interactive to skip prompts and provide them as positional args)",
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
const prompted = await promptInitOptions({
|
|
1072
|
+
account,
|
|
1073
|
+
gateway,
|
|
1074
|
+
destination,
|
|
1075
|
+
name,
|
|
1076
|
+
domain,
|
|
1077
|
+
withHost,
|
|
1078
|
+
});
|
|
1079
|
+
account = prompted.account;
|
|
1080
|
+
gateway = prompted.gateway;
|
|
1081
|
+
destination = prompted.destination;
|
|
1082
|
+
name = prompted.name;
|
|
1083
|
+
domain = prompted.domain;
|
|
1084
|
+
withHost = prompted.withHost;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
destination = destination || gateway;
|
|
1088
|
+
|
|
1089
|
+
const { sourceDir, cleanup } = await resolveSourceDir({
|
|
1090
|
+
account,
|
|
1091
|
+
gateway,
|
|
1092
|
+
source: input.source,
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
try {
|
|
1096
|
+
const patterns = await readTemplatekeep(sourceDir);
|
|
1097
|
+
if (patterns.length === 0) {
|
|
1098
|
+
return {
|
|
1099
|
+
status: "error" as const,
|
|
1100
|
+
destination,
|
|
1101
|
+
parentAccount: account,
|
|
1102
|
+
parentGateway: gateway,
|
|
1103
|
+
name,
|
|
1104
|
+
domain,
|
|
1105
|
+
extends: `bos://${account}/${gateway}`,
|
|
1106
|
+
filesCopied: 0,
|
|
1107
|
+
error: "No .templatekeep found in template source",
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const filesCopied = await copyFilteredFiles(sourceDir, destination, patterns, {
|
|
1112
|
+
withHost,
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
await personalizeConfig(destination, {
|
|
1116
|
+
parentAccount: account,
|
|
1117
|
+
parentGateway: gateway,
|
|
1118
|
+
name: name || account,
|
|
1119
|
+
domain: domain || gateway,
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
if (!input.noInstall) {
|
|
1123
|
+
await runBunInstall(destination);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
return {
|
|
1127
|
+
status: "initialized" as const,
|
|
1128
|
+
destination: resolve(destination),
|
|
1129
|
+
parentAccount: account,
|
|
1130
|
+
parentGateway: gateway,
|
|
1131
|
+
name,
|
|
1132
|
+
domain,
|
|
1133
|
+
extends: `bos://${account}/${gateway}`,
|
|
1134
|
+
filesCopied,
|
|
1135
|
+
};
|
|
1136
|
+
} finally {
|
|
1137
|
+
await cleanup();
|
|
1138
|
+
}
|
|
1139
|
+
} catch (error) {
|
|
1140
|
+
return {
|
|
1141
|
+
status: "error" as const,
|
|
1142
|
+
destination: input.destination ?? input.gateway ?? "",
|
|
1143
|
+
parentAccount: input.account ?? "",
|
|
1144
|
+
parentGateway: input.gateway ?? "",
|
|
1145
|
+
name: input.name,
|
|
1146
|
+
domain: input.domain,
|
|
1147
|
+
extends: input.account && input.gateway ? `bos://${input.account}/${input.gateway}` : "",
|
|
1148
|
+
filesCopied: 0,
|
|
1149
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
}),
|
|
1153
|
+
}),
|
|
2350
1154
|
});
|
|
1155
|
+
|
|
1156
|
+
function extractTransactionHash(error: unknown) {
|
|
1157
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1158
|
+
const match =
|
|
1159
|
+
message.match(/Transaction ID:\s*([A-Za-z0-9]+)/i) ||
|
|
1160
|
+
message.match(/([A-HJ-NP-Za-km-z1-9]{43,44})/);
|
|
1161
|
+
|
|
1162
|
+
return match?.[1];
|
|
1163
|
+
}
|