everything-dev 1.27.0 → 1.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/cli/infra.cjs +1 -1
  2. package/dist/cli/infra.mjs +1 -1
  3. package/dist/cli/init.cjs +34 -9
  4. package/dist/cli/init.cjs.map +1 -1
  5. package/dist/cli/init.d.cts +2 -1
  6. package/dist/cli/init.d.cts.map +1 -1
  7. package/dist/cli/init.d.mts +2 -1
  8. package/dist/cli/init.d.mts.map +1 -1
  9. package/dist/cli/init.mjs +34 -9
  10. package/dist/cli/init.mjs.map +1 -1
  11. package/dist/cli/prompts.cjs +28 -24
  12. package/dist/cli/prompts.cjs.map +1 -1
  13. package/dist/cli/prompts.mjs +27 -24
  14. package/dist/cli/prompts.mjs.map +1 -1
  15. package/dist/cli/sync.cjs +40 -3
  16. package/dist/cli/sync.cjs.map +1 -1
  17. package/dist/cli/sync.mjs +40 -3
  18. package/dist/cli/sync.mjs.map +1 -1
  19. package/dist/cli.cjs +187 -12
  20. package/dist/cli.cjs.map +1 -1
  21. package/dist/cli.mjs +186 -11
  22. package/dist/cli.mjs.map +1 -1
  23. package/dist/config.cjs +1 -0
  24. package/dist/config.cjs.map +1 -1
  25. package/dist/config.d.cts.map +1 -1
  26. package/dist/config.d.mts.map +1 -1
  27. package/dist/config.mjs +1 -0
  28. package/dist/config.mjs.map +1 -1
  29. package/dist/contract.cjs +1 -1
  30. package/dist/contract.cjs.map +1 -1
  31. package/dist/contract.d.cts +38 -34
  32. package/dist/contract.d.cts.map +1 -1
  33. package/dist/contract.d.mts +38 -34
  34. package/dist/contract.d.mts.map +1 -1
  35. package/dist/contract.mjs +1 -0
  36. package/dist/contract.mjs.map +1 -1
  37. package/dist/dev-session.cjs +0 -1
  38. package/dist/dev-session.mjs +1 -1
  39. package/dist/index.cjs +0 -2
  40. package/dist/index.d.cts +2 -2
  41. package/dist/index.d.mts +2 -2
  42. package/dist/index.mjs +0 -1
  43. package/dist/near-cli.cjs +1 -1
  44. package/dist/near-cli.mjs +1 -1
  45. package/dist/orchestrator.cjs +1 -1
  46. package/dist/orchestrator.mjs +1 -1
  47. package/dist/plugin.cjs +183 -151
  48. package/dist/plugin.cjs.map +1 -1
  49. package/dist/plugin.d.cts +67 -34
  50. package/dist/plugin.d.cts.map +1 -1
  51. package/dist/plugin.d.mts +66 -34
  52. package/dist/plugin.d.mts.map +1 -1
  53. package/dist/plugin.mjs +173 -142
  54. package/dist/plugin.mjs.map +1 -1
  55. package/dist/service-descriptor.d.cts +34 -0
  56. package/dist/service-descriptor.d.cts.map +1 -0
  57. package/dist/service-descriptor.d.mts +36 -0
  58. package/dist/service-descriptor.d.mts.map +1 -0
  59. package/dist/types.d.cts +2 -2
  60. package/dist/types.d.mts +2 -2
  61. package/dist/utils/run.cjs +9 -20
  62. package/dist/utils/run.cjs.map +1 -1
  63. package/dist/utils/run.mjs +9 -20
  64. package/dist/utils/run.mjs.map +1 -1
  65. package/package.json +2 -2
  66. package/src/api-contract.ts +0 -623
  67. package/src/app.ts +0 -193
  68. package/src/cli/catalog.ts +0 -49
  69. package/src/cli/framework-version.ts +0 -61
  70. package/src/cli/help.ts +0 -13
  71. package/src/cli/infra.ts +0 -190
  72. package/src/cli/init.ts +0 -1145
  73. package/src/cli/parse.ts +0 -147
  74. package/src/cli/prompts.ts +0 -135
  75. package/src/cli/snapshot.ts +0 -46
  76. package/src/cli/status.ts +0 -99
  77. package/src/cli/sync.ts +0 -429
  78. package/src/cli/timing.ts +0 -63
  79. package/src/cli/upgrade.ts +0 -869
  80. package/src/cli.ts +0 -516
  81. package/src/components/dev-view.tsx +0 -352
  82. package/src/components/streaming-view.ts +0 -177
  83. package/src/config.ts +0 -893
  84. package/src/contract.meta.ts +0 -140
  85. package/src/contract.ts +0 -326
  86. package/src/dev-logs.ts +0 -92
  87. package/src/dev-session.ts +0 -283
  88. package/src/fastkv.ts +0 -181
  89. package/src/index.ts +0 -8
  90. package/src/integrity.ts +0 -138
  91. package/src/internal/manifest-normalizer.ts +0 -290
  92. package/src/merge.ts +0 -187
  93. package/src/mf.ts +0 -147
  94. package/src/near-cli.ts +0 -259
  95. package/src/network.ts +0 -3
  96. package/src/orchestrator.ts +0 -493
  97. package/src/plugin.ts +0 -1799
  98. package/src/sdk.ts +0 -14
  99. package/src/service-descriptor.ts +0 -281
  100. package/src/shared.ts +0 -249
  101. package/src/sidebar.ts +0 -140
  102. package/src/types.ts +0 -330
  103. package/src/ui/head.ts +0 -83
  104. package/src/ui/index.ts +0 -5
  105. package/src/ui/metadata.ts +0 -95
  106. package/src/ui/router.ts +0 -88
  107. package/src/ui/runtime.ts +0 -42
  108. package/src/ui/types.ts +0 -65
  109. package/src/utils/banner.ts +0 -21
  110. package/src/utils/linkify.ts +0 -11
  111. package/src/utils/path-match.ts +0 -16
  112. package/src/utils/run.ts +0 -31
  113. package/src/utils/save-config.ts +0 -20
  114. package/src/utils/theme.ts +0 -39
package/src/config.ts DELETED
@@ -1,893 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { dirname, isAbsolute, join, resolve } from "node:path";
3
- import { fetchBosConfigFromFastKv } from "./fastkv";
4
- import {
5
- type BosEnv,
6
- bosConfigMerger,
7
- isPlainObject,
8
- mergeBosConfigWithExtends,
9
- type ResolvedConfigMeta,
10
- rebuildOrderedConfig,
11
- resolveExtendsRef,
12
- } from "./merge";
13
- import { getNetworkIdForAccount } from "./network";
14
- import type {
15
- BosConfig,
16
- BosConfigInput,
17
- BosPluginRef,
18
- ExtendsConfig,
19
- PluginEntryValue,
20
- RuntimeConfig,
21
- RuntimePluginConfig,
22
- } from "./types";
23
- import { BosConfigSchema } from "./types";
24
-
25
- const LOCAL_PREFIX = "local:";
26
- const DEFAULT_HOST_PORT = 3000;
27
- const RESOLVED_CONFIG_FILENAME = "bos.resolved-config.json";
28
-
29
- interface RuntimeTarget {
30
- source: "local" | "remote";
31
- url: string;
32
- localPath?: string;
33
- port?: number;
34
- }
35
-
36
- let cachedConfig: BosConfig | null = null;
37
- let projectRoot: string | null = null;
38
-
39
- export function clearConfigCache(): void {
40
- cachedConfig = null;
41
- projectRoot = null;
42
- }
43
-
44
- export function findConfigPath(cwd?: string): string | null {
45
- let dir = cwd ?? process.cwd();
46
- while (dir !== "/") {
47
- const configPath = join(dir, "bos.config.json");
48
- if (existsSync(configPath)) {
49
- return configPath;
50
- }
51
- dir = dirname(dir);
52
- }
53
- return null;
54
- }
55
-
56
- export function getConfig(): BosConfig | null {
57
- return cachedConfig;
58
- }
59
-
60
- export function getProjectRoot(): string {
61
- if (!projectRoot) {
62
- throw new Error("Config not loaded. Call loadConfig() first.");
63
- }
64
- return projectRoot;
65
- }
66
-
67
- export interface ConfigResult {
68
- config: BosConfig;
69
- runtime: RuntimeConfig;
70
- source: {
71
- path: string;
72
- extended?: string[];
73
- remote?: boolean;
74
- };
75
- }
76
-
77
- export interface ResolvedComposableReference {
78
- entry: BosPluginRef;
79
- providerBaseDir: string;
80
- targetPath: string;
81
- associatedUi?: Record<string, unknown>;
82
- }
83
-
84
- interface ParsedExtendsTarget {
85
- configPath: string;
86
- targetPath?: string;
87
- }
88
-
89
- export async function loadConfig(options?: {
90
- cwd?: string;
91
- path?: string;
92
- env?: BosEnv;
93
- }): Promise<ConfigResult | null> {
94
- const configPath = options?.path ?? findConfigPath(options?.cwd);
95
- if (!configPath) {
96
- projectRoot = options?.cwd ?? process.cwd();
97
- return null;
98
- }
99
-
100
- const baseDir = dirname(configPath);
101
- const env = options?.env ?? "development";
102
- const runtimeEnv: BosEnv = env === "staging" ? "production" : env;
103
-
104
- try {
105
- const extendedChain: string[] = [];
106
- const parsed = await resolveConfigWithExtends(
107
- configPath,
108
- baseDir,
109
- new Set(),
110
- extendedChain,
111
- env,
112
- );
113
- const config = await resolveRootComposableEntries(
114
- BosConfigSchema.parse(parsed),
115
- baseDir,
116
- runtimeEnv,
117
- );
118
-
119
- cachedConfig = config;
120
- projectRoot = baseDir;
121
-
122
- const pluginRuntime = await resolveRuntimePlugins(config.plugins ?? {}, baseDir, runtimeEnv);
123
- const runtime = buildRuntimeConfig(config, baseDir, runtimeEnv, {
124
- plugins: pluginRuntime,
125
- });
126
-
127
- return {
128
- config,
129
- runtime,
130
- source: {
131
- path: configPath,
132
- extended: extendedChain.length > 0 ? extendedChain : undefined,
133
- remote: extendedChain.some((entry) => entry.startsWith("bos://")),
134
- },
135
- };
136
- } catch (error) {
137
- throw new Error(`Failed to load config from ${configPath}: ${error}`);
138
- }
139
- }
140
-
141
- export async function loadBosConfig(options?: {
142
- cwd?: string;
143
- path?: string;
144
- env?: BosEnv;
145
- }): Promise<RuntimeConfig> {
146
- const result = await loadConfig(options);
147
- if (!result) {
148
- throw new Error("No bos.config.json found");
149
- }
150
-
151
- return result.runtime;
152
- }
153
-
154
- export async function buildRuntimePluginsForConfig(
155
- config: BosConfig,
156
- baseDir: string,
157
- env: BosEnv,
158
- ): Promise<Record<string, RuntimePluginConfig> | undefined> {
159
- const plugins = await resolveRuntimePlugins(config.plugins ?? {}, baseDir, env);
160
- return Object.keys(plugins).length > 0 ? plugins : undefined;
161
- }
162
-
163
- async function resolveRootComposableEntries(
164
- config: BosConfig,
165
- baseDir: string,
166
- env: BosEnv,
167
- ): Promise<BosConfig> {
168
- const resolvedApi = await resolveComposableReference(
169
- config.app.api as BosPluginRef,
170
- baseDir,
171
- env,
172
- "app.api",
173
- );
174
- const resolvedAuth = config.app.auth
175
- ? await resolveComposableReference(config.app.auth as BosPluginRef, baseDir, env, "app.auth")
176
- : undefined;
177
-
178
- return {
179
- ...config,
180
- app: {
181
- ...config.app,
182
- api: resolvedApi.entry,
183
- auth: resolvedAuth?.entry,
184
- },
185
- };
186
- }
187
-
188
- export function getResolvedConfigPath(configDir: string): string {
189
- return join(configDir, ".bos", RESOLVED_CONFIG_FILENAME);
190
- }
191
-
192
- export function loadResolvedConfig(configDir: string): BosConfig | null {
193
- const resolvedPath = getResolvedConfigPath(configDir);
194
- if (!existsSync(resolvedPath)) return null;
195
- try {
196
- const raw = JSON.parse(readFileSync(resolvedPath, "utf-8"));
197
- if (!isPlainObject(raw)) return null;
198
- const { _resolved, ...configData } = raw;
199
- return BosConfigSchema.parse(configData);
200
- } catch {
201
- return null;
202
- }
203
- }
204
-
205
- export function writeResolvedConfig(
206
- configDir: string,
207
- config: BosConfig,
208
- env: BosEnv,
209
- extendsChain?: string[],
210
- source?: string,
211
- ): void {
212
- const resolvedPath = getResolvedConfigPath(configDir);
213
- const resolvedDir = dirname(resolvedPath);
214
- if (!existsSync(resolvedDir)) {
215
- mkdirSync(resolvedDir, { recursive: true });
216
- }
217
-
218
- const ordered = rebuildOrderedConfig(config);
219
- const meta: ResolvedConfigMeta = {
220
- env,
221
- resolvedAt: new Date().toISOString(),
222
- extendsChain: extendsChain ?? [],
223
- ...(source ? { source } : {}),
224
- };
225
- const output = {
226
- _resolved: meta,
227
- ...ordered,
228
- };
229
-
230
- const content = `${JSON.stringify(output, null, 2)}\n`;
231
- try {
232
- if (readFileSync(resolvedPath, "utf-8") === content) return;
233
- } catch {
234
- // file doesn't exist yet
235
- }
236
- writeFileSync(resolvedPath, content);
237
- }
238
-
239
- export function resolveBosConfigPath(configDir: string): string {
240
- const resolvedPath = getResolvedConfigPath(configDir);
241
- if (existsSync(resolvedPath)) return resolvedPath;
242
- return join(configDir, "bos.config.json");
243
- }
244
-
245
- export function readBosConfigForBuild(configDir: string): Record<string, unknown> {
246
- const resolvedPath = getResolvedConfigPath(configDir);
247
- if (existsSync(resolvedPath)) {
248
- try {
249
- const raw = JSON.parse(readFileSync(resolvedPath, "utf-8"));
250
- if (isPlainObject(raw)) {
251
- const { _resolved, ...configData } = raw;
252
- return configData as Record<string, unknown>;
253
- }
254
- } catch {}
255
- }
256
- const bosConfigPath = join(configDir, "bos.config.json");
257
- return JSON.parse(readFileSync(bosConfigPath, "utf-8")) as Record<string, unknown>;
258
- }
259
-
260
- function parseExtendsTarget(ref: string): ParsedExtendsTarget {
261
- const hashIndex = ref.indexOf("#");
262
- if (hashIndex === -1) {
263
- return { configPath: ref };
264
- }
265
-
266
- const configPath = ref.slice(0, hashIndex);
267
- const targetPath = ref.slice(hashIndex + 1);
268
- return {
269
- configPath,
270
- targetPath: targetPath.length > 0 ? targetPath : undefined,
271
- };
272
- }
273
-
274
- function getConfigBaseDir(configPath: string, baseDir: string): string {
275
- if (configPath.startsWith("bos://")) return baseDir;
276
- return dirname(isAbsolute(configPath) ? configPath : resolve(baseDir, configPath));
277
- }
278
-
279
- function asComposableEntry(value: unknown): BosPluginRef {
280
- if (typeof value === "string") {
281
- return { extends: value };
282
- }
283
- if (!isPlainObject(value)) {
284
- throw new Error(`Expected config entry object, received ${typeof value}`);
285
- }
286
- return value as BosPluginRef;
287
- }
288
-
289
- function getTargetedEntry(config: BosConfigInput, targetPath: string): BosPluginRef {
290
- if (targetPath === "app.api") {
291
- return asComposableEntry(config.app?.api);
292
- }
293
-
294
- if (targetPath === "app.auth") {
295
- return asComposableEntry(config.app?.auth);
296
- }
297
-
298
- if (targetPath.startsWith("plugins.")) {
299
- const pluginId = targetPath.slice("plugins.".length);
300
- if (pluginId.length === 0) {
301
- throw new Error(`Invalid plugin target path: ${targetPath}`);
302
- }
303
- return asComposableEntry(config.plugins?.[pluginId]);
304
- }
305
-
306
- throw new Error(`Unsupported extends target path: ${targetPath}`);
307
- }
308
-
309
- function getAssociatedUi(
310
- config: BosConfigInput,
311
- _targetPath: string,
312
- ): Record<string, unknown> | undefined {
313
- return isPlainObject(config.app?.ui) ? (config.app.ui as Record<string, unknown>) : undefined;
314
- }
315
-
316
- function mergeComposableEntries(
317
- parent: Partial<BosPluginRef>,
318
- child: Partial<BosPluginRef>,
319
- ): BosPluginRef {
320
- return bosConfigMerger({ ...child }, parent) as BosPluginRef;
321
- }
322
-
323
- function stripUnsafeLocalDevelopment<T extends Record<string, unknown> | undefined>(
324
- entry: T,
325
- allowLocalPaths: boolean,
326
- ): T {
327
- if (!entry || allowLocalPaths) {
328
- return entry;
329
- }
330
-
331
- if (typeof entry.development === "string" && entry.development.startsWith(LOCAL_PREFIX)) {
332
- const { development: _ignored, ...rest } = entry;
333
- return rest as T;
334
- }
335
-
336
- return entry;
337
- }
338
-
339
- export async function resolveComposableReference(
340
- source: BosPluginRef,
341
- baseDir: string,
342
- env: BosEnv,
343
- defaultTargetPath: string,
344
- ): Promise<ResolvedComposableReference> {
345
- let resolvedEntry: BosPluginRef = {};
346
- let providerBaseDir = baseDir;
347
- let targetPath = defaultTargetPath;
348
- let associatedUi: Record<string, unknown> | undefined;
349
- let allowLocalPaths = false;
350
- let extendsError: unknown;
351
-
352
- const extendsRef = source.extends ? resolveExtendsRef(source.extends, env) : undefined;
353
- if (extendsRef) {
354
- const parsed = parseExtendsTarget(extendsRef);
355
- targetPath = parsed.targetPath ?? defaultTargetPath;
356
- const extendsBaseDir = getConfigBaseDir(parsed.configPath, baseDir);
357
- try {
358
- const extendedConfig = await resolveConfigWithExtends(
359
- parsed.configPath,
360
- extendsBaseDir,
361
- new Set(),
362
- [],
363
- env,
364
- );
365
- resolvedEntry = mergeComposableEntries(
366
- resolvedEntry,
367
- getTargetedEntry(extendedConfig, targetPath),
368
- );
369
- providerBaseDir = extendsBaseDir;
370
- associatedUi = getAssociatedUi(extendedConfig, targetPath);
371
- } catch (error) {
372
- extendsError = error;
373
- }
374
- }
375
-
376
- const localDevelopment =
377
- typeof source.development === "string" && source.development.startsWith(LOCAL_PREFIX)
378
- ? source.development
379
- : undefined;
380
-
381
- if (localDevelopment) {
382
- const localPath = resolve(baseDir, localDevelopment.slice(LOCAL_PREFIX.length).trim());
383
- const localConfigPath = join(localPath, "bos.config.json");
384
- if (existsSync(localConfigPath)) {
385
- const localConfig = await resolveConfigWithExtends(
386
- localConfigPath,
387
- localPath,
388
- new Set(),
389
- [],
390
- env,
391
- );
392
- resolvedEntry = mergeComposableEntries(
393
- resolvedEntry,
394
- getTargetedEntry(localConfig, targetPath),
395
- );
396
- providerBaseDir = localPath;
397
- associatedUi = getAssociatedUi(localConfig, targetPath);
398
- allowLocalPaths = true;
399
- }
400
- }
401
-
402
- const sourceOverrides = { ...source };
403
- if (allowLocalPaths && localDevelopment) {
404
- delete sourceOverrides.development;
405
- }
406
-
407
- resolvedEntry = mergeComposableEntries(resolvedEntry, sourceOverrides);
408
-
409
- if (
410
- extendsError &&
411
- !allowLocalPaths &&
412
- typeof resolvedEntry.development !== "string" &&
413
- typeof resolvedEntry.production !== "string" &&
414
- typeof resolvedEntry.name !== "string"
415
- ) {
416
- throw extendsError;
417
- }
418
-
419
- return {
420
- entry: stripUnsafeLocalDevelopment(resolvedEntry, allowLocalPaths || Boolean(localDevelopment)),
421
- providerBaseDir,
422
- targetPath,
423
- associatedUi: stripUnsafeLocalDevelopment(associatedUi, allowLocalPaths),
424
- };
425
- }
426
-
427
- function resolveDevelopmentTarget(
428
- development: string | undefined,
429
- production: string | undefined,
430
- baseDir: string,
431
- forceSource?: "local" | "remote",
432
- ): RuntimeTarget {
433
- if (forceSource === "remote") {
434
- return resolveRuntimeTarget(production, baseDir, "remote");
435
- }
436
- if (!development) {
437
- return resolveRuntimeTarget(production, baseDir, "remote");
438
- }
439
- const devTarget = resolveRuntimeTarget(development, baseDir);
440
- if (devTarget.source === "local" && (!devTarget.localPath || !existsSync(devTarget.localPath))) {
441
- return resolveRuntimeTarget(production, baseDir, "remote");
442
- }
443
- return devTarget;
444
- }
445
-
446
- export interface BuildRuntimeConfigOptions {
447
- plugins?: Record<string, RuntimePluginConfig>;
448
- hostSource?: "local" | "remote";
449
- uiSource?: "local" | "remote";
450
- apiSource?: "local" | "remote";
451
- authSource?: "local" | "remote";
452
- proxy?: string;
453
- }
454
-
455
- export function buildRuntimeConfig(
456
- config: BosConfig,
457
- baseDir: string,
458
- env: BosEnv,
459
- options?: BuildRuntimeConfigOptions,
460
- ): RuntimeConfig {
461
- const uiConfig = config.app.ui;
462
- const apiConfig = config.app.api;
463
- const authConfig = config.app.auth;
464
- const uiRuntime =
465
- env === "development"
466
- ? resolveDevelopmentTarget(
467
- uiConfig.development,
468
- uiConfig.production,
469
- baseDir,
470
- options?.uiSource,
471
- )
472
- : resolveRuntimeTarget(uiConfig.production, baseDir, "remote");
473
- const apiRuntime =
474
- env === "development"
475
- ? resolveDevelopmentTarget(
476
- apiConfig.development,
477
- apiConfig.production,
478
- baseDir,
479
- options?.apiSource,
480
- )
481
- : resolveRuntimeTarget(apiConfig.production, baseDir, "remote");
482
- const authRuntime = authConfig
483
- ? env === "development"
484
- ? resolveDevelopmentTarget(
485
- authConfig.development,
486
- authConfig.production,
487
- baseDir,
488
- options?.authSource,
489
- )
490
- : resolveRuntimeTarget(authConfig.production, baseDir, "remote")
491
- : undefined;
492
-
493
- const hostConfig = config.app.host;
494
- const hostRuntime =
495
- env === "development"
496
- ? resolveDevelopmentTarget(
497
- hostConfig.development,
498
- hostConfig.production,
499
- baseDir,
500
- options?.hostSource,
501
- )
502
- : resolveRuntimeTarget(hostConfig.production, baseDir, "remote");
503
-
504
- const hostListeningUrl =
505
- env === "development"
506
- ? resolveDevelopmentHostUrl(hostConfig.development)
507
- : `http://localhost:${hostRuntime.port ?? DEFAULT_HOST_PORT}`;
508
-
509
- const hostIsRemote = hostRuntime.source === "remote";
510
- const uiIsRemote = uiRuntime.source === "remote";
511
- const apiIsRemote = apiRuntime.source === "remote";
512
- const resolvedApiName = resolvePluginRuntimeName(apiConfig.name, apiRuntime.localPath, "api");
513
-
514
- return {
515
- env,
516
- account: config.account,
517
- domain: config.domain,
518
- title: config.title,
519
- description: config.description,
520
- networkId: getNetworkIdForAccount(config.account),
521
- repository: config.repository,
522
- host: {
523
- name: "host",
524
- url: hostListeningUrl,
525
- entry: `${hostListeningUrl}/mf-manifest.json`,
526
- localPath: hostRuntime.localPath,
527
- port: hostRuntime.port ?? DEFAULT_HOST_PORT,
528
- secrets: hostConfig.secrets,
529
- integrity: hostIsRemote ? hostConfig.integrity : undefined,
530
- source: hostRuntime.source,
531
- remoteUrl: hostIsRemote ? hostRuntime.url : undefined,
532
- },
533
- shared: config.shared,
534
- ui: {
535
- name: uiConfig.name,
536
- url: uiRuntime.url,
537
- entry: uiRuntime.url ? `${uiRuntime.url}/mf-manifest.json` : "/mf-manifest.json",
538
- localPath: uiRuntime.localPath,
539
- port: uiRuntime.port,
540
- ssrUrl: uiIsRemote ? uiConfig.ssr : undefined,
541
- ssrIntegrity: uiIsRemote ? uiConfig.ssrIntegrity : undefined,
542
- integrity: uiIsRemote ? uiConfig.integrity : undefined,
543
- source: uiRuntime.source,
544
- },
545
- api: {
546
- name: resolvedApiName,
547
- url: apiRuntime.url,
548
- entry: apiRuntime.url ? `${apiRuntime.url}/mf-manifest.json` : "/mf-manifest.json",
549
- localPath: apiRuntime.localPath,
550
- port: apiRuntime.port,
551
- source: apiRuntime.source,
552
- proxy: options?.proxy ?? apiConfig.proxy,
553
- variables: apiConfig.variables,
554
- secrets: apiConfig.secrets,
555
- integrity: apiIsRemote ? apiConfig.integrity : undefined,
556
- },
557
- auth: (() => {
558
- if (!authConfig || !authRuntime) return undefined;
559
- return {
560
- name: resolvePluginRuntimeName(authConfig.name, authRuntime.localPath, "auth"),
561
- url: authRuntime.url,
562
- entry: authRuntime.url ? `${authRuntime.url}/mf-manifest.json` : "/mf-manifest.json",
563
- localPath: authRuntime.localPath,
564
- port: authRuntime.port,
565
- source: authRuntime.source,
566
- proxy: authConfig.proxy,
567
- variables: authConfig.variables,
568
- secrets: authConfig.secrets,
569
- integrity: authRuntime.source === "remote" ? authConfig.integrity : undefined,
570
- sidebar: authConfig.sidebar?.map((item) => ({
571
- ...item,
572
- to: item.to ?? "/auth",
573
- roleRequired: item.roleRequired ?? ("member" as const),
574
- })),
575
- };
576
- })(),
577
- plugins:
578
- options?.plugins && Object.keys(options.plugins).length > 0 ? options.plugins : undefined,
579
- };
580
- }
581
-
582
- async function loadConfigFile(configPath: string, baseDir: string): Promise<BosConfigInput> {
583
- if (configPath.startsWith("bos://")) {
584
- return fetchBosConfigFromFastKv<BosConfigInput>(configPath);
585
- }
586
-
587
- const resolvedPath = isAbsolute(configPath) ? configPath : resolve(baseDir, configPath);
588
- return JSON.parse(readFileSync(resolvedPath, "utf-8")) as BosConfigInput;
589
- }
590
-
591
- async function resolveConfigWithExtends(
592
- configPath: string,
593
- baseDir: string,
594
- visited: Set<string>,
595
- chain: string[],
596
- env: BosEnv = "development",
597
- ): Promise<BosConfigInput> {
598
- if (visited.has(configPath)) {
599
- throw new Error(`Circular extends detected: ${[...visited, configPath].join(" -> ")}`);
600
- }
601
-
602
- const config = await loadConfigFile(configPath, baseDir);
603
- chain.push(configPath);
604
-
605
- if (!config.extends) {
606
- return config;
607
- }
608
-
609
- const extendsRef = resolveExtendsRef(config.extends as string | ExtendsConfig, env);
610
- if (!extendsRef) {
611
- return config;
612
- }
613
-
614
- const parsedParentRef = parseExtendsTarget(extendsRef);
615
-
616
- const nextVisited = new Set(visited);
617
- nextVisited.add(configPath);
618
- const parentBaseDir = getConfigBaseDir(parsedParentRef.configPath, baseDir);
619
- const parent = await resolveConfigWithExtends(
620
- parsedParentRef.configPath,
621
- parentBaseDir,
622
- nextVisited,
623
- chain,
624
- env,
625
- );
626
-
627
- return mergeBosConfigWithExtends(parent, config);
628
- }
629
-
630
- type PluginOverrideValue = PluginEntryValue | null | false;
631
-
632
- function normalizePluginEntry(raw: PluginOverrideValue): BosPluginRef | null | false {
633
- if (raw === null || raw === false) return raw;
634
- if (typeof raw === "string") {
635
- return { extends: raw };
636
- }
637
- return raw;
638
- }
639
-
640
- async function resolveRuntimePlugins(
641
- plugins: Record<string, PluginOverrideValue>,
642
- baseDir: string,
643
- env: BosEnv,
644
- ): Promise<Record<string, RuntimePluginConfig>> {
645
- const out: Record<string, RuntimePluginConfig> = {};
646
-
647
- for (const [pluginId, rawInput] of Object.entries(plugins)) {
648
- const normalized = normalizePluginEntry(rawInput);
649
- if (normalized === null || normalized === false) continue;
650
-
651
- const resolvedReference = await resolveComposableReference(
652
- normalized,
653
- baseDir,
654
- env,
655
- `plugins.${pluginId}`,
656
- );
657
-
658
- const pluginRuntime = buildRuntimePluginConfig(pluginId, env, resolvedReference);
659
-
660
- if (
661
- pluginRuntime.source === "remote" &&
662
- pluginRuntime.url &&
663
- !pluginRuntime.localPath &&
664
- typeof resolvedReference.entry.name !== "string"
665
- ) {
666
- pluginRuntime.name = await resolveRemotePluginRuntimeName(
667
- pluginRuntime.url,
668
- pluginRuntime.name,
669
- );
670
- }
671
-
672
- out[pluginId] = pluginRuntime;
673
- }
674
-
675
- return out;
676
- }
677
-
678
- async function resolveRemotePluginRuntimeName(baseUrl: string, fallback: string): Promise<string> {
679
- try {
680
- const controller = new AbortController();
681
- const timeout = setTimeout(() => controller.abort(), 5000);
682
- const response = await fetch(`${baseUrl.replace(/\/$/, "")}/plugin.manifest.json`, {
683
- signal: controller.signal,
684
- });
685
- clearTimeout(timeout);
686
-
687
- if (!response.ok) {
688
- return fallback;
689
- }
690
-
691
- const manifest = (await response.json()) as {
692
- plugin?: { name?: unknown };
693
- };
694
-
695
- return typeof manifest.plugin?.name === "string" && manifest.plugin.name.length > 0
696
- ? manifest.plugin.name
697
- : fallback;
698
- } catch {
699
- return fallback;
700
- }
701
- }
702
-
703
- function buildRuntimePluginConfig(
704
- pluginId: string,
705
- env: BosEnv,
706
- resolved: ResolvedComposableReference,
707
- ): RuntimePluginConfig {
708
- const source = resolved.entry;
709
- const development = typeof source.development === "string" ? source.development : undefined;
710
- const production = typeof source.production === "string" ? source.production : undefined;
711
-
712
- if (production?.startsWith("bos://")) {
713
- throw new Error(
714
- `Plugin "${pluginId}" has unsupported production target "${production}". Use extends: "bos://account/domain" for plugin configs or a CDN URL for production.`,
715
- );
716
- }
717
-
718
- const runtimeTarget =
719
- env === "development"
720
- ? resolveDevelopmentTarget(development, production, resolved.providerBaseDir)
721
- : resolveRuntimeTarget(production, resolved.providerBaseDir, "remote");
722
- const apiName = resolvePluginRuntimeName(source.name, runtimeTarget.localPath, pluginId);
723
-
724
- const uiConfig = resolved.associatedUi;
725
- const uiDevelopment =
726
- typeof uiConfig?.development === "string" ? uiConfig.development : undefined;
727
- const uiProduction = typeof uiConfig?.production === "string" ? uiConfig.production : undefined;
728
- const uiRuntime =
729
- uiConfig && (uiDevelopment || uiProduction)
730
- ? env === "development"
731
- ? resolveDevelopmentTarget(uiDevelopment, uiProduction, resolved.providerBaseDir)
732
- : resolveRuntimeTarget(uiProduction, resolved.providerBaseDir, "remote")
733
- : undefined;
734
-
735
- const sidebar = source.sidebar?.map((item) => ({
736
- ...item,
737
- to: item.to ?? `/${pluginId}`,
738
- roleRequired: item.roleRequired ?? ("member" as const),
739
- }));
740
-
741
- const routes = source.routes;
742
-
743
- return {
744
- name: apiName,
745
- url: runtimeTarget.url,
746
- entry: runtimeTarget.url
747
- ? `${runtimeTarget.url.replace(/\/$/, "")}/mf-manifest.json`
748
- : "/mf-manifest.json",
749
- source: runtimeTarget.source,
750
- localPath: runtimeTarget.localPath,
751
- port: runtimeTarget.port,
752
- proxy: typeof source.proxy === "string" ? source.proxy : undefined,
753
- variables: normalizeStringRecord(source.variables),
754
- secrets: normalizeStringArray(source.secrets),
755
- integrity: runtimeTarget.source === "remote" ? source.integrity : undefined,
756
- ui: uiRuntime
757
- ? {
758
- name: typeof uiConfig?.name === "string" ? uiConfig.name : `${apiName}-ui`,
759
- url: uiRuntime.url,
760
- entry: uiRuntime.url
761
- ? `${uiRuntime.url.replace(/\/$/, "")}/mf-manifest.json`
762
- : "/mf-manifest.json",
763
- source: uiRuntime.source,
764
- localPath: uiRuntime.localPath,
765
- port: uiRuntime.port,
766
- integrity:
767
- uiRuntime.source === "remote" && typeof uiConfig?.integrity === "string"
768
- ? uiConfig.integrity
769
- : undefined,
770
- }
771
- : undefined,
772
- sidebar,
773
- routes,
774
- };
775
- }
776
-
777
- export function resolvePluginRuntimeName(
778
- explicitName: string | undefined,
779
- localPath: string | undefined,
780
- fallback: string,
781
- ): string {
782
- if (explicitName) {
783
- return explicitName;
784
- }
785
-
786
- if (!localPath) {
787
- return fallback;
788
- }
789
-
790
- try {
791
- const packageJsonPath = join(localPath, "package.json");
792
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as { name?: unknown };
793
- if (typeof packageJson.name === "string" && packageJson.name.length > 0) {
794
- return packageJson.name;
795
- }
796
- } catch {}
797
-
798
- return fallback;
799
- }
800
-
801
- function normalizeStringRecord(value: unknown): Record<string, string> | undefined {
802
- if (!isPlainObject(value)) return undefined;
803
- const out: Record<string, string> = {};
804
- for (const [key, raw] of Object.entries(value)) {
805
- if (typeof raw === "string") {
806
- out[key] = raw;
807
- }
808
- }
809
- return Object.keys(out).length > 0 ? out : undefined;
810
- }
811
-
812
- function normalizeStringArray(value: unknown): string[] | undefined {
813
- if (!Array.isArray(value)) return undefined;
814
- const out = value.filter((item): item is string => typeof item === "string" && item.length > 0);
815
- return out.length > 0 ? out : undefined;
816
- }
817
-
818
- function resolveRuntimeTarget(
819
- value: string | undefined,
820
- baseDir: string,
821
- defaultSource: "local" | "remote" = "remote",
822
- ): RuntimeTarget {
823
- if (!value) {
824
- return { source: defaultSource, url: "" };
825
- }
826
-
827
- if (value.startsWith(LOCAL_PREFIX)) {
828
- const localTarget = value?.slice(LOCAL_PREFIX.length).trim();
829
- if (!localTarget) {
830
- throw new Error(`Invalid local development target: ${value}`);
831
- }
832
-
833
- const localPath = resolve(baseDir, localTarget);
834
- if (!existsSync(localPath)) {
835
- return { source: "local", url: "" };
836
- }
837
-
838
- return {
839
- source: "local",
840
- url: "",
841
- localPath,
842
- };
843
- }
844
-
845
- return {
846
- source: defaultSource,
847
- url: value.replace(/\/$/, ""),
848
- port: parsePort(value),
849
- };
850
- }
851
-
852
- export function isLocalDevelopmentTarget(
853
- value: string | undefined,
854
- ): value is `${typeof LOCAL_PREFIX}${string}` {
855
- return typeof value === "string" && value.startsWith(LOCAL_PREFIX);
856
- }
857
-
858
- export function resolveLocalDevelopmentPath(
859
- value: string | undefined,
860
- baseDir: string,
861
- ): string | null {
862
- if (!isLocalDevelopmentTarget(value)) {
863
- return null;
864
- }
865
-
866
- const localTarget = value.slice(LOCAL_PREFIX.length).trim();
867
- return localTarget ? resolve(baseDir, localTarget) : null;
868
- }
869
-
870
- export function resolveDevelopmentHostUrl(value: string | undefined): string {
871
- if (!value || isLocalDevelopmentTarget(value)) {
872
- return `http://localhost:${DEFAULT_HOST_PORT}`;
873
- }
874
-
875
- return value.replace(/\/$/, "");
876
- }
877
-
878
- export function getHostDevelopmentPort(value: string | undefined): number {
879
- return parsePort(resolveDevelopmentHostUrl(value));
880
- }
881
-
882
- export function parsePort(url: string): number {
883
- try {
884
- const parsed = new URL(url);
885
- return parsed.port ? parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80;
886
- } catch {
887
- return 3000;
888
- }
889
- }
890
-
891
- export { BOS_CONFIG_ORDER, rebuildOrderedConfig } from "./merge";
892
- export type { BosConfig, RuntimeConfig } from "./types";
893
- export { BosConfigSchema } from "./types";