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.
- package/README.md +2 -1
- package/dist/_chunks/{account-Cflj6rw3.js → account-BHY3fvrA.js} +6 -6
- package/dist/_chunks/{account-resources-CsmVowJp.js → account-resources-BdKj8q1h.js} +3 -3
- package/dist/_chunks/{ai-CBEuLdoP.js → ai-CN_SREim.js} +1 -1
- package/dist/_chunks/{build-BZ6lIG7n.js → build-DCgW1i8g.js} +2 -2
- package/dist/_chunks/{build-artifacts-CXLdD1PE.js → build-artifacts-Btqs5gPy.js} +12 -12
- package/dist/_chunks/{cli-z3FJ-9cO.js → cli-DXnofibT.js} +63 -20
- package/dist/_chunks/{cloudflare-BaloqI3H.js → cloudflare-Ic5ubQza.js} +6 -6
- package/dist/_chunks/{command-utils-CSnoFdxM.js → command-utils-DjaaMQX7.js} +2 -2
- package/dist/_chunks/{compiler-CgIFPfRA.js → compiler-UxXQgEPq.js} +3 -3
- package/dist/_chunks/{config-path-C3ac6r66.js → config-path-CYGlwKDs.js} +2 -2
- package/dist/_chunks/{config-BwRb2_vD.js → config-zQd4jmXU.js} +3 -3
- package/dist/_chunks/{deploy-CujD7j-R.js → deploy-DqYme109.js} +13 -13
- package/dist/_chunks/dev-DOoDj7SJ.js +757 -0
- package/dist/_chunks/{doctor-n_eqvv52.js → doctor-CAsJ-rSZ.js} +7 -7
- package/dist/_chunks/{env-_QKteEYJ.js → env-DyL6JOXI.js} +1 -1
- package/dist/_chunks/{family-D3SV6I0N.js → family-BTTpTwUk.js} +1 -1
- package/dist/_chunks/{init-DnkJOmpW.js → init-DJ5y3mu5.js} +3 -3
- package/dist/_chunks/{loader-E_UGyTrJ.js → loader-BC0skbnH.js} +5 -5
- package/dist/_chunks/{local-secrets-6sMstHXw.js → local-secrets-C0n5oRgI.js} +1 -1
- package/dist/_chunks/{login-D9-EwpSA.js → login-CgNcbpEq.js} +4 -4
- package/dist/_chunks/{preferences-BKp_7XJx.js → preferences-CbYERA_m.js} +1 -1
- package/dist/_chunks/{preview-registry-ClAbLTHH.js → preview-registry-CTXU4mBc.js} +2 -2
- package/dist/_chunks/{preview-resources-D_aFwtZR.js → preview-resources-CQIu2-NY.js} +5 -5
- package/dist/_chunks/{previews-D-GPVCeq.js → previews-BIaw6NVC.js} +10 -10
- package/dist/_chunks/{productions-me2YvmWK.js → productions-CF8oQcX7.js} +7 -7
- package/dist/_chunks/{r2-presign-runtime-zTOLS0Ea.js → r2-presign-runtime-C75FkLMd.js} +4 -4
- package/dist/_chunks/{remote-CZnP-HLJ.js → remote-8jEHLVGg.js} +2 -2
- package/dist/_chunks/{resolve-package-qizBfCIm.js → resolve-package-DeJUGxoE.js} +1 -1
- package/dist/_chunks/{routes-BMU7ga_l.js → routes-CRXB3-iw.js} +6 -6
- package/dist/_chunks/{runtime-Ckf-KKc7.js → runtime-CuMe2D6n.js} +1 -1
- package/dist/_chunks/{secrets-CxVCXmdh.js → secrets-BJuEa9Jz.js} +1 -1
- package/dist/_chunks/{tail-aMFzd-2K.js → tail-DjGRt8gw.js} +4 -4
- package/dist/_chunks/{token-Dmi0KXyX.js → token-wmeF8-IQ.js} +4 -4
- package/dist/_chunks/{tokens-iVcgNXi-.js → tokens-KaEr4B8e.js} +1 -1
- package/dist/_chunks/{types-ByjwxoVA.js → types-BLGfxIq-.js} +9 -9
- package/dist/_chunks/{ui-BUoZApvE.js → ui-BhCdDC2N.js} +1 -1
- package/dist/_chunks/{usage-BBTGzjon.js → usage-rRusKejc.js} +2 -2
- package/dist/_chunks/{vite-BBiqD3k7.js → vite-Dxuhm1BP.js} +11 -11
- package/dist/_chunks/{dev-C7Z23WNH.js → vite-process-BxZ8pb7i.js} +24 -765
- package/dist/_chunks/{worker-CtTVZeTH.js → worker-DgczaXVW.js} +6 -6
- package/dist/_chunks/workspace-DE60No0K.js +517 -0
- package/dist/_chunks/workspace-Ds3xMjOE.js +175 -0
- package/dist/browser.js +4 -4
- package/dist/cli/commands/workspace.d.ts +8 -0
- package/dist/cli/commands/workspace.d.ts.map +1 -0
- package/dist/cli/help-pages/pages/core.d.ts +1 -1
- package/dist/cli/help-pages/pages/core.d.ts.map +1 -1
- package/dist/cli/help-pages/shared.d.ts +1 -1
- package/dist/cli/help-pages/shared.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cloudflare/index.js +3 -3
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/workspace.d.ts +282 -0
- package/dist/config/workspace.d.ts.map +1 -0
- package/dist/config-entry.js +1 -1
- package/dist/decorators/index.js +1 -1
- package/dist/dev-server/miniflare-dev-config.d.ts +8 -0
- package/dist/dev-server/miniflare-dev-config.d.ts.map +1 -1
- package/dist/dev-server/workspace/merge-config.d.ts +81 -0
- package/dist/dev-server/workspace/merge-config.d.ts.map +1 -0
- package/dist/dev-server/workspace/prepare-app.d.ts +84 -0
- package/dist/dev-server/workspace/prepare-app.d.ts.map +1 -0
- package/dist/dev-server/workspace/server.d.ts +47 -0
- package/dist/dev-server/workspace/server.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -8
- package/dist/runtime/index.js +3 -3
- package/dist/sveltekit/index.js +6 -6
- package/dist/test/index.js +17 -17
- package/dist/vite/index.js +1 -1
- package/package.json +1 -1
- package/dist/_chunks/{api-TzdliH-6.js → api-BKClUlUf.js} +0 -0
- package/dist/_chunks/{colors-DiK6wPJ5.js → colors-D0NVzAFN.js} +0 -0
- package/dist/_chunks/{context-CQkLyah9.js → context-ZQydom21.js} +0 -0
- package/dist/_chunks/{decorators-QmV57ixr.js → decorators-eXNZ1S0N.js} +0 -0
- package/dist/_chunks/{dependencies-C2oxFjU_.js → dependencies-wKqrxKV_.js} +0 -0
- package/dist/_chunks/{durable-object-DMtH0XYX.js → durable-object-DuMPM8re.js} +0 -0
- package/dist/_chunks/{generated-artifacts-dMJY4KAt.js → generated-artifacts-Bb13pGUG.js} +0 -0
- package/dist/_chunks/{glob-CmQOvunB.js → glob-DRw9sbfF.js} +0 -0
- package/dist/_chunks/{local-dev-vars-CTSa-wvF.js → local-dev-vars-BFLS-HoI.js} +1 -1
- package/dist/_chunks/{local-hyperdrive-CJ90j46O.js → local-hyperdrive-CcORzo76.js} +0 -0
- package/dist/_chunks/{local-media-bindings-DsAW4AMF.js → local-media-bindings-ZQe-epuw.js} +0 -0
- package/dist/_chunks/{package-metadata-DjHBLB-z.js → package-metadata-CLI488gB.js} +1 -1
- package/dist/_chunks/{preview-C3Cdr8an.js → preview-j9Kbx4-G.js} +1 -1
- /package/dist/_chunks/{remote-config-D39BY9ji.js → remote-config-Bw6qIVM4.js} +0 -0
- /package/dist/_chunks/{schema-normalization-Zgm4W6O6.js → schema-normalization-CTdiY17P.js} +0 -0
- /package/dist/_chunks/{vite-utils-OVLMV605.js → vite-utils-D8kPPxUy.js} +0 -0
- /package/dist/_chunks/{worker-bundler-dTTnYU3n.js → worker-bundler-Isj5jiW6.js} +0 -0
- /package/dist/_chunks/{worker-entrypoint-CQW77lG8.js → worker-entrypoint-C8j23t6d.js} +0 -0
- /package/dist/_chunks/{workerName-CFJsLZA-.js → workerName-kEXwmV2m.js} +0 -0
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
import { c as getDevflareDotenvPaths, d as resolveConfigEnvVars, o as EnvVarResolutionError } from "./preview-j9Kbx4-G.js";
|
|
2
|
+
import { r as loadConfig } from "./loader-BC0skbnH.js";
|
|
3
|
+
import { t as applyLocalDevVarsToConfig } from "./local-dev-vars-BFLS-HoI.js";
|
|
4
|
+
import { a as cyanBold, f as logLine, g as yellow, o as dim, r as createCliTheme } from "./ui-BhCdDC2N.js";
|
|
5
|
+
import { clearLocalSendEmailBindings, setLocalSendEmailBindings } from "../utils/send-email.js";
|
|
6
|
+
import { s as resolveServiceBindings, t as discoverRoutes } from "./routes-CRXB3-iw.js";
|
|
7
|
+
import { l as prepareComposedWorkerEntrypoint, m as hasWorkerSurfacePaths, n as resolveEffectiveViteProject } from "./vite-Dxuhm1BP.js";
|
|
8
|
+
import { t as bundleWorkerEntry } from "./worker-bundler-Isj5jiW6.js";
|
|
9
|
+
import { _ as createMiniflareLog, a as logWorkerHandlerDetection, c as resolveViteIntegration, d as createRuntimeStdioForwarder, f as buildMiniflareDevConfig, g as runD1Migrations, h as resolveMainWorkerSurfacePaths, i as logRemoteBindingRequirements, l as resolveWorkerConfigWatchPath, m as collectWorkerWatchRoots, n as logMiniflareBindingDiagnostics, o as maybeStartBrowserShim, p as resolveR2PresignOrigin, r as logMiniflareConfigDiagnostics, s as maybeStartDOBundler, t as startViteProcess } from "./vite-process-BxZ8pb7i.js";
|
|
10
|
+
import { n as bundleWorkflowEntrypointScript } from "./r2-presign-runtime-C75FkLMd.js";
|
|
11
|
+
import { r as stopSpawnedProcessTree, t as detectViteProject } from "./vite-utils-D8kPPxUy.js";
|
|
12
|
+
import { dirname, relative, resolve } from "pathe";
|
|
13
|
+
import { exec } from "node:child_process";
|
|
14
|
+
import { createConsola } from "consola";
|
|
15
|
+
import { promisify } from "node:util";
|
|
16
|
+
//#region src/bridge/miniflare.ts
|
|
17
|
+
function isIgnorableMiniflareDisposeError(error) {
|
|
18
|
+
if (!(error instanceof Error)) return false;
|
|
19
|
+
const details = error;
|
|
20
|
+
return details.code === "EBADF" && details.syscall === "kill";
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/cli/wrangler-auth.ts
|
|
24
|
+
const execAsync = promisify(exec);
|
|
25
|
+
/**
|
|
26
|
+
* Check if the config contains any bindings that require remote access
|
|
27
|
+
*/
|
|
28
|
+
function detectRemoteBindings(config) {
|
|
29
|
+
const remoteBindings = [];
|
|
30
|
+
const bindings = config.bindings;
|
|
31
|
+
if (!bindings) return remoteBindings;
|
|
32
|
+
if (bindings.ai) remoteBindings.push(`AI (binding: ${bindings.ai.binding})`);
|
|
33
|
+
if (bindings.vectorize) for (const [name] of Object.entries(bindings.vectorize)) remoteBindings.push(`Vectorize (binding: ${name})`);
|
|
34
|
+
return remoteBindings;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if wrangler is logged in by running `wrangler whoami`
|
|
38
|
+
*
|
|
39
|
+
* Returns:
|
|
40
|
+
* - loggedIn: true if user is authenticated
|
|
41
|
+
* - accountId: the account ID if available
|
|
42
|
+
* - email: the email if available
|
|
43
|
+
* - error: error message if check failed
|
|
44
|
+
*/
|
|
45
|
+
async function checkWranglerAuth() {
|
|
46
|
+
try {
|
|
47
|
+
const { stdout, stderr } = await execAsync("bunx wrangler whoami", { timeout: 15e3 });
|
|
48
|
+
const output = stdout + stderr;
|
|
49
|
+
if (output.includes("not authenticated") || output.includes("Not logged in") || output.includes("wrangler login")) return {
|
|
50
|
+
loggedIn: false,
|
|
51
|
+
error: "Not logged in to Wrangler"
|
|
52
|
+
};
|
|
53
|
+
const emailMatch = output.match(/email[:\s]+([^\s!]+)/i);
|
|
54
|
+
const accountMatch = output.match(/Account\s+ID[:\s]+([a-f0-9]+)/i);
|
|
55
|
+
return {
|
|
56
|
+
loggedIn: true,
|
|
57
|
+
email: emailMatch?.[1],
|
|
58
|
+
accountId: accountMatch?.[1]
|
|
59
|
+
};
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
62
|
+
if (msg.includes("ENOENT") || msg.includes("not found")) return {
|
|
63
|
+
loggedIn: false,
|
|
64
|
+
error: "Wrangler not installed. Run: npm install -g wrangler"
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
loggedIn: false,
|
|
68
|
+
error: msg
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Check if remote bindings are properly configured
|
|
74
|
+
*
|
|
75
|
+
* Returns warnings if:
|
|
76
|
+
* - Config has remote-only bindings but no accountId
|
|
77
|
+
* - Config has remote-only bindings but user is not logged in to wrangler
|
|
78
|
+
*/
|
|
79
|
+
async function checkRemoteBindingRequirements(config) {
|
|
80
|
+
const remoteBindings = detectRemoteBindings(config);
|
|
81
|
+
if (remoteBindings.length === 0) return {
|
|
82
|
+
hasRemoteBindings: false,
|
|
83
|
+
remoteBindings: [],
|
|
84
|
+
missingAccountId: false,
|
|
85
|
+
notLoggedIn: false
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
hasRemoteBindings: true,
|
|
89
|
+
remoteBindings,
|
|
90
|
+
missingAccountId: !config.accountId,
|
|
91
|
+
notLoggedIn: !(await checkWranglerAuth()).loggedIn
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/dev-server/dev-server-state.ts
|
|
96
|
+
/**
|
|
97
|
+
* Build a fresh `DevServerState` with all handles in their not-yet-started
|
|
98
|
+
* positions. `enableVite` is the user's request — `start()` may downgrade it
|
|
99
|
+
* later via `resolveViteIntegration`.
|
|
100
|
+
*/
|
|
101
|
+
function createDevServerState(initial) {
|
|
102
|
+
return {
|
|
103
|
+
enableVite: initial.enableVite,
|
|
104
|
+
miniflare: null,
|
|
105
|
+
doBundler: null,
|
|
106
|
+
workerSourceWatcher: null,
|
|
107
|
+
workerWatchTargets: [],
|
|
108
|
+
viteProcess: null,
|
|
109
|
+
config: null,
|
|
110
|
+
serviceBindingResolution: null,
|
|
111
|
+
browserShim: null,
|
|
112
|
+
browserShimPort: initial.browserShimPort ?? 8788,
|
|
113
|
+
mainWorkerSurfacePaths: {
|
|
114
|
+
fetch: null,
|
|
115
|
+
queue: null,
|
|
116
|
+
scheduled: null,
|
|
117
|
+
email: null,
|
|
118
|
+
tail: null
|
|
119
|
+
},
|
|
120
|
+
resolvedWorkerConfigPath: null,
|
|
121
|
+
mainWorkerScriptPath: null,
|
|
122
|
+
bundledMainWorkerScriptPath: null,
|
|
123
|
+
workflowEntrypointScript: "",
|
|
124
|
+
currentDoResult: null,
|
|
125
|
+
mainWorkerRoutes: null,
|
|
126
|
+
generatedViteConfigPath: null
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Tear down everything in `state` in the same order the legacy `stop()`
|
|
131
|
+
* function used. After this returns, every handle on `state` is `null` again
|
|
132
|
+
* and the local sendEmail bindings have been cleared.
|
|
133
|
+
*
|
|
134
|
+
* Order is important and intentionally mirrors the production sequence:
|
|
135
|
+
* 1. DO bundler (stop rebuilds before Miniflare goes away)
|
|
136
|
+
* 2. Worker source watcher (stop FS callbacks before Miniflare disposal)
|
|
137
|
+
* 3. Miniflare itself
|
|
138
|
+
* 4. Vite child process (after Miniflare so requests cannot race shutdown)
|
|
139
|
+
* 5. Browser shim
|
|
140
|
+
* 6. Local sendEmail registry reset
|
|
141
|
+
*/
|
|
142
|
+
async function disposeDevServerState(state) {
|
|
143
|
+
if (state.doBundler) {
|
|
144
|
+
await state.doBundler.close();
|
|
145
|
+
state.doBundler = null;
|
|
146
|
+
}
|
|
147
|
+
if (state.workerSourceWatcher) {
|
|
148
|
+
await state.workerSourceWatcher.close();
|
|
149
|
+
state.workerSourceWatcher = null;
|
|
150
|
+
}
|
|
151
|
+
if (state.miniflare) {
|
|
152
|
+
try {
|
|
153
|
+
await state.miniflare.dispose();
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (!isIgnorableMiniflareDisposeError(error)) throw error;
|
|
156
|
+
}
|
|
157
|
+
state.miniflare = null;
|
|
158
|
+
}
|
|
159
|
+
if (state.viteProcess) {
|
|
160
|
+
await stopSpawnedProcessTree(state.viteProcess);
|
|
161
|
+
state.viteProcess = null;
|
|
162
|
+
}
|
|
163
|
+
if (state.browserShim) {
|
|
164
|
+
await state.browserShim.stop();
|
|
165
|
+
state.browserShim = null;
|
|
166
|
+
}
|
|
167
|
+
clearLocalSendEmailBindings();
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/dev-server/reload-queue.ts
|
|
171
|
+
function createReloadQueue({ reload, logger }) {
|
|
172
|
+
let running = null;
|
|
173
|
+
let pending = null;
|
|
174
|
+
async function runOnce() {
|
|
175
|
+
try {
|
|
176
|
+
await reload();
|
|
177
|
+
} catch (error) {
|
|
178
|
+
logger?.error("[devflare dev] reload failed:", error);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function schedule() {
|
|
182
|
+
if (!running) {
|
|
183
|
+
running = runOnce().finally(() => {
|
|
184
|
+
running = null;
|
|
185
|
+
});
|
|
186
|
+
return running;
|
|
187
|
+
}
|
|
188
|
+
if (!pending) pending = running.then(() => {
|
|
189
|
+
pending = null;
|
|
190
|
+
running = runOnce().finally(() => {
|
|
191
|
+
running = null;
|
|
192
|
+
});
|
|
193
|
+
return running;
|
|
194
|
+
});
|
|
195
|
+
return pending;
|
|
196
|
+
}
|
|
197
|
+
async function drain() {
|
|
198
|
+
while (running || pending) if (pending) await pending;
|
|
199
|
+
else if (running) await running;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
schedule,
|
|
203
|
+
drain
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/dev-server/worker-source-watcher.ts
|
|
208
|
+
async function startWorkerSourceWatcher(options) {
|
|
209
|
+
const { watchTargets, resolvedWorkerConfigPath, logger, onConfigChange, onWorkerChange } = options;
|
|
210
|
+
if (watchTargets.length === 0) return null;
|
|
211
|
+
const chokidar = await import("chokidar");
|
|
212
|
+
const isWindows = process.platform === "win32";
|
|
213
|
+
const ignoredSegments = [
|
|
214
|
+
"/node_modules/",
|
|
215
|
+
"/.git/",
|
|
216
|
+
"/.devflare/",
|
|
217
|
+
"/dist/"
|
|
218
|
+
];
|
|
219
|
+
const normalizePath = (filePath) => filePath.replace(/\\/g, "/");
|
|
220
|
+
const isIgnoredPath = (filePath) => {
|
|
221
|
+
const normalizedPath = normalizePath(filePath);
|
|
222
|
+
return ignoredSegments.some((segment) => normalizedPath.includes(segment));
|
|
223
|
+
};
|
|
224
|
+
let reloadTimeout = null;
|
|
225
|
+
let reloadInProgress = false;
|
|
226
|
+
let pendingReloadPath = null;
|
|
227
|
+
const flushPendingReload = async () => {
|
|
228
|
+
if (!pendingReloadPath) return;
|
|
229
|
+
const nextPath = pendingReloadPath;
|
|
230
|
+
pendingReloadPath = null;
|
|
231
|
+
await triggerReload(nextPath);
|
|
232
|
+
};
|
|
233
|
+
const triggerReload = async (filePath) => {
|
|
234
|
+
if (reloadInProgress) {
|
|
235
|
+
pendingReloadPath = filePath;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
reloadInProgress = true;
|
|
239
|
+
try {
|
|
240
|
+
const normalizedConfigPath = resolvedWorkerConfigPath ? normalizePath(resolvedWorkerConfigPath) : null;
|
|
241
|
+
if (normalizedConfigPath && normalizePath(filePath) === normalizedConfigPath) {
|
|
242
|
+
logger?.info(`Devflare config changed: ${filePath}`);
|
|
243
|
+
await onConfigChange();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
logger?.info(`Worker source changed: ${filePath}`);
|
|
247
|
+
await onWorkerChange();
|
|
248
|
+
} catch (error) {
|
|
249
|
+
logger?.error("Worker source reload failed:", error);
|
|
250
|
+
} finally {
|
|
251
|
+
reloadInProgress = false;
|
|
252
|
+
await flushPendingReload();
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
const scheduleReload = (filePath) => {
|
|
256
|
+
if (reloadTimeout) clearTimeout(reloadTimeout);
|
|
257
|
+
reloadTimeout = setTimeout(() => {
|
|
258
|
+
reloadTimeout = null;
|
|
259
|
+
triggerReload(filePath);
|
|
260
|
+
}, 150);
|
|
261
|
+
};
|
|
262
|
+
const watcher = chokidar.watch(watchTargets, {
|
|
263
|
+
ignoreInitial: true,
|
|
264
|
+
usePolling: isWindows,
|
|
265
|
+
interval: isWindows ? 300 : void 0,
|
|
266
|
+
awaitWriteFinish: {
|
|
267
|
+
stabilityThreshold: 100,
|
|
268
|
+
pollInterval: 50
|
|
269
|
+
},
|
|
270
|
+
ignored: (filePath) => isIgnoredPath(filePath)
|
|
271
|
+
});
|
|
272
|
+
const onFileEvent = (filePath) => {
|
|
273
|
+
if (isIgnoredPath(filePath)) return;
|
|
274
|
+
scheduleReload(filePath);
|
|
275
|
+
};
|
|
276
|
+
watcher.on("change", onFileEvent);
|
|
277
|
+
watcher.on("add", onFileEvent);
|
|
278
|
+
watcher.on("unlink", onFileEvent);
|
|
279
|
+
watcher.on("error", (error) => {
|
|
280
|
+
logger?.error("Worker source watcher error:", error);
|
|
281
|
+
});
|
|
282
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
283
|
+
const handleReady = () => {
|
|
284
|
+
watcher.off("error", handleInitialError);
|
|
285
|
+
logger?.info(`Worker source watcher ready (${watchTargets.length} target(s))`);
|
|
286
|
+
resolvePromise();
|
|
287
|
+
};
|
|
288
|
+
const handleInitialError = (error) => {
|
|
289
|
+
watcher.off("ready", handleReady);
|
|
290
|
+
rejectPromise(error instanceof Error ? error : new Error(String(error)));
|
|
291
|
+
};
|
|
292
|
+
watcher.once("ready", handleReady);
|
|
293
|
+
watcher.once("error", handleInitialError);
|
|
294
|
+
});
|
|
295
|
+
return watcher;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Compute the diff between current and next watch-target lists and apply it
|
|
299
|
+
* to the given watcher. Returns the deduped next-targets array which the
|
|
300
|
+
* caller should store as the new "current" state.
|
|
301
|
+
*/
|
|
302
|
+
async function applyWatcherTargetDiff(watcher, currentTargets, nextTargets) {
|
|
303
|
+
const nextSet = new Set(nextTargets);
|
|
304
|
+
const targetsToRemove = currentTargets.filter((t) => !nextSet.has(t));
|
|
305
|
+
const targetsToAdd = nextTargets.filter((t) => !currentTargets.includes(t));
|
|
306
|
+
if (targetsToRemove.length > 0) await watcher.unwatch(targetsToRemove);
|
|
307
|
+
if (targetsToAdd.length > 0) watcher.add(targetsToAdd);
|
|
308
|
+
return nextTargets;
|
|
309
|
+
}
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/dev-server/server.ts
|
|
312
|
+
function createDevServer(options) {
|
|
313
|
+
const { cwd, configPath, vitePort = 5173, miniflarePort = 8787, miniflareHost = "127.0.0.1", enableVite: enableViteRequested = true, persist = true, logger, verbose = false, debug = process.env.DEVFLARE_DEBUG === "true" } = options;
|
|
314
|
+
const state = createDevServerState({ enableVite: enableViteRequested });
|
|
315
|
+
const r2PresignSecret = `${crypto.randomUUID()}${crypto.randomUUID()}`;
|
|
316
|
+
const reloadQueue = createReloadQueue({
|
|
317
|
+
reload: async () => {
|
|
318
|
+
if (!state.miniflare) return;
|
|
319
|
+
const { Log, LogLevel } = await import("miniflare");
|
|
320
|
+
const mfConfig = buildMiniflareConfig(state.currentDoResult);
|
|
321
|
+
const log = createMiniflareLog(Log, LogLevel, "DEBUG", logger);
|
|
322
|
+
if (log) mfConfig.log = log;
|
|
323
|
+
mfConfig.handleRuntimeStdio = createRuntimeStdioForwarder(logger);
|
|
324
|
+
logger?.info("Reloading Miniflare...");
|
|
325
|
+
await state.miniflare.setOptions(mfConfig);
|
|
326
|
+
logger?.success("Miniflare reloaded");
|
|
327
|
+
},
|
|
328
|
+
logger
|
|
329
|
+
});
|
|
330
|
+
async function bundleMainWorker() {
|
|
331
|
+
if (!state.mainWorkerScriptPath || !state.config) {
|
|
332
|
+
state.bundledMainWorkerScriptPath = null;
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
state.bundledMainWorkerScriptPath = await bundleWorkerEntry({
|
|
336
|
+
cwd,
|
|
337
|
+
inputFile: state.mainWorkerScriptPath,
|
|
338
|
+
outFile: resolve(cwd, ".devflare", "worker-entrypoints", "main.js"),
|
|
339
|
+
rolldownOptions: state.config.rolldown?.options,
|
|
340
|
+
sourcemap: state.config.rolldown?.sourcemap,
|
|
341
|
+
minify: state.config.rolldown?.minify,
|
|
342
|
+
logger
|
|
343
|
+
});
|
|
344
|
+
logger?.debug(`Bundled main worker → ${state.bundledMainWorkerScriptPath}`);
|
|
345
|
+
}
|
|
346
|
+
function buildMiniflareConfig(doResult) {
|
|
347
|
+
if (!state.config) throw new Error("Config not loaded");
|
|
348
|
+
return buildMiniflareDevConfig({
|
|
349
|
+
config: state.config,
|
|
350
|
+
cwd,
|
|
351
|
+
miniflarePort,
|
|
352
|
+
miniflareHost,
|
|
353
|
+
persist,
|
|
354
|
+
enableVite: state.enableVite,
|
|
355
|
+
debug,
|
|
356
|
+
mainWorkerSurfacePaths: state.mainWorkerSurfacePaths,
|
|
357
|
+
mainWorkerRoutes: state.mainWorkerRoutes,
|
|
358
|
+
mainWorkerScriptPath: state.mainWorkerScriptPath,
|
|
359
|
+
bundledMainWorkerScriptPath: state.bundledMainWorkerScriptPath,
|
|
360
|
+
workflowEntrypointScript: state.workflowEntrypointScript,
|
|
361
|
+
browserShimPort: state.browserShimPort,
|
|
362
|
+
doResult,
|
|
363
|
+
serviceBindingResolution: state.serviceBindingResolution,
|
|
364
|
+
r2PresignSecret,
|
|
365
|
+
logger
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
async function bundleWorkflowEntrypoints() {
|
|
369
|
+
if (!state.config) {
|
|
370
|
+
state.workflowEntrypointScript = "";
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
state.workflowEntrypointScript = await bundleWorkflowEntrypointScript(state.config, cwd, { logger });
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Start Miniflare with current config
|
|
377
|
+
*/
|
|
378
|
+
async function startMiniflare(doResult) {
|
|
379
|
+
const { Miniflare, Log, LogLevel } = await import("miniflare");
|
|
380
|
+
const mfConfig = buildMiniflareConfig(doResult);
|
|
381
|
+
const log = createMiniflareLog(Log, LogLevel, "DEBUG", logger);
|
|
382
|
+
if (log) mfConfig.log = log;
|
|
383
|
+
mfConfig.handleRuntimeStdio = createRuntimeStdioForwarder(logger);
|
|
384
|
+
const shouldLogMiniflareDiagnostics = verbose || debug;
|
|
385
|
+
if (shouldLogMiniflareDiagnostics) logMiniflareConfigDiagnostics(logger, mfConfig);
|
|
386
|
+
state.miniflare = new Miniflare(mfConfig);
|
|
387
|
+
await state.miniflare.ready;
|
|
388
|
+
const displayHost = miniflareHost === "0.0.0.0" || miniflareHost === "::" ? "localhost" : miniflareHost;
|
|
389
|
+
logger?.success(`Miniflare ready on http://${displayHost}:${miniflarePort}`);
|
|
390
|
+
if (shouldLogMiniflareDiagnostics) await logMiniflareBindingDiagnostics(logger, state.miniflare, mfConfig);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Reload Miniflare with updated DO bundles
|
|
394
|
+
*/
|
|
395
|
+
async function reloadMiniflare(doResult) {
|
|
396
|
+
state.currentDoResult = doResult;
|
|
397
|
+
await reloadQueue.schedule();
|
|
398
|
+
}
|
|
399
|
+
async function refreshWorkerOnlySurfaceState() {
|
|
400
|
+
if (!state.config) return;
|
|
401
|
+
state.mainWorkerSurfacePaths = await resolveMainWorkerSurfacePaths(cwd, state.config);
|
|
402
|
+
state.mainWorkerRoutes = await discoverRoutes(cwd, state.config);
|
|
403
|
+
const composedMainEntry = await prepareComposedWorkerEntrypoint(cwd, state.config, void 0, { devInternalEmail: true });
|
|
404
|
+
state.mainWorkerScriptPath = composedMainEntry ? composedMainEntry : null;
|
|
405
|
+
if (state.mainWorkerScriptPath) await bundleMainWorker();
|
|
406
|
+
else state.bundledMainWorkerScriptPath = null;
|
|
407
|
+
await syncWorkerWatchTargets();
|
|
408
|
+
}
|
|
409
|
+
function getWorkerWatchTargets() {
|
|
410
|
+
if (state.enableVite || !state.config) return [];
|
|
411
|
+
const targets = collectWorkerWatchRoots(cwd, state.config, state.mainWorkerSurfacePaths);
|
|
412
|
+
if (state.resolvedWorkerConfigPath) targets.push(state.resolvedWorkerConfigPath);
|
|
413
|
+
return [...new Set(targets)];
|
|
414
|
+
}
|
|
415
|
+
async function syncWorkerWatchTargets() {
|
|
416
|
+
if (!state.workerSourceWatcher) return;
|
|
417
|
+
state.workerWatchTargets = await applyWatcherTargetDiff(state.workerSourceWatcher, state.workerWatchTargets, getWorkerWatchTargets());
|
|
418
|
+
}
|
|
419
|
+
async function reloadWorkerOnlyConfig() {
|
|
420
|
+
await loadRuntimeConfigWhenEnvReady();
|
|
421
|
+
if (!state.config) return;
|
|
422
|
+
setLocalSendEmailBindings(state.config.bindings?.sendEmail ?? {});
|
|
423
|
+
await bundleWorkflowEntrypoints();
|
|
424
|
+
await refreshWorkerOnlySurfaceState();
|
|
425
|
+
await reloadMiniflare(state.currentDoResult);
|
|
426
|
+
}
|
|
427
|
+
async function loadRuntimeConfig() {
|
|
428
|
+
state.resolvedWorkerConfigPath = await resolveWorkerConfigWatchPath(cwd, configPath);
|
|
429
|
+
state.config = await applyLocalDevVarsToConfig(await resolveConfigEnvVars(await loadConfig({
|
|
430
|
+
cwd,
|
|
431
|
+
configFile: configPath
|
|
432
|
+
}), {
|
|
433
|
+
cwd,
|
|
434
|
+
configPath: state.resolvedWorkerConfigPath ?? configPath,
|
|
435
|
+
mode: "dev"
|
|
436
|
+
}), {
|
|
437
|
+
cwd,
|
|
438
|
+
configPath: state.resolvedWorkerConfigPath ?? void 0
|
|
439
|
+
});
|
|
440
|
+
state.serviceBindingResolution = state.config.bindings?.services ? await resolveServiceBindings(state.config, state.resolvedWorkerConfigPath ? dirname(state.resolvedWorkerConfigPath) : cwd) : null;
|
|
441
|
+
}
|
|
442
|
+
async function waitForDotenvChange() {
|
|
443
|
+
const configWatchPath = state.resolvedWorkerConfigPath ?? await resolveWorkerConfigWatchPath(cwd, configPath);
|
|
444
|
+
const watchPaths = getDevflareDotenvPaths(configWatchPath ? dirname(configWatchPath) : cwd);
|
|
445
|
+
const { watch } = await import("chokidar");
|
|
446
|
+
await new Promise((resolveWait, rejectWait) => {
|
|
447
|
+
const watcher = watch(watchPaths, {
|
|
448
|
+
ignoreInitial: true,
|
|
449
|
+
awaitWriteFinish: {
|
|
450
|
+
stabilityThreshold: 100,
|
|
451
|
+
pollInterval: 25
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
const finish = () => {
|
|
455
|
+
watcher.close().then(resolveWait, rejectWait);
|
|
456
|
+
};
|
|
457
|
+
watcher.on("add", finish);
|
|
458
|
+
watcher.on("change", finish);
|
|
459
|
+
watcher.on("unlink", finish);
|
|
460
|
+
watcher.on("error", rejectWait);
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
async function loadRuntimeConfigWhenEnvReady() {
|
|
464
|
+
while (true) try {
|
|
465
|
+
await loadRuntimeConfig();
|
|
466
|
+
return;
|
|
467
|
+
} catch (error) {
|
|
468
|
+
if (!(error instanceof EnvVarResolutionError)) throw error;
|
|
469
|
+
logger?.warn(error.message);
|
|
470
|
+
logger?.info("Devflare dev is waiting for .env or .env.dev to change before starting.");
|
|
471
|
+
await waitForDotenvChange();
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
async function startWorkerSourceWatcher$1() {
|
|
475
|
+
if (state.enableVite || !state.config) return;
|
|
476
|
+
const watchTargets = getWorkerWatchTargets();
|
|
477
|
+
if (watchTargets.length === 0) return;
|
|
478
|
+
state.workerWatchTargets = watchTargets;
|
|
479
|
+
state.workerSourceWatcher = await startWorkerSourceWatcher({
|
|
480
|
+
watchTargets,
|
|
481
|
+
resolvedWorkerConfigPath: state.resolvedWorkerConfigPath,
|
|
482
|
+
logger,
|
|
483
|
+
onConfigChange: reloadWorkerOnlyConfig,
|
|
484
|
+
onWorkerChange: async () => {
|
|
485
|
+
await refreshWorkerOnlySurfaceState();
|
|
486
|
+
await reloadMiniflare(state.currentDoResult);
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Start the complete dev server
|
|
492
|
+
*/
|
|
493
|
+
async function start() {
|
|
494
|
+
logger?.info("Starting unified dev server...");
|
|
495
|
+
await loadRuntimeConfigWhenEnvReady();
|
|
496
|
+
if (!state.config) throw new Error("Config not loaded");
|
|
497
|
+
setLocalSendEmailBindings(state.config.bindings?.sendEmail ?? {});
|
|
498
|
+
logger?.debug("Loaded config:", state.config.name);
|
|
499
|
+
await bundleWorkflowEntrypoints();
|
|
500
|
+
const viteIntegration = await resolveViteIntegration({
|
|
501
|
+
cwd,
|
|
502
|
+
configPath,
|
|
503
|
+
miniflarePort,
|
|
504
|
+
enableViteRequested: state.enableVite,
|
|
505
|
+
logger
|
|
506
|
+
});
|
|
507
|
+
state.enableVite = viteIntegration.enableVite;
|
|
508
|
+
state.generatedViteConfigPath = viteIntegration.generatedViteConfigPath;
|
|
509
|
+
await refreshWorkerOnlySurfaceState();
|
|
510
|
+
if (!state.enableVite && (hasWorkerSurfacePaths(state.mainWorkerSurfacePaths) || Boolean(state.mainWorkerRoutes?.routes.length))) logWorkerHandlerDetection(logger, state.enableVite, true, state.mainWorkerSurfacePaths, state.mainWorkerRoutes);
|
|
511
|
+
else if (!state.enableVite) logWorkerHandlerDetection(logger, state.enableVite, false, state.mainWorkerSurfacePaths, state.mainWorkerRoutes);
|
|
512
|
+
logRemoteBindingRequirements(logger, await checkRemoteBindingRequirements(state.config));
|
|
513
|
+
state.browserShim = await maybeStartBrowserShim(state.config, {
|
|
514
|
+
browserShimPort: state.browserShimPort,
|
|
515
|
+
logger,
|
|
516
|
+
verbose
|
|
517
|
+
});
|
|
518
|
+
const doInit = await maybeStartDOBundler(state.config, {
|
|
519
|
+
cwd,
|
|
520
|
+
logger,
|
|
521
|
+
onRebuild: async (result) => {
|
|
522
|
+
await reloadMiniflare(result);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
state.doBundler = doInit.bundler;
|
|
526
|
+
const doResult = doInit.result;
|
|
527
|
+
state.currentDoResult = doResult;
|
|
528
|
+
await startMiniflare(doResult);
|
|
529
|
+
await startWorkerSourceWatcher$1();
|
|
530
|
+
if (state.enableVite) state.viteProcess = await startViteProcess({
|
|
531
|
+
cwd,
|
|
532
|
+
configPath,
|
|
533
|
+
vitePort,
|
|
534
|
+
miniflarePort,
|
|
535
|
+
generatedViteConfigPath: state.generatedViteConfigPath,
|
|
536
|
+
r2Presign: state.config.bindings?.r2 ? {
|
|
537
|
+
secret: r2PresignSecret,
|
|
538
|
+
origin: resolveR2PresignOrigin(state.config.server, miniflareHost, miniflarePort)
|
|
539
|
+
} : null,
|
|
540
|
+
logger
|
|
541
|
+
});
|
|
542
|
+
else logger?.info("Vite startup skipped (no effective Vite config found for this package)");
|
|
543
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
544
|
+
await runD1Migrations({
|
|
545
|
+
cwd,
|
|
546
|
+
config: state.config,
|
|
547
|
+
miniflarePort,
|
|
548
|
+
logger
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Stop the dev server
|
|
553
|
+
*/
|
|
554
|
+
async function stop() {
|
|
555
|
+
await disposeDevServerState(state);
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Get Miniflare instance
|
|
559
|
+
*/
|
|
560
|
+
function getMiniflare() {
|
|
561
|
+
return state.miniflare;
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
start,
|
|
565
|
+
stop,
|
|
566
|
+
getMiniflare
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
//#endregion
|
|
570
|
+
//#region src/cli/commands/dev.ts
|
|
571
|
+
function readStringOption(value) {
|
|
572
|
+
return typeof value === "string" ? value : void 0;
|
|
573
|
+
}
|
|
574
|
+
function parsePort(value, source) {
|
|
575
|
+
const port = Number(value);
|
|
576
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) throw new Error(`${source} must be an integer between 1 and 65535`);
|
|
577
|
+
return port;
|
|
578
|
+
}
|
|
579
|
+
function resolveSinglePort(runtimePort, bridgePort, source) {
|
|
580
|
+
if (runtimePort && bridgePort && runtimePort !== bridgePort) throw new Error(`Conflicting Devflare runtime ports: ${source} runtime port is ${runtimePort}, but bridge port is ${bridgePort}. Use one value for both.`);
|
|
581
|
+
const value = runtimePort ?? bridgePort;
|
|
582
|
+
return value ? parsePort(value, source) : void 0;
|
|
583
|
+
}
|
|
584
|
+
function resolveDevRuntimePort(options, env = process.env, configPort) {
|
|
585
|
+
return resolveSinglePort(readStringOption(options["runtime-port"]), readStringOption(options["bridge-port"]), "CLI") ?? resolveSinglePort(env.DEVFLARE_RUNTIME_PORT, env.DEVFLARE_BRIDGE_PORT, "environment") ?? configPort ?? 8787;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Resolve the host the local Miniflare runtime instance binds to.
|
|
589
|
+
*
|
|
590
|
+
* Precedence: `--runtime-host` CLI flag > `DEVFLARE_RUNTIME_HOST` env var >
|
|
591
|
+
* `server.host` config value > `127.0.0.1`.
|
|
592
|
+
*/
|
|
593
|
+
function resolveDevRuntimeHost(options, env = process.env, configHost) {
|
|
594
|
+
const envHost = env.DEVFLARE_RUNTIME_HOST?.trim();
|
|
595
|
+
return readStringOption(options["runtime-host"]) ?? (envHost ? envHost : void 0) ?? configHost ?? "127.0.0.1";
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Create a log writer that writes to both terminal and file
|
|
599
|
+
*/
|
|
600
|
+
async function createLogWriter(cwd, options) {
|
|
601
|
+
if (!options.log && !options.logTemp) return null;
|
|
602
|
+
const fs = await import("node:fs");
|
|
603
|
+
let logPath;
|
|
604
|
+
if (options.logTemp) logPath = resolve(cwd, ".log");
|
|
605
|
+
else logPath = resolve(cwd, `.log-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19)}`);
|
|
606
|
+
const fileStream = fs.createWriteStream(logPath, { flags: "w" });
|
|
607
|
+
const ansiRegex = /\x1b\[[0-9;]*m/g;
|
|
608
|
+
return {
|
|
609
|
+
path: logPath,
|
|
610
|
+
write(data, source) {
|
|
611
|
+
const str = typeof data === "string" ? data : data.toString();
|
|
612
|
+
if (!str.trim()) return;
|
|
613
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
614
|
+
const prefix = source ? `[${timestamp}][${source.toUpperCase()}] ` : `[${timestamp}] `;
|
|
615
|
+
const cleanStr = str.replace(ansiRegex, "");
|
|
616
|
+
fileStream.write(prefix + cleanStr + (cleanStr.endsWith("\n") ? "" : "\n"));
|
|
617
|
+
},
|
|
618
|
+
close() {
|
|
619
|
+
fileStream.end();
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
async function runDevCommand(parsed, logger, options) {
|
|
624
|
+
const cwd = options.cwd || parsed.options.cwd || process.cwd();
|
|
625
|
+
const configPath = parsed.options.config;
|
|
626
|
+
const port = parsed.options.port;
|
|
627
|
+
const logEnabled = parsed.options.log === true;
|
|
628
|
+
const logTempEnabled = parsed.options["log-temp"] === true;
|
|
629
|
+
const persistEnabled = parsed.options.persist === true;
|
|
630
|
+
const debugEnabled = parsed.options.debug === true || process.env.DEVFLARE_DEBUG === "true";
|
|
631
|
+
const verbose = parsed.options.verbose === true || debugEnabled;
|
|
632
|
+
const theme = createCliTheme(parsed.options);
|
|
633
|
+
const config = await loadConfig({
|
|
634
|
+
cwd,
|
|
635
|
+
configFile: configPath
|
|
636
|
+
});
|
|
637
|
+
let miniflarePort;
|
|
638
|
+
let miniflareHost;
|
|
639
|
+
try {
|
|
640
|
+
miniflarePort = resolveDevRuntimePort(parsed.options, process.env, config.server?.port);
|
|
641
|
+
miniflareHost = resolveDevRuntimeHost(parsed.options, process.env, config.server?.host);
|
|
642
|
+
} catch (error) {
|
|
643
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
644
|
+
return { exitCode: 1 };
|
|
645
|
+
}
|
|
646
|
+
const viteProject = resolveEffectiveViteProject(await detectViteProject(cwd), config);
|
|
647
|
+
const logWriter = await createLogWriter(cwd, {
|
|
648
|
+
log: logEnabled,
|
|
649
|
+
logTemp: logTempEnabled
|
|
650
|
+
});
|
|
651
|
+
if (logWriter) {
|
|
652
|
+
const logFile = relative(cwd, logWriter.path) || ".log";
|
|
653
|
+
logLine(logger, `${dim("logging", theme)} ${logFile}`);
|
|
654
|
+
}
|
|
655
|
+
const devLogger = createConsola({ level: verbose ? 4 : 3 });
|
|
656
|
+
if (logWriter) {
|
|
657
|
+
const wrapLog = (original, prefix = "") => {
|
|
658
|
+
return (message, ...args) => {
|
|
659
|
+
original(message, ...args);
|
|
660
|
+
const formatted = prefix ? `${prefix} ${[message, ...args].join(" ")}` : [message, ...args].join(" ");
|
|
661
|
+
logWriter.write(formatted);
|
|
662
|
+
};
|
|
663
|
+
};
|
|
664
|
+
Object.assign(devLogger.log, wrapLog(devLogger.log.bind(devLogger)));
|
|
665
|
+
Object.assign(devLogger.info, wrapLog(devLogger.info.bind(devLogger)));
|
|
666
|
+
Object.assign(devLogger.error, wrapLog(devLogger.error.bind(devLogger), "[ERROR]"));
|
|
667
|
+
Object.assign(devLogger.warn, wrapLog(devLogger.warn.bind(devLogger), "[WARN]"));
|
|
668
|
+
Object.assign(devLogger.success, wrapLog(devLogger.success.bind(devLogger), "[OK]"));
|
|
669
|
+
Object.assign(devLogger.debug, wrapLog(devLogger.debug.bind(devLogger), "[DEBUG]"));
|
|
670
|
+
}
|
|
671
|
+
try {
|
|
672
|
+
logLine(logger);
|
|
673
|
+
if (viteProject.shouldStartVite) {
|
|
674
|
+
logLine(logger, `${cyanBold("dev", theme)} ${dim("Unified Dev Server", theme)}`);
|
|
675
|
+
logLine(logger, " ├─ Vite: Full HMR for frontend");
|
|
676
|
+
logLine(logger, " ├─ Miniflare: All Cloudflare bindings");
|
|
677
|
+
logLine(logger, " ├─ Rolldown: Worker + DO bundling with watch");
|
|
678
|
+
logLine(logger, " └─ Bridge: WebSocket RPC connection");
|
|
679
|
+
} else {
|
|
680
|
+
logLine(logger, `${cyanBold("dev", theme)} ${dim("Worker Dev Server", theme)}`);
|
|
681
|
+
logLine(logger, " ├─ Miniflare: All Cloudflare bindings");
|
|
682
|
+
logLine(logger, " ├─ Rolldown: Worker + DO bundling with watch");
|
|
683
|
+
logLine(logger, " └─ Vite: Disabled (no effective Vite config found)");
|
|
684
|
+
if (viteProject.wantsViteIntegration) {
|
|
685
|
+
logger.warn("Vite-related settings were detected, but no effective Vite config was available");
|
|
686
|
+
logger.warn("Skipping Vite startup and running in worker-only mode");
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
logLine(logger);
|
|
690
|
+
const devServer = createDevServer({
|
|
691
|
+
cwd,
|
|
692
|
+
configPath,
|
|
693
|
+
vitePort: port ? Number.parseInt(port, 10) : 5173,
|
|
694
|
+
miniflarePort,
|
|
695
|
+
miniflareHost,
|
|
696
|
+
enableVite: viteProject.shouldStartVite,
|
|
697
|
+
persist: persistEnabled,
|
|
698
|
+
logger: devLogger,
|
|
699
|
+
verbose,
|
|
700
|
+
debug: debugEnabled
|
|
701
|
+
});
|
|
702
|
+
let isCleaningUp = false;
|
|
703
|
+
const cleanupHandlers = /* @__PURE__ */ new Map();
|
|
704
|
+
const removeCleanupHandlers = () => {
|
|
705
|
+
for (const [event, handler] of cleanupHandlers) process.off(event, handler);
|
|
706
|
+
cleanupHandlers.clear();
|
|
707
|
+
};
|
|
708
|
+
const cleanup = async (exitCode, reason) => {
|
|
709
|
+
if (isCleaningUp) return;
|
|
710
|
+
isCleaningUp = true;
|
|
711
|
+
removeCleanupHandlers();
|
|
712
|
+
if (reason) {
|
|
713
|
+
const message = reason instanceof Error ? reason.stack ?? reason.message : String(reason);
|
|
714
|
+
logger.error(message);
|
|
715
|
+
}
|
|
716
|
+
logLine(logger);
|
|
717
|
+
logLine(logger, `${yellow("dev", theme)} ${dim("Shutting down…", theme)}`);
|
|
718
|
+
try {
|
|
719
|
+
await devServer.stop();
|
|
720
|
+
} finally {
|
|
721
|
+
logWriter?.close();
|
|
722
|
+
process.exit(exitCode);
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
const registerCleanupHandler = (event, handler) => {
|
|
726
|
+
cleanupHandlers.set(event, handler);
|
|
727
|
+
process.on(event, handler);
|
|
728
|
+
};
|
|
729
|
+
registerCleanupHandler("SIGINT", () => {
|
|
730
|
+
cleanup(0);
|
|
731
|
+
});
|
|
732
|
+
registerCleanupHandler("SIGTERM", () => {
|
|
733
|
+
cleanup(0);
|
|
734
|
+
});
|
|
735
|
+
registerCleanupHandler("SIGHUP", () => {
|
|
736
|
+
cleanup(0);
|
|
737
|
+
});
|
|
738
|
+
registerCleanupHandler("uncaughtException", (error) => {
|
|
739
|
+
cleanup(1, error);
|
|
740
|
+
});
|
|
741
|
+
registerCleanupHandler("unhandledRejection", (reason) => {
|
|
742
|
+
cleanup(1, reason);
|
|
743
|
+
});
|
|
744
|
+
await devServer.start();
|
|
745
|
+
await new Promise(() => {});
|
|
746
|
+
return { exitCode: 0 };
|
|
747
|
+
} catch (error) {
|
|
748
|
+
logWriter?.close();
|
|
749
|
+
if (error instanceof Error) {
|
|
750
|
+
logger.error("Dev server failed:", error.message);
|
|
751
|
+
if (verbose) logger.error(error.stack);
|
|
752
|
+
}
|
|
753
|
+
return { exitCode: 1 };
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
//#endregion
|
|
757
|
+
export { runDevCommand };
|