everything-dev 1.12.4 → 1.13.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 (124) hide show
  1. package/cli.js +1 -1
  2. package/dist/app.cjs +17 -5
  3. package/dist/app.cjs.map +1 -1
  4. package/dist/app.mjs +17 -5
  5. package/dist/app.mjs.map +1 -1
  6. package/dist/cli/init.cjs +143 -66
  7. package/dist/cli/init.cjs.map +1 -1
  8. package/dist/cli/init.d.cts +1 -1
  9. package/dist/cli/init.d.cts.map +1 -1
  10. package/dist/cli/init.d.mts +1 -1
  11. package/dist/cli/init.d.mts.map +1 -1
  12. package/dist/cli/init.mjs +144 -67
  13. package/dist/cli/init.mjs.map +1 -1
  14. package/dist/cli/prompts.cjs +3 -3
  15. package/dist/cli/prompts.cjs.map +1 -1
  16. package/dist/cli/prompts.mjs +3 -3
  17. package/dist/cli/prompts.mjs.map +1 -1
  18. package/dist/cli/sync.cjs +15 -56
  19. package/dist/cli/sync.cjs.map +1 -1
  20. package/dist/cli/sync.mjs +15 -56
  21. package/dist/cli/sync.mjs.map +1 -1
  22. package/dist/cli/upgrade.cjs +3 -1
  23. package/dist/cli/upgrade.cjs.map +1 -1
  24. package/dist/cli/upgrade.mjs +3 -1
  25. package/dist/cli/upgrade.mjs.map +1 -1
  26. package/dist/config.cjs +206 -69
  27. package/dist/config.cjs.map +1 -1
  28. package/dist/config.d.cts +13 -6
  29. package/dist/config.d.cts.map +1 -1
  30. package/dist/config.d.mts +13 -6
  31. package/dist/config.d.mts.map +1 -1
  32. package/dist/config.mjs +201 -71
  33. package/dist/config.mjs.map +1 -1
  34. package/dist/contract.d.cts +104 -8
  35. package/dist/contract.d.cts.map +1 -1
  36. package/dist/contract.d.mts +104 -8
  37. package/dist/contract.d.mts.map +1 -1
  38. package/dist/host.cjs +34 -1
  39. package/dist/host.cjs.map +1 -1
  40. package/dist/host.d.cts.map +1 -1
  41. package/dist/host.d.mts.map +1 -1
  42. package/dist/host.mjs +34 -1
  43. package/dist/host.mjs.map +1 -1
  44. package/dist/index.cjs +16 -0
  45. package/dist/index.d.cts +5 -3
  46. package/dist/index.d.mts +5 -3
  47. package/dist/index.mjs +5 -3
  48. package/dist/merge.cjs +113 -0
  49. package/dist/merge.cjs.map +1 -0
  50. package/dist/merge.d.cts +7 -0
  51. package/dist/merge.d.cts.map +1 -0
  52. package/dist/merge.d.mts +7 -0
  53. package/dist/merge.d.mts.map +1 -0
  54. package/dist/merge.mjs +107 -0
  55. package/dist/merge.mjs.map +1 -0
  56. package/dist/plugin.cjs +117 -105
  57. package/dist/plugin.cjs.map +1 -1
  58. package/dist/plugin.d.cts +114 -8
  59. package/dist/plugin.d.cts.map +1 -1
  60. package/dist/plugin.d.mts +114 -8
  61. package/dist/plugin.d.mts.map +1 -1
  62. package/dist/plugin.mjs +117 -105
  63. package/dist/plugin.mjs.map +1 -1
  64. package/dist/service-descriptor.cjs +21 -0
  65. package/dist/service-descriptor.cjs.map +1 -1
  66. package/dist/service-descriptor.d.cts +23 -1
  67. package/dist/service-descriptor.d.cts.map +1 -1
  68. package/dist/service-descriptor.d.mts +23 -1
  69. package/dist/service-descriptor.d.mts.map +1 -1
  70. package/dist/service-descriptor.mjs +21 -0
  71. package/dist/service-descriptor.mjs.map +1 -1
  72. package/dist/shared.cjs +24 -2
  73. package/dist/shared.cjs.map +1 -1
  74. package/dist/shared.d.cts +3 -0
  75. package/dist/shared.d.cts.map +1 -1
  76. package/dist/shared.d.mts +3 -0
  77. package/dist/shared.d.mts.map +1 -1
  78. package/dist/shared.mjs +25 -3
  79. package/dist/shared.mjs.map +1 -1
  80. package/dist/sidebar.cjs +124 -0
  81. package/dist/sidebar.cjs.map +1 -0
  82. package/dist/sidebar.d.cts +8 -0
  83. package/dist/sidebar.d.cts.map +1 -0
  84. package/dist/sidebar.d.mts +8 -0
  85. package/dist/sidebar.d.mts.map +1 -0
  86. package/dist/sidebar.mjs +122 -0
  87. package/dist/sidebar.mjs.map +1 -0
  88. package/dist/types.cjs +104 -10
  89. package/dist/types.cjs.map +1 -1
  90. package/dist/types.d.cts +256 -29
  91. package/dist/types.d.cts.map +1 -1
  92. package/dist/types.d.mts +256 -29
  93. package/dist/types.d.mts.map +1 -1
  94. package/dist/types.mjs +100 -11
  95. package/dist/types.mjs.map +1 -1
  96. package/dist/utils/path-match.cjs +18 -0
  97. package/dist/utils/path-match.cjs.map +1 -0
  98. package/dist/utils/path-match.mjs +17 -0
  99. package/dist/utils/path-match.mjs.map +1 -0
  100. package/dist/utils/save-config.cjs +19 -0
  101. package/dist/utils/save-config.cjs.map +1 -0
  102. package/dist/utils/save-config.mjs +18 -0
  103. package/dist/utils/save-config.mjs.map +1 -0
  104. package/package.json +3 -2
  105. package/skills/dev-workflow/SKILL.md +8 -0
  106. package/skills/extends-config/SKILL.md +132 -0
  107. package/skills/init-upgrade/SKILL.md +128 -0
  108. package/skills/publish-sync/SKILL.md +30 -0
  109. package/src/app.ts +15 -5
  110. package/src/cli/init.ts +199 -100
  111. package/src/cli/prompts.ts +2 -2
  112. package/src/cli/sync.ts +27 -96
  113. package/src/cli/upgrade.ts +2 -0
  114. package/src/config.ts +306 -119
  115. package/src/host.ts +45 -0
  116. package/src/index.ts +1 -0
  117. package/src/merge.ts +198 -0
  118. package/src/plugin.ts +340 -318
  119. package/src/service-descriptor.ts +23 -0
  120. package/src/shared.ts +48 -5
  121. package/src/sidebar.ts +162 -0
  122. package/src/types.ts +134 -28
  123. package/src/utils/path-match.ts +16 -0
  124. package/src/utils/save-config.ts +20 -0
package/src/plugin.ts CHANGED
@@ -27,37 +27,21 @@ import {
27
27
  getProjectRoot,
28
28
  loadConfig,
29
29
  resolveLocalDevelopmentPath,
30
+ writeResolvedConfig,
30
31
  } from "./config";
31
- import {
32
- type BosConfigResult,
33
- type BuildOptions,
34
- bosContract,
35
- type DevOptions,
36
- type InitOptions,
37
- type KeyPublishOptions,
38
- type PluginAddOptions,
39
- type PluginListResult,
40
- type PluginPublishOptions,
41
- type PluginRemoveOptions,
42
- type PublishOptions,
43
- type StartOptions,
44
- type SyncOptions,
45
- type TypesGenOptions,
46
- type UpgradeOptions,
47
- } from "./contract";
32
+ import { type BosConfigResult, bosContract, type PluginListResult } from "./contract";
48
33
  import { devApp, startApp } from "./dev-session";
49
34
  import {
50
35
  buildRegistryConfigUrl,
51
36
  buildRegistryConfigUrlForNetwork,
52
37
  fetchBosConfigFromFastKv,
53
- fetchPluginFromRegistry,
54
38
  fetchRemotePluginManifest,
55
39
  getRegistryNamespaceForAccount,
56
40
  getRegistryNamespaceForNetwork,
57
41
  type PluginManifest,
58
- parsePluginBosUrl,
59
42
  } from "./fastkv";
60
43
  import { computeSriHashForUrl } from "./integrity";
44
+ import type { BosEnv } from "./merge";
61
45
  import { addFunctionCallAccessKey, ensureNearCli, executeTransaction } from "./near-cli";
62
46
  import { getNetworkIdForAccount } from "./network";
63
47
  import { createPlugin, z } from "./sdk";
@@ -67,8 +51,10 @@ import {
67
51
  buildServiceDescriptorMap,
68
52
  } from "./service-descriptor";
69
53
  import { syncAndGenerateSharedUi } from "./shared";
70
- import type { BosConfig, RuntimeConfig, SourceMode } from "./types";
54
+ import { writePluginSidebarGen } from "./sidebar";
55
+ import type { BosConfig, BosPluginRef, RuntimeConfig, SourceMode } from "./types";
71
56
  import { run } from "./utils/run";
57
+ import { saveBosConfig } from "./utils/save-config";
72
58
  import { colors } from "./utils/theme";
73
59
 
74
60
  function ensureEnvFile(configDir: string, opts?: { domain?: string }): void {
@@ -119,6 +105,11 @@ type BosDeps = {
119
105
 
120
106
  type PluginAttachmentConfig = NonNullable<BosConfig["plugins"]>[string];
121
107
 
108
+ function getPluginRef(entry: string | BosPluginRef | undefined | null): BosPluginRef | null {
109
+ if (!entry || typeof entry === "string") return null;
110
+ return entry;
111
+ }
112
+
122
113
  function parseSourceMode(value: string | undefined, defaultValue: SourceMode): SourceMode {
123
114
  if (value === "local" || value === "remote") return value;
124
115
  return defaultValue;
@@ -167,7 +158,7 @@ function resolveWorkspaceTarget(
167
158
  const runtimePlugin = runtimeConfig?.plugins?.[key];
168
159
  const pluginPath =
169
160
  runtimePlugin?.localPath ??
170
- resolveLocalDevelopmentPath(bosConfig?.plugins?.[key]?.development, configDir);
161
+ resolveLocalDevelopmentPath(getPluginRef(bosConfig?.plugins?.[key])?.development, configDir);
171
162
  if (pluginPath) {
172
163
  return {
173
164
  key,
@@ -223,7 +214,8 @@ function defaultPluginKey(source: string): string {
223
214
  }
224
215
 
225
216
  function pluginLocalPath(configDir: string, attachment: PluginAttachmentConfig): string | null {
226
- const source = attachment.development ?? attachment.production;
217
+ const ref = getPluginRef(attachment);
218
+ const source = ref?.development ?? ref?.production;
227
219
  if (!source?.startsWith("local:")) {
228
220
  return null;
229
221
  }
@@ -231,34 +223,23 @@ function pluginLocalPath(configDir: string, attachment: PluginAttachmentConfig):
231
223
  return join(configDir, source.slice("local:".length));
232
224
  }
233
225
 
234
- async function saveBosConfig(configDir: string, config: BosConfig): Promise<void> {
235
- const filePath = join(configDir, "bos.config.json");
236
- const next = `${JSON.stringify(config, null, 2)}\n`;
237
- try {
238
- if (readFileSync(filePath, "utf8") === next) return;
239
- } catch {
240
- // file does not exist yet
241
- }
242
-
243
- writeFileSync(filePath, next);
244
- }
245
-
246
226
  function listPluginAttachments(config: BosConfig | null) {
247
227
  return (Object.entries(config?.plugins ?? {}) as Array<[string, PluginAttachmentConfig]>)
248
- .map(([key, attachment]) => ({
249
- key,
250
- development: attachment.development,
251
- production: attachment.production,
252
- localPath: attachment.development?.startsWith("local:")
253
- ? attachment.development.slice("local:".length)
254
- : undefined,
255
- source: attachment.development?.startsWith("local:")
256
- ? ("local" as const)
257
- : ("remote" as const),
258
- integrity: attachment.integrity,
259
- version: attachment.version,
260
- name: attachment.name,
261
- }))
228
+ .map(([key, attachment]) => {
229
+ const ref = getPluginRef(attachment);
230
+ return {
231
+ key,
232
+ development: ref?.development,
233
+ production: ref?.production,
234
+ localPath: ref?.development?.startsWith("local:")
235
+ ? ref.development.slice("local:".length)
236
+ : undefined,
237
+ source: ref?.development?.startsWith("local:") ? ("local" as const) : ("remote" as const),
238
+ integrity: ref?.integrity,
239
+ version: ref?.version,
240
+ name: ref?.name,
241
+ };
242
+ })
262
243
  .sort((a, b) => a.key.localeCompare(b.key));
263
244
  }
264
245
 
@@ -274,6 +255,8 @@ async function refreshApiContractBridge(
274
255
  runtimeConfig: refreshed.runtime,
275
256
  apiBaseUrl: refreshed.runtime.api.url,
276
257
  });
258
+
259
+ writePluginSidebarGen(configDir, refreshed.config);
277
260
  }
278
261
 
279
262
  function extractPublishedUrl(output: string): string | null {
@@ -417,6 +400,7 @@ async function buildWorkspaceTargets(opts: {
417
400
  configDir: opts.configDir,
418
401
  hostMode: "local",
419
402
  bosConfig: opts.bosConfig ?? undefined,
403
+ extendsChain: [],
420
404
  });
421
405
  if (sharedSync.catalogChanged) {
422
406
  await run("bun", ["install"], { cwd: opts.configDir });
@@ -472,7 +456,7 @@ export default createPlugin({
472
456
  }),
473
457
  secrets: z.object({}),
474
458
  contract: bosContract,
475
- initialize: (config: any) =>
459
+ initialize: (config) =>
476
460
  Effect.promise(async () => {
477
461
  const configResult = await loadConfig({ path: config.variables.configPath });
478
462
  return {
@@ -482,10 +466,10 @@ export default createPlugin({
482
466
  } satisfies BosDeps;
483
467
  }),
484
468
  shutdown: () => Effect.void,
485
- createRouter: (deps: BosDeps, builder: any) => ({
469
+ createRouter: (deps, builder) => ({
486
470
  config: builder.config.handler(async () => buildConfigResult(deps.bosConfig)),
487
471
 
488
- pluginAdd: builder.pluginAdd.handler(async ({ input }: { input: PluginAddOptions }) => {
472
+ pluginAdd: builder.pluginAdd.handler(async ({ input }) => {
489
473
  if (!deps.bosConfig) {
490
474
  return {
491
475
  status: "error" as const,
@@ -494,90 +478,33 @@ export default createPlugin({
494
478
  };
495
479
  }
496
480
 
497
- const pluginRef = parsePluginBosUrl(input.source);
498
- let production = input.production ?? input.source;
499
- let integrity: string | undefined;
500
- let version: string | undefined;
501
- let name: string | undefined;
502
-
503
- if (pluginRef) {
504
- try {
505
- const entry = await fetchPluginFromRegistry(pluginRef.accountId, pluginRef.pluginName);
506
- if (!entry) {
507
- return {
508
- status: "error" as const,
509
- key: "",
510
- error: `Plugin not found in registry: bos://${pluginRef.accountId}/plugins/${pluginRef.pluginName}`,
511
- };
512
- }
513
-
514
- const manifest = entry.manifest;
515
- if (
516
- manifest.schemaVersion !== 1 ||
517
- manifest.kind !== "every-plugin/manifest" ||
518
- !manifest.plugin?.name ||
519
- !manifest.plugin?.version ||
520
- !manifest.runtime?.remoteEntry
521
- ) {
522
- return {
523
- status: "error" as const,
524
- key: "",
525
- error: `Invalid plugin manifest for bos://${pluginRef.accountId}/plugins/${pluginRef.pluginName}`,
526
- };
527
- }
528
-
529
- production = entry.metadata.cdnUrl || input.production || input.source;
530
- name = manifest.plugin.name;
531
- version = manifest.plugin.version;
532
- } catch (error) {
533
- return {
534
- status: "error" as const,
535
- key: "",
536
- error: `Failed to resolve plugin from registry: ${error instanceof Error ? error.message : error}`,
537
- };
538
- }
539
- }
540
-
541
- if (!input.source.startsWith("local:") && !pluginRef && production.startsWith("https://")) {
542
- try {
543
- const manifest = await fetchRemotePluginManifest(production);
544
- if (manifest) {
545
- name = manifest.plugin.name;
546
- version = manifest.plugin.version;
547
- }
548
- } catch {
549
- console.warn(`[plugin add] Could not fetch manifest from ${production}`);
550
- }
551
- }
552
-
553
- if (!input.source.startsWith("local:") && production.startsWith("https://")) {
554
- try {
555
- const computed = await computeSriHashForUrl(production);
556
- if (computed) integrity = computed;
557
- } catch {
558
- console.warn(`[plugin add] Could not compute integrity for ${production}`);
559
- }
560
- }
561
-
481
+ const isBosRef = input.source.startsWith("bos://");
482
+ const isLocal = input.source.startsWith("local:");
562
483
  const key = sanitizePluginKey(
563
- input.as ?? (pluginRef ? pluginRef.pluginName : defaultPluginKey(input.source)),
484
+ input.as ??
485
+ (isBosRef ? (input.source.split("/").pop() ?? "plugin") : defaultPluginKey(input.source)),
564
486
  );
565
487
  const existing = deps.bosConfig.plugins?.[key];
488
+ const existingEntry = existing && typeof existing === "object" ? existing : {};
566
489
  const nextPlugins = { ...(deps.bosConfig.plugins ?? {}) };
567
490
 
568
- nextPlugins[key] = input.source.startsWith("local:")
569
- ? {
570
- ...(existing ?? {}),
571
- development: input.source,
572
- production: input.production ?? existing?.production,
573
- }
574
- : {
575
- ...(existing ?? {}),
576
- production,
577
- ...(integrity ? { integrity } : {}),
578
- ...(name ? { name } : {}),
579
- ...(version ? { version } : {}),
580
- };
491
+ if (isBosRef) {
492
+ nextPlugins[key] = {
493
+ ...existingEntry,
494
+ extends: input.source,
495
+ };
496
+ } else if (isLocal) {
497
+ nextPlugins[key] = {
498
+ ...existingEntry,
499
+ development: input.source,
500
+ ...(existingEntry.extends ? {} : {}),
501
+ };
502
+ } else {
503
+ nextPlugins[key] = {
504
+ ...existingEntry,
505
+ production: input.production ?? input.source,
506
+ };
507
+ }
581
508
 
582
509
  deps.bosConfig = {
583
510
  ...deps.bosConfig,
@@ -587,50 +514,51 @@ export default createPlugin({
587
514
  await saveBosConfig(deps.configDir, deps.bosConfig);
588
515
  await refreshApiContractBridge(deps.configDir);
589
516
 
517
+ const stored = deps.bosConfig.plugins?.[key];
518
+ const storedObj = stored && typeof stored === "object" ? stored : {};
519
+
590
520
  return {
591
521
  status: "added" as const,
592
522
  key,
593
- development: deps.bosConfig.plugins?.[key]?.development,
594
- production: deps.bosConfig.plugins?.[key]?.production,
595
- integrity,
596
- version,
523
+ development: storedObj.development,
524
+ production: storedObj.production,
525
+ integrity: storedObj.integrity,
526
+ version: storedObj.version,
597
527
  };
598
528
  }),
599
529
 
600
- pluginRemove: builder.pluginRemove.handler(
601
- async ({ input }: { input: PluginRemoveOptions }) => {
602
- if (!deps.bosConfig) {
603
- return {
604
- status: "error" as const,
605
- key: input.key,
606
- error: "No bos.config.json found",
607
- };
608
- }
609
-
610
- if (!deps.bosConfig.plugins?.[input.key]) {
611
- return {
612
- status: "error" as const,
613
- key: input.key,
614
- error: `Plugin '${input.key}' is not configured`,
615
- };
616
- }
617
-
618
- const nextPlugins = { ...(deps.bosConfig.plugins ?? {}) };
619
- delete nextPlugins[input.key];
620
- deps.bosConfig = {
621
- ...deps.bosConfig,
622
- plugins: Object.keys(nextPlugins).length > 0 ? nextPlugins : undefined,
530
+ pluginRemove: builder.pluginRemove.handler(async ({ input }) => {
531
+ if (!deps.bosConfig) {
532
+ return {
533
+ status: "error" as const,
534
+ key: input.key,
535
+ error: "No bos.config.json found",
623
536
  };
537
+ }
624
538
 
625
- await saveBosConfig(deps.configDir, deps.bosConfig);
626
- await refreshApiContractBridge(deps.configDir);
627
-
539
+ if (!deps.bosConfig.plugins?.[input.key]) {
628
540
  return {
629
- status: "removed" as const,
541
+ status: "error" as const,
630
542
  key: input.key,
543
+ error: `Plugin '${input.key}' is not configured`,
631
544
  };
632
- },
633
- ),
545
+ }
546
+
547
+ const nextPlugins = { ...(deps.bosConfig.plugins ?? {}) };
548
+ delete nextPlugins[input.key];
549
+ deps.bosConfig = {
550
+ ...deps.bosConfig,
551
+ plugins: Object.keys(nextPlugins).length > 0 ? nextPlugins : undefined,
552
+ };
553
+
554
+ await saveBosConfig(deps.configDir, deps.bosConfig);
555
+ await refreshApiContractBridge(deps.configDir);
556
+
557
+ return {
558
+ status: "removed" as const,
559
+ key: input.key,
560
+ };
561
+ }),
634
562
 
635
563
  pluginList: builder.pluginList.handler(async () => {
636
564
  const plugins: PluginListResult["plugins"] = listPluginAttachments(deps.bosConfig);
@@ -640,166 +568,202 @@ export default createPlugin({
640
568
  };
641
569
  }),
642
570
 
643
- pluginPublish: builder.pluginPublish.handler(
644
- async ({ input }: { input: PluginPublishOptions }) => {
645
- if (!deps.bosConfig) {
646
- return {
647
- status: "error" as const,
648
- key: input.key,
649
- error: "No bos.config.json found",
650
- };
651
- }
571
+ pluginPublish: builder.pluginPublish.handler(async ({ input }) => {
572
+ if (!deps.bosConfig) {
573
+ return {
574
+ status: "error" as const,
575
+ key: input.key,
576
+ error: "No bos.config.json found",
577
+ };
578
+ }
652
579
 
653
- const attachment = deps.bosConfig.plugins?.[input.key];
654
- if (!attachment) {
655
- return {
656
- status: "error" as const,
657
- key: input.key,
658
- error: `Plugin '${input.key}' is not configured`,
659
- };
660
- }
580
+ const attachment = deps.bosConfig.plugins?.[input.key];
581
+ if (!attachment) {
582
+ return {
583
+ status: "error" as const,
584
+ key: input.key,
585
+ error: `Plugin '${input.key}' is not configured`,
586
+ };
587
+ }
661
588
 
662
- const localPath = pluginLocalPath(deps.configDir, attachment);
663
- if (!localPath) {
664
- return {
665
- status: "error" as const,
666
- key: input.key,
667
- error: `Plugin '${input.key}' does not have a local development path`,
668
- };
669
- }
589
+ const attachmentRef = getPluginRef(attachment);
670
590
 
671
- const pkgPath = join(localPath, "package.json");
672
- if (!(await Bun.file(pkgPath).exists())) {
673
- return {
674
- status: "error" as const,
675
- key: input.key,
676
- error: `Missing package.json at ${localPath}`,
677
- };
678
- }
591
+ const localPath = pluginLocalPath(deps.configDir, attachment);
592
+ if (!localPath) {
593
+ return {
594
+ status: "error" as const,
595
+ key: input.key,
596
+ error: `Plugin '${input.key}' does not have a local development path`,
597
+ };
598
+ }
679
599
 
680
- const pkgJson = (await Bun.file(pkgPath).json()) as {
681
- scripts?: Record<string, string>;
682
- name?: string;
683
- version?: string;
600
+ const pkgPath = join(localPath, "package.json");
601
+ if (!(await Bun.file(pkgPath).exists())) {
602
+ return {
603
+ status: "error" as const,
604
+ key: input.key,
605
+ error: `Missing package.json at ${localPath}`,
684
606
  };
685
- const script = pkgJson.scripts?.deploy ? "deploy" : "build";
607
+ }
686
608
 
687
- const { stdout, stderr, exitCode } = (await run("bun", ["run", script], {
688
- cwd: localPath,
689
- capture: true,
690
- })) as { stdout: string; stderr: string; exitCode: number };
609
+ const pkgJson = (await Bun.file(pkgPath).json()) as {
610
+ scripts?: Record<string, string>;
611
+ name?: string;
612
+ version?: string;
613
+ };
614
+ const script = pkgJson.scripts?.deploy ? "deploy" : "build";
691
615
 
692
- if (exitCode !== 0) {
693
- if (stdout.trim()) process.stdout.write(stdout);
694
- if (stderr.trim()) process.stderr.write(stderr);
695
- return {
696
- status: "error" as const,
697
- key: input.key,
698
- error: `Publish failed with exit code ${exitCode}`,
699
- };
700
- }
616
+ const { stdout, stderr, exitCode } = (await run("bun", ["run", script], {
617
+ cwd: localPath,
618
+ capture: true,
619
+ })) as { stdout: string; stderr: string; exitCode: number };
701
620
 
621
+ if (exitCode !== 0) {
702
622
  if (stdout.trim()) process.stdout.write(stdout);
703
623
  if (stderr.trim()) process.stderr.write(stderr);
624
+ return {
625
+ status: "error" as const,
626
+ key: input.key,
627
+ error: `Publish failed with exit code ${exitCode}`,
628
+ };
629
+ }
630
+
631
+ if (stdout.trim()) process.stdout.write(stdout);
632
+ if (stderr.trim()) process.stderr.write(stderr);
633
+
634
+ let publishedUrl = extractPublishedUrl(`${stdout}\n${stderr}`);
635
+
636
+ let manifest: PluginManifest | null = null;
637
+ if (publishedUrl) {
638
+ manifest = await fetchRemotePluginManifest(publishedUrl);
639
+ } else if (attachmentRef?.production) {
640
+ manifest = await fetchRemotePluginManifest(attachmentRef.production);
641
+ if (manifest) {
642
+ publishedUrl = attachmentRef.production;
643
+ }
644
+ }
704
645
 
705
- let publishedUrl = extractPublishedUrl(`${stdout}\n${stderr}`);
646
+ const integrity = publishedUrl ? await computeSriHashForUrl(publishedUrl) : null;
647
+ const version = manifest?.plugin.version ?? pkgJson.version;
706
648
 
707
- let manifest: PluginManifest | null = null;
708
- if (publishedUrl) {
709
- manifest = await fetchRemotePluginManifest(publishedUrl);
710
- } else if (attachment.production) {
711
- manifest = await fetchRemotePluginManifest(attachment.production);
712
- if (manifest) {
713
- publishedUrl = attachment.production;
649
+ if (publishedUrl) {
650
+ const pluginConfigPath = join(localPath, "bos.config.json");
651
+ if (existsSync(pluginConfigPath)) {
652
+ try {
653
+ const pluginConfig = JSON.parse(readFileSync(pluginConfigPath, "utf-8")) as Record<
654
+ string,
655
+ unknown
656
+ >;
657
+ if (!pluginConfig.app) pluginConfig.app = {};
658
+ const app = pluginConfig.app as Record<string, unknown>;
659
+ if (!app.api) app.api = {};
660
+ const api = app.api as Record<string, unknown>;
661
+ api.production = publishedUrl;
662
+ if (integrity) {
663
+ api.integrity = integrity;
664
+ } else {
665
+ delete api.integrity;
666
+ }
667
+ writeFileSync(pluginConfigPath, `${JSON.stringify(pluginConfig, null, 2)}\n`);
668
+ console.log(` ✅ Updated ${pluginConfigPath}: app.api.production`);
669
+ } catch (err) {
670
+ console.error(
671
+ ` ❌ Failed to update plugin bos.config.json:`,
672
+ err instanceof Error ? err.message : err,
673
+ );
714
674
  }
715
675
  }
716
676
 
717
- const integrity = publishedUrl ? await computeSriHashForUrl(publishedUrl) : null;
718
- const version = manifest?.plugin.version ?? pkgJson.version;
719
-
720
- if (publishedUrl) {
721
- deps.bosConfig = {
722
- ...deps.bosConfig,
723
- plugins: {
724
- ...(deps.bosConfig.plugins ?? {}),
725
- [input.key]: {
726
- ...(deps.bosConfig.plugins?.[input.key] ?? {}),
727
- production: publishedUrl,
728
- ...(integrity ? { integrity } : {}),
729
- ...(manifest?.plugin.name ? { name: manifest.plugin.name } : {}),
730
- ...(version ? { version } : {}),
731
- },
732
- },
733
- };
734
- await saveBosConfig(deps.configDir, deps.bosConfig);
677
+ const account = deps.bosConfig.account;
678
+ const network = getNetworkIdForAccount(account);
679
+
680
+ let pluginDomain: string | undefined;
681
+ if (existsSync(pluginConfigPath)) {
682
+ try {
683
+ const pluginConfig = JSON.parse(readFileSync(pluginConfigPath, "utf-8"));
684
+ if (typeof pluginConfig.domain === "string") {
685
+ pluginDomain = pluginConfig.domain;
686
+ }
687
+ } catch {}
688
+ }
689
+ if (!pluginDomain) {
690
+ pluginDomain = `${input.key}.${deps.bosConfig.domain ?? "everything.dev"}`;
691
+ }
692
+
693
+ if (manifest && version) {
694
+ try {
695
+ const registryEntries: Record<string, string> = {
696
+ [`plugins/${account}/${input.key}/manifest.json`]: JSON.stringify(manifest),
697
+ [`plugins/${account}/${input.key}/metadata`]: JSON.stringify({
698
+ title: null,
699
+ description: null,
700
+ repoUrl: deps.bosConfig.repository ?? null,
701
+ version,
702
+ publishedAt: new Date().toISOString(),
703
+ cdnUrl: publishedUrl,
704
+ integrity,
705
+ }),
706
+ [`plugins/${account}/${input.key}/versions/${version}/manifest.json`]:
707
+ JSON.stringify(manifest),
708
+ };
709
+
710
+ if (existsSync(pluginConfigPath)) {
711
+ try {
712
+ const publishedPluginConfig = JSON.parse(readFileSync(pluginConfigPath, "utf-8"));
713
+ delete publishedPluginConfig.development;
714
+ registryEntries[`apps/${account}/${pluginDomain}/bos.config.json`] =
715
+ JSON.stringify(publishedPluginConfig);
716
+ } catch {}
717
+ }
718
+
719
+ const payload = JSON.stringify(registryEntries);
720
+ const argsBase64 = Buffer.from(payload).toString("base64");
721
+ const privateKey = process.env.NEAR_PRIVATE_KEY || process.env.BOS_NEAR_PRIVATE_KEY;
735
722
 
736
- const account = deps.bosConfig.account;
737
- const network = getNetworkIdForAccount(account);
738
- if (manifest && version) {
723
+ await Effect.runPromise(ensureNearCli);
739
724
  try {
740
- const registryEntries: Record<string, string> = {
741
- [`plugins/${account}/${input.key}/manifest.json`]: JSON.stringify(manifest),
742
- [`plugins/${account}/${input.key}/metadata`]: JSON.stringify({
743
- title: null,
744
- description: null,
745
- repoUrl: deps.bosConfig.repository ?? null,
746
- version,
747
- publishedAt: new Date().toISOString(),
748
- cdnUrl: publishedUrl,
749
- integrity,
725
+ await Effect.runPromise(
726
+ executeTransaction({
727
+ account,
728
+ contract: getRegistryNamespaceForNetwork(network),
729
+ method: "__fastdata_kv",
730
+ argsBase64,
731
+ network,
732
+ privateKey,
733
+ gas: "50Tgas",
734
+ deposit: "0NEAR",
750
735
  }),
751
- [`plugins/${account}/${input.key}/versions/${version}/manifest.json`]:
752
- JSON.stringify(manifest),
753
- };
754
- const payload = JSON.stringify(registryEntries);
755
- const argsBase64 = Buffer.from(payload).toString("base64");
756
- const privateKey = process.env.NEAR_PRIVATE_KEY || process.env.BOS_NEAR_PRIVATE_KEY;
757
-
758
- await Effect.runPromise(ensureNearCli);
759
- try {
760
- await Effect.runPromise(
761
- executeTransaction({
762
- account,
763
- contract: getRegistryNamespaceForNetwork(network),
764
- method: "__fastdata_kv",
765
- argsBase64,
766
- network,
767
- privateKey,
768
- gas: "50Tgas",
769
- deposit: "0NEAR",
770
- }),
736
+ );
737
+ } catch (registryError) {
738
+ const txHash = extractTransactionHash(registryError);
739
+ if (!txHash) {
740
+ console.warn(
741
+ `[publish] Plugin registry write failed: ${registryError instanceof Error ? registryError.message : registryError}`,
771
742
  );
772
- } catch (registryError) {
773
- const txHash = extractTransactionHash(registryError);
774
- if (!txHash) {
775
- console.warn(
776
- `[publish] Plugin registry write failed: ${registryError instanceof Error ? registryError.message : registryError}`,
777
- );
778
- }
779
743
  }
780
- } catch (registryError) {
781
- console.warn(
782
- `[publish] Plugin registry write skipped: ${registryError instanceof Error ? registryError.message : registryError}`,
783
- );
784
744
  }
745
+ } catch (registryError) {
746
+ console.warn(
747
+ `[publish] Plugin registry write skipped: ${registryError instanceof Error ? registryError.message : registryError}`,
748
+ );
785
749
  }
786
-
787
- await refreshApiContractBridge(deps.configDir);
788
750
  }
789
751
 
790
- return {
791
- status: "published" as const,
792
- key: input.key,
793
- path: localPath,
794
- script,
795
- production: publishedUrl ?? attachment.production,
796
- integrity: integrity ?? undefined,
797
- version: version ?? undefined,
798
- };
799
- },
800
- ),
752
+ await refreshApiContractBridge(deps.configDir);
753
+ }
801
754
 
802
- dev: builder.dev.handler(async ({ input }: { input: DevOptions }) => {
755
+ return {
756
+ status: "published" as const,
757
+ key: input.key,
758
+ path: localPath,
759
+ script,
760
+ production: publishedUrl ?? attachmentRef?.production,
761
+ integrity: integrity ?? undefined,
762
+ version: version ?? undefined,
763
+ };
764
+ }),
765
+
766
+ dev: builder.dev.handler(async ({ input }) => {
803
767
  ensureEnvFile(deps.configDir);
804
768
 
805
769
  const localPackages = detectLocalPackages(
@@ -808,16 +772,16 @@ export default createPlugin({
808
772
  );
809
773
 
810
774
  const hostSource: SourceMode = localPackages.includes("host")
811
- ? parseSourceMode(input.host as string, "local")
775
+ ? parseSourceMode(input.host, "local")
812
776
  : "remote";
813
777
  const uiSource: SourceMode = localPackages.includes("ui")
814
- ? parseSourceMode(input.ui as string, "local")
778
+ ? parseSourceMode(input.ui, "local")
815
779
  : "remote";
816
780
  const apiSource: SourceMode = localPackages.includes("api")
817
- ? parseSourceMode(input.api as string, "local")
781
+ ? parseSourceMode(input.api, "local")
818
782
  : "remote";
819
783
  const authSource: SourceMode = localPackages.includes("auth")
820
- ? parseSourceMode(input.auth as string, "local")
784
+ ? parseSourceMode(input.auth, "local")
821
785
  : "remote";
822
786
  const ssr = input.ssr ?? false;
823
787
  const proxy = input.proxy ?? false;
@@ -826,6 +790,7 @@ export default createPlugin({
826
790
  configDir: deps.configDir,
827
791
  hostMode: hostSource,
828
792
  bosConfig: deps.bosConfig ?? undefined,
793
+ extendsChain: [],
829
794
  });
830
795
  if (sharedSync.catalogChanged) {
831
796
  await run("bun", ["install"], { cwd: deps.configDir });
@@ -843,6 +808,16 @@ export default createPlugin({
843
808
  deps.bosConfig = refreshed?.config ?? deps.bosConfig;
844
809
  deps.runtimeConfig = refreshed?.runtime ?? deps.runtimeConfig;
845
810
 
811
+ if (deps.bosConfig) {
812
+ writeResolvedConfig(
813
+ deps.configDir,
814
+ deps.bosConfig,
815
+ "development",
816
+ refreshed?.source.extended,
817
+ );
818
+ writePluginSidebarGen(deps.configDir, deps.bosConfig);
819
+ }
820
+
846
821
  if (!deps.bosConfig) {
847
822
  return {
848
823
  status: "error" as const,
@@ -905,7 +880,7 @@ export default createPlugin({
905
880
  };
906
881
  }),
907
882
 
908
- start: builder.start.handler(async ({ input }: { input: StartOptions }) => {
883
+ start: builder.start.handler(async ({ input }) => {
909
884
  ensureEnvFile(deps.configDir);
910
885
 
911
886
  const account = input.account ?? process.env.BOS_ACCOUNT;
@@ -962,6 +937,8 @@ export default createPlugin({
962
937
  plugins: runtimePlugins,
963
938
  });
964
939
 
940
+ writePluginSidebarGen(deps.configDir, config);
941
+
965
942
  // ── Production Readiness Validation ──
966
943
  const productionEnv: Record<string, string> = {};
967
944
  const warnings: string[] = [];
@@ -1064,7 +1041,7 @@ export default createPlugin({
1064
1041
  };
1065
1042
  }),
1066
1043
 
1067
- build: builder.build.handler(async ({ input }: { input: BuildOptions }) => {
1044
+ build: builder.build.handler(async ({ input }) => {
1068
1045
  if (!deps.bosConfig) {
1069
1046
  return {
1070
1047
  status: "error" as const,
@@ -1073,6 +1050,11 @@ export default createPlugin({
1073
1050
  };
1074
1051
  }
1075
1052
 
1053
+ const buildEnv: BosEnv = input.deploy ? "production" : "development";
1054
+
1055
+ writeResolvedConfig(deps.configDir, deps.bosConfig, buildEnv);
1056
+ writePluginSidebarGen(deps.configDir, deps.bosConfig);
1057
+
1076
1058
  const targets = selectWorkspaceTargets(input.packages, deps.bosConfig);
1077
1059
  if (targets.length === 0) {
1078
1060
  return {
@@ -1087,7 +1069,7 @@ export default createPlugin({
1087
1069
  apiSource: deps.bosConfig.app.api?.development ? "local" : "remote",
1088
1070
  authSource: deps.bosConfig.app.auth?.development ? "local" : "remote",
1089
1071
  hostSource: deps.bosConfig.app.host?.development ? "local" : "remote",
1090
- env: "development",
1072
+ env: buildEnv,
1091
1073
  plugins: deps.runtimeConfig?.plugins,
1092
1074
  });
1093
1075
 
@@ -1121,7 +1103,7 @@ export default createPlugin({
1121
1103
  };
1122
1104
  }),
1123
1105
 
1124
- publish: builder.publish.handler(async ({ input }: { input: PublishOptions }) => {
1106
+ publish: builder.publish.handler(async ({ input }) => {
1125
1107
  if (!deps.bosConfig) {
1126
1108
  return {
1127
1109
  status: "error" as const,
@@ -1177,9 +1159,34 @@ export default createPlugin({
1177
1159
  }
1178
1160
  }
1179
1161
 
1180
- const payload = JSON.stringify({
1162
+ const registryEntries: Record<string, string> = {
1181
1163
  [`apps/${account}/${gateway}/bos.config.json`]: JSON.stringify(publishConfig),
1182
- });
1164
+ };
1165
+
1166
+ for (const [pluginKey, pluginEntry] of Object.entries(publishConfig.plugins ?? {})) {
1167
+ const pluginRef = getPluginRef(pluginEntry);
1168
+ if (!pluginRef?.development?.startsWith("local:")) continue;
1169
+
1170
+ const localPath = join(deps.configDir, pluginRef.development.slice("local:".length));
1171
+ const pluginConfigPath = join(localPath, "bos.config.json");
1172
+ if (!existsSync(pluginConfigPath)) continue;
1173
+
1174
+ try {
1175
+ const pluginConfig = JSON.parse(readFileSync(pluginConfigPath, "utf-8")) as Record<
1176
+ string,
1177
+ unknown
1178
+ >;
1179
+ const pluginDomain =
1180
+ typeof pluginConfig.domain === "string"
1181
+ ? pluginConfig.domain
1182
+ : `${pluginKey}.${gateway}`;
1183
+ delete pluginConfig.development;
1184
+ registryEntries[`apps/${account}/${pluginDomain}/bos.config.json`] =
1185
+ JSON.stringify(pluginConfig);
1186
+ } catch {}
1187
+ }
1188
+
1189
+ const payload = JSON.stringify(registryEntries);
1183
1190
  const argsBase64 = Buffer.from(payload).toString("base64");
1184
1191
  const privateKey =
1185
1192
  input.privateKey || process.env.NEAR_PRIVATE_KEY || process.env.BOS_NEAR_PRIVATE_KEY;
@@ -1238,7 +1245,7 @@ export default createPlugin({
1238
1245
  }
1239
1246
  }),
1240
1247
 
1241
- keyPublish: builder.keyPublish.handler(async ({ input }: { input: KeyPublishOptions }) => {
1248
+ keyPublish: builder.keyPublish.handler(async ({ input }) => {
1242
1249
  if (!deps.bosConfig) {
1243
1250
  return {
1244
1251
  status: "error" as const,
@@ -1287,7 +1294,7 @@ export default createPlugin({
1287
1294
  }
1288
1295
  }),
1289
1296
 
1290
- init: builder.init.handler(async ({ input }: { input: InitOptions }) => {
1297
+ init: builder.init.handler(async ({ input }) => {
1291
1298
  try {
1292
1299
  let extendsAccount = input.extendsAccount;
1293
1300
  let extendsGateway = input.extendsGateway;
@@ -1328,7 +1335,7 @@ export default createPlugin({
1328
1335
  extendsAccount = extendsAccount || "dev.everything.near";
1329
1336
  extendsGateway = extendsGateway || "everything.dev";
1330
1337
  directory = directory || domain || extendsGateway;
1331
- plugins = plugins?.length ? plugins : ["_template"];
1338
+ plugins = plugins?.length ? plugins : ["settings"];
1332
1339
 
1333
1340
  try {
1334
1341
  await fetchParentConfig(extendsAccount, extendsGateway);
@@ -1372,9 +1379,10 @@ export default createPlugin({
1372
1379
 
1373
1380
  const pluginRoutes: Record<string, string[]> = {};
1374
1381
  if (parentConfig.plugins) {
1375
- for (const [key, ref] of Object.entries(parentConfig.plugins)) {
1376
- if (ref.routes && ref.routes.length > 0) {
1377
- pluginRoutes[key] = ref.routes;
1382
+ for (const [key, entry] of Object.entries(parentConfig.plugins)) {
1383
+ const entryRef = getPluginRef(entry);
1384
+ if (entryRef?.routes && entryRef.routes.length > 0) {
1385
+ pluginRoutes[key] = entryRef.routes;
1378
1386
  }
1379
1387
  }
1380
1388
  }
@@ -1413,6 +1421,11 @@ export default createPlugin({
1413
1421
  await generateDatabaseMigrations(directory);
1414
1422
  }
1415
1423
 
1424
+ const initConfig = await loadConfig({ cwd: directory });
1425
+ if (initConfig?.config) {
1426
+ writePluginSidebarGen(directory, initConfig.config);
1427
+ }
1428
+
1416
1429
  s.stop("Project initialized");
1417
1430
 
1418
1431
  return {
@@ -1448,7 +1461,7 @@ export default createPlugin({
1448
1461
  }
1449
1462
  }),
1450
1463
 
1451
- sync: builder.sync.handler(async ({ input }: { input: SyncOptions }) => {
1464
+ sync: builder.sync.handler(async ({ input }) => {
1452
1465
  try {
1453
1466
  const configPath = findConfigPath();
1454
1467
  if (!configPath) {
@@ -1462,7 +1475,16 @@ export default createPlugin({
1462
1475
  }
1463
1476
 
1464
1477
  const projectDir = resolve(dirname(configPath));
1465
- return await syncTemplate(projectDir, input);
1478
+ const result = await syncTemplate(projectDir, input);
1479
+
1480
+ if (result.status === "synced" || result.status === "dry-run") {
1481
+ const syncedConfig = await loadConfig({ cwd: projectDir });
1482
+ if (syncedConfig?.config) {
1483
+ writePluginSidebarGen(projectDir, syncedConfig.config);
1484
+ }
1485
+ }
1486
+
1487
+ return result;
1466
1488
  } catch (error) {
1467
1489
  return {
1468
1490
  status: "error" as const,
@@ -1474,7 +1496,7 @@ export default createPlugin({
1474
1496
  }
1475
1497
  }),
1476
1498
 
1477
- upgrade: builder.upgrade.handler(async ({ input }: { input: UpgradeOptions }) => {
1499
+ upgrade: builder.upgrade.handler(async ({ input }) => {
1478
1500
  try {
1479
1501
  const configPath = findConfigPath();
1480
1502
  if (!configPath) {
@@ -1496,7 +1518,7 @@ export default createPlugin({
1496
1518
  }
1497
1519
  }),
1498
1520
 
1499
- typesGen: builder.typesGen.handler(async ({ input }: { input: TypesGenOptions }) => {
1521
+ typesGen: builder.typesGen.handler(async ({ input }) => {
1500
1522
  try {
1501
1523
  const configPath = findConfigPath();
1502
1524
  if (!configPath) {