veryfront 0.1.1228 → 0.1.1229
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/esm/cli/commands/build/command-help.js +1 -1
- package/esm/cli/commands/build/command.d.ts +12 -0
- package/esm/cli/commands/build/command.d.ts.map +1 -1
- package/esm/cli/commands/build/command.js +55 -5
- package/esm/cli/commands/demo/demo.d.ts.map +1 -1
- package/esm/cli/commands/demo/demo.js +12 -23
- package/esm/cli/commands/demo/dev-step.d.ts +29 -0
- package/esm/cli/commands/demo/dev-step.d.ts.map +1 -0
- package/esm/cli/commands/demo/dev-step.js +38 -0
- package/esm/cli/commands/dev/command.d.ts +46 -0
- package/esm/cli/commands/dev/command.d.ts.map +1 -1
- package/esm/cli/commands/dev/command.js +74 -25
- package/esm/cli/commands/dev/handler.d.ts.map +1 -1
- package/esm/cli/commands/dev/handler.js +5 -3
- package/esm/cli/commands/dev/port-fallback.d.ts +22 -0
- package/esm/cli/commands/dev/port-fallback.d.ts.map +1 -0
- package/esm/cli/commands/dev/port-fallback.js +89 -0
- package/esm/cli/commands/doctor/ai-checks.d.ts.map +1 -1
- package/esm/cli/commands/doctor/ai-checks.js +76 -19
- package/esm/cli/commands/doctor/index.d.ts.map +1 -1
- package/esm/cli/commands/doctor/index.js +12 -7
- package/esm/cli/commands/doctor/project-config.d.ts +10 -0
- package/esm/cli/commands/doctor/project-config.d.ts.map +1 -0
- package/esm/cli/commands/doctor/project-config.js +18 -0
- package/esm/cli/commands/doctor/project-structure.d.ts +7 -0
- package/esm/cli/commands/doctor/project-structure.d.ts.map +1 -1
- package/esm/cli/commands/doctor/project-structure.js +32 -14
- package/esm/cli/commands/doctor/server-checks.d.ts +8 -1
- package/esm/cli/commands/doctor/server-checks.d.ts.map +1 -1
- package/esm/cli/commands/doctor/server-checks.js +31 -6
- package/esm/cli/commands/init/config-generator.d.ts.map +1 -1
- package/esm/cli/commands/init/config-generator.js +14 -0
- package/esm/cli/commands/open/command-help.d.ts.map +1 -1
- package/esm/cli/commands/open/command-help.js +4 -1
- package/esm/cli/commands/open/handler.d.ts +13 -0
- package/esm/cli/commands/open/handler.d.ts.map +1 -1
- package/esm/cli/commands/open/handler.js +58 -11
- package/esm/cli/commands/routes/command.d.ts.map +1 -1
- package/esm/cli/commands/routes/command.js +4 -1
- package/esm/cli/commands/serve/command.d.ts +20 -0
- package/esm/cli/commands/serve/command.d.ts.map +1 -1
- package/esm/cli/commands/serve/command.js +35 -0
- package/esm/cli/commands/up/command.d.ts.map +1 -1
- package/esm/cli/commands/up/command.js +33 -4
- package/esm/cli/help/tips.js +1 -1
- package/esm/cli/scaffold/engine.d.ts.map +1 -1
- package/esm/cli/scaffold/engine.js +7 -3
- package/esm/cli/shared/project-creation.d.ts.map +1 -1
- package/esm/cli/shared/project-creation.js +7 -3
- package/esm/cli/templates/manifest.d.ts +6 -0
- package/esm/cli/templates/manifest.js +8 -2
- package/esm/cli/utils/env-prompt.js +1 -1
- package/esm/deno.js +1 -1
- package/esm/src/agent/composition/composition.d.ts.map +1 -1
- package/esm/src/agent/composition/composition.js +3 -2
- package/esm/src/agent/factory.d.ts.map +1 -1
- package/esm/src/agent/factory.js +14 -6
- package/esm/src/agent/hosted/cloud-agent-chat-execution.js +2 -2
- package/esm/src/agent/hosted/cloud-agent-child-tools.d.ts +3 -3
- package/esm/src/agent/hosted/cloud-agent-child-tools.d.ts.map +1 -1
- package/esm/src/agent/hosted/cloud-agent-child-tools.js +2 -2
- package/esm/src/agent/runtime/agent-delegation.d.ts +15 -0
- package/esm/src/agent/runtime/agent-delegation.d.ts.map +1 -1
- package/esm/src/agent/runtime/agent-delegation.js +55 -1
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +5 -2
- package/esm/src/agent/runtime/local-tool.d.ts +4 -0
- package/esm/src/agent/runtime/local-tool.d.ts.map +1 -1
- package/esm/src/agent/runtime/local-tool.js +15 -0
- package/esm/src/agent/runtime/mcp-server-tool-sources.js +2 -2
- package/esm/src/agent/runtime/skill-delegation-overrides.d.ts +1 -1
- package/esm/src/agent/runtime/skill-delegation-overrides.d.ts.map +1 -1
- package/esm/src/agent/runtime/skill-delegation-overrides.js +5 -2
- package/esm/src/build/production-build/build/build-setup.d.ts.map +1 -1
- package/esm/src/build/production-build/build/build-setup.js +6 -1
- package/esm/src/chat/index.d.ts +3 -1
- package/esm/src/chat/index.d.ts.map +1 -1
- package/esm/src/chat/index.js +3 -1
- package/esm/src/config/environment-config.d.ts.map +1 -1
- package/esm/src/config/environment-config.js +15 -4
- package/esm/src/discovery/import-rewriter.d.ts +8 -1
- package/esm/src/discovery/import-rewriter.d.ts.map +1 -1
- package/esm/src/discovery/import-rewriter.js +42 -2
- package/esm/src/errors/error-registry/config.d.ts +7 -0
- package/esm/src/errors/error-registry/config.d.ts.map +1 -1
- package/esm/src/errors/error-registry/config.js +14 -0
- package/esm/src/errors/error-registry/server.d.ts.map +1 -1
- package/esm/src/errors/error-registry/server.js +1 -1
- package/esm/src/errors/error-registry.d.ts +1 -0
- package/esm/src/errors/error-registry.d.ts.map +1 -1
- package/esm/src/errors/index.d.ts +1 -1
- package/esm/src/errors/index.d.ts.map +1 -1
- package/esm/src/errors/index.js +1 -1
- package/esm/src/extensions/first-party-import.js +4 -0
- package/esm/src/html/hydration-script-builder/hydration-runtime.generated.js +1 -1
- package/esm/src/html/styles-builder/tailwind-compiler.d.ts.map +1 -1
- package/esm/src/html/styles-builder/tailwind-compiler.js +17 -2
- package/esm/src/integrations/remote-tools.d.ts.map +1 -1
- package/esm/src/integrations/remote-tools.js +35 -6
- package/esm/src/markdown/index.d.ts +1 -1
- package/esm/src/markdown/index.js +1 -1
- package/esm/src/platform/compat/http/pinned-fetch.d.ts +1 -1
- package/esm/src/react/components/chat/chat/composition/chat-empty.d.ts +6 -1
- package/esm/src/react/components/chat/chat/composition/chat-empty.d.ts.map +1 -1
- package/esm/src/react/components/chat/chat/composition/chat-empty.js +6 -1
- package/esm/src/react/components/chat/chat/index.d.ts +1 -1
- package/esm/src/react/components/chat/chat/index.js +1 -1
- package/esm/src/react/components/chat/missing-renderer-warning.d.ts.map +1 -1
- package/esm/src/react/components/chat/missing-renderer-warning.js +1 -1
- package/esm/src/react/components/ui/alert.d.ts +2 -2
- package/esm/src/react/components/ui/alert.js +5 -5
- package/esm/src/react/components/ui/design-tokens.d.ts.map +1 -1
- package/esm/src/react/components/ui/design-tokens.js +4 -0
- package/esm/src/server/dev-server/server.d.ts +1 -0
- package/esm/src/server/dev-server/server.d.ts.map +1 -1
- package/esm/src/server/dev-server/server.js +50 -3
- package/esm/src/server/handlers/dev/framework-candidates.generated.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/framework-candidates.generated.js +16 -3
- package/esm/src/transforms/mdx/esm-module-loader/cache/index.d.ts.map +1 -1
- package/esm/src/transforms/mdx/esm-module-loader/cache/index.js +4 -1
- package/esm/src/utils/cache-dir.d.ts +15 -0
- package/esm/src/utils/cache-dir.d.ts.map +1 -1
- package/esm/src/utils/cache-dir.js +61 -0
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +8 -8
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type VeryfrontConfig } from "../../../src/config/index.js";
|
|
1
2
|
import type { BuildOptions } from "./types.js";
|
|
2
3
|
/** @internal */
|
|
3
4
|
export declare function runWithBundlerShutdown<T>(operation: () => Promise<T>, stopBundler?: () => Promise<void>): Promise<T>;
|
|
@@ -19,5 +20,16 @@ export declare function releaseBuildExtensions(loader: {
|
|
|
19
20
|
teardownAll: () => Promise<void>;
|
|
20
21
|
} | undefined): Promise<void>;
|
|
21
22
|
export declare function formatBuildOutputPath(projectDir: string, outputDir: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Decide where the build writes.
|
|
25
|
+
*
|
|
26
|
+
* `-o/--output` wins, then `build.outDir` from veryfront.config.ts, then
|
|
27
|
+
* `dist`. `build.outDir` used to be read into the config object and dropped:
|
|
28
|
+
* the build wrote (and cleared) `dist` regardless, with no warning, so the
|
|
29
|
+
* documented way to keep the framework out of a project's own `dist/` did
|
|
30
|
+
* nothing. A relative `outDir` resolves against the project directory, which
|
|
31
|
+
* is how a config file naturally reads.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveBuildOutputDir(projectDir: string, explicitOutputDir: string | undefined, config: Pick<VeryfrontConfig, "build">): string;
|
|
22
34
|
export declare function buildCommand(options: BuildOptions): Promise<void>;
|
|
23
35
|
//# sourceMappingURL=command.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/build/command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/build/command.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAQ/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,gBAAgB;AAChB,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,WAAW,GAAE,MAAM,OAAO,CAAC,IAAI,CAG9B,GACA,OAAO,CAAC,CAAC,CAAC,CAiBZ;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE;IAAE,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,GAAG,SAAS,GACvD,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,MAAM,GAAG,SAAS,EACrC,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,GACrC,MAAM,CAIR;AAyCD,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA4GjE"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { join, relative, resolve } from "../../../src/platform/compat/path/index.js";
|
|
1
|
+
import { isAbsolute, join, relative, resolve } from "../../../src/platform/compat/path/index.js";
|
|
2
2
|
import { runtime } from "../../../src/platform/index.js";
|
|
3
3
|
import { getConfig } from "../../../src/config/index.js";
|
|
4
|
+
import { CONFIG_INVALID } from "../../../src/errors/index.js";
|
|
4
5
|
import { buildProduction } from "../../../src/build/index.js";
|
|
5
6
|
import { withSpan } from "../../../src/observability/tracing/otlp-setup.js";
|
|
6
7
|
import { cliLogger } from "../../utils/index.js";
|
|
@@ -62,9 +63,56 @@ export async function releaseBuildExtensions(loader) {
|
|
|
62
63
|
export function formatBuildOutputPath(projectDir, outputDir) {
|
|
63
64
|
return relative(projectDir, resolve(projectDir, outputDir)).replace(/\\/g, "/");
|
|
64
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Decide where the build writes.
|
|
68
|
+
*
|
|
69
|
+
* `-o/--output` wins, then `build.outDir` from veryfront.config.ts, then
|
|
70
|
+
* `dist`. `build.outDir` used to be read into the config object and dropped:
|
|
71
|
+
* the build wrote (and cleared) `dist` regardless, with no warning, so the
|
|
72
|
+
* documented way to keep the framework out of a project's own `dist/` did
|
|
73
|
+
* nothing. A relative `outDir` resolves against the project directory, which
|
|
74
|
+
* is how a config file naturally reads.
|
|
75
|
+
*/
|
|
76
|
+
export function resolveBuildOutputDir(projectDir, explicitOutputDir, config) {
|
|
77
|
+
const outputDir = explicitOutputDir ?? resolveConfiguredOutputDir(projectDir, config);
|
|
78
|
+
assertOutputDirExcludesProject(projectDir, outputDir, explicitOutputDir !== undefined);
|
|
79
|
+
return outputDir;
|
|
80
|
+
}
|
|
81
|
+
function resolveConfiguredOutputDir(projectDir, config) {
|
|
82
|
+
const configured = config.build?.outDir;
|
|
83
|
+
if (configured === undefined || configured === "")
|
|
84
|
+
return join(projectDir, "dist");
|
|
85
|
+
return resolve(projectDir, configured);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Refuse an output directory that is the project directory or an ancestor of it.
|
|
89
|
+
*
|
|
90
|
+
* The build clears its output directory before writing, so an `outDir` of `.`
|
|
91
|
+
* or `..` would recursively delete the project's own source — or the workspace
|
|
92
|
+
* above it. That was unreachable while `build.outDir` was ignored; now that the
|
|
93
|
+
* config value is honored, a stale compatibility-era config could reach it.
|
|
94
|
+
* Failing loudly is the only safe answer: silently substituting `dist` would
|
|
95
|
+
* reintroduce the ignored-configuration bug this change exists to fix.
|
|
96
|
+
*/
|
|
97
|
+
function assertOutputDirExcludesProject(projectDir, outputDir, fromFlag) {
|
|
98
|
+
const resolvedOutput = resolve(projectDir, outputDir);
|
|
99
|
+
const relativeToOutput = relative(resolvedOutput, resolve(projectDir));
|
|
100
|
+
const containsProject = relativeToOutput === "" ||
|
|
101
|
+
(!relativeToOutput.startsWith("..") && !isAbsolute(relativeToOutput));
|
|
102
|
+
if (!containsProject)
|
|
103
|
+
return;
|
|
104
|
+
const source = fromFlag ? "-o/--output" : "build.outDir";
|
|
105
|
+
throw CONFIG_INVALID.create({
|
|
106
|
+
detail: `${source} resolves to ${resolvedOutput}, which is the project directory or contains it. ` +
|
|
107
|
+
`The build clears its output directory before writing, so this would delete the project. ` +
|
|
108
|
+
`Point the build at a directory of its own, such as dist.`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
65
111
|
export function buildCommand(options) {
|
|
66
112
|
return withSpan("cli.command.build", async () => {
|
|
67
|
-
|
|
113
|
+
// Placeholder until the config is loaded; every path that reports or
|
|
114
|
+
// writes the output directory runs after resolveBuildOutputDir below.
|
|
115
|
+
let outputDir = options.outputDir ?? join(options.projectDir, "dist");
|
|
68
116
|
const startTime = Date.now();
|
|
69
117
|
const dryRun = options.dryRun ?? false;
|
|
70
118
|
let extensions;
|
|
@@ -79,12 +127,14 @@ export function buildCommand(options) {
|
|
|
79
127
|
if (isJsonMode()) {
|
|
80
128
|
streamJsonLine({ type: "step", name: "config", status: "started" });
|
|
81
129
|
}
|
|
82
|
-
else {
|
|
83
|
-
displayBuildConfig({ ...options, outputDir });
|
|
84
|
-
}
|
|
85
130
|
const stats = await runWithBundlerShutdown(async () => {
|
|
86
131
|
const adapter = await runtime.get();
|
|
87
132
|
const config = await getConfig(options.projectDir, adapter);
|
|
133
|
+
// Resolved from the loaded config, so the displayed, reported and
|
|
134
|
+
// written output directory are the same one.
|
|
135
|
+
outputDir = resolveBuildOutputDir(options.projectDir, options.outputDir, config);
|
|
136
|
+
if (!isJsonMode())
|
|
137
|
+
displayBuildConfig({ ...options, outputDir });
|
|
88
138
|
// Compose the project's extensions before anything that resolves a
|
|
89
139
|
// contract. Only server bootstrap used to do this, so the build ran
|
|
90
140
|
// with whatever one-off shims had been added and failed on the rest.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/demo/demo.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA2DH,KAAK,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/demo/demo.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA2DH,KAAK,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;AAsZ9D,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8CAA8C;IAC9C,WAAW,CAAC,EAAE,UAAU,CAAC;CAC1B;AA+DD,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmH1E"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { chdir, cwd, promptSync, writeStdout } from "../../../src/platform/index.js";
|
|
7
7
|
import { getStdinReader, setRawMode } from "../../../src/platform/index.js";
|
|
8
|
-
import { AnimatedDotMatrix, bold, brand, BRAND_TRUECOLOR, dim, error, formatDuration, HIDE_CURSOR, muted, SHOW_CURSOR,
|
|
8
|
+
import { AnimatedDotMatrix, bold, brand, BRAND_TRUECOLOR, dim, error, formatDuration, HIDE_CURSOR, muted, SHOW_CURSOR, successBanner, typeCommand, typeLine, } from "../../ui/index.js";
|
|
9
9
|
import { exitProcess, isTTY, logError } from "../../utils/index.js";
|
|
10
10
|
import { createOAuthAuthorizationUrl, createOAuthState, readToken, saveToken, validateToken, } from "../../auth/index.js";
|
|
11
11
|
import { canOpenBrowser, openBrowser } from "../../auth/browser.js";
|
|
@@ -21,6 +21,7 @@ import { resolveOrCreateProject } from "../../shared/project-resolution.js";
|
|
|
21
21
|
import { getProjectTarget } from "../../shared/deployment-provenance.js";
|
|
22
22
|
import { reserveProjectSlug } from "../../shared/reserve-slug.js";
|
|
23
23
|
import { DEMO_STEPS } from "./steps.js";
|
|
24
|
+
import { runDemoDevStep } from "./dev-step.js";
|
|
24
25
|
// ANSI escape codes
|
|
25
26
|
const ESC = "\x1b";
|
|
26
27
|
const CLEAR_SCREEN = `${ESC}[2J`;
|
|
@@ -336,29 +337,17 @@ async function executeStepAction(step, projectName, loginMethod) {
|
|
|
336
337
|
console.log();
|
|
337
338
|
console.log(` ${dim("Starting dev server...")}`);
|
|
338
339
|
try {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
340
|
+
await runDemoDevStep({
|
|
341
|
+
start: () => devCommand({
|
|
342
|
+
port: 3000,
|
|
343
|
+
projectDir,
|
|
344
|
+
hmr: true,
|
|
345
|
+
demoMode: true,
|
|
346
|
+
}),
|
|
347
|
+
open: (url) => openBrowser(url),
|
|
348
|
+
waitForStop: () => waitForEnter(),
|
|
349
|
+
log: (line) => console.log(line),
|
|
344
350
|
});
|
|
345
|
-
await result.ready;
|
|
346
|
-
console.log(` ${success("●")} ${brand("http://localhost:3000/")}`);
|
|
347
|
-
console.log();
|
|
348
|
-
console.log(` ${dim("Opening browser...")}`);
|
|
349
|
-
try {
|
|
350
|
-
await openBrowser("http://localhost:3000");
|
|
351
|
-
}
|
|
352
|
-
catch {
|
|
353
|
-
// Ignore if browser can't be opened
|
|
354
|
-
}
|
|
355
|
-
console.log();
|
|
356
|
-
console.log(` ${dim("Press Enter to stop the dev server and continue...")}`);
|
|
357
|
-
await waitForEnter();
|
|
358
|
-
console.log();
|
|
359
|
-
console.log(` ${dim("Stopping dev server...")}`);
|
|
360
|
-
await result.stop();
|
|
361
|
-
await result.done;
|
|
362
351
|
}
|
|
363
352
|
catch (e) {
|
|
364
353
|
console.log(` ${error("✗")} ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The demo's dev-server step.
|
|
3
|
+
*
|
|
4
|
+
* Lives apart from `demo.ts` so the address the demo shows and opens can be
|
|
5
|
+
* tested without booting a dev server, a browser, or stdin.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/commands/demo/dev-step
|
|
8
|
+
*/
|
|
9
|
+
import type { DevCommandResult } from "../dev/index.js";
|
|
10
|
+
export interface DemoDevStepDeps {
|
|
11
|
+
/** Starts the dev server. */
|
|
12
|
+
start: () => Promise<DevCommandResult>;
|
|
13
|
+
/** Opens the demo's browser at the given URL. */
|
|
14
|
+
open: (url: string) => Promise<unknown>;
|
|
15
|
+
/** Resolves when the viewer asks for the dev server to stop. */
|
|
16
|
+
waitForStop: () => Promise<unknown>;
|
|
17
|
+
/** Writes one line of demo output. */
|
|
18
|
+
log: (line: string) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Shows the running dev server, opens it, then stops it on request.
|
|
22
|
+
*
|
|
23
|
+
* Both the printed URL and the opened URL come from `result.port` - the port
|
|
24
|
+
* the server actually bound. `veryfront dev` asks for 3000 but falls forward
|
|
25
|
+
* when that is taken, so keying off the requested port would send the viewer to
|
|
26
|
+
* whatever process caused the collision instead of to the demo.
|
|
27
|
+
*/
|
|
28
|
+
export declare function runDemoDevStep(deps: DemoDevStepDeps): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=dev-step.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-step.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/demo/dev-step.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,eAAe;IAC9B,6BAA6B;IAC7B,KAAK,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACvC,iDAAiD;IACjD,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,gEAAgE;IAChE,WAAW,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACpC,sCAAsC;IACtC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBzE"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The demo's dev-server step.
|
|
3
|
+
*
|
|
4
|
+
* Lives apart from `demo.ts` so the address the demo shows and opens can be
|
|
5
|
+
* tested without booting a dev server, a browser, or stdin.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/commands/demo/dev-step
|
|
8
|
+
*/
|
|
9
|
+
import { brand, dim, success } from "../../ui/index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Shows the running dev server, opens it, then stops it on request.
|
|
12
|
+
*
|
|
13
|
+
* Both the printed URL and the opened URL come from `result.port` - the port
|
|
14
|
+
* the server actually bound. `veryfront dev` asks for 3000 but falls forward
|
|
15
|
+
* when that is taken, so keying off the requested port would send the viewer to
|
|
16
|
+
* whatever process caused the collision instead of to the demo.
|
|
17
|
+
*/
|
|
18
|
+
export async function runDemoDevStep(deps) {
|
|
19
|
+
const result = await deps.start();
|
|
20
|
+
await result.ready;
|
|
21
|
+
const serverUrl = `http://localhost:${result.port}`;
|
|
22
|
+
deps.log(` ${success("●")} ${brand(`${serverUrl}/`)}`);
|
|
23
|
+
deps.log("");
|
|
24
|
+
deps.log(` ${dim("Opening browser...")}`);
|
|
25
|
+
try {
|
|
26
|
+
await deps.open(serverUrl);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Ignore if browser can't be opened
|
|
30
|
+
}
|
|
31
|
+
deps.log("");
|
|
32
|
+
deps.log(` ${dim("Press Enter to stop the dev server and continue...")}`);
|
|
33
|
+
await deps.waitForStop();
|
|
34
|
+
deps.log("");
|
|
35
|
+
deps.log(` ${dim("Stopping dev server...")}`);
|
|
36
|
+
await result.stop();
|
|
37
|
+
await result.done;
|
|
38
|
+
}
|
|
@@ -11,11 +11,24 @@ export interface DevOptions {
|
|
|
11
11
|
open?: boolean;
|
|
12
12
|
/** Demo mode: don't exit process on shutdown, resolve done promise instead */
|
|
13
13
|
demoMode?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Clear the shared on-disk ESM caches before starting. Only honoured once the
|
|
16
|
+
* requested dev port is confirmed free, because the cache directory is shared
|
|
17
|
+
* with any dev server already serving this project.
|
|
18
|
+
*/
|
|
19
|
+
clearLocalCaches?: boolean;
|
|
14
20
|
}
|
|
15
21
|
export type DevCommandOptions = DevOptions;
|
|
16
22
|
export interface DevCommandResult {
|
|
17
23
|
ready: Promise<void>;
|
|
18
24
|
done: Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* The port the server actually bound, which is not always the requested one:
|
|
27
|
+
* a taken port falls forward. Embedded callers must use this rather than the
|
|
28
|
+
* port they asked for, or they will point the user at the process that caused
|
|
29
|
+
* the collision.
|
|
30
|
+
*/
|
|
31
|
+
port: number;
|
|
19
32
|
/** Stop the dev server programmatically (for demo mode) */
|
|
20
33
|
stop: () => Promise<void>;
|
|
21
34
|
}
|
|
@@ -24,5 +37,38 @@ export declare function preloadDevAuth(apiToken?: string): Promise<{
|
|
|
24
37
|
projects: RemoteProject[];
|
|
25
38
|
}>;
|
|
26
39
|
export declare function createSelectedProjectPushOptions(projectDir: string, project: RemoteProject): PushOptions;
|
|
40
|
+
/**
|
|
41
|
+
* Starts the dev server on the first free port at or after `requestedPort`.
|
|
42
|
+
*
|
|
43
|
+
* Port 3000 is the most contended port on a developer machine, and the docs
|
|
44
|
+
* tell readers to run a bare `veryfront dev`, so a taken port scans forward
|
|
45
|
+
* rather than killing the command. Everything downstream - the MCP port, the
|
|
46
|
+
* printed URL, the browser the demo opens - must key off the returned `port`
|
|
47
|
+
* rather than the requested one, or a fall-forward points the user at the
|
|
48
|
+
* process that caused the collision.
|
|
49
|
+
*
|
|
50
|
+
* Takes `start` as a callback so the whole scan costs one `DevServer.start()`:
|
|
51
|
+
* probing is a bare bind/release, and a failed `start()` has already registered
|
|
52
|
+
* watchers and reload subscriptions that only `stop()` releases.
|
|
53
|
+
*/
|
|
54
|
+
export declare function startDevServerOnFreePort<T>(requestedPort: number, start: (port: number) => Promise<T>): Promise<{
|
|
55
|
+
server: T;
|
|
56
|
+
port: number;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Clears the shared on-disk ESM caches, but only if `requestedPort` is free.
|
|
60
|
+
*
|
|
61
|
+
* The caches live under the project's `.cache` directory, which every dev
|
|
62
|
+
* server rooted at that project shares. A taken dev port is the signal that one
|
|
63
|
+
* of them is already running and still serving modules it compiled, so the
|
|
64
|
+
* clear is skipped rather than wiping that server's work out from under it -
|
|
65
|
+
* the second `veryfront dev` falls forward to a free port and starts on a cache
|
|
66
|
+
* it did not just destroy.
|
|
67
|
+
*
|
|
68
|
+
* Returns whether the clear ran. Takes `clear` and `probe` as parameters so the
|
|
69
|
+
* decision can be tested without booting a dev server, the same seam
|
|
70
|
+
* `startDevServerOnFreePort` uses.
|
|
71
|
+
*/
|
|
72
|
+
export declare function clearLocalCachesIfPortFree(requestedPort: number, clear?: () => Promise<void>, probe?: (port: number) => Promise<boolean>): Promise<boolean>;
|
|
27
73
|
export declare function devCommand(options: DevOptions): Promise<DevCommandResult>;
|
|
28
74
|
//# sourceMappingURL=command.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/dev/command.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/dev/command.ts"],"names":[],"mappings":"AAAA;;GAEG;AAmBH,OAAO,EAAE,KAAK,YAAY,EAA2B,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE9E,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAK1F,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,MAAM,iBAAiB,GAAG,UAAU,CAAC;AAE3C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAQD,wBAAsB,cAAc,CAClC,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,aAAa,EAAE,CAAA;CAAE,CAAC,CAQvE;AAED,wBAAgB,gCAAgC,CAC9C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,aAAa,GACrB,WAAW,CAEb;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBtC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,0BAA0B,CAC9C,aAAa,EAAE,MAAM,EACrB,KAAK,GAAE,MAAM,OAAO,CAAC,IAAI,CAAuB,EAChD,KAAK,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAmB,GAC1D,OAAO,CAAC,OAAO,CAAC,CAIlB;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyRzE"}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* Dev Command - Development server with HMR
|
|
3
3
|
*/
|
|
4
4
|
import { compileAllMDX, watchMDX } from "../../../src/build/index.js";
|
|
5
|
-
import { CONFIG_NOT_FOUND,
|
|
5
|
+
import { CONFIG_NOT_FOUND, PORT_IN_USE } from "../../../src/errors/index.js";
|
|
6
6
|
import { join } from "../../../src/platform/compat/path/index.js";
|
|
7
7
|
import { runtime } from "../../../src/platform/index.js";
|
|
8
8
|
import { getConfig } from "../../../src/config/index.js";
|
|
9
9
|
import { getEnvironmentConfig } from "../../../src/config/index.js";
|
|
10
10
|
import { startDevServer } from "../../../src/server/index.js";
|
|
11
|
+
import { clearAllLocalCaches } from "../../../src/transforms/mdx/esm-module-loader/cache/index.js";
|
|
11
12
|
import { validateProviderConfig } from "../../../src/discovery/index.js";
|
|
12
13
|
import { brand, devShortcuts, dim, error as errorColor, formatDuration, warning } from "../../ui/index.js";
|
|
13
14
|
import { exitProcess, isTTY, isVerbose, registerTerminationSignals } from "../../utils/index.js";
|
|
@@ -23,6 +24,7 @@ import { pullCommand } from "../pull/index.js";
|
|
|
23
24
|
import { createStagedPushOptions, pushCommand } from "../push/index.js";
|
|
24
25
|
import { createProjectSelector } from "./project-selector.js";
|
|
25
26
|
import { createDevLogController } from "./log-controller.js";
|
|
27
|
+
import { findAvailablePort, isPortAvailable, isPortInUseError } from "./port-fallback.js";
|
|
26
28
|
function authStatus(identity) {
|
|
27
29
|
return isApiKeyIdentity(identity)
|
|
28
30
|
? "Authenticated with an API key"
|
|
@@ -40,9 +42,64 @@ export async function preloadDevAuth(apiToken) {
|
|
|
40
42
|
export function createSelectedProjectPushOptions(projectDir, project) {
|
|
41
43
|
return createStagedPushOptions(project.slug, projectDir);
|
|
42
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Starts the dev server on the first free port at or after `requestedPort`.
|
|
47
|
+
*
|
|
48
|
+
* Port 3000 is the most contended port on a developer machine, and the docs
|
|
49
|
+
* tell readers to run a bare `veryfront dev`, so a taken port scans forward
|
|
50
|
+
* rather than killing the command. Everything downstream - the MCP port, the
|
|
51
|
+
* printed URL, the browser the demo opens - must key off the returned `port`
|
|
52
|
+
* rather than the requested one, or a fall-forward points the user at the
|
|
53
|
+
* process that caused the collision.
|
|
54
|
+
*
|
|
55
|
+
* Takes `start` as a callback so the whole scan costs one `DevServer.start()`:
|
|
56
|
+
* probing is a bare bind/release, and a failed `start()` has already registered
|
|
57
|
+
* watchers and reload subscriptions that only `stop()` releases.
|
|
58
|
+
*/
|
|
59
|
+
export async function startDevServerOnFreePort(requestedPort, start) {
|
|
60
|
+
const port = await findAvailablePort(requestedPort);
|
|
61
|
+
if (port !== requestedPort) {
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(` ${warning("!")} Port ${requestedPort} is in use, using ${port} instead`);
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
return { server: await start(port), port };
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
// Lost the race between probing the port and binding it.
|
|
70
|
+
if (isPortInUseError(error)) {
|
|
71
|
+
throw PORT_IN_USE.create({
|
|
72
|
+
detail: `Port ${port} is already in use`,
|
|
73
|
+
cause: error,
|
|
74
|
+
context: { port },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Clears the shared on-disk ESM caches, but only if `requestedPort` is free.
|
|
82
|
+
*
|
|
83
|
+
* The caches live under the project's `.cache` directory, which every dev
|
|
84
|
+
* server rooted at that project shares. A taken dev port is the signal that one
|
|
85
|
+
* of them is already running and still serving modules it compiled, so the
|
|
86
|
+
* clear is skipped rather than wiping that server's work out from under it -
|
|
87
|
+
* the second `veryfront dev` falls forward to a free port and starts on a cache
|
|
88
|
+
* it did not just destroy.
|
|
89
|
+
*
|
|
90
|
+
* Returns whether the clear ran. Takes `clear` and `probe` as parameters so the
|
|
91
|
+
* decision can be tested without booting a dev server, the same seam
|
|
92
|
+
* `startDevServerOnFreePort` uses.
|
|
93
|
+
*/
|
|
94
|
+
export async function clearLocalCachesIfPortFree(requestedPort, clear = clearAllLocalCaches, probe = isPortAvailable) {
|
|
95
|
+
if (!await probe(requestedPort))
|
|
96
|
+
return false;
|
|
97
|
+
await clear();
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
43
100
|
export function devCommand(options) {
|
|
44
101
|
return withSpan("cli.command.dev", async () => {
|
|
45
|
-
const { port, projectDir, hmr = true, open = false, demoMode = false } = options;
|
|
102
|
+
const { port, projectDir, hmr = true, open = false, demoMode = false, clearLocalCaches = false, } = options;
|
|
46
103
|
const startTime = Date.now();
|
|
47
104
|
let doneResolve;
|
|
48
105
|
const done = new Promise((resolve) => {
|
|
@@ -65,6 +122,8 @@ export function devCommand(options) {
|
|
|
65
122
|
const DEFAULT_DEV_PORT = 3000;
|
|
66
123
|
const finalPort = port !== DEFAULT_DEV_PORT ? port : (config?.dev?.port ?? port);
|
|
67
124
|
const enableHMR = config?.dev?.hmr !== false && hmr;
|
|
125
|
+
if (clearLocalCaches)
|
|
126
|
+
await clearLocalCachesIfPortFree(finalPort);
|
|
68
127
|
const env = getEnvironmentConfig();
|
|
69
128
|
const isProxyMode = config?.fs?.veryfront?.proxyMode === true;
|
|
70
129
|
const linkedProjectSlug = await resolveLinkedProjectSlug(projectDir, config?.projectSlug ?? config?.fs?.veryfront?.projectSlug ?? env.projectSlug);
|
|
@@ -101,29 +160,17 @@ export function devCommand(options) {
|
|
|
101
160
|
let identity = null;
|
|
102
161
|
let projects = [];
|
|
103
162
|
let selectedProject = null;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
catch (error) {
|
|
114
|
-
if (error instanceof Error) {
|
|
115
|
-
const msg = error.message.toLowerCase();
|
|
116
|
-
if (msg.includes("eaddrinuse") || msg.includes("address already in use")) {
|
|
117
|
-
throw INITIALIZATION_ERROR.create({
|
|
118
|
-
detail: `Port ${finalPort} is already in use`,
|
|
119
|
-
context: { port: finalPort },
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
throw error;
|
|
124
|
-
}
|
|
163
|
+
const started = await startDevServerOnFreePort(finalPort, (port) => startDevServer({
|
|
164
|
+
port,
|
|
165
|
+
projectDir,
|
|
166
|
+
enableHMR,
|
|
167
|
+
enableFastRefresh: true,
|
|
168
|
+
signal: shutdownController.signal,
|
|
169
|
+
}));
|
|
170
|
+
devServer = started.server;
|
|
171
|
+
const boundPort = started.port;
|
|
125
172
|
const DEV_MCP_PORT_OFFSET = 2;
|
|
126
|
-
const mcpPort =
|
|
173
|
+
const mcpPort = boundPort + DEV_MCP_PORT_OFFSET;
|
|
127
174
|
try {
|
|
128
175
|
mcpServer = await createMCPServer({ httpPort: mcpPort });
|
|
129
176
|
}
|
|
@@ -196,10 +243,11 @@ export function devCommand(options) {
|
|
|
196
243
|
return {
|
|
197
244
|
ready: devServer.ready,
|
|
198
245
|
done,
|
|
246
|
+
port: boundPort,
|
|
199
247
|
stop: shutdown,
|
|
200
248
|
};
|
|
201
249
|
}
|
|
202
|
-
const serverUrl = `http://veryfront.me:${
|
|
250
|
+
const serverUrl = `http://veryfront.me:${boundPort}`;
|
|
203
251
|
const elapsed = Date.now() - startTime;
|
|
204
252
|
console.log();
|
|
205
253
|
console.log(` ✓ Ready in ${formatDuration(elapsed)}`);
|
|
@@ -275,6 +323,7 @@ export function devCommand(options) {
|
|
|
275
323
|
return {
|
|
276
324
|
ready: devServer.ready,
|
|
277
325
|
done,
|
|
326
|
+
port: boundPort,
|
|
278
327
|
stop: shutdown,
|
|
279
328
|
};
|
|
280
329
|
}, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/dev/handler.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/dev/handler.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAexD,eAAO,MAAM,YAAY;;;;;;;GAOvB,CAAC;AAwBH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BtE"}
|
|
@@ -7,7 +7,6 @@ import { cwd, setEnv } from "../../../src/platform/index.js";
|
|
|
7
7
|
import { createFileSystem } from "../../../src/platform/index.js";
|
|
8
8
|
import { cliLogger, DEFAULT_DEV_SERVER_PORT, showHeader } from "../../utils/index.js";
|
|
9
9
|
import { refreshLoggerConfig } from "../../../src/utils/index.js";
|
|
10
|
-
import { clearAllLocalCaches } from "../../../src/transforms/mdx/esm-module-loader/cache/index.js";
|
|
11
10
|
import { createArgParser, parseArgsOrThrow } from "../../shared/args.js";
|
|
12
11
|
import { ensureCliBundlerContracts } from "../../shared/default-contracts.js";
|
|
13
12
|
const getDevArgsSchema = defineSchema((v) => v.object({
|
|
@@ -54,14 +53,17 @@ export async function handleDevCommand(args) {
|
|
|
54
53
|
setEnv("VERYFRONT_DEBUG", "1");
|
|
55
54
|
refreshLoggerConfig();
|
|
56
55
|
}
|
|
57
|
-
// Clear stale ESM caches to prevent module resolution issues from previous runs
|
|
58
|
-
await clearAllLocalCaches();
|
|
59
56
|
const { devCommand } = await import("./index.js");
|
|
60
57
|
const { done } = await devCommand({
|
|
61
58
|
port: opts.port,
|
|
62
59
|
projectDir,
|
|
63
60
|
hmr: opts.hmr && !opts.noHmr,
|
|
64
61
|
open: opts.open,
|
|
62
|
+
// Clear stale ESM caches to prevent module resolution issues from previous
|
|
63
|
+
// runs. devCommand does this only once it has resolved the dev port and
|
|
64
|
+
// found it free — the caches are shared with any dev server already running
|
|
65
|
+
// against this project.
|
|
66
|
+
clearLocalCaches: true,
|
|
65
67
|
});
|
|
66
68
|
// Block until the dev server shuts down.
|
|
67
69
|
// Without this, main.ts reaches exitProcess(0) and terminates immediately.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** How many consecutive ports to try before giving up. */
|
|
2
|
+
export declare const MAX_PORT_FALLBACK_ATTEMPTS = 10;
|
|
3
|
+
/** The highest port a TCP listener can bind. */
|
|
4
|
+
export declare const MAX_TCP_PORT = 65535;
|
|
5
|
+
/** True when `error` means "that port is taken", on either Deno or Node. */
|
|
6
|
+
export declare function isPortInUseError(error: unknown): boolean;
|
|
7
|
+
/** Binds `port` and releases it again, to see whether the dev server could have it. */
|
|
8
|
+
export declare function isPortAvailable(port: number): Promise<boolean>;
|
|
9
|
+
/**
|
|
10
|
+
* Returns `requestedPort`, or the first free port after it.
|
|
11
|
+
*
|
|
12
|
+
* The scan stops at `MAX_TCP_PORT`: a runtime rejects port 65536 as an invalid
|
|
13
|
+
* port rather than as an address in use, so scanning past the end of the range
|
|
14
|
+
* would let that raw error escape instead of the `PORT_IN_USE` below.
|
|
15
|
+
* `requestedPort` itself is always probed, so an out-of-range `--port` still
|
|
16
|
+
* surfaces the runtime's own complaint about the value the user passed.
|
|
17
|
+
*
|
|
18
|
+
* Throws `PORT_IN_USE` - whose suggestion names `--port` - once the whole scan
|
|
19
|
+
* range is taken.
|
|
20
|
+
*/
|
|
21
|
+
export declare function findAvailablePort(requestedPort: number, maxAttempts?: number, probe?: (port: number) => Promise<boolean>): Promise<number>;
|
|
22
|
+
//# sourceMappingURL=port-fallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port-fallback.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/dev/port-fallback.ts"],"names":[],"mappings":"AAoBA,0DAA0D;AAC1D,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C,gDAAgD;AAChD,eAAO,MAAM,YAAY,QAAQ,CAAC;AAElC,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAQxD;AAED,uFAAuF;AACvF,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAwBpE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CACrC,aAAa,EAAE,MAAM,EACrB,WAAW,GAAE,MAAmC,EAChD,KAAK,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAmB,GAC1D,OAAO,CAAC,MAAM,CAAC,CAejB"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev server port selection.
|
|
3
|
+
*
|
|
4
|
+
* Port 3000 is the most contended port on a developer machine, and the
|
|
5
|
+
* getting-started docs tell readers to run a bare `veryfront dev`. Rather than
|
|
6
|
+
* hard-failing when that port is taken, scan forward for the first free one -
|
|
7
|
+
* the same fallback the login callback server already uses.
|
|
8
|
+
*
|
|
9
|
+
* The scan probes ports by binding and releasing them, rather than by retrying
|
|
10
|
+
* `startDevServer`: a failed `DevServer.start()` has already registered file
|
|
11
|
+
* watchers and reload subscriptions that only `stop()` releases, so retrying it
|
|
12
|
+
* would leak a watcher set per busy port.
|
|
13
|
+
*/
|
|
14
|
+
import * as dntShim from "../../../_dnt.shims.js";
|
|
15
|
+
import { LOCALHOST } from "../../../src/config/index.js";
|
|
16
|
+
import { PORT_IN_USE } from "../../../src/errors/index.js";
|
|
17
|
+
import { isDeno } from "../../../src/platform/index.js";
|
|
18
|
+
/** How many consecutive ports to try before giving up. */
|
|
19
|
+
export const MAX_PORT_FALLBACK_ATTEMPTS = 10;
|
|
20
|
+
/** The highest port a TCP listener can bind. */
|
|
21
|
+
export const MAX_TCP_PORT = 65535;
|
|
22
|
+
/** True when `error` means "that port is taken", on either Deno or Node. */
|
|
23
|
+
export function isPortInUseError(error) {
|
|
24
|
+
if (!(error instanceof Error))
|
|
25
|
+
return false;
|
|
26
|
+
// Deno: name is "AddrInUse", message "Address already in use (os error 48)".
|
|
27
|
+
// Node: code is "EADDRINUSE".
|
|
28
|
+
const code = error.code ?? "";
|
|
29
|
+
const message = error.message.toLowerCase();
|
|
30
|
+
return error.name === "AddrInUse" || code === "EADDRINUSE" ||
|
|
31
|
+
message.includes("eaddrinuse") || message.includes("address already in use");
|
|
32
|
+
}
|
|
33
|
+
/** Binds `port` and releases it again, to see whether the dev server could have it. */
|
|
34
|
+
export async function isPortAvailable(port) {
|
|
35
|
+
if (isDeno) {
|
|
36
|
+
try {
|
|
37
|
+
// @ts-ignore - Deno global
|
|
38
|
+
dntShim.Deno.listen({ hostname: LOCALHOST.IPV4, port }).close();
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (isPortInUseError(error))
|
|
43
|
+
return false;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const net = await import("node:net");
|
|
48
|
+
return await new Promise((resolve, reject) => {
|
|
49
|
+
const server = net.createServer();
|
|
50
|
+
server.unref?.();
|
|
51
|
+
server.once("error", (error) => {
|
|
52
|
+
if (isPortInUseError(error))
|
|
53
|
+
resolve(false);
|
|
54
|
+
else
|
|
55
|
+
reject(error);
|
|
56
|
+
});
|
|
57
|
+
server.listen({ port, host: LOCALHOST.IPV4, exclusive: true }, () => {
|
|
58
|
+
server.close(() => resolve(true));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Returns `requestedPort`, or the first free port after it.
|
|
64
|
+
*
|
|
65
|
+
* The scan stops at `MAX_TCP_PORT`: a runtime rejects port 65536 as an invalid
|
|
66
|
+
* port rather than as an address in use, so scanning past the end of the range
|
|
67
|
+
* would let that raw error escape instead of the `PORT_IN_USE` below.
|
|
68
|
+
* `requestedPort` itself is always probed, so an out-of-range `--port` still
|
|
69
|
+
* surfaces the runtime's own complaint about the value the user passed.
|
|
70
|
+
*
|
|
71
|
+
* Throws `PORT_IN_USE` - whose suggestion names `--port` - once the whole scan
|
|
72
|
+
* range is taken.
|
|
73
|
+
*/
|
|
74
|
+
export async function findAvailablePort(requestedPort, maxAttempts = MAX_PORT_FALLBACK_ATTEMPTS, probe = isPortAvailable) {
|
|
75
|
+
const attempts = Math.max(1, maxAttempts);
|
|
76
|
+
let lastPort = requestedPort;
|
|
77
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
78
|
+
const port = requestedPort + attempt;
|
|
79
|
+
if (attempt > 0 && port > MAX_TCP_PORT)
|
|
80
|
+
break;
|
|
81
|
+
if (await probe(port))
|
|
82
|
+
return port;
|
|
83
|
+
lastPort = port;
|
|
84
|
+
}
|
|
85
|
+
throw PORT_IN_USE.create({
|
|
86
|
+
detail: `Ports ${requestedPort}-${lastPort} are all in use`,
|
|
87
|
+
context: { requestedPort, lastPort, attempts },
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-checks.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/doctor/ai-checks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"ai-checks.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/doctor/ai-checks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAuDnD;;GAEG;AACH,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA6EnF"}
|