everything-dev 0.1.4 → 0.2.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/package.json +22 -2
- package/src/cli.ts +50 -57
- package/src/components/dev-view.tsx +49 -9
- package/src/components/streaming-view.ts +10 -1
- package/src/config.ts +46 -9
- package/src/contract.ts +9 -10
- package/src/lib/process.ts +80 -44
- package/src/lib/resource-monitor/diff.ts +7 -11
- package/src/lib/resource-monitor/platform/darwin.ts +1 -1
- package/src/lib/sync.ts +1 -133
- package/src/plugin.ts +65 -21
- package/src/types.ts +48 -3
- package/src/ui/files.ts +134 -0
- package/src/ui/head.ts +65 -0
- package/src/ui/index.ts +5 -0
- package/src/ui/router.ts +72 -0
- package/src/ui/runtime.ts +35 -0
- package/src/ui/types.ts +51 -0
package/src/lib/process.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Command } from "@effect/platform";
|
|
2
2
|
import { Deferred, Effect, Fiber, Ref, Stream } from "every-plugin/effect";
|
|
3
|
-
import { type BosConfig, getConfigDir, getPortsFromConfig, type SourceMode } from "../config";
|
|
4
3
|
import type { ProcessStatus } from "../components/dev-view";
|
|
5
|
-
import {
|
|
4
|
+
import { type BosConfig, getConfigDir, getPortsFromConfig, type RemoteConfig, type SourceMode } from "../config";
|
|
5
|
+
import type { RuntimeConfig } from "../types";
|
|
6
6
|
|
|
7
7
|
export interface DevProcess {
|
|
8
8
|
name: string;
|
|
@@ -68,7 +68,7 @@ export const getProcessConfig = (
|
|
|
68
68
|
if (!base) return null;
|
|
69
69
|
|
|
70
70
|
const ports = getPortsFromConfig();
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
let port: number;
|
|
73
73
|
if (pkg === "host") {
|
|
74
74
|
port = portOverride ?? ports.host;
|
|
@@ -157,9 +157,45 @@ const killProcessTree = (pid: number) =>
|
|
|
157
157
|
}
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
+
export function buildRuntimeConfig(
|
|
161
|
+
bosConfig: BosConfig,
|
|
162
|
+
options: {
|
|
163
|
+
uiSource: SourceMode;
|
|
164
|
+
apiSource: SourceMode;
|
|
165
|
+
hostUrl: string;
|
|
166
|
+
proxy?: string;
|
|
167
|
+
env?: "development" | "production";
|
|
168
|
+
}
|
|
169
|
+
): RuntimeConfig {
|
|
170
|
+
const uiConfig = bosConfig.app.ui as RemoteConfig;
|
|
171
|
+
const apiConfig = bosConfig.app.api as RemoteConfig;
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
env: options.env ?? "development",
|
|
175
|
+
account: bosConfig.account,
|
|
176
|
+
hostUrl: options.hostUrl,
|
|
177
|
+
shared: (bosConfig as { shared?: { ui?: Record<string, unknown> } }).shared as RuntimeConfig["shared"],
|
|
178
|
+
ui: {
|
|
179
|
+
name: uiConfig.name,
|
|
180
|
+
url: options.uiSource === "remote" ? uiConfig.production : uiConfig.development,
|
|
181
|
+
ssrUrl: options.uiSource === "remote" ? uiConfig.ssr : undefined,
|
|
182
|
+
source: options.uiSource,
|
|
183
|
+
},
|
|
184
|
+
api: {
|
|
185
|
+
name: apiConfig.name,
|
|
186
|
+
url: options.apiSource === "remote" ? apiConfig.production : apiConfig.development,
|
|
187
|
+
source: options.apiSource,
|
|
188
|
+
proxy: options.proxy,
|
|
189
|
+
variables: apiConfig.variables,
|
|
190
|
+
secrets: apiConfig.secrets,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
160
195
|
export const spawnDevProcess = (
|
|
161
196
|
config: DevProcess,
|
|
162
|
-
callbacks: ProcessCallbacks
|
|
197
|
+
callbacks: ProcessCallbacks,
|
|
198
|
+
runtimeConfig?: RuntimeConfig
|
|
163
199
|
) =>
|
|
164
200
|
Effect.gen(function* () {
|
|
165
201
|
const configDir = getConfigDir();
|
|
@@ -169,15 +205,20 @@ export const spawnDevProcess = (
|
|
|
169
205
|
|
|
170
206
|
callbacks.onStatus(config.name, "starting");
|
|
171
207
|
|
|
208
|
+
const envVars: Record<string, string> = {
|
|
209
|
+
...process.env as Record<string, string>,
|
|
210
|
+
...config.env,
|
|
211
|
+
FORCE_COLOR: "1",
|
|
212
|
+
...(config.port > 0 ? { PORT: String(config.port) } : {}),
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (runtimeConfig && config.name === "host") {
|
|
216
|
+
envVars.BOS_RUNTIME_CONFIG = JSON.stringify(runtimeConfig);
|
|
217
|
+
}
|
|
218
|
+
|
|
172
219
|
const cmd = Command.make(config.command, ...config.args).pipe(
|
|
173
220
|
Command.workingDirectory(fullCwd),
|
|
174
|
-
Command.env(
|
|
175
|
-
...process.env,
|
|
176
|
-
...config.env,
|
|
177
|
-
BOS_CONFIG_PATH: "../bos.config.json",
|
|
178
|
-
FORCE_COLOR: "1",
|
|
179
|
-
...(config.port > 0 ? { PORT: String(config.port) } : {}),
|
|
180
|
-
})
|
|
221
|
+
Command.env(envVars)
|
|
181
222
|
);
|
|
182
223
|
|
|
183
224
|
const proc = yield* Command.start(cmd);
|
|
@@ -247,13 +288,8 @@ interface ServerHandle {
|
|
|
247
288
|
shutdown: () => Promise<void>;
|
|
248
289
|
}
|
|
249
290
|
|
|
250
|
-
interface
|
|
251
|
-
config
|
|
252
|
-
secrets?: Record<string, string>;
|
|
253
|
-
host?: { url?: string };
|
|
254
|
-
ui?: { source?: SourceMode };
|
|
255
|
-
api?: { source?: SourceMode; proxy?: string };
|
|
256
|
-
database?: { url?: string };
|
|
291
|
+
interface ServerInput {
|
|
292
|
+
config: RuntimeConfig;
|
|
257
293
|
}
|
|
258
294
|
|
|
259
295
|
const patchConsole = (
|
|
@@ -300,7 +336,7 @@ const patchConsole = (
|
|
|
300
336
|
export const spawnRemoteHost = (
|
|
301
337
|
config: DevProcess,
|
|
302
338
|
callbacks: ProcessCallbacks,
|
|
303
|
-
|
|
339
|
+
runtimeConfig: RuntimeConfig
|
|
304
340
|
) =>
|
|
305
341
|
Effect.gen(function* () {
|
|
306
342
|
const remoteUrl = config.env?.HOST_REMOTE_URL;
|
|
@@ -317,27 +353,6 @@ export const spawnRemoteHost = (
|
|
|
317
353
|
|
|
318
354
|
callbacks.onStatus(config.name, "starting");
|
|
319
355
|
|
|
320
|
-
let hostUrl = `http://localhost:${config.port}`;
|
|
321
|
-
if (process.env.HOST_URL) {
|
|
322
|
-
hostUrl = process.env.HOST_URL;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const hostSecrets = loadSecretsFor("host");
|
|
326
|
-
const apiSecrets = loadSecretsFor("api");
|
|
327
|
-
const allSecrets = { ...hostSecrets, ...apiSecrets };
|
|
328
|
-
|
|
329
|
-
const uiSource = (config.env?.UI_SOURCE as SourceMode) ?? "local";
|
|
330
|
-
const apiSource = (config.env?.API_SOURCE as SourceMode) ?? "local";
|
|
331
|
-
const apiProxy = config.env?.API_PROXY;
|
|
332
|
-
|
|
333
|
-
const bootstrap: BootstrapConfig = {
|
|
334
|
-
config: bosConfig,
|
|
335
|
-
secrets: allSecrets,
|
|
336
|
-
host: { url: hostUrl },
|
|
337
|
-
ui: { source: uiSource },
|
|
338
|
-
api: { source: apiSource, proxy: apiProxy },
|
|
339
|
-
};
|
|
340
|
-
|
|
341
356
|
callbacks.onLog(config.name, `Remote: ${remoteUrl}`);
|
|
342
357
|
|
|
343
358
|
const restoreConsole = patchConsole(config.name, callbacks);
|
|
@@ -369,7 +384,7 @@ export const spawnRemoteHost = (
|
|
|
369
384
|
callbacks.onLog(config.name, `Loading host from ${remoteEntryUrl}...`);
|
|
370
385
|
|
|
371
386
|
const hostModule = yield* Effect.tryPromise({
|
|
372
|
-
try: () => mf.loadRemote<{ runServer: (
|
|
387
|
+
try: () => mf.loadRemote<{ runServer: (input: ServerInput) => ServerHandle }>("host/Server"),
|
|
373
388
|
catch: (e) => new Error(`Failed to load host module: ${e}`),
|
|
374
389
|
});
|
|
375
390
|
|
|
@@ -378,7 +393,7 @@ export const spawnRemoteHost = (
|
|
|
378
393
|
}
|
|
379
394
|
|
|
380
395
|
callbacks.onLog(config.name, "Starting server...");
|
|
381
|
-
const serverHandle = hostModule.runServer(
|
|
396
|
+
const serverHandle = hostModule.runServer({ config: runtimeConfig });
|
|
382
397
|
|
|
383
398
|
yield* Effect.tryPromise({
|
|
384
399
|
try: () => serverHandle.ready,
|
|
@@ -415,8 +430,29 @@ export const makeDevProcess = (
|
|
|
415
430
|
return yield* Effect.fail(new Error(`Unknown package: ${pkg}`));
|
|
416
431
|
}
|
|
417
432
|
|
|
418
|
-
if (pkg === "host" &&
|
|
419
|
-
|
|
433
|
+
if (pkg === "host" && bosConfig) {
|
|
434
|
+
const uiSource = (env?.UI_SOURCE as SourceMode) ?? "local";
|
|
435
|
+
const apiSource = (env?.API_SOURCE as SourceMode) ?? "local";
|
|
436
|
+
const apiProxy = env?.API_PROXY;
|
|
437
|
+
|
|
438
|
+
let hostUrl = `http://localhost:${config.port}`;
|
|
439
|
+
if (process.env.HOST_URL) {
|
|
440
|
+
hostUrl = process.env.HOST_URL;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const runtimeConfig = buildRuntimeConfig(bosConfig, {
|
|
444
|
+
uiSource,
|
|
445
|
+
apiSource,
|
|
446
|
+
hostUrl,
|
|
447
|
+
proxy: apiProxy,
|
|
448
|
+
env: "development",
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
if (env?.HOST_SOURCE === "remote") {
|
|
452
|
+
return yield* spawnRemoteHost(config, callbacks, runtimeConfig);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return yield* spawnDevProcess(config, callbacks, runtimeConfig);
|
|
420
456
|
}
|
|
421
457
|
|
|
422
458
|
return yield* spawnDevProcess(config, callbacks);
|
|
@@ -21,17 +21,13 @@ export const diffSnapshots = (from: Snapshot, to: Snapshot): SnapshotDiff => {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
!toPids.has(p.pid) &&
|
|
32
|
-
isProcessAliveSync(p.pid) &&
|
|
33
|
-
stillBoundPids.has(p.pid)
|
|
34
|
-
);
|
|
24
|
+
const orphanedProcesses = from.processes.filter((p) => {
|
|
25
|
+
const wasInBaseline = fromPids.has(p.pid);
|
|
26
|
+
const notInAfter = !toPids.has(p.pid);
|
|
27
|
+
const stillAlive = isProcessAliveSync(p.pid);
|
|
28
|
+
|
|
29
|
+
return wasInBaseline && notInAfter && stillAlive;
|
|
30
|
+
});
|
|
35
31
|
|
|
36
32
|
const newProcesses = to.processes.filter((p) => !fromPids.has(p.pid));
|
|
37
33
|
const killedProcesses = from.processes.filter((p) => !toPids.has(p.pid));
|
package/src/lib/sync.ts
CHANGED
|
@@ -1,133 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { cp, mkdir, mkdtemp, rm } from "fs/promises";
|
|
3
|
-
import { tmpdir } from "os";
|
|
4
|
-
import { dirname, join } from "path";
|
|
5
|
-
import type { BosConfig } from "../config";
|
|
6
|
-
|
|
7
|
-
export interface FileSyncResult {
|
|
8
|
-
package: string;
|
|
9
|
-
files: string[];
|
|
10
|
-
depsAdded?: string[];
|
|
11
|
-
depsUpdated?: string[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface FileSyncOptions {
|
|
15
|
-
configDir: string;
|
|
16
|
-
packages: string[];
|
|
17
|
-
bosConfig: BosConfig;
|
|
18
|
-
catalog?: Record<string, string>;
|
|
19
|
-
force?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function syncFiles(options: FileSyncOptions): Promise<FileSyncResult[]> {
|
|
23
|
-
const { configDir, packages, bosConfig, catalog = {}, force } = options;
|
|
24
|
-
const results: FileSyncResult[] = [];
|
|
25
|
-
|
|
26
|
-
for (const pkg of packages) {
|
|
27
|
-
const pkgDir = `${configDir}/${pkg}`;
|
|
28
|
-
const pkgDirExists = await Bun.file(`${pkgDir}/package.json`).exists();
|
|
29
|
-
if (!pkgDirExists) continue;
|
|
30
|
-
|
|
31
|
-
const appConfig = bosConfig.app[pkg] as {
|
|
32
|
-
template?: string;
|
|
33
|
-
files?: string[];
|
|
34
|
-
sync?: { dependencies?: boolean; devDependencies?: boolean };
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
if (!appConfig?.template || !appConfig?.files) {
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const tempDir = await mkdtemp(join(tmpdir(), `bos-files-${pkg}-`));
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
await execa("npx", ["degit", appConfig.template, tempDir, "--force"], {
|
|
45
|
-
stdio: "pipe",
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const filesSynced: string[] = [];
|
|
49
|
-
const depsAdded: string[] = [];
|
|
50
|
-
const depsUpdated: string[] = [];
|
|
51
|
-
|
|
52
|
-
for (const file of appConfig.files) {
|
|
53
|
-
const srcPath = join(tempDir, file);
|
|
54
|
-
const destPath = join(pkgDir, file);
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const destDir = dirname(destPath);
|
|
58
|
-
await mkdir(destDir, { recursive: true });
|
|
59
|
-
await cp(srcPath, destPath, { force: true, recursive: true });
|
|
60
|
-
filesSynced.push(file);
|
|
61
|
-
} catch {
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const syncConfig = appConfig.sync ?? { dependencies: true, devDependencies: true };
|
|
66
|
-
|
|
67
|
-
if (syncConfig.dependencies !== false || syncConfig.devDependencies !== false) {
|
|
68
|
-
const templatePkgPath = join(tempDir, "package.json");
|
|
69
|
-
const localPkgPath = join(pkgDir, "package.json");
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
const templatePkg = await Bun.file(templatePkgPath).json() as {
|
|
73
|
-
dependencies?: Record<string, string>;
|
|
74
|
-
devDependencies?: Record<string, string>;
|
|
75
|
-
scripts?: Record<string, string>;
|
|
76
|
-
};
|
|
77
|
-
const localPkg = await Bun.file(localPkgPath).json() as {
|
|
78
|
-
dependencies?: Record<string, string>;
|
|
79
|
-
devDependencies?: Record<string, string>;
|
|
80
|
-
scripts?: Record<string, string>;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
if (syncConfig.dependencies !== false && templatePkg.dependencies) {
|
|
84
|
-
if (!localPkg.dependencies) localPkg.dependencies = {};
|
|
85
|
-
for (const [name, version] of Object.entries(templatePkg.dependencies)) {
|
|
86
|
-
if (!(name in localPkg.dependencies)) {
|
|
87
|
-
localPkg.dependencies[name] = name in catalog ? "catalog:" : version;
|
|
88
|
-
depsAdded.push(name);
|
|
89
|
-
} else if (localPkg.dependencies[name] !== "catalog:" && version !== localPkg.dependencies[name]) {
|
|
90
|
-
localPkg.dependencies[name] = name in catalog ? "catalog:" : version;
|
|
91
|
-
depsUpdated.push(name);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (syncConfig.devDependencies !== false && templatePkg.devDependencies) {
|
|
97
|
-
if (!localPkg.devDependencies) localPkg.devDependencies = {};
|
|
98
|
-
for (const [name, version] of Object.entries(templatePkg.devDependencies)) {
|
|
99
|
-
if (!(name in localPkg.devDependencies)) {
|
|
100
|
-
localPkg.devDependencies[name] = name in catalog ? "catalog:" : version;
|
|
101
|
-
depsAdded.push(name);
|
|
102
|
-
} else if (localPkg.devDependencies[name] !== "catalog:" && version !== localPkg.devDependencies[name]) {
|
|
103
|
-
localPkg.devDependencies[name] = name in catalog ? "catalog:" : version;
|
|
104
|
-
depsUpdated.push(name);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (templatePkg.scripts) {
|
|
110
|
-
if (!localPkg.scripts) localPkg.scripts = {};
|
|
111
|
-
for (const [name, script] of Object.entries(templatePkg.scripts)) {
|
|
112
|
-
localPkg.scripts[name] = script;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
await Bun.write(localPkgPath, JSON.stringify(localPkg, null, 2));
|
|
117
|
-
} catch {
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
results.push({
|
|
122
|
-
package: pkg,
|
|
123
|
-
files: filesSynced,
|
|
124
|
-
depsAdded: depsAdded.length > 0 ? depsAdded : undefined,
|
|
125
|
-
depsUpdated: depsUpdated.length > 0 ? depsUpdated : undefined,
|
|
126
|
-
});
|
|
127
|
-
} finally {
|
|
128
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return results;
|
|
133
|
-
}
|
|
1
|
+
export { syncFiles, type FileSyncOptions, type FileSyncResult } from "../ui/files";
|
package/src/plugin.ts
CHANGED
|
@@ -159,7 +159,33 @@ function determineProcesses(config: AppConfig): string[] {
|
|
|
159
159
|
return processes;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
function
|
|
162
|
+
function isValidProxyUrl(url: string): boolean {
|
|
163
|
+
try {
|
|
164
|
+
const parsed = new URL(url);
|
|
165
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function resolveProxyUrl(bosConfig: BosConfigType | null): string | null {
|
|
172
|
+
if (!bosConfig) return null;
|
|
173
|
+
|
|
174
|
+
const apiConfig = bosConfig.app.api as RemoteConfig | undefined;
|
|
175
|
+
if (!apiConfig) return null;
|
|
176
|
+
|
|
177
|
+
if (apiConfig.proxy && isValidProxyUrl(apiConfig.proxy)) {
|
|
178
|
+
return apiConfig.proxy;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (apiConfig.production && isValidProxyUrl(apiConfig.production)) {
|
|
182
|
+
return apiConfig.production;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function buildEnvVars(config: AppConfig, bosConfig?: BosConfigType | null): Record<string, string> {
|
|
163
189
|
const env: Record<string, string> = {};
|
|
164
190
|
|
|
165
191
|
env.HOST_SOURCE = config.host;
|
|
@@ -174,9 +200,11 @@ function buildEnvVars(config: AppConfig): Record<string, string> {
|
|
|
174
200
|
}
|
|
175
201
|
|
|
176
202
|
if (config.proxy) {
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
|
|
203
|
+
const resolvedBosConfig = bosConfig ?? loadConfig();
|
|
204
|
+
const proxyUrl = resolveProxyUrl(resolvedBosConfig);
|
|
205
|
+
if (proxyUrl) {
|
|
206
|
+
env.API_PROXY = proxyUrl;
|
|
207
|
+
}
|
|
180
208
|
}
|
|
181
209
|
|
|
182
210
|
return env;
|
|
@@ -248,8 +276,28 @@ export default createPlugin({
|
|
|
248
276
|
}
|
|
249
277
|
}
|
|
250
278
|
|
|
279
|
+
let proxyUrl: string | undefined;
|
|
280
|
+
if (appConfig.proxy) {
|
|
281
|
+
proxyUrl = resolveProxyUrl(deps.bosConfig) ?? undefined;
|
|
282
|
+
if (!proxyUrl) {
|
|
283
|
+
console.log();
|
|
284
|
+
console.log(colors.red(` ${icons.err} Proxy mode requested but no valid proxy URL found`));
|
|
285
|
+
console.log(colors.dim(` Configure 'api.proxy' or 'api.production' in bos.config.json`));
|
|
286
|
+
console.log();
|
|
287
|
+
return {
|
|
288
|
+
status: "error" as const,
|
|
289
|
+
description: "No valid proxy URL configured in bos.config.json",
|
|
290
|
+
processes: [],
|
|
291
|
+
autoRemote,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
console.log();
|
|
295
|
+
console.log(colors.cyan(` ${icons.arrow} API Proxy: ${colors.bold(proxyUrl)}`));
|
|
296
|
+
console.log();
|
|
297
|
+
}
|
|
298
|
+
|
|
251
299
|
const processes = determineProcesses(appConfig);
|
|
252
|
-
const env = buildEnvVars(appConfig);
|
|
300
|
+
const env = buildEnvVars(appConfig, deps.bosConfig);
|
|
253
301
|
const description = buildDescription(appConfig);
|
|
254
302
|
|
|
255
303
|
const orchestrator: AppOrchestrator = {
|
|
@@ -1302,7 +1350,7 @@ export default createPlugin({
|
|
|
1302
1350
|
};
|
|
1303
1351
|
}),
|
|
1304
1352
|
|
|
1305
|
-
|
|
1353
|
+
update: builder.update.handler(async ({ input }) => {
|
|
1306
1354
|
const { configDir, bosConfig } = deps;
|
|
1307
1355
|
|
|
1308
1356
|
const DEFAULT_ACCOUNT = "every.near";
|
|
@@ -1476,24 +1524,20 @@ export default createPlugin({
|
|
|
1476
1524
|
}
|
|
1477
1525
|
}
|
|
1478
1526
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
catalog: rootPkg.workspaces?.catalog ?? {},
|
|
1487
|
-
force: input.force,
|
|
1488
|
-
});
|
|
1527
|
+
const results = await syncFiles({
|
|
1528
|
+
configDir,
|
|
1529
|
+
packages: Object.keys(updatedBosConfig.app),
|
|
1530
|
+
bosConfig: updatedBosConfig,
|
|
1531
|
+
catalog: rootPkg.workspaces?.catalog ?? {},
|
|
1532
|
+
force: input.force,
|
|
1533
|
+
});
|
|
1489
1534
|
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
}
|
|
1535
|
+
const filesSynced = results.length > 0
|
|
1536
|
+
? results.map(r => ({ package: r.package, files: r.files }))
|
|
1537
|
+
: undefined;
|
|
1494
1538
|
|
|
1495
1539
|
return {
|
|
1496
|
-
status: "
|
|
1540
|
+
status: "updated" as const,
|
|
1497
1541
|
account,
|
|
1498
1542
|
gateway,
|
|
1499
1543
|
socialUrl,
|
package/src/types.ts
CHANGED
|
@@ -4,8 +4,6 @@ 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
|
-
title: z.string(),
|
|
8
|
-
description: z.string().optional(),
|
|
9
7
|
development: z.string(),
|
|
10
8
|
production: z.string(),
|
|
11
9
|
secrets: z.array(z.string()).optional(),
|
|
@@ -21,7 +19,6 @@ export const RemoteConfigSchema = z.object({
|
|
|
21
19
|
production: z.string(),
|
|
22
20
|
ssr: z.string().optional(),
|
|
23
21
|
proxy: z.string().optional(),
|
|
24
|
-
exposes: z.record(z.string(), z.string()).optional(),
|
|
25
22
|
variables: z.record(z.string(), z.string()).optional(),
|
|
26
23
|
secrets: z.array(z.string()).optional(),
|
|
27
24
|
template: z.string().optional(),
|
|
@@ -88,3 +85,51 @@ export const PortConfigSchema = z.object({
|
|
|
88
85
|
api: z.number(),
|
|
89
86
|
});
|
|
90
87
|
export type PortConfig = z.infer<typeof PortConfigSchema>;
|
|
88
|
+
|
|
89
|
+
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(),
|
|
95
|
+
});
|
|
96
|
+
export type SharedConfig = z.infer<typeof SharedConfigSchema>;
|
|
97
|
+
|
|
98
|
+
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
|
+
}),
|
|
120
|
+
});
|
|
121
|
+
export type RuntimeConfig = z.infer<typeof RuntimeConfigSchema>;
|
|
122
|
+
|
|
123
|
+
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(),
|
|
134
|
+
});
|
|
135
|
+
export type ClientRuntimeConfig = z.infer<typeof ClientRuntimeConfigSchema>;
|
package/src/ui/files.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { execa } from "execa";
|
|
2
|
+
import { cp, mkdir, mkdtemp, rm } from "fs/promises";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { dirname, join } from "path";
|
|
5
|
+
|
|
6
|
+
export interface FileSyncResult {
|
|
7
|
+
package: string;
|
|
8
|
+
files: string[];
|
|
9
|
+
depsAdded?: string[];
|
|
10
|
+
depsUpdated?: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface FileSyncOptions {
|
|
14
|
+
configDir: string;
|
|
15
|
+
packages: string[];
|
|
16
|
+
bosConfig: {
|
|
17
|
+
app: Record<string, {
|
|
18
|
+
template?: string;
|
|
19
|
+
files?: string[];
|
|
20
|
+
sync?: { dependencies?: boolean; devDependencies?: boolean };
|
|
21
|
+
}>;
|
|
22
|
+
};
|
|
23
|
+
catalog?: Record<string, string>;
|
|
24
|
+
force?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function syncFiles(options: FileSyncOptions): Promise<FileSyncResult[]> {
|
|
28
|
+
const { configDir, packages, bosConfig, catalog = {}, force } = options;
|
|
29
|
+
const results: FileSyncResult[] = [];
|
|
30
|
+
|
|
31
|
+
for (const pkg of packages) {
|
|
32
|
+
const pkgDir = `${configDir}/${pkg}`;
|
|
33
|
+
const pkgDirExists = await Bun.file(`${pkgDir}/package.json`).exists();
|
|
34
|
+
if (!pkgDirExists) continue;
|
|
35
|
+
|
|
36
|
+
const appConfig = bosConfig.app[pkg];
|
|
37
|
+
|
|
38
|
+
if (!appConfig?.template || !appConfig?.files) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const tempDir = await mkdtemp(join(tmpdir(), `bos-files-${pkg}-`));
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
await execa("npx", ["degit", appConfig.template, tempDir, "--force"], {
|
|
46
|
+
stdio: "pipe",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const filesSynced: string[] = [];
|
|
50
|
+
const depsAdded: string[] = [];
|
|
51
|
+
const depsUpdated: string[] = [];
|
|
52
|
+
|
|
53
|
+
for (const file of appConfig.files) {
|
|
54
|
+
const srcPath = join(tempDir, file);
|
|
55
|
+
const destPath = join(pkgDir, file);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const destDir = dirname(destPath);
|
|
59
|
+
await mkdir(destDir, { recursive: true });
|
|
60
|
+
await cp(srcPath, destPath, { force: true, recursive: true });
|
|
61
|
+
filesSynced.push(file);
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const syncConfig = appConfig.sync ?? { dependencies: true, devDependencies: true };
|
|
67
|
+
|
|
68
|
+
if (syncConfig.dependencies !== false || syncConfig.devDependencies !== false) {
|
|
69
|
+
const templatePkgPath = join(tempDir, "package.json");
|
|
70
|
+
const localPkgPath = join(pkgDir, "package.json");
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const templatePkg = await Bun.file(templatePkgPath).json() as {
|
|
74
|
+
dependencies?: Record<string, string>;
|
|
75
|
+
devDependencies?: Record<string, string>;
|
|
76
|
+
scripts?: Record<string, string>;
|
|
77
|
+
};
|
|
78
|
+
const localPkg = await Bun.file(localPkgPath).json() as {
|
|
79
|
+
dependencies?: Record<string, string>;
|
|
80
|
+
devDependencies?: Record<string, string>;
|
|
81
|
+
scripts?: Record<string, string>;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (syncConfig.dependencies !== false && templatePkg.dependencies) {
|
|
85
|
+
if (!localPkg.dependencies) localPkg.dependencies = {};
|
|
86
|
+
for (const [name, version] of Object.entries(templatePkg.dependencies)) {
|
|
87
|
+
if (!(name in localPkg.dependencies)) {
|
|
88
|
+
localPkg.dependencies[name] = name in catalog ? "catalog:" : version;
|
|
89
|
+
depsAdded.push(name);
|
|
90
|
+
} else if (localPkg.dependencies[name] !== "catalog:" && version !== localPkg.dependencies[name]) {
|
|
91
|
+
localPkg.dependencies[name] = name in catalog ? "catalog:" : version;
|
|
92
|
+
depsUpdated.push(name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (syncConfig.devDependencies !== false && templatePkg.devDependencies) {
|
|
98
|
+
if (!localPkg.devDependencies) localPkg.devDependencies = {};
|
|
99
|
+
for (const [name, version] of Object.entries(templatePkg.devDependencies)) {
|
|
100
|
+
if (!(name in localPkg.devDependencies)) {
|
|
101
|
+
localPkg.devDependencies[name] = name in catalog ? "catalog:" : version;
|
|
102
|
+
depsAdded.push(name);
|
|
103
|
+
} else if (localPkg.devDependencies[name] !== "catalog:" && version !== localPkg.devDependencies[name]) {
|
|
104
|
+
localPkg.devDependencies[name] = name in catalog ? "catalog:" : version;
|
|
105
|
+
depsUpdated.push(name);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (templatePkg.scripts) {
|
|
111
|
+
if (!localPkg.scripts) localPkg.scripts = {};
|
|
112
|
+
for (const [name, script] of Object.entries(templatePkg.scripts)) {
|
|
113
|
+
localPkg.scripts[name] = script;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await Bun.write(localPkgPath, JSON.stringify(localPkg, null, 2));
|
|
118
|
+
} catch {
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
results.push({
|
|
123
|
+
package: pkg,
|
|
124
|
+
files: filesSynced,
|
|
125
|
+
depsAdded: depsAdded.length > 0 ? depsAdded : undefined,
|
|
126
|
+
depsUpdated: depsUpdated.length > 0 ? depsUpdated : undefined,
|
|
127
|
+
});
|
|
128
|
+
} finally {
|
|
129
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return results;
|
|
134
|
+
}
|