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
@@ -205,6 +205,29 @@ export function buildServiceDescriptorMap(
205
205
  defaultPort: resolvedPort,
206
206
  readinessPath: "/remoteEntry.js",
207
207
  });
208
+
209
+ if (pluginConfig.ui?.localPath && pluginConfig.ui.source === "local") {
210
+ const uiKey = `plugin-ui:${pluginId}`;
211
+ const uiPort = pluginConfig.ui.port ?? pluginBasePort;
212
+ pluginBasePort = uiPort + 1;
213
+
214
+ map.set(uiKey, {
215
+ key: uiKey,
216
+ source: pluginConfig.ui.source,
217
+ url: pluginConfig.ui.url,
218
+ entry: pluginConfig.ui.entry,
219
+ name: pluginConfig.ui.name,
220
+ localPath: pluginConfig.ui.localPath,
221
+ port: uiPort,
222
+ integrity: pluginConfig.ui.integrity,
223
+ command: "bun",
224
+ args: ["run", "dev"],
225
+ readyPatterns: PLUGIN_READY_PATTERNS,
226
+ errorPatterns: PLUGIN_ERROR_PATTERNS,
227
+ defaultPort: uiPort,
228
+ readinessPath: "/remoteEntry.js",
229
+ });
230
+ }
208
231
  }
209
232
  }
210
233
 
package/src/shared.ts CHANGED
@@ -1,7 +1,22 @@
1
1
  import { createHash } from "node:crypto";
2
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
+ import { type BosEnv, type ResolvedConfigMeta, rebuildOrderedConfig } from "./merge";
4
5
  import type { BosConfig, SharedDepConfig } from "./types";
6
+ import { BosConfigSchema } from "./types";
7
+
8
+ interface PackageJson {
9
+ name?: string;
10
+ private?: boolean;
11
+ version?: string;
12
+ workspaces?: {
13
+ packages?: string[];
14
+ catalog?: Record<string, string>;
15
+ };
16
+ dependencies?: Record<string, string>;
17
+ devDependencies?: Record<string, string>;
18
+ scripts?: Record<string, string>;
19
+ }
5
20
 
6
21
  export interface SharedUiResolvedDep {
7
22
  name: string;
@@ -77,15 +92,24 @@ export async function syncAndGenerateSharedUi(opts: {
77
92
  configDir: string;
78
93
  hostMode: "local" | "remote";
79
94
  bosConfig?: BosConfig;
95
+ env?: BosEnv;
96
+ extendsChain?: string[];
80
97
  }): Promise<SharedSyncResult> {
81
98
  const bosConfigPath = join(opts.configDir, "bos.config.json");
99
+ const resolvedConfigPath = join(opts.configDir, ".bos", "bos.resolved-config.json");
82
100
  const packageJsonPath = join(opts.configDir, "package.json");
83
101
  const generatedPath = join(opts.configDir, ".bos", "generated", "shared-ui.json");
84
102
 
85
- const bosConfig: BosConfig = opts.bosConfig ?? JSON.parse(readFileSync(bosConfigPath, "utf-8"));
86
- let pkgJson: any = {};
103
+ let bosConfig: BosConfig;
104
+ if (opts.bosConfig) {
105
+ bosConfig = opts.bosConfig;
106
+ } else {
107
+ const raw = JSON.parse(readFileSync(bosConfigPath, "utf-8")) as Record<string, unknown>;
108
+ bosConfig = BosConfigSchema.parse(raw);
109
+ }
110
+ let pkgJson: PackageJson = {};
87
111
  try {
88
- pkgJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
112
+ pkgJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as PackageJson;
89
113
  } catch {
90
114
  // package.json might not exist
91
115
  }
@@ -130,7 +154,26 @@ export async function syncAndGenerateSharedUi(opts: {
130
154
  const catalogChanged = nextPkg !== originalPkg;
131
155
 
132
156
  if (bosConfigChanged) {
133
- writeFileIfChanged(bosConfigPath, `${JSON.stringify(bosConfig, null, 2)}\n`);
157
+ if (mode === "catalog->bos") {
158
+ const resolvedDir = dirname(resolvedConfigPath);
159
+ if (!existsSync(resolvedDir)) {
160
+ mkdirSync(resolvedDir, { recursive: true });
161
+ }
162
+ const ordered = rebuildOrderedConfig(bosConfig);
163
+ const meta: ResolvedConfigMeta = {
164
+ env: opts.env ?? "development",
165
+ resolvedAt: new Date().toISOString(),
166
+ extendsChain: opts.extendsChain ?? [],
167
+ source: "shared-sync",
168
+ };
169
+ const resolvedOutput = {
170
+ _resolved: meta,
171
+ ...ordered,
172
+ };
173
+ writeFileIfChanged(resolvedConfigPath, `${JSON.stringify(resolvedOutput, null, 2)}\n`);
174
+ } else {
175
+ writeFileIfChanged(bosConfigPath, `${JSON.stringify(bosConfig, null, 2)}\n`);
176
+ }
134
177
  }
135
178
  if (catalogChanged) {
136
179
  writeFileIfChanged(packageJsonPath, `${JSON.stringify(pkgJson, null, 2)}\n`);
package/src/sidebar.ts ADDED
@@ -0,0 +1,162 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import type { BosConfig, SidebarItem } from "./types";
4
+
5
+ const ICON_IMPORTS: Record<string, string> = {
6
+ Home: "lucide-react",
7
+ Globe: "lucide-react",
8
+ FolderKanban: "lucide-react",
9
+ Building2: "lucide-react",
10
+ Settings: "lucide-react",
11
+ User: "lucide-react",
12
+ Users: "lucide-react",
13
+ Shield: "lucide-react",
14
+ LayoutDashboard: "lucide-react",
15
+ CreditCard: "lucide-react",
16
+ Bell: "lucide-react",
17
+ Key: "lucide-react",
18
+ FileText: "lucide-react",
19
+ Database: "lucide-react",
20
+ Activity: "lucide-react",
21
+ BarChart3: "lucide-react",
22
+ Zap: "lucide-react",
23
+ Terminal: "lucide-react",
24
+ Code: "lucide-react",
25
+ Package: "lucide-react",
26
+ Store: "lucide-react",
27
+ ShoppingBag: "lucide-react",
28
+ Wallet: "lucide-react",
29
+ Coins: "lucide-react",
30
+ Plug: "lucide-react",
31
+ Link: "lucide-react",
32
+ ExternalLink: "lucide-react",
33
+ Puzzle: "lucide-react",
34
+ Layers: "lucide-react",
35
+ Grid3X3: "lucide-react",
36
+ AppWindow: "lucide-react",
37
+ };
38
+
39
+ function resolveIconModule(iconName: string): string {
40
+ if (ICON_IMPORTS[iconName]) return ICON_IMPORTS[iconName];
41
+ return "lucide-react";
42
+ }
43
+
44
+ function collectIconImports(items: SidebarItem[]): Map<string, Set<string>> {
45
+ const moduleMap = new Map<string, Set<string>>();
46
+ for (const item of items) {
47
+ const module = resolveIconModule(item.icon);
48
+ if (!moduleMap.has(module)) moduleMap.set(module, new Set());
49
+ moduleMap.get(module)!.add(item.icon);
50
+ }
51
+ return moduleMap;
52
+ }
53
+
54
+ export function generatePluginSidebarContent(config: BosConfig, configDir?: string): string {
55
+ const coreItems: SidebarItem[] = [{ icon: "Home", label: "home", to: "/", roleRequired: "anon" }];
56
+
57
+ if (config.app.auth?.sidebar) {
58
+ for (const item of config.app.auth.sidebar) {
59
+ coreItems.push({
60
+ ...item,
61
+ to: item.to ?? "/auth",
62
+ roleRequired: item.roleRequired ?? "member",
63
+ });
64
+ }
65
+ }
66
+
67
+ const pluginItems: SidebarItem[] = [];
68
+ if (config.plugins) {
69
+ for (const [key, entry] of Object.entries(config.plugins)) {
70
+ let sidebar: SidebarItem[] | undefined;
71
+
72
+ if (typeof entry === "object" && entry.sidebar) {
73
+ sidebar = entry.sidebar;
74
+ } else if (
75
+ typeof entry === "object" &&
76
+ entry.development?.startsWith("local:") &&
77
+ configDir
78
+ ) {
79
+ const localPath = join(configDir, entry.development.slice("local:".length).trim());
80
+ const pluginConfigPath = join(localPath, "bos.config.json");
81
+ if (existsSync(pluginConfigPath)) {
82
+ try {
83
+ const pluginConfig = JSON.parse(readFileSync(pluginConfigPath, "utf-8")) as {
84
+ sidebar?: SidebarItem[];
85
+ };
86
+ if (pluginConfig.sidebar) {
87
+ sidebar = pluginConfig.sidebar;
88
+ }
89
+ } catch {}
90
+ }
91
+ }
92
+
93
+ if (!sidebar) continue;
94
+ for (const item of sidebar) {
95
+ pluginItems.push({
96
+ ...item,
97
+ to: item.to ?? `/${key}`,
98
+ roleRequired: item.roleRequired ?? "member",
99
+ });
100
+ }
101
+ }
102
+ }
103
+
104
+ const allItems = [...coreItems, ...pluginItems];
105
+ const moduleMap = collectIconImports(allItems);
106
+
107
+ const importLines: string[] = [];
108
+ for (const [module, icons] of moduleMap) {
109
+ const iconList = [...icons].join(", ");
110
+ importLines.push(`import { ${iconList} } from "${module}";`);
111
+ }
112
+
113
+ const itemsCode = allItems
114
+ .map(
115
+ (item) =>
116
+ ` { icon: ${item.icon}, label: "${item.label}", to: "${item.to}" as const, roleRequired: "${item.roleRequired}" as const },`,
117
+ )
118
+ .join("\n");
119
+
120
+ return `// Auto-generated by bos sync/pluginAdd/pluginRemove. Do not edit.
121
+ ${importLines.join("\n")}
122
+
123
+ export type SidebarRole = "anon" | "member" | "admin";
124
+
125
+ export interface SidebarItem {
126
+ icon: React.ComponentType<{ className?: string }>;
127
+ label: string;
128
+ to: string;
129
+ roleRequired: SidebarRole;
130
+ }
131
+
132
+ export const pluginSidebarItems: SidebarItem[] = [
133
+ ${itemsCode}
134
+ ];
135
+ `;
136
+ }
137
+
138
+ export function writePluginSidebarGen(configDir: string, config: BosConfig): string {
139
+ const outputPath = join(configDir, "ui/src/lib/plugin-sidebar.gen.ts");
140
+
141
+ const content = generatePluginSidebarContent(config, configDir);
142
+
143
+ const outputDir = dirname(outputPath);
144
+ if (!existsSync(outputDir)) {
145
+ mkdirSync(outputDir, { recursive: true });
146
+ }
147
+
148
+ let existingContent: string | null = null;
149
+ try {
150
+ existingContent = existsSync(outputPath)
151
+ ? // eslint-disable-next-line no-restricted-syntax
152
+ readFileSync(outputPath, "utf-8")
153
+ : null;
154
+ } catch {
155
+ // file doesn't exist yet
156
+ }
157
+
158
+ if (existingContent === content) return outputPath;
159
+
160
+ writeFileSync(outputPath, content);
161
+ return outputPath;
162
+ }
package/src/types.ts CHANGED
@@ -1,28 +1,15 @@
1
1
  import { z } from "./sdk";
2
2
 
3
- export interface BosConfigInput extends Record<string, unknown> {
4
- extends?: string;
5
- account?: string;
6
- domain?: string;
7
- testnet?: string;
8
- template?: string;
9
- gateway?: {
10
- development?: string;
11
- production?: string;
12
- account?: string;
13
- };
14
- development?: string;
15
- production?: string;
16
- integrity?: string;
17
- name?: string;
18
- version?: string;
19
- proxy?: string;
20
- variables?: Record<string, string>;
21
- secrets?: string[];
22
- app?: Record<string, Record<string, unknown>>;
23
- shared?: Record<string, Record<string, Record<string, unknown>>>;
24
- plugins?: Record<string, BosConfigInput>;
25
- }
3
+ export const ExtendsSchema = z.union([
4
+ z.string(),
5
+ z.object({
6
+ development: z.string().optional(),
7
+ production: z.string().optional(),
8
+ staging: z.string().optional(),
9
+ }),
10
+ ]);
11
+ export type Extends = z.infer<typeof ExtendsSchema>;
12
+ export type ExtendsConfig = Extract<Extends, Record<string, string | undefined>>;
26
13
 
27
14
  export const SourceModeSchema = z.enum(["local", "remote"]);
28
15
  export type SourceMode = z.infer<typeof SourceModeSchema>;
@@ -48,6 +35,17 @@ export const FederationEntrySchema = z.object({
48
35
  });
49
36
  export type FederationEntry = z.infer<typeof FederationEntrySchema>;
50
37
 
38
+ export const SidebarRoleSchema = z.enum(["anon", "member", "admin"]);
39
+ export type SidebarRole = z.infer<typeof SidebarRoleSchema>;
40
+
41
+ export const SidebarItemSchema = z.object({
42
+ icon: z.string(),
43
+ label: z.string(),
44
+ to: z.string().optional(),
45
+ roleRequired: SidebarRoleSchema.optional(),
46
+ });
47
+ export type SidebarItem = z.infer<typeof SidebarItemSchema>;
48
+
51
49
  export const ApiPluginConfigSchema = z.object({
52
50
  name: z.string(),
53
51
  development: z.string().optional(),
@@ -56,11 +54,20 @@ export const ApiPluginConfigSchema = z.object({
56
54
  proxy: z.string().optional(),
57
55
  variables: z.record(z.string(), z.string()).optional(),
58
56
  secrets: z.array(z.string()).optional(),
57
+ sidebar: z.array(SidebarItemSchema).optional(),
59
58
  });
60
59
  export type ApiPluginConfig = z.infer<typeof ApiPluginConfigSchema>;
61
60
 
61
+ export const PluginUiConfigSchema = z.object({
62
+ name: z.string(),
63
+ development: z.string().optional(),
64
+ production: z.string().optional(),
65
+ integrity: z.string().optional(),
66
+ });
67
+ export type PluginUiConfig = z.infer<typeof PluginUiConfigSchema>;
68
+
62
69
  export const BosPluginRefSchema = z.object({
63
- extends: z.string().optional(),
70
+ extends: ExtendsSchema.optional(),
64
71
  development: z.string().optional(),
65
72
  production: z.string().optional(),
66
73
  integrity: z.string().optional(),
@@ -70,8 +77,25 @@ export const BosPluginRefSchema = z.object({
70
77
  variables: z.record(z.string(), z.string()).optional(),
71
78
  secrets: z.array(z.string()).optional(),
72
79
  routes: z.array(z.string()).optional(),
80
+ sidebar: z.array(SidebarItemSchema).optional(),
81
+ app: z.record(z.string(), z.unknown()).optional(),
82
+ shared: z.record(z.string(), z.record(z.string(), SharedConfigSchema)).optional(),
83
+ plugins: z.record(z.string(), z.unknown()).optional(),
73
84
  });
74
85
  export type BosPluginRef = z.infer<typeof BosPluginRefSchema>;
86
+ export type PluginEntryValue = string | BosPluginRef;
87
+ export type PluginEntries = Record<string, PluginEntryValue>;
88
+
89
+ const PluginRuntimeUiSchema = z.object({
90
+ name: z.string(),
91
+ url: z.string(),
92
+ entry: z.string(),
93
+ source: SourceModeSchema,
94
+ localPath: z.string().optional(),
95
+ port: z.number().optional(),
96
+ integrity: z.string().optional(),
97
+ });
98
+ export type PluginRuntimeUi = z.infer<typeof PluginRuntimeUiSchema>;
75
99
 
76
100
  export const RuntimePluginConfigSchema = z.object({
77
101
  name: z.string(),
@@ -84,6 +108,9 @@ export const RuntimePluginConfigSchema = z.object({
84
108
  variables: z.record(z.string(), z.string()).optional(),
85
109
  secrets: z.array(z.string()).optional(),
86
110
  integrity: z.string().optional(),
111
+ ui: PluginRuntimeUiSchema.optional(),
112
+ sidebar: z.array(SidebarItemSchema).optional(),
113
+ routes: z.array(z.string()).optional(),
87
114
  });
88
115
  export type RuntimePluginConfig = z.infer<typeof RuntimePluginConfigSchema>;
89
116
 
@@ -119,15 +146,74 @@ export const BosStagingSchema = z.object({
119
146
  });
120
147
  export type BosStaging = z.infer<typeof BosStagingSchema>;
121
148
 
149
+ const BosConfigInputAppEntrySchema = z.record(z.string(), z.unknown());
150
+ export type BosConfigInputAppEntry = z.infer<typeof BosConfigInputAppEntrySchema>;
151
+
152
+ export const BosConfigInputSchema: z.ZodType<BosConfigInput> = z.lazy(() =>
153
+ z.object({
154
+ extends: ExtendsSchema.optional(),
155
+ account: z.string().optional(),
156
+ domain: z.string().optional(),
157
+ testnet: z.string().optional(),
158
+ template: z.string().optional(),
159
+ gateway: z
160
+ .object({
161
+ development: z.string().optional(),
162
+ production: z.string().optional(),
163
+ account: z.string().optional(),
164
+ })
165
+ .optional(),
166
+ development: z.string().optional(),
167
+ production: z.string().optional(),
168
+ integrity: z.string().optional(),
169
+ name: z.string().optional(),
170
+ version: z.string().optional(),
171
+ proxy: z.string().optional(),
172
+ variables: z.record(z.string(), z.string()).optional(),
173
+ secrets: z.array(z.string()).optional(),
174
+ routes: z.array(z.string()).optional(),
175
+ sidebar: z.array(SidebarItemSchema).optional(),
176
+ app: z.record(z.string(), BosConfigInputAppEntrySchema).optional(),
177
+ shared: z.record(z.string(), z.record(z.string(), SharedConfigSchema)).optional(),
178
+ plugins: z.record(z.string(), z.union([z.string(), BosConfigInputSchema])).optional(),
179
+ }),
180
+ );
181
+
182
+ export interface BosConfigInput {
183
+ extends?: string | ExtendsConfig;
184
+ account?: string;
185
+ domain?: string;
186
+ testnet?: string;
187
+ template?: string;
188
+ gateway?: {
189
+ development?: string;
190
+ production?: string;
191
+ account?: string;
192
+ };
193
+ development?: string;
194
+ production?: string;
195
+ integrity?: string;
196
+ name?: string;
197
+ version?: string;
198
+ proxy?: string;
199
+ variables?: Record<string, string>;
200
+ secrets?: string[];
201
+ routes?: string[];
202
+ sidebar?: SidebarItem[];
203
+ app?: Record<string, BosConfigInputAppEntry>;
204
+ shared?: Record<string, Record<string, SharedDepConfig>>;
205
+ plugins?: Record<string, string | BosConfigInput>;
206
+ }
207
+
122
208
  export const BosConfigSchema = z.object({
123
209
  account: z.string(),
124
- extends: z.string().optional(),
210
+ extends: ExtendsSchema.optional(),
125
211
  domain: z.string().optional(),
126
212
  testnet: z.string().optional(),
127
213
  staging: BosStagingSchema.optional(),
128
214
  repository: z.string().optional(),
129
215
  shared: z.record(z.string(), z.record(z.string(), SharedConfigSchema)).optional(),
130
- plugins: z.record(z.string(), BosPluginRefSchema).optional(),
216
+ plugins: z.record(z.string(), z.union([z.string(), BosPluginRefSchema])).optional(),
131
217
  app: z.object({
132
218
  host: HostConfigSchema,
133
219
  ui: UiConfigSchema,
@@ -138,7 +224,7 @@ export const BosConfigSchema = z.object({
138
224
  export type BosConfig = z.infer<typeof BosConfigSchema>;
139
225
 
140
226
  export const RuntimeConfigSchema = z.object({
141
- env: z.enum(["development", "production"]),
227
+ env: z.enum(["development", "production", "staging"]),
142
228
  account: z.string(),
143
229
  domain: z.string().optional(),
144
230
  networkId: z.enum(["mainnet", "testnet"]),
@@ -175,6 +261,7 @@ export const RuntimeConfigSchema = z.object({
175
261
  proxy: z.string().optional(),
176
262
  variables: z.record(z.string(), z.string()).optional(),
177
263
  secrets: z.array(z.string()).optional(),
264
+ sidebar: z.array(SidebarItemSchema).optional(),
178
265
  }).optional(),
179
266
  plugins: z.record(z.string(), RuntimePluginConfigSchema).optional(),
180
267
  });
@@ -182,7 +269,7 @@ export type RuntimeConfig = z.infer<typeof RuntimeConfigSchema>;
182
269
 
183
270
  export const ClientRuntimeConfigSchema = z.object({
184
271
  cspNonce: z.string().optional(),
185
- env: z.enum(["development", "production"]),
272
+ env: z.enum(["development", "production", "staging"]),
186
273
  account: z.string(),
187
274
  networkId: z.enum(["mainnet", "testnet"]),
188
275
  hostUrl: z.string().optional(),
@@ -208,6 +295,15 @@ export const ClientRuntimeConfigSchema = z.object({
208
295
  integrity: z.string().optional(),
209
296
  })
210
297
  .optional(),
298
+ auth: z
299
+ .object({
300
+ name: z.string(),
301
+ url: z.string(),
302
+ entry: z.string(),
303
+ integrity: z.string().optional(),
304
+ sidebar: z.array(SidebarItemSchema).optional(),
305
+ })
306
+ .optional(),
211
307
  plugins: z
212
308
  .record(
213
309
  z.string(),
@@ -216,6 +312,16 @@ export const ClientRuntimeConfigSchema = z.object({
216
312
  url: z.string(),
217
313
  entry: z.string(),
218
314
  integrity: z.string().optional(),
315
+ ui: z
316
+ .object({
317
+ name: z.string(),
318
+ url: z.string(),
319
+ entry: z.string(),
320
+ source: SourceModeSchema,
321
+ integrity: z.string().optional(),
322
+ })
323
+ .optional(),
324
+ sidebar: z.array(SidebarItemSchema).optional(),
219
325
  }),
220
326
  )
221
327
  .optional(),
@@ -0,0 +1,16 @@
1
+ export function isPathExcluded(filePath: string, excludePatterns: string[]): boolean {
2
+ if (excludePatterns.length === 0) return false;
3
+ for (const pattern of excludePatterns) {
4
+ if (pattern.endsWith("/**")) {
5
+ const prefix = pattern.slice(0, -3);
6
+ if (filePath.startsWith(`${prefix}/`) || filePath === prefix) return true;
7
+ } else if (pattern.endsWith("/*")) {
8
+ const prefix = pattern.slice(0, -2);
9
+ const slashIdx = filePath.indexOf("/", prefix.length + 1);
10
+ if (filePath.startsWith(`${prefix}/`) && slashIdx === -1) return true;
11
+ } else if (filePath === pattern || filePath.startsWith(`${pattern}/`)) {
12
+ return true;
13
+ }
14
+ }
15
+ return false;
16
+ }
@@ -0,0 +1,20 @@
1
+ import { readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { rebuildOrderedConfig } from "../merge";
4
+ import type { BosConfig } from "../types";
5
+
6
+ export async function saveBosConfig(
7
+ configDir: string,
8
+ config: BosConfig | Record<string, unknown>,
9
+ ): Promise<void> {
10
+ const filePath = join(configDir, "bos.config.json");
11
+ const ordered = rebuildOrderedConfig(config as Record<string, unknown>);
12
+ const next = `${JSON.stringify(ordered, null, 2)}\n`;
13
+ try {
14
+ if (readFileSync(filePath, "utf8") === next) return;
15
+ } catch {
16
+ // file does not exist yet
17
+ }
18
+
19
+ writeFileSync(filePath, next);
20
+ }