everything-dev 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts CHANGED
@@ -4,132 +4,231 @@ export const SourceModeSchema = z.enum(["local", "remote"]);
4
4
  export type SourceMode = z.infer<typeof SourceModeSchema>;
5
5
 
6
6
  export const HostConfigSchema = z.object({
7
- development: z.string(),
8
- production: z.string(),
9
- secrets: z.array(z.string()).optional(),
10
- template: z.string().optional(),
11
- files: z.array(z.string()).optional(),
12
- sync: z.lazy(() => SyncConfigSchema).optional(),
7
+ development: z.string(),
8
+ production: z.string(),
9
+ secrets: z.array(z.string()).optional(),
10
+ template: z.string().optional(),
11
+ files: z.array(z.string()).optional(),
12
+ sync: z.lazy(() => SyncConfigSchema).optional(),
13
13
  });
14
14
  export type HostConfig = z.infer<typeof HostConfigSchema>;
15
15
 
16
16
  export const RemoteConfigSchema = z.object({
17
- name: z.string(),
18
- development: z.string(),
19
- production: z.string(),
20
- ssr: z.string().optional(),
21
- proxy: z.string().optional(),
22
- variables: z.record(z.string(), z.string()).optional(),
23
- secrets: z.array(z.string()).optional(),
24
- template: z.string().optional(),
25
- files: z.array(z.string()).optional(),
26
- sync: z.lazy(() => SyncConfigSchema).optional(),
17
+ name: z.string(),
18
+ development: z.string(),
19
+ production: z.string(),
20
+ ssr: z.string().optional(),
21
+ proxy: z.string().optional(),
22
+ variables: z.record(z.string(), z.string()).optional(),
23
+ secrets: z.array(z.string()).optional(),
24
+ template: z.string().optional(),
25
+ files: z.array(z.string()).optional(),
26
+ sync: z.lazy(() => SyncConfigSchema).optional(),
27
27
  });
28
28
  export type RemoteConfig = z.infer<typeof RemoteConfigSchema>;
29
29
 
30
+ /**
31
+ * Partial RemoteConfig for input configs (child configs that extend a base).
32
+ * Only 'name' is required; development/production can be inherited.
33
+ */
34
+ export const PartialRemoteConfigSchema = z.object({
35
+ name: z.string(),
36
+ development: z.string().optional(),
37
+ production: z.string().optional(),
38
+ proxy: z.string().optional(),
39
+ variables: z.record(z.string(), z.string()).optional(),
40
+ secrets: z.array(z.string()).optional(),
41
+ template: z.string().optional(),
42
+ files: z.array(z.string()).optional(),
43
+ sync: z.lazy(() => SyncConfigSchema).optional(),
44
+ });
45
+ export type PartialRemoteConfig = z.infer<typeof PartialRemoteConfigSchema>;
46
+
30
47
  export const NovaConfigSchema = z.object({
31
- account: z.string(),
48
+ account: z.string(),
32
49
  });
33
50
  export type NovaConfig = z.infer<typeof NovaConfigSchema>;
34
51
 
35
52
  export const GatewayConfigSchema = z.object({
36
- development: z.string(),
37
- production: z.string(),
38
- account: z.string().optional(),
39
- nova: NovaConfigSchema.optional(),
53
+ development: z.string(),
54
+ production: z.string(),
55
+ account: z.string().optional(),
56
+ nova: NovaConfigSchema.optional(),
40
57
  });
41
58
  export type GatewayConfig = z.infer<typeof GatewayConfigSchema>;
42
59
 
43
60
  export const SharedDepConfigSchema = z.object({
44
- requiredVersion: z.string().optional(),
45
- singleton: z.boolean().optional(),
46
- eager: z.boolean().optional(),
47
- strictVersion: z.boolean().optional(),
61
+ requiredVersion: z.string().optional(),
62
+ singleton: z.boolean().optional(),
63
+ eager: z.boolean().optional(),
64
+ strictVersion: z.boolean().optional(),
48
65
  });
49
66
  export type SharedDepConfig = z.infer<typeof SharedDepConfigSchema>;
50
67
 
51
68
  export const SyncConfigSchema = z.object({
52
- scripts: z.union([z.array(z.string()), z.literal(true)]).optional(),
53
- dependencies: z.boolean().default(true),
54
- devDependencies: z.boolean().default(true),
69
+ scripts: z.union([z.array(z.string()), z.literal(true)]).optional(),
70
+ dependencies: z.boolean().default(true),
71
+ devDependencies: z.boolean().default(true),
55
72
  });
56
73
  export type SyncConfig = z.infer<typeof SyncConfigSchema>;
57
74
 
58
75
  export const BosConfigSchema = z.object({
59
- account: z.string(),
60
- testnet: z.string().optional(),
61
- nova: NovaConfigSchema.optional(),
62
- gateway: GatewayConfigSchema,
63
- template: z.string().optional(),
64
- cli: z.object({
65
- version: z.string().optional(),
66
- }).optional(),
67
- shared: z.record(z.string(), z.record(z.string(), SharedDepConfigSchema)).optional(),
68
- app: z.object({
69
- host: HostConfigSchema,
70
- }).catchall(z.union([HostConfigSchema, RemoteConfigSchema])),
76
+ extends: z.string().optional(),
77
+ account: z.string(),
78
+ testnet: z.string().optional(),
79
+ nova: NovaConfigSchema.optional(),
80
+ gateway: GatewayConfigSchema,
81
+ template: z.string().optional(),
82
+ cli: z
83
+ .object({
84
+ version: z.string().optional(),
85
+ })
86
+ .optional(),
87
+ shared: z
88
+ .record(z.string(), z.record(z.string(), SharedDepConfigSchema))
89
+ .optional(),
90
+ app: z
91
+ .object({
92
+ host: HostConfigSchema,
93
+ })
94
+ .catchall(z.union([RemoteConfigSchema, HostConfigSchema])),
71
95
  });
72
96
  export type BosConfig = z.infer<typeof BosConfigSchema>;
73
97
 
98
+ /**
99
+ * BosConfigInputSchema - permissive schema for parsing input configs.
100
+ * Supports extends and allows partial app configs (child overrides only what it needs).
101
+ * After resolving extends, the result must be validated against the strict BosConfigSchema.
102
+ */
103
+ export const BosConfigInputSchema = z.object({
104
+ extends: z.string().optional(),
105
+ account: z.string().optional(),
106
+ testnet: z.string().optional(),
107
+ nova: NovaConfigSchema.optional(),
108
+ gateway: GatewayConfigSchema.optional(),
109
+ template: z.string().optional(),
110
+ cli: z
111
+ .object({
112
+ version: z.string().optional(),
113
+ })
114
+ .optional(),
115
+ shared: z
116
+ .record(z.string(), z.record(z.string(), SharedDepConfigSchema))
117
+ .optional(),
118
+ app: z
119
+ .object({
120
+ host: HostConfigSchema.optional(),
121
+ ui: PartialRemoteConfigSchema.optional(),
122
+ api: PartialRemoteConfigSchema.optional(),
123
+ })
124
+ .partial()
125
+ .catchall(z.union([HostConfigSchema, PartialRemoteConfigSchema])),
126
+ });
127
+ export type BosConfigInput = z.infer<typeof BosConfigInputSchema>;
128
+
74
129
  export const AppConfigSchema = z.object({
75
- host: SourceModeSchema,
76
- ui: SourceModeSchema,
77
- api: SourceModeSchema,
78
- proxy: z.boolean().optional(),
130
+ host: SourceModeSchema,
131
+ ui: SourceModeSchema,
132
+ api: SourceModeSchema,
133
+ proxy: z.boolean().optional(),
79
134
  });
80
135
  export type AppConfig = z.infer<typeof AppConfigSchema>;
81
136
 
82
137
  export const PortConfigSchema = z.object({
83
- host: z.number(),
84
- ui: z.number(),
85
- api: z.number(),
138
+ host: z.number(),
139
+ ui: z.number(),
140
+ api: z.number(),
86
141
  });
87
142
  export type PortConfig = z.infer<typeof PortConfigSchema>;
88
143
 
89
144
  export const SharedConfigSchema = z.object({
90
- requiredVersion: z.string().optional(),
91
- singleton: z.boolean().optional(),
92
- eager: z.boolean().optional(),
93
- strictVersion: z.boolean().optional(),
94
- shareScope: z.string().optional(),
145
+ requiredVersion: z.string().optional(),
146
+ singleton: z.boolean().optional(),
147
+ eager: z.boolean().optional(),
148
+ strictVersion: z.boolean().optional(),
149
+ shareScope: z.string().optional(),
95
150
  });
96
151
  export type SharedConfig = z.infer<typeof SharedConfigSchema>;
97
152
 
153
+ /**
154
+ * Federation entry info - both base URL and computed manifest entry
155
+ */
156
+ export const FederationEntrySchema = z.object({
157
+ name: z.string(),
158
+ url: z.string(), // Base/assets URL
159
+ entry: z.string(), // Computed federation entry (mf-manifest.json or remoteEntry.js)
160
+ source: SourceModeSchema,
161
+ });
162
+ export type FederationEntry = z.infer<typeof FederationEntrySchema>;
163
+
98
164
  export const RuntimeConfigSchema = z.object({
99
- env: z.enum(["development", "production"]),
100
- account: z.string(),
101
- title: z.string().optional(),
102
- hostUrl: z.string(),
103
- shared: z.object({
104
- ui: z.record(z.string(), SharedConfigSchema).optional(),
105
- }).optional(),
106
- ui: z.object({
107
- name: z.string(),
108
- url: z.string(),
109
- ssrUrl: z.string().optional(),
110
- source: SourceModeSchema,
111
- }),
112
- api: z.object({
113
- name: z.string(),
114
- url: z.string(),
115
- source: SourceModeSchema,
116
- proxy: z.string().optional(),
117
- variables: z.record(z.string(), z.string()).optional(),
118
- secrets: z.array(z.string()).optional(),
119
- }),
165
+ env: z.enum(["development", "production"]),
166
+ account: z.string(),
167
+ title: z.string().optional(),
168
+ hostUrl: z.string(),
169
+ shared: z
170
+ .object({
171
+ ui: z.record(z.string(), SharedConfigSchema).optional(),
172
+ })
173
+ .optional(),
174
+ ui: FederationEntrySchema.extend({
175
+ ssrUrl: z.string().optional(),
176
+ }),
177
+ api: FederationEntrySchema.extend({
178
+ proxy: z.string().optional(),
179
+ variables: z.record(z.string(), z.string()).optional(),
180
+ secrets: z.array(z.string()).optional(),
181
+ }),
120
182
  });
121
183
  export type RuntimeConfig = z.infer<typeof RuntimeConfigSchema>;
122
184
 
185
+ /**
186
+ * Client-side runtime config (injected via window.__RUNTIME_CONFIG__)
187
+ * Contains federation entry info so browser can load remotes via manifest
188
+ */
123
189
  export const ClientRuntimeConfigSchema = z.object({
124
- env: z.enum(["development", "production"]),
125
- account: z.string(),
126
- hostUrl: z.string().optional(),
127
- assetsUrl: z.string(),
128
- apiBase: z.string(),
129
- rpcBase: z.string(),
130
- ui: z.object({
131
- name: z.string(),
132
- url: z.string(),
133
- }).optional(),
190
+ env: z.enum(["development", "production"]),
191
+ account: z.string(),
192
+ hostUrl: z.string().optional(),
193
+ assetsUrl: z.string(), // Legacy: use ui.entry for MF loading
194
+ apiBase: z.string(),
195
+ rpcBase: z.string(),
196
+ ui: z
197
+ .object({
198
+ name: z.string(),
199
+ url: z.string(), // Base URL for assets
200
+ entry: z.string(), // Federation entry (mf-manifest.json)
201
+ })
202
+ .optional(),
134
203
  });
135
204
  export type ClientRuntimeConfig = z.infer<typeof ClientRuntimeConfigSchema>;
205
+
206
+ /**
207
+ * Error thrown when a circular extends dependency is detected
208
+ */
209
+ export class ConfigCircularExtendsError extends Error {
210
+ constructor(chain: string[]) {
211
+ super(`Circular extends detected: ${chain.join(" → ")}`);
212
+ this.name = "ConfigCircularExtendsError";
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Error thrown when config fetching fails
218
+ */
219
+ export class ConfigFetchError extends Error {
220
+ constructor(url: string, cause: unknown) {
221
+ super(`Failed to fetch config from ${url}: ${cause}`);
222
+ this.name = "ConfigFetchError";
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Error thrown when config resolution fails
228
+ */
229
+ export class ConfigResolutionError extends Error {
230
+ constructor(message: string) {
231
+ super(message);
232
+ this.name = "ConfigResolutionError";
233
+ }
234
+ }
package/src/ui/head.ts CHANGED
@@ -2,35 +2,46 @@ import type { ClientRuntimeConfig } from "../types";
2
2
  import type { HeadScript } from "./types";
3
3
 
4
4
  export interface RemoteScriptsOptions {
5
- assetsUrl: string;
6
- runtimeConfig?: ClientRuntimeConfig;
7
- containerName?: string;
8
- hydratePath?: string;
5
+ assetsUrl: string;
6
+ runtimeConfig?: ClientRuntimeConfig;
7
+ containerName?: string;
8
+ hydratePath?: string;
9
9
  }
10
10
 
11
11
  export function getRemoteEntryScript(assetsUrl: string): HeadScript {
12
- return {
13
- src: `${assetsUrl}/remoteEntry.js`,
14
- };
12
+ return {
13
+ src: `${assetsUrl}/remoteEntry.js`,
14
+ };
15
15
  }
16
16
 
17
17
  export function getThemeInitScript(): HeadScript {
18
- return {
19
- children: `(function(){var t=localStorage.getItem('theme');if(t==='dark'||(!t&&window.matchMedia('(prefers-color-scheme: dark)').matches)){document.documentElement.classList.add('dark');}})();`,
20
- };
18
+ return {
19
+ children: `(function(){var t=localStorage.getItem('theme');if(t==='dark'||(!t&&window.matchMedia('(prefers-color-scheme: dark)').matches)){document.documentElement.classList.add('dark');}})();`,
20
+ };
21
21
  }
22
22
 
23
23
  export function getHydrateScript(
24
- runtimeConfig: ClientRuntimeConfig | undefined,
25
- containerName = "ui",
26
- hydratePath = "./Hydrate"
24
+ runtimeConfig: ClientRuntimeConfig | undefined,
25
+ containerName = "ui",
26
+ hydratePath = "./Hydrate",
27
27
  ): HeadScript {
28
- return {
29
- children: `
30
- window.__RUNTIME_CONFIG__=${JSON.stringify(runtimeConfig)};
31
- function __hydrate(){
28
+ return {
29
+ children: `
30
+ window.__RUNTIME_CONFIG__=${JSON.stringify(runtimeConfig)};
31
+ function __hydrate(){
32
32
  var container = window['${containerName}'];
33
- if (!container) { console.error('[Hydrate] Container not found'); return; }
33
+ if (!container) {
34
+ console.warn('[Hydrate] Container not ready yet, waiting...');
35
+ window.__hydrateRetry = window.__hydrateRetry || 0;
36
+ if (window.__hydrateRetry < 10) {
37
+ window.__hydrateRetry++;
38
+ setTimeout(__hydrate, 100);
39
+ return;
40
+ }
41
+ console.error('[Hydrate] Container not found after 10 retries');
42
+ return;
43
+ }
44
+ console.log('[Hydrate] Container available, starting init...');
34
45
  container.init({}).then(function(){
35
46
  return container.get('${hydratePath}');
36
47
  }).then(function(mod){
@@ -41,21 +52,21 @@ function __hydrate(){
41
52
  }
42
53
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',__hydrate);}else{__hydrate();}
43
54
  `.trim(),
44
- };
55
+ };
45
56
  }
46
57
 
47
58
  export function getRemoteScripts(options: RemoteScriptsOptions): HeadScript[] {
48
- const { assetsUrl, runtimeConfig, containerName, hydratePath } = options;
59
+ const { assetsUrl, runtimeConfig, containerName, hydratePath } = options;
49
60
 
50
- return [
51
- getRemoteEntryScript(assetsUrl),
52
- getThemeInitScript(),
53
- getHydrateScript(runtimeConfig, containerName, hydratePath),
54
- ];
61
+ return [
62
+ getRemoteEntryScript(assetsUrl),
63
+ getThemeInitScript(),
64
+ getHydrateScript(runtimeConfig, containerName, hydratePath),
65
+ ];
55
66
  }
56
67
 
57
68
  export function getBaseStyles(): string {
58
- return `
69
+ return `
59
70
  *, *::before, *::after { box-sizing: border-box; }
60
71
  html { height: 100%; height: 100dvh; -webkit-text-size-adjust: 100%; text-size-adjust: 100%; color-scheme: light dark; }
61
72
  body { min-height: 100%; min-height: 100dvh; margin: 0; background-color: var(--background); color: var(--foreground); -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
@@ -1,4 +1,4 @@
1
- import { gradients, colors, divider } from "./theme";
1
+ import { colors, divider, gradients } from "./theme";
2
2
 
3
3
  const ASCII_BOS = `
4
4
  ██████╗ ██████╗ ███████╗
@@ -8,12 +8,10 @@ const ASCII_BOS = `
8
8
  ██████╔╝╚██████╔╝███████║
9
9
  ╚═════╝ ╚═════╝ ╚══════╝`;
10
10
 
11
- export function printBanner(title?: string, version = "1.0.0") {
12
- console.log(gradients.cyber(ASCII_BOS));
13
- console.log();
14
- if (title) {
15
- console.log(colors.dim(` ${title} ${colors.cyan(`v${version}`)}`));
16
- console.log(colors.dim(` ${divider(30)}`));
17
- }
18
- console.log();
11
+ export function printBanner(title = "everything-dev", version = "1.0.0") {
12
+ console.log(gradients.cyber(ASCII_BOS));
13
+ console.log();
14
+ console.log(colors.dim(` ${title} ${colors.cyan(`v${version}`)}`));
15
+ console.log(colors.dim(` ${divider(30)}`));
16
+ console.log();
19
17
  }
package/src/utils/run.ts CHANGED
@@ -1,21 +1,32 @@
1
- import { execa, type Options as ExecaOptions } from "execa";
2
1
  import chalk from "chalk";
3
- import { getConfigDir } from "../config";
2
+ import { type Options as ExecaOptions, execa } from "execa";
3
+ import { getProjectRoot } from "../config";
4
4
 
5
5
  export async function run(
6
- cmd: string,
7
- args: string[],
8
- options: { cwd?: string; env?: Record<string, string> } = {}
6
+ cmd: string,
7
+ args: string[],
8
+ options: { cwd?: string; env?: Record<string, string> } = {},
9
9
  ) {
10
- console.log(chalk.dim(`$ ${cmd} ${args.join(" ")}`));
11
- const execaOptions: ExecaOptions = {
12
- cwd: options.cwd ?? getConfigDir(),
13
- stdio: "inherit",
14
- reject: false,
15
- env: options.env ? { ...process.env, ...options.env } : undefined,
16
- };
17
- const result = await execa(cmd, args, execaOptions);
18
- if (result.exitCode !== 0) {
19
- process.exit(result.exitCode);
20
- }
10
+ console.log(chalk.dim(`$ ${cmd} ${args.join(" ")}`));
11
+
12
+ // Get project root, fallback to cwd if config not loaded
13
+ let cwd = options.cwd;
14
+ if (!cwd) {
15
+ try {
16
+ cwd = getProjectRoot();
17
+ } catch {
18
+ cwd = process.cwd();
19
+ }
20
+ }
21
+
22
+ const execaOptions: ExecaOptions = {
23
+ cwd,
24
+ stdio: "inherit",
25
+ reject: false,
26
+ env: options.env ? { ...process.env, ...options.env } : undefined,
27
+ };
28
+ const result = await execa(cmd, args, execaOptions);
29
+ if (result.exitCode !== 0) {
30
+ process.exit(result.exitCode);
31
+ }
21
32
  }
@@ -1,29 +0,0 @@
1
- import { loadConfig } from "../config";
2
-
3
- export function loadSecretsFor(component: string): Record<string, string> {
4
- const config = loadConfig();
5
- if (!config) return {};
6
- const componentConfig = config.app[component];
7
- if (!componentConfig) return {};
8
-
9
- const secretNames = ("secrets" in componentConfig ? componentConfig.secrets : undefined) ?? [];
10
- if (secretNames.length === 0) return {};
11
-
12
- const secrets: Record<string, string> = {};
13
- for (const name of secretNames) {
14
- const value = process.env[name];
15
- if (value) secrets[name] = value;
16
- }
17
-
18
- return secrets;
19
- }
20
-
21
- export function loadAllSecrets(): {
22
- host: Record<string, string>;
23
- api: Record<string, string>;
24
- } {
25
- return {
26
- host: loadSecretsFor("host"),
27
- api: loadSecretsFor("api"),
28
- };
29
- }