devflare 1.0.0-next.65 → 1.0.0-next.67

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 (94) hide show
  1. package/README.md +2 -1
  2. package/dist/_chunks/{account-Cflj6rw3.js → account-BHY3fvrA.js} +6 -6
  3. package/dist/_chunks/{account-resources-CsmVowJp.js → account-resources-BdKj8q1h.js} +3 -3
  4. package/dist/_chunks/{ai-CBEuLdoP.js → ai-CN_SREim.js} +1 -1
  5. package/dist/_chunks/{build-BZ6lIG7n.js → build-DCgW1i8g.js} +2 -2
  6. package/dist/_chunks/{build-artifacts-CXLdD1PE.js → build-artifacts-Btqs5gPy.js} +12 -12
  7. package/dist/_chunks/{cli-z3FJ-9cO.js → cli-DXnofibT.js} +63 -20
  8. package/dist/_chunks/{cloudflare-BaloqI3H.js → cloudflare-Ic5ubQza.js} +6 -6
  9. package/dist/_chunks/{command-utils-CSnoFdxM.js → command-utils-DjaaMQX7.js} +2 -2
  10. package/dist/_chunks/{compiler-CgIFPfRA.js → compiler-UxXQgEPq.js} +3 -3
  11. package/dist/_chunks/{config-path-C3ac6r66.js → config-path-CYGlwKDs.js} +2 -2
  12. package/dist/_chunks/{config-BwRb2_vD.js → config-zQd4jmXU.js} +3 -3
  13. package/dist/_chunks/{deploy-CujD7j-R.js → deploy-DqYme109.js} +13 -13
  14. package/dist/_chunks/dev-DOoDj7SJ.js +757 -0
  15. package/dist/_chunks/{doctor-n_eqvv52.js → doctor-CAsJ-rSZ.js} +7 -7
  16. package/dist/_chunks/{env-_QKteEYJ.js → env-DyL6JOXI.js} +1 -1
  17. package/dist/_chunks/{family-D3SV6I0N.js → family-BTTpTwUk.js} +1 -1
  18. package/dist/_chunks/{init-DnkJOmpW.js → init-DJ5y3mu5.js} +3 -3
  19. package/dist/_chunks/{loader-E_UGyTrJ.js → loader-BC0skbnH.js} +5 -5
  20. package/dist/_chunks/{local-secrets-6sMstHXw.js → local-secrets-C0n5oRgI.js} +1 -1
  21. package/dist/_chunks/{login-D9-EwpSA.js → login-CgNcbpEq.js} +4 -4
  22. package/dist/_chunks/{preferences-BKp_7XJx.js → preferences-CbYERA_m.js} +1 -1
  23. package/dist/_chunks/{preview-registry-ClAbLTHH.js → preview-registry-CTXU4mBc.js} +2 -2
  24. package/dist/_chunks/{preview-resources-D_aFwtZR.js → preview-resources-CQIu2-NY.js} +5 -5
  25. package/dist/_chunks/{previews-D-GPVCeq.js → previews-BIaw6NVC.js} +10 -10
  26. package/dist/_chunks/{productions-me2YvmWK.js → productions-CF8oQcX7.js} +7 -7
  27. package/dist/_chunks/{r2-presign-runtime-zTOLS0Ea.js → r2-presign-runtime-C75FkLMd.js} +4 -4
  28. package/dist/_chunks/{remote-CZnP-HLJ.js → remote-8jEHLVGg.js} +2 -2
  29. package/dist/_chunks/{resolve-package-qizBfCIm.js → resolve-package-DeJUGxoE.js} +1 -1
  30. package/dist/_chunks/{routes-BMU7ga_l.js → routes-CRXB3-iw.js} +6 -6
  31. package/dist/_chunks/{runtime-Ckf-KKc7.js → runtime-CuMe2D6n.js} +1 -1
  32. package/dist/_chunks/{secrets-CxVCXmdh.js → secrets-BJuEa9Jz.js} +1 -1
  33. package/dist/_chunks/{tail-aMFzd-2K.js → tail-DjGRt8gw.js} +4 -4
  34. package/dist/_chunks/{token-Dmi0KXyX.js → token-wmeF8-IQ.js} +4 -4
  35. package/dist/_chunks/{tokens-iVcgNXi-.js → tokens-KaEr4B8e.js} +1 -1
  36. package/dist/_chunks/{types-ByjwxoVA.js → types-BLGfxIq-.js} +9 -9
  37. package/dist/_chunks/{ui-BUoZApvE.js → ui-BhCdDC2N.js} +1 -1
  38. package/dist/_chunks/{usage-BBTGzjon.js → usage-rRusKejc.js} +2 -2
  39. package/dist/_chunks/{vite-BBiqD3k7.js → vite-Dxuhm1BP.js} +11 -11
  40. package/dist/_chunks/{dev-C7Z23WNH.js → vite-process-BxZ8pb7i.js} +24 -765
  41. package/dist/_chunks/{worker-CtTVZeTH.js → worker-DgczaXVW.js} +6 -6
  42. package/dist/_chunks/workspace-DE60No0K.js +517 -0
  43. package/dist/_chunks/workspace-Ds3xMjOE.js +175 -0
  44. package/dist/browser.js +4 -4
  45. package/dist/cli/commands/workspace.d.ts +8 -0
  46. package/dist/cli/commands/workspace.d.ts.map +1 -0
  47. package/dist/cli/help-pages/pages/core.d.ts +1 -1
  48. package/dist/cli/help-pages/pages/core.d.ts.map +1 -1
  49. package/dist/cli/help-pages/shared.d.ts +1 -1
  50. package/dist/cli/help-pages/shared.d.ts.map +1 -1
  51. package/dist/cli/index.d.ts.map +1 -1
  52. package/dist/cli/index.js +1 -1
  53. package/dist/cloudflare/index.js +3 -3
  54. package/dist/config/index.d.ts +1 -0
  55. package/dist/config/index.d.ts.map +1 -1
  56. package/dist/config/workspace.d.ts +282 -0
  57. package/dist/config/workspace.d.ts.map +1 -0
  58. package/dist/config-entry.js +1 -1
  59. package/dist/decorators/index.js +1 -1
  60. package/dist/dev-server/miniflare-dev-config.d.ts +8 -0
  61. package/dist/dev-server/miniflare-dev-config.d.ts.map +1 -1
  62. package/dist/dev-server/workspace/merge-config.d.ts +81 -0
  63. package/dist/dev-server/workspace/merge-config.d.ts.map +1 -0
  64. package/dist/dev-server/workspace/prepare-app.d.ts +84 -0
  65. package/dist/dev-server/workspace/prepare-app.d.ts.map +1 -0
  66. package/dist/dev-server/workspace/server.d.ts +47 -0
  67. package/dist/dev-server/workspace/server.d.ts.map +1 -0
  68. package/dist/index.d.ts +1 -1
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +9 -8
  71. package/dist/runtime/index.js +3 -3
  72. package/dist/sveltekit/index.js +6 -6
  73. package/dist/test/index.js +17 -17
  74. package/dist/vite/index.js +1 -1
  75. package/package.json +1 -1
  76. package/dist/_chunks/{api-TzdliH-6.js → api-BKClUlUf.js} +0 -0
  77. package/dist/_chunks/{colors-DiK6wPJ5.js → colors-D0NVzAFN.js} +0 -0
  78. package/dist/_chunks/{context-CQkLyah9.js → context-ZQydom21.js} +0 -0
  79. package/dist/_chunks/{decorators-QmV57ixr.js → decorators-eXNZ1S0N.js} +0 -0
  80. package/dist/_chunks/{dependencies-C2oxFjU_.js → dependencies-wKqrxKV_.js} +0 -0
  81. package/dist/_chunks/{durable-object-DMtH0XYX.js → durable-object-DuMPM8re.js} +0 -0
  82. package/dist/_chunks/{generated-artifacts-dMJY4KAt.js → generated-artifacts-Bb13pGUG.js} +0 -0
  83. package/dist/_chunks/{glob-CmQOvunB.js → glob-DRw9sbfF.js} +0 -0
  84. package/dist/_chunks/{local-dev-vars-CTSa-wvF.js → local-dev-vars-BFLS-HoI.js} +1 -1
  85. package/dist/_chunks/{local-hyperdrive-CJ90j46O.js → local-hyperdrive-CcORzo76.js} +0 -0
  86. package/dist/_chunks/{local-media-bindings-DsAW4AMF.js → local-media-bindings-ZQe-epuw.js} +0 -0
  87. package/dist/_chunks/{package-metadata-DjHBLB-z.js → package-metadata-CLI488gB.js} +1 -1
  88. package/dist/_chunks/{preview-C3Cdr8an.js → preview-j9Kbx4-G.js} +1 -1
  89. /package/dist/_chunks/{remote-config-D39BY9ji.js → remote-config-Bw6qIVM4.js} +0 -0
  90. /package/dist/_chunks/{schema-normalization-Zgm4W6O6.js → schema-normalization-CTdiY17P.js} +0 -0
  91. /package/dist/_chunks/{vite-utils-OVLMV605.js → vite-utils-D8kPPxUy.js} +0 -0
  92. /package/dist/_chunks/{worker-bundler-dTTnYU3n.js → worker-bundler-Isj5jiW6.js} +0 -0
  93. /package/dist/_chunks/{worker-entrypoint-CQW77lG8.js → worker-entrypoint-C8j23t6d.js} +0 -0
  94. /package/dist/_chunks/{workerName-CFJsLZA-.js → workerName-kEXwmV2m.js} +0 -0
@@ -1,9 +1,9 @@
1
- import { r as loadConfig } from "./loader-E_UGyTrJ.js";
2
- import { o as normalizeDOBinding } from "./schema-normalization-Zgm4W6O6.js";
3
- import { t as account } from "./cloudflare-BaloqI3H.js";
4
- import { d as green, f as logLine, g as yellow, h as whiteDim, n as bold, o as dim, r as createCliTheme } from "./ui-BUoZApvE.js";
5
- import { r as resolveCloudflareAccountId, t as asOptionalString } from "./command-utils-CSnoFdxM.js";
6
- import { n as formatSupportedConfigFilenames, r as resolveConfigCandidatePath, t as findConfigPathsUnderDirectory } from "./config-path-C3ac6r66.js";
1
+ import { r as loadConfig } from "./loader-BC0skbnH.js";
2
+ import { o as normalizeDOBinding } from "./schema-normalization-CTdiY17P.js";
3
+ import { t as account } from "./cloudflare-Ic5ubQza.js";
4
+ import { d as green, f as logLine, g as yellow, h as whiteDim, n as bold, o as dim, r as createCliTheme } from "./ui-BhCdDC2N.js";
5
+ import { r as resolveCloudflareAccountId, t as asOptionalString } from "./command-utils-DjaaMQX7.js";
6
+ import { n as formatSupportedConfigFilenames, r as resolveConfigCandidatePath, t as findConfigPathsUnderDirectory } from "./config-path-CYGlwKDs.js";
7
7
  import { basename, dirname, relative, resolve } from "pathe";
8
8
  import MagicString from "magic-string";
9
9
  //#region src/cli/commands/worker.ts
@@ -0,0 +1,517 @@
1
+ import { i as resolveAppDirectSocketPort, r as loadWorkspaceManifest, t as assertSharedBindingIds } from "./workspace-Ds3xMjOE.js";
2
+ import { d as resolveConfigEnvVars } from "./preview-j9Kbx4-G.js";
3
+ import { r as loadConfig } from "./loader-BC0skbnH.js";
4
+ import { g as normalizeR2Binding, n as getLocalKVNamespaceIdentifier, r as getSingleBrowserBindingName, t as getLocalD1DatabaseIdentifier } from "./schema-normalization-CTdiY17P.js";
5
+ import { t as applyLocalDevVarsToConfig } from "./local-dev-vars-BFLS-HoI.js";
6
+ import { a as cyanBold, f as logLine, g as yellow, o as dim, r as createCliTheme } from "./ui-BhCdDC2N.js";
7
+ import { setLocalSendEmailBindings } from "../utils/send-email.js";
8
+ import { s as resolveServiceBindings, t as discoverRoutes } from "./routes-CRXB3-iw.js";
9
+ import { l as prepareComposedWorkerEntrypoint } from "./vite-Dxuhm1BP.js";
10
+ import { t as bundleWorkerEntry } from "./worker-bundler-Isj5jiW6.js";
11
+ import { _ as createMiniflareLog, c as resolveViteIntegration, d as createRuntimeStdioForwarder, f as buildMiniflareDevConfig, g as runD1Migrations, h as resolveMainWorkerSurfacePaths, p as resolveR2PresignOrigin, t as startViteProcess, u as createBrowserShim, v as createDOBundler } from "./vite-process-BxZ8pb7i.js";
12
+ import { n as bundleWorkflowEntrypointScript } from "./r2-presign-runtime-C75FkLMd.js";
13
+ import { dirname, resolve } from "pathe";
14
+ import { createConsola } from "consola";
15
+ //#region src/dev-server/workspace/merge-config.ts
16
+ const NAMESPACE_SEPARATOR = "-";
17
+ /** Join an app namespace to one of its worker names for the merged instance. */
18
+ function namespacedWorkerName(appName, workerName) {
19
+ return `${appName}${NAMESPACE_SEPARATOR}${workerName}`;
20
+ }
21
+ /**
22
+ * Prefix `name` with `${appName}/` — but only when it is one of this app's own
23
+ * worker names. External/cross-app targets are left untouched.
24
+ */
25
+ function namespaceIfLocal(name, appName, localNames) {
26
+ return localNames.has(name) ? namespacedWorkerName(appName, name) : name;
27
+ }
28
+ /** Rewrite an intra-app worker reference on a service-binding value (object or string form). */
29
+ function rewriteServiceBindingTarget(value, appName, localNames) {
30
+ if (typeof value === "string") return namespaceIfLocal(value, appName, localNames);
31
+ if (value && typeof value === "object" && typeof value.name === "string") value.name = namespaceIfLocal(value.name, appName, localNames);
32
+ return value;
33
+ }
34
+ /**
35
+ * Namespace one app's workers in place: rename each worker `${appName}/<name>`,
36
+ * rewrite every intra-app reference (service bindings, `durableObjects`
37
+ * scriptName, `outboundService`) to the new names, drop `routes`, and attach the
38
+ * app's direct socket to its gateway (entry) worker.
39
+ *
40
+ * @param appName - The app namespace (must be unique across the workspace).
41
+ * @param workers - The app's worker array (mutated). The gateway is `workers[0]`.
42
+ * @param options - `host`/`directSocketPort` for the gateway's direct socket.
43
+ * @returns The (same) workers plus the namespaced gateway worker name — the key
44
+ * for `miniflare.unsafeGetDirectURL(...)`.
45
+ */
46
+ function namespaceAppWorkers(appName, workers, options) {
47
+ const localNames = new Set(workers.map((worker) => String(worker.name)));
48
+ for (const worker of workers) {
49
+ delete worker.routes;
50
+ if (worker.serviceBindings && typeof worker.serviceBindings === "object") for (const [bindingName, target] of Object.entries(worker.serviceBindings)) worker.serviceBindings[bindingName] = rewriteServiceBindingTarget(target, appName, localNames);
51
+ if (worker.durableObjects && typeof worker.durableObjects === "object") {
52
+ for (const value of Object.values(worker.durableObjects)) if (value && typeof value === "object" && typeof value.scriptName === "string") value.scriptName = namespaceIfLocal(value.scriptName, appName, localNames);
53
+ }
54
+ if (worker.outboundService !== void 0) worker.outboundService = rewriteServiceBindingTarget(worker.outboundService, appName, localNames);
55
+ worker.name = namespacedWorkerName(appName, String(worker.name));
56
+ }
57
+ const gatewayWorkerName = namespacedWorkerName(appName, "gateway");
58
+ const gatewayWorker = workers.find((worker) => worker.name === gatewayWorkerName) ?? workers[0];
59
+ gatewayWorker.unsafeDirectSockets = [{
60
+ host: options.host,
61
+ port: options.directSocketPort,
62
+ entrypoint: "default"
63
+ }];
64
+ return {
65
+ workers,
66
+ gatewayWorkerName: gatewayWorker.name
67
+ };
68
+ }
69
+ /** Compute the one persist block for the whole workspace, or `undefined` when off. */
70
+ function resolveWorkspacePersistPaths(persist, persistDir) {
71
+ if (!persist) return;
72
+ return {
73
+ kvPersist: resolve(persistDir, "kv"),
74
+ r2Persist: resolve(persistDir, "r2"),
75
+ d1Persist: resolve(persistDir, "d1"),
76
+ cachePersist: resolve(persistDir, "cache"),
77
+ durableObjectsPersist: resolve(persistDir, "do"),
78
+ workflowsPersist: resolve(persistDir, "workflows"),
79
+ imagesPersist: resolve(persistDir, "images"),
80
+ streamPersist: resolve(persistDir, "stream")
81
+ };
82
+ }
83
+ /**
84
+ * Assemble the final single-instance Miniflare config: one entry socket, one
85
+ * persist dir, and the union of every app's namespaced workers (each with its
86
+ * own direct socket).
87
+ *
88
+ * @param input - Apps, host, persist settings, and entry port.
89
+ * @returns `{ config, directSockets }` where `config` is the Miniflare options
90
+ * object and `directSockets` maps each app name to its gateway worker name
91
+ * and port (for `unsafeGetDirectURL` + logging).
92
+ * @throws When two apps share a namespace or a direct-socket port (which would
93
+ * collide inside the one instance).
94
+ */
95
+ function buildMergedWorkspaceConfig(input) {
96
+ const seenAppNames = /* @__PURE__ */ new Set();
97
+ const seenPorts = /* @__PURE__ */ new Map();
98
+ const mergedWorkers = [];
99
+ const directSockets = [];
100
+ for (const app of input.apps) {
101
+ if (seenAppNames.has(app.appName)) throw new Error(`Duplicate workspace app namespace "${app.appName}". Give each app a unique name.`);
102
+ seenAppNames.add(app.appName);
103
+ const portOwner = seenPorts.get(app.directSocketPort);
104
+ if (portOwner !== void 0) throw new Error(`Workspace apps "${portOwner}" and "${app.appName}" both use port ${app.directSocketPort}. Each app needs a distinct direct-socket port.`);
105
+ seenPorts.set(app.directSocketPort, app.appName);
106
+ const { workers, gatewayWorkerName } = namespaceAppWorkers(app.appName, app.workers, {
107
+ host: input.host,
108
+ directSocketPort: app.directSocketPort
109
+ });
110
+ mergedWorkers.push(...workers);
111
+ directSockets.push({
112
+ appName: app.appName,
113
+ gatewayWorkerName,
114
+ port: app.directSocketPort
115
+ });
116
+ }
117
+ const seenWorkerNames = /* @__PURE__ */ new Set();
118
+ for (const worker of mergedWorkers) {
119
+ const workerName = String(worker.name);
120
+ if (seenWorkerNames.has(workerName)) throw new Error(`Workspace produced two workers named "${workerName}" after namespacing. Rename one of the conflicting apps so their namespaced worker names stay distinct.`);
121
+ seenWorkerNames.add(workerName);
122
+ }
123
+ const persistPaths = resolveWorkspacePersistPaths(input.persist, input.persistDir);
124
+ return {
125
+ config: {
126
+ host: input.host,
127
+ port: input.entryPort ?? 0,
128
+ ...persistPaths ?? {},
129
+ workers: mergedWorkers
130
+ },
131
+ directSockets
132
+ };
133
+ }
134
+ //#endregion
135
+ //#region src/dev-server/workspace/prepare-app.ts
136
+ /**
137
+ * Load an app's config and build its Miniflare worker set, mirroring the
138
+ * single-app dev startup up to (but not including) `new Miniflare(...)`.
139
+ *
140
+ * Side effects: bundles the worker/workflow/DO entrypoints under the app's
141
+ * `.devflare/`, and starts a browser-rendering shim when the app binds one. The
142
+ * returned `doBundler`/`browserShim` are disposed by the coordinator.
143
+ *
144
+ * @param input - App identity, ports, and shared instance settings.
145
+ * @returns The prepared app (see {@link PreparedWorkspaceApp}).
146
+ */
147
+ async function prepareWorkspaceApp(input) {
148
+ const { appCwd, configPath, directSocketPort, host, logger } = input;
149
+ const config = await applyLocalDevVarsToConfig(await resolveConfigEnvVars(await loadConfig({
150
+ cwd: appCwd,
151
+ configFile: configPath
152
+ }), {
153
+ cwd: appCwd,
154
+ configPath,
155
+ mode: "dev"
156
+ }), {
157
+ cwd: appCwd,
158
+ configPath
159
+ });
160
+ const appName = input.appName ?? config.name;
161
+ const serviceBindingResolution = config.bindings?.services ? await resolveServiceBindings(config, appCwd) : null;
162
+ let enableVite = false;
163
+ let generatedViteConfigPath = null;
164
+ if (input.vite) {
165
+ const viteIntegration = await resolveViteIntegration({
166
+ cwd: appCwd,
167
+ configPath,
168
+ miniflarePort: directSocketPort,
169
+ enableViteRequested: true,
170
+ logger
171
+ });
172
+ enableVite = viteIntegration.enableVite;
173
+ generatedViteConfigPath = viteIntegration.generatedViteConfigPath;
174
+ if (!enableVite) logger?.warn(`Workspace app "${appName}" set vite: true but no Vite config was found; running it worker-only.`);
175
+ }
176
+ const workflowEntrypointScript = await bundleWorkflowEntrypointScript(config, appCwd, { logger });
177
+ const mainWorkerSurfacePaths = await resolveMainWorkerSurfacePaths(appCwd, config);
178
+ const mainWorkerRoutes = await discoverRoutes(appCwd, config);
179
+ const mainWorkerScriptPath = await prepareComposedWorkerEntrypoint(appCwd, config, void 0, { devInternalEmail: true }) ?? null;
180
+ let bundledMainWorkerScriptPath = null;
181
+ if (mainWorkerScriptPath && !enableVite) bundledMainWorkerScriptPath = await bundleWorkerEntry({
182
+ cwd: appCwd,
183
+ inputFile: mainWorkerScriptPath,
184
+ outFile: resolve(appCwd, ".devflare", "worker-entrypoints", "main.js"),
185
+ rolldownOptions: config.rolldown?.options,
186
+ sourcemap: config.rolldown?.sourcemap,
187
+ minify: config.rolldown?.minify,
188
+ logger
189
+ });
190
+ let doResult = null;
191
+ let doBundler = null;
192
+ const doPattern = config.files?.durableObjects;
193
+ if (typeof doPattern === "string" && doPattern) {
194
+ doBundler = createDOBundler({
195
+ cwd: appCwd,
196
+ pattern: doPattern,
197
+ outDir: resolve(appCwd, ".devflare", "do-bundles"),
198
+ rolldownOptions: config.rolldown?.options,
199
+ sourcemap: config.rolldown?.sourcemap,
200
+ minify: config.rolldown?.minify,
201
+ logger,
202
+ onRebuild: async () => {}
203
+ });
204
+ doResult = await doBundler.build();
205
+ }
206
+ let browserShim = null;
207
+ if (getSingleBrowserBindingName(config.bindings?.browser)) {
208
+ browserShim = createBrowserShim({
209
+ port: input.browserShimPort,
210
+ host: "127.0.0.1",
211
+ logger,
212
+ verbose: input.verbose
213
+ });
214
+ await browserShim.start();
215
+ }
216
+ const workers = buildMiniflareDevConfig({
217
+ config,
218
+ cwd: appCwd,
219
+ miniflarePort: directSocketPort,
220
+ miniflareHost: host,
221
+ persist: input.persist,
222
+ enableVite,
223
+ debug: input.debug,
224
+ mainWorkerSurfacePaths,
225
+ mainWorkerRoutes,
226
+ mainWorkerScriptPath,
227
+ bundledMainWorkerScriptPath,
228
+ workflowEntrypointScript,
229
+ browserShimPort: input.browserShimPort,
230
+ doResult,
231
+ serviceBindingResolution,
232
+ r2PresignSecret: input.r2PresignSecret,
233
+ additionalInjectedVars: input.env,
234
+ logger
235
+ }).workers;
236
+ const sharedIds = {
237
+ d1: {},
238
+ r2: {},
239
+ kv: {}
240
+ };
241
+ for (const [name, cfg] of Object.entries(config.bindings?.d1 ?? {})) sharedIds.d1[name] = getLocalD1DatabaseIdentifier(cfg);
242
+ for (const [name, cfg] of Object.entries(config.bindings?.r2 ?? {})) sharedIds.r2[name] = normalizeR2Binding(cfg).bucketName;
243
+ for (const [name, cfg] of Object.entries(config.bindings?.kv ?? {})) sharedIds.kv[name] = getLocalKVNamespaceIdentifier(cfg);
244
+ return {
245
+ appName,
246
+ appCwd,
247
+ configPath,
248
+ config,
249
+ workers,
250
+ directSocketPort,
251
+ vitePort: input.vitePort,
252
+ enableVite,
253
+ generatedViteConfigPath,
254
+ doBundler,
255
+ browserShim,
256
+ sendEmailBindings: config.bindings?.sendEmail ?? {},
257
+ sharedIds
258
+ };
259
+ }
260
+ //#endregion
261
+ //#region src/dev-server/workspace/server.ts
262
+ /** The first browser-shim port; each app that binds browser rendering gets `+index`. */
263
+ const BROWSER_SHIM_BASE_PORT = 9700;
264
+ /** Delay before running migrations, matching the single-app path's stabilization wait. */
265
+ const MIGRATION_STABILIZE_DELAY_MS = 1e3;
266
+ /**
267
+ * Create a workspace dev server that co-hosts every manifest app in one
268
+ * Miniflare instance with live-shared bindings.
269
+ *
270
+ * @param options - The manifest, its directory, and logging/persist settings.
271
+ * @returns A {@link WorkspaceDevServer} handle.
272
+ */
273
+ function createWorkspaceDevServer(options) {
274
+ const { manifest, manifestDir, logger } = options;
275
+ const persist = options.persist ?? true;
276
+ const verbose = options.verbose ?? false;
277
+ const debug = options.debug ?? process.env.DEVFLARE_DEBUG === "true";
278
+ const host = manifest.host ?? "127.0.0.1";
279
+ const persistDir = resolve(manifestDir, manifest.persist ?? ".devflare/workspace-data");
280
+ const r2PresignSecret = `${crypto.randomUUID()}${crypto.randomUUID()}`;
281
+ let miniflare = null;
282
+ let preparedApps = [];
283
+ const viteChildren = [];
284
+ const appOrigins = [];
285
+ /**
286
+ * Prepare each manifest app (sequentially — config load + bundling mutate
287
+ * app-local `.devflare/` dirs and we want deterministic, non-interleaved
288
+ * work).
289
+ */
290
+ async function prepareAllApps() {
291
+ const prepared = [];
292
+ for (let index = 0; index < manifest.apps.length; index++) {
293
+ const app = manifest.apps[index];
294
+ const label = app.name ?? app.config;
295
+ const directSocketPort = resolveAppDirectSocketPort(app, label);
296
+ const configPath = resolve(manifestDir, app.config);
297
+ logger?.info(`Preparing workspace app "${label}" (port ${directSocketPort})…`);
298
+ prepared.push(await prepareWorkspaceApp({
299
+ appName: app.name,
300
+ configPath,
301
+ appCwd: dirname(configPath),
302
+ directSocketPort,
303
+ vite: app.vite ?? false,
304
+ vitePort: app.vitePort ?? 5173,
305
+ env: app.env,
306
+ persist,
307
+ debug,
308
+ verbose,
309
+ browserShimPort: BROWSER_SHIM_BASE_PORT + index,
310
+ r2PresignSecret,
311
+ host,
312
+ logger
313
+ }));
314
+ }
315
+ return prepared;
316
+ }
317
+ /** Merge sendEmail bindings from all apps and register them once (global registry). */
318
+ function registerSharedSendEmailBindings(apps) {
319
+ const merged = {};
320
+ for (const app of apps) Object.assign(merged, app.sendEmailBindings);
321
+ setLocalSendEmailBindings(merged);
322
+ }
323
+ async function startMiniflare(apps) {
324
+ const { Miniflare, Log, LogLevel } = await import("miniflare");
325
+ const { config, directSockets } = buildMergedWorkspaceConfig({
326
+ apps: apps.map((app) => ({
327
+ appName: app.appName,
328
+ workers: app.workers,
329
+ directSocketPort: app.directSocketPort
330
+ })),
331
+ host,
332
+ persist,
333
+ persistDir,
334
+ entryPort: manifest.entryPort
335
+ });
336
+ const log = createMiniflareLog(Log, LogLevel, "DEBUG", logger);
337
+ if (log) config.log = log;
338
+ config.handleRuntimeStdio = createRuntimeStdioForwarder(logger);
339
+ miniflare = new Miniflare(config);
340
+ await miniflare.ready;
341
+ for (const socket of directSockets) {
342
+ const url = await miniflare.unsafeGetDirectURL(socket.gatewayWorkerName);
343
+ const app = apps.find((candidate) => candidate.appName === socket.appName);
344
+ const isVite = app?.enableVite ?? false;
345
+ const origin = isVite ? `http://${host}:${app?.vitePort ?? socket.port}` : url.href;
346
+ appOrigins.push({
347
+ appName: socket.appName,
348
+ url: origin,
349
+ vite: isVite
350
+ });
351
+ logger?.success(isVite ? `App "${socket.appName}" → Vite ${origin} (bridge ${url.href})` : `App "${socket.appName}" → ${url.href}`);
352
+ }
353
+ }
354
+ /** Run each app's D1 migrations once against the shared store (ledger dedupes). */
355
+ async function runMigrations(apps) {
356
+ await new Promise((r) => setTimeout(r, MIGRATION_STABILIZE_DELAY_MS));
357
+ for (const app of apps) {
358
+ if (!app.config.bindings?.d1) continue;
359
+ await runD1Migrations({
360
+ cwd: app.appCwd,
361
+ config: app.config,
362
+ miniflarePort: app.directSocketPort,
363
+ logger
364
+ });
365
+ }
366
+ }
367
+ /** Spawn a Vite child per Vite app, pointed at its gateway's direct socket (bridge). */
368
+ async function startViteChildren(apps) {
369
+ for (const app of apps) {
370
+ if (!app.enableVite) continue;
371
+ const child = await startViteProcess({
372
+ cwd: app.appCwd,
373
+ configPath: app.configPath,
374
+ vitePort: app.vitePort,
375
+ miniflarePort: app.directSocketPort,
376
+ generatedViteConfigPath: app.generatedViteConfigPath,
377
+ r2Presign: app.config.bindings?.r2 ? {
378
+ secret: r2PresignSecret,
379
+ origin: resolveR2PresignOrigin(app.config.server, host, app.directSocketPort)
380
+ } : null,
381
+ logger
382
+ });
383
+ viteChildren.push(child);
384
+ }
385
+ }
386
+ async function start() {
387
+ logger?.info("Starting workspace dev server…");
388
+ preparedApps = await prepareAllApps();
389
+ if (manifest.shared) assertSharedBindingIds(manifest.shared, preparedApps.map((app) => ({
390
+ appLabel: app.appName,
391
+ d1: app.sharedIds.d1,
392
+ r2: app.sharedIds.r2,
393
+ kv: app.sharedIds.kv
394
+ })));
395
+ registerSharedSendEmailBindings(preparedApps);
396
+ await startMiniflare(preparedApps);
397
+ await startViteChildren(preparedApps);
398
+ await runMigrations(preparedApps);
399
+ logger?.success(`Workspace ready — ${preparedApps.length} app(s) sharing one instance`);
400
+ }
401
+ async function stop() {
402
+ for (const child of viteChildren) child.kill();
403
+ viteChildren.length = 0;
404
+ for (const app of preparedApps) {
405
+ try {
406
+ await app.doBundler?.close();
407
+ } catch (error) {
408
+ logger?.debug(`DO bundler close failed for ${app.appName}: ${String(error)}`);
409
+ }
410
+ try {
411
+ await app.browserShim?.stop();
412
+ } catch (error) {
413
+ logger?.debug(`Browser shim stop failed for ${app.appName}: ${String(error)}`);
414
+ }
415
+ }
416
+ if (miniflare) {
417
+ await miniflare.dispose();
418
+ miniflare = null;
419
+ }
420
+ }
421
+ return {
422
+ start,
423
+ stop,
424
+ getMiniflare: () => miniflare,
425
+ getAppOrigins: () => appOrigins
426
+ };
427
+ }
428
+ //#endregion
429
+ //#region src/cli/commands/workspace.ts
430
+ /**
431
+ * Run the `workspace` command. Only the `dev` subcommand exists today; any
432
+ * other (or missing) subcommand prints guidance and exits non-zero.
433
+ */
434
+ async function runWorkspaceCommand(parsed, logger, options) {
435
+ const subcommand = parsed.args[0] ?? "dev";
436
+ if (subcommand !== "dev") {
437
+ logger.error(`Unknown workspace subcommand: ${subcommand}`);
438
+ logger.info("Usage: devflare workspace dev");
439
+ return { exitCode: 1 };
440
+ }
441
+ const cwd = options.cwd || parsed.options.cwd || process.cwd();
442
+ const configFile = parsed.options.config;
443
+ const persist = parsed.options["no-persist"] !== true;
444
+ const debugEnabled = parsed.options.debug === true || process.env.DEVFLARE_DEBUG === "true";
445
+ const verbose = parsed.options.verbose === true || debugEnabled;
446
+ const theme = createCliTheme(parsed.options);
447
+ const devLogger = createConsola({ level: verbose ? 4 : 3 });
448
+ let loaded;
449
+ try {
450
+ loaded = await loadWorkspaceManifest({
451
+ cwd,
452
+ configFile
453
+ });
454
+ } catch (error) {
455
+ logger.error(error instanceof Error ? error.message : String(error));
456
+ return { exitCode: 1 };
457
+ }
458
+ logLine(logger);
459
+ logLine(logger, `${cyanBold("workspace", theme)} ${dim("Shared Dev Runtime", theme)}`);
460
+ logLine(logger, " ├─ One Miniflare: live-shared D1/KV/R2/Durable Objects");
461
+ logLine(logger, " ├─ Per-app direct sockets: cross-origin preserved");
462
+ logLine(logger, ` └─ ${loaded.manifest.apps.length} app(s) from the workspace manifest`);
463
+ logLine(logger);
464
+ const devServer = createWorkspaceDevServer({
465
+ manifest: loaded.manifest,
466
+ manifestDir: loaded.manifestDir,
467
+ persist,
468
+ logger: devLogger,
469
+ verbose,
470
+ debug: debugEnabled
471
+ });
472
+ let isCleaningUp = false;
473
+ const cleanupHandlers = /* @__PURE__ */ new Map();
474
+ const removeCleanupHandlers = () => {
475
+ for (const [event, handler] of cleanupHandlers) process.off(event, handler);
476
+ cleanupHandlers.clear();
477
+ };
478
+ const cleanup = async (exitCode, reason) => {
479
+ if (isCleaningUp) return;
480
+ isCleaningUp = true;
481
+ removeCleanupHandlers();
482
+ if (reason) {
483
+ const message = reason instanceof Error ? reason.stack ?? reason.message : String(reason);
484
+ logger.error(message);
485
+ }
486
+ logLine(logger);
487
+ logLine(logger, `${yellow("workspace", theme)} ${dim("Shutting down…", theme)}`);
488
+ try {
489
+ await devServer.stop();
490
+ } finally {
491
+ process.exit(exitCode);
492
+ }
493
+ };
494
+ const registerCleanupHandler = (event, handler) => {
495
+ cleanupHandlers.set(event, handler);
496
+ process.on(event, handler);
497
+ };
498
+ registerCleanupHandler("SIGINT", () => void cleanup(0));
499
+ registerCleanupHandler("SIGTERM", () => void cleanup(0));
500
+ registerCleanupHandler("SIGHUP", () => void cleanup(0));
501
+ registerCleanupHandler("uncaughtException", (error) => void cleanup(1, error));
502
+ registerCleanupHandler("unhandledRejection", (reason) => void cleanup(1, reason));
503
+ try {
504
+ await devServer.start();
505
+ await new Promise(() => {});
506
+ return { exitCode: 0 };
507
+ } catch (error) {
508
+ if (error instanceof Error) {
509
+ logger.error("Workspace dev server failed:", error.message);
510
+ if (verbose) logger.error(error.stack);
511
+ }
512
+ await devServer.stop().catch(() => {});
513
+ return { exitCode: 1 };
514
+ }
515
+ }
516
+ //#endregion
517
+ export { runWorkspaceCommand };