@timber-js/app 0.2.0-alpha.96 → 0.2.0-alpha.98
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/dist/_chunks/{metadata-routes-DS3eKNmf.js → metadata-routes-BU684ls2.js} +1 -1
- package/dist/_chunks/{metadata-routes-DS3eKNmf.js.map → metadata-routes-BU684ls2.js.map} +1 -1
- package/dist/_chunks/segment-classify-BjfuctV2.js +137 -0
- package/dist/_chunks/segment-classify-BjfuctV2.js.map +1 -0
- package/dist/_chunks/{interception-BsLCA9gk.js → walkers-VOXgavMF.js} +66 -92
- package/dist/_chunks/walkers-VOXgavMF.js.map +1 -0
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +55 -5
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +189 -62
- package/dist/index.js.map +1 -1
- package/dist/plugins/build-report.d.ts +6 -4
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/dev-404-page.d.ts +8 -18
- package/dist/plugins/dev-404-page.d.ts.map +1 -1
- package/dist/routing/index.d.ts +5 -3
- package/dist/routing/index.d.ts.map +1 -1
- package/dist/routing/index.js +3 -3
- package/dist/routing/link-codegen.d.ts.map +1 -1
- package/dist/routing/scanner.d.ts +1 -10
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/segment-classify.d.ts +37 -8
- package/dist/routing/segment-classify.d.ts.map +1 -1
- package/dist/routing/types.d.ts +63 -23
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/routing/walkers.d.ts +51 -0
- package/dist/routing/walkers.d.ts.map +1 -0
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/dev-holding-server.d.ts +4 -2
- package/dist/server/dev-holding-server.d.ts.map +1 -1
- package/dist/server/html-injector-core.d.ts +212 -0
- package/dist/server/html-injector-core.d.ts.map +1 -0
- package/dist/server/html-injectors.d.ts +59 -59
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/internal.js +710 -563
- package/dist/server/internal.js.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +46 -49
- package/dist/server/node-stream-transforms.d.ts.map +1 -1
- package/dist/server/pipeline-helpers.d.ts +88 -0
- package/dist/server/pipeline-helpers.d.ts.map +1 -0
- package/dist/server/pipeline-phases.d.ts +97 -0
- package/dist/server/pipeline-phases.d.ts.map +1 -0
- package/dist/server/pipeline.d.ts +53 -32
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/port-resolution.d.ts +117 -0
- package/dist/server/port-resolution.d.ts.map +1 -0
- package/dist/server/route-matcher.d.ts +20 -47
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +74 -0
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -0
- package/dist/server/status-code-resolver.d.ts +16 -11
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/utils/directive-parser.d.ts +0 -45
- package/dist/utils/directive-parser.d.ts.map +1 -1
- package/package.json +7 -6
- package/src/adapters/nitro.ts +55 -5
- package/src/cli.ts +0 -0
- package/src/index.ts +84 -31
- package/src/plugins/build-report.ts +13 -22
- package/src/plugins/dev-404-page.ts +15 -41
- package/src/plugins/routing.ts +14 -12
- package/src/routing/codegen.ts +1 -1
- package/src/routing/convention-lint.ts +4 -4
- package/src/routing/index.ts +5 -3
- package/src/routing/interception.ts +1 -1
- package/src/routing/link-codegen.ts +25 -13
- package/src/routing/scanner.ts +17 -93
- package/src/routing/segment-classify.ts +107 -8
- package/src/routing/status-file-lint.ts +3 -3
- package/src/routing/types.ts +63 -23
- package/src/routing/walkers.ts +90 -0
- package/src/server/action-handler.ts +6 -0
- package/src/server/deny-renderer.ts +5 -5
- package/src/server/dev-holding-server.ts +4 -2
- package/src/server/fallback-error.ts +1 -1
- package/src/server/html-injector-core.ts +403 -0
- package/src/server/html-injectors.ts +158 -297
- package/src/server/node-stream-transforms.ts +108 -248
- package/src/server/pipeline-helpers.ts +180 -0
- package/src/server/pipeline-phases.ts +591 -0
- package/src/server/pipeline.ts +76 -539
- package/src/server/port-resolution.ts +215 -0
- package/src/server/route-element-builder.ts +1 -1
- package/src/server/route-matcher.ts +28 -60
- package/src/server/rsc-entry/api-handler.ts +2 -2
- package/src/server/rsc-entry/error-renderer.ts +1 -1
- package/src/server/rsc-entry/index.ts +52 -98
- package/src/server/rsc-entry/wrap-action-dispatch.ts +156 -0
- package/src/server/sitemap-generator.ts +1 -1
- package/src/server/slot-resolver.ts +1 -1
- package/src/server/status-code-resolver.ts +112 -128
- package/src/server/tree-builder.ts +6 -4
- package/src/utils/directive-parser.ts +0 -392
- package/LICENSE +0 -8
- package/dist/_chunks/interception-BsLCA9gk.js.map +0 -1
- package/dist/_chunks/segment-classify-BDNn6EzD.js +0 -65
- package/dist/_chunks/segment-classify-BDNn6EzD.js.map +0 -1
- package/dist/server/manifest-status-resolver.d.ts +0 -58
- package/dist/server/manifest-status-resolver.d.ts.map +0 -1
- package/src/server/manifest-status-resolver.ts +0 -215
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM, r as __exportAll, t as __commonJSMin } from "./_chunks/chunk-BYIpzuS7.js";
|
|
2
2
|
import { n as setViteServer } from "./_chunks/dev-warnings-DpGRGoDi.js";
|
|
3
|
-
import { i as scanRoutes, n as generateRouteMap, t as
|
|
3
|
+
import { i as scanRoutes, n as collectInterceptionRewrites, r as generateRouteMap, t as collectLeafRoutes } from "./_chunks/walkers-VOXgavMF.js";
|
|
4
4
|
import { t as formatSize } from "./_chunks/format-CYBGxKtc.js";
|
|
5
5
|
import { dirname, extname, join, normalize, resolve } from "node:path";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
@@ -12392,10 +12392,6 @@ var require_acorn = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
12392
12392
|
* avoiding false positives from regex matching inside string literals,
|
|
12393
12393
|
* comments, or template expressions.
|
|
12394
12394
|
*
|
|
12395
|
-
* The function-body directive detection (findFunctionsWithDirective) is a
|
|
12396
|
-
* general-purpose utility kept for future use. Custom directives like
|
|
12397
|
-
* 'use cache' are not currently implemented — see design/06-caching.md.
|
|
12398
|
-
*
|
|
12399
12395
|
* @module
|
|
12400
12396
|
*/
|
|
12401
12397
|
var import_acorn_jsx = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
@@ -12850,10 +12846,10 @@ function lintStatusFileDirectives(tree) {
|
|
|
12850
12846
|
}
|
|
12851
12847
|
function walkNode(node, warnings) {
|
|
12852
12848
|
if (node.error) checkFile(node.error.filePath, node.error.extension, "error", warnings);
|
|
12853
|
-
if (node.statusFiles) for (const [code, file] of node.statusFiles) checkFile(file.filePath, file.extension, code, warnings);
|
|
12854
|
-
if (node.legacyStatusFiles) for (const [name, file] of node.legacyStatusFiles) checkFile(file.filePath, file.extension, name, warnings);
|
|
12849
|
+
if (node.statusFiles) for (const [code, file] of Object.entries(node.statusFiles)) checkFile(file.filePath, file.extension, code, warnings);
|
|
12850
|
+
if (node.legacyStatusFiles) for (const [name, file] of Object.entries(node.legacyStatusFiles)) checkFile(file.filePath, file.extension, name, warnings);
|
|
12855
12851
|
for (const child of node.children) walkNode(child, warnings);
|
|
12856
|
-
for (const
|
|
12852
|
+
for (const slotNode of Object.values(node.slots)) walkNode(slotNode, warnings);
|
|
12857
12853
|
}
|
|
12858
12854
|
function checkFile(filePath, extension, fileType, warnings) {
|
|
12859
12855
|
if (!CLIENT_REQUIRED_EXTENSIONS.has(extension)) return;
|
|
@@ -12935,7 +12931,7 @@ function checkEmptyApp(root, appDir, warnings) {
|
|
|
12935
12931
|
function hasAnyRoutable(node) {
|
|
12936
12932
|
if (node.page || node.route) return true;
|
|
12937
12933
|
for (const child of node.children) if (hasAnyRoutable(child)) return true;
|
|
12938
|
-
for (const
|
|
12934
|
+
for (const slot of Object.values(node.slots)) if (hasAnyRoutable(slot)) return true;
|
|
12939
12935
|
return false;
|
|
12940
12936
|
}
|
|
12941
12937
|
/**
|
|
@@ -12946,7 +12942,7 @@ function hasAnyRoutable(node) {
|
|
|
12946
12942
|
function hasAnyPage(node) {
|
|
12947
12943
|
if (node.page) return true;
|
|
12948
12944
|
for (const child of node.children) if (hasAnyPage(child)) return true;
|
|
12949
|
-
for (const
|
|
12945
|
+
for (const slot of Object.values(node.slots)) if (hasAnyPage(slot)) return true;
|
|
12950
12946
|
return false;
|
|
12951
12947
|
}
|
|
12952
12948
|
/** HTTP methods that route.ts can export. */
|
|
@@ -12995,7 +12991,7 @@ function checkRouteExports(node, warnings) {
|
|
|
12995
12991
|
} catch {}
|
|
12996
12992
|
}
|
|
12997
12993
|
for (const child of node.children) checkRouteExports(child, warnings);
|
|
12998
|
-
for (const
|
|
12994
|
+
for (const slot of Object.values(node.slots)) checkRouteExports(slot, warnings);
|
|
12999
12995
|
}
|
|
13000
12996
|
/**
|
|
13001
12997
|
* Warn when the root segment has no layout.tsx.
|
|
@@ -13036,7 +13032,7 @@ function checkDefaultExports(node, warnings) {
|
|
|
13036
13032
|
if (node.page && isScriptExtension(node.page.extension)) checkFileDefaultExport(node.page.filePath, "page", warnings);
|
|
13037
13033
|
if (node.layout && isScriptExtension(node.layout.extension)) checkFileDefaultExport(node.layout.filePath, "layout", warnings);
|
|
13038
13034
|
for (const child of node.children) checkDefaultExports(child, warnings);
|
|
13039
|
-
for (const
|
|
13035
|
+
for (const slot of Object.values(node.slots)) checkDefaultExports(slot, warnings);
|
|
13040
13036
|
}
|
|
13041
13037
|
function isScriptExtension(ext) {
|
|
13042
13038
|
return ext === "tsx" || ext === "ts" || ext === "jsx" || ext === "js";
|
|
@@ -13169,7 +13165,7 @@ function timberRouting(ctx) {
|
|
|
13169
13165
|
segmentType: "static",
|
|
13170
13166
|
urlPath: "/",
|
|
13171
13167
|
children: [],
|
|
13172
|
-
slots:
|
|
13168
|
+
slots: {}
|
|
13173
13169
|
} };
|
|
13174
13170
|
return;
|
|
13175
13171
|
}
|
|
@@ -13293,7 +13289,7 @@ function generateSearchParamsRegistryModule(tree) {
|
|
|
13293
13289
|
});
|
|
13294
13290
|
}
|
|
13295
13291
|
for (const child of node.children) walk(child);
|
|
13296
|
-
for (const
|
|
13292
|
+
for (const slot of Object.values(node.slots)) walk(slot);
|
|
13297
13293
|
}
|
|
13298
13294
|
walk(tree.root);
|
|
13299
13295
|
entries.sort((a, b) => a.urlPath.localeCompare(b.urlPath));
|
|
@@ -13395,33 +13391,33 @@ function generateManifestModule(tree, viteRoot) {
|
|
|
13395
13391
|
const v = addImport(node.params);
|
|
13396
13392
|
parts.push(`${nextIndent}params: { load: ${v}, filePath: ${JSON.stringify(node.params.filePath)} },`);
|
|
13397
13393
|
}
|
|
13398
|
-
if (node.statusFiles && node.statusFiles.
|
|
13394
|
+
if (node.statusFiles && Object.keys(node.statusFiles).length > 0) {
|
|
13399
13395
|
const statusEntries = [];
|
|
13400
|
-
for (const [code, file] of node.statusFiles) {
|
|
13396
|
+
for (const [code, file] of Object.entries(node.statusFiles)) {
|
|
13401
13397
|
const v = addImport(file);
|
|
13402
13398
|
statusEntries.push(`${nextIndent} ${JSON.stringify(code)}: { load: ${v}, filePath: ${JSON.stringify(file.filePath)} }`);
|
|
13403
13399
|
}
|
|
13404
13400
|
parts.push(`${nextIndent}statusFiles: {\n${statusEntries.join(",\n")}\n${nextIndent}},`);
|
|
13405
13401
|
}
|
|
13406
|
-
if (node.jsonStatusFiles && node.jsonStatusFiles.
|
|
13402
|
+
if (node.jsonStatusFiles && Object.keys(node.jsonStatusFiles).length > 0) {
|
|
13407
13403
|
const jsonEntries = [];
|
|
13408
|
-
for (const [code, file] of node.jsonStatusFiles) {
|
|
13404
|
+
for (const [code, file] of Object.entries(node.jsonStatusFiles)) {
|
|
13409
13405
|
const v = addImport(file);
|
|
13410
13406
|
jsonEntries.push(`${nextIndent} ${JSON.stringify(code)}: { load: ${v}, filePath: ${JSON.stringify(file.filePath)} }`);
|
|
13411
13407
|
}
|
|
13412
13408
|
parts.push(`${nextIndent}jsonStatusFiles: {\n${jsonEntries.join(",\n")}\n${nextIndent}},`);
|
|
13413
13409
|
}
|
|
13414
|
-
if (node.legacyStatusFiles && node.legacyStatusFiles.
|
|
13410
|
+
if (node.legacyStatusFiles && Object.keys(node.legacyStatusFiles).length > 0) {
|
|
13415
13411
|
const legacyEntries = [];
|
|
13416
|
-
for (const [name, file] of node.legacyStatusFiles) {
|
|
13412
|
+
for (const [name, file] of Object.entries(node.legacyStatusFiles)) {
|
|
13417
13413
|
const v = addImport(file);
|
|
13418
13414
|
legacyEntries.push(`${nextIndent} ${JSON.stringify(name)}: { load: ${v}, filePath: ${JSON.stringify(file.filePath)} }`);
|
|
13419
13415
|
}
|
|
13420
13416
|
parts.push(`${nextIndent}legacyStatusFiles: {\n${legacyEntries.join(",\n")}\n${nextIndent}},`);
|
|
13421
13417
|
}
|
|
13422
|
-
if (node.metadataRoutes && node.metadataRoutes.
|
|
13418
|
+
if (node.metadataRoutes && Object.keys(node.metadataRoutes).length > 0) {
|
|
13423
13419
|
const metaEntries = [];
|
|
13424
|
-
for (const [name, file] of node.metadataRoutes) {
|
|
13420
|
+
for (const [name, file] of Object.entries(node.metadataRoutes)) {
|
|
13425
13421
|
const v = addImport(file);
|
|
13426
13422
|
metaEntries.push(`${nextIndent} ${JSON.stringify(name)}: { load: ${v}, filePath: ${JSON.stringify(file.filePath)} }`);
|
|
13427
13423
|
}
|
|
@@ -13431,9 +13427,13 @@ function generateManifestModule(tree, viteRoot) {
|
|
|
13431
13427
|
const childNodes = node.children.map((c) => serializeNode(c, nextIndent));
|
|
13432
13428
|
parts.push(`${nextIndent}children: [\n${childNodes.join(",\n")}\n${nextIndent}],`);
|
|
13433
13429
|
} else parts.push(`${nextIndent}children: [],`);
|
|
13434
|
-
|
|
13430
|
+
const slotKeys = Object.keys(node.slots);
|
|
13431
|
+
if (slotKeys.length > 0) {
|
|
13435
13432
|
const slotEntries = [];
|
|
13436
|
-
for (const
|
|
13433
|
+
for (const slotName of slotKeys) {
|
|
13434
|
+
const slotNode = node.slots[slotName];
|
|
13435
|
+
slotEntries.push(`${nextIndent} ${JSON.stringify(slotName)}: ${serializeNode(slotNode, nextIndent + " ")}`);
|
|
13436
|
+
}
|
|
13437
13437
|
parts.push(`${nextIndent}slots: {\n${slotEntries.join(",\n")}\n${nextIndent}},`);
|
|
13438
13438
|
} else parts.push(`${nextIndent}slots: {},`);
|
|
13439
13439
|
return `${indent}{\n${parts.join("\n")}\n${indent}}`;
|
|
@@ -16211,32 +16211,22 @@ function green(text) {
|
|
|
16211
16211
|
/**
|
|
16212
16212
|
* Walk the route tree and collect all leaf routes (pages + API endpoints).
|
|
16213
16213
|
*
|
|
16214
|
-
*
|
|
16215
|
-
*
|
|
16216
|
-
*
|
|
16214
|
+
* Wraps the shared `collectLeafRoutes` walker (TIM-848). Parallel slots
|
|
16215
|
+
* (`@artists`, `@shows`, etc.) are intentionally skipped — they render
|
|
16216
|
+
* alongside the parent page at the same URL and are not separately
|
|
16217
|
+
* URL-addressable. Their JS is captured in shared/layout chunks.
|
|
16217
16218
|
*
|
|
16218
16219
|
* After collection, entries are deduplicated by URL path so that overlapping
|
|
16219
16220
|
* route groups (e.g. `(browse)` and `(marketing)` both producing `/`) only
|
|
16220
|
-
* appear once. The entry with the
|
|
16221
|
+
* appear once. The entry with the longest segment chain (most specific
|
|
16222
|
+
* match) wins.
|
|
16221
16223
|
*/
|
|
16222
16224
|
function collectRoutes(tree) {
|
|
16223
|
-
const routes =
|
|
16224
|
-
|
|
16225
|
-
|
|
16226
|
-
|
|
16227
|
-
|
|
16228
|
-
path,
|
|
16229
|
-
segments: currentChain,
|
|
16230
|
-
entryFilePath: node.page.filePath
|
|
16231
|
-
});
|
|
16232
|
-
if (node.route) routes.push({
|
|
16233
|
-
path,
|
|
16234
|
-
segments: currentChain,
|
|
16235
|
-
entryFilePath: node.route.filePath
|
|
16236
|
-
});
|
|
16237
|
-
for (const child of node.children) walk(child, currentChain);
|
|
16238
|
-
}
|
|
16239
|
-
walk(tree.root, []);
|
|
16225
|
+
const routes = collectLeafRoutes(tree.root).map((leaf) => ({
|
|
16226
|
+
path: leaf.urlPath,
|
|
16227
|
+
segments: leaf.segments,
|
|
16228
|
+
entryFilePath: leaf.page?.filePath ?? leaf.route?.filePath ?? null
|
|
16229
|
+
}));
|
|
16240
16230
|
const seen = /* @__PURE__ */ new Map();
|
|
16241
16231
|
for (const route of routes) {
|
|
16242
16232
|
const existing = seen.get(route.path);
|
|
@@ -16645,9 +16635,11 @@ var HOLDING_PAGE_HTML = [
|
|
|
16645
16635
|
*
|
|
16646
16636
|
* Usage (inside Vite plugin):
|
|
16647
16637
|
* ```ts
|
|
16648
|
-
* // In config() hook — earliest point where port is known
|
|
16638
|
+
* // In config() hook — earliest point where port is known.
|
|
16639
|
+
* // Use bindWithBump() to honor the default-3000 + auto-bump policy
|
|
16640
|
+
* // (see ../server/port-resolution.ts and TIM-842).
|
|
16649
16641
|
* const holding = createHoldingServer();
|
|
16650
|
-
* holding.listen(
|
|
16642
|
+
* await bindWithBump((p) => holding.listen(p), { startPort: 3000, autoBump: true });
|
|
16651
16643
|
*
|
|
16652
16644
|
* // In last plugin's configureServer() — wrap listen for seamless handoff
|
|
16653
16645
|
* const originalListen = server.listen.bind(server);
|
|
@@ -16701,6 +16693,122 @@ function createHoldingServer() {
|
|
|
16701
16693
|
};
|
|
16702
16694
|
}
|
|
16703
16695
|
//#endregion
|
|
16696
|
+
//#region src/server/port-resolution.ts
|
|
16697
|
+
/**
|
|
16698
|
+
* Port resolution for the dev server, Vite preview, and the Node
|
|
16699
|
+
* production preview server.
|
|
16700
|
+
*
|
|
16701
|
+
* Behavior (see TIM-842):
|
|
16702
|
+
*
|
|
16703
|
+
* 1. Default port is **3000** for both dev and prod.
|
|
16704
|
+
* 2. If the user did NOT set an explicit port, auto-bump from 3000
|
|
16705
|
+
* until a free port is found (3000 → 3001 → 3002 → …).
|
|
16706
|
+
* 3. If the user DID set an explicit port (via `--port`, `PORT` env
|
|
16707
|
+
* var, or `vite.config.ts` `server.port`), use it as-is and let
|
|
16708
|
+
* the bind fail loudly on conflict (`strictPort: true`).
|
|
16709
|
+
*
|
|
16710
|
+
* The port-bump probe is performed by binding the **actual** server
|
|
16711
|
+
* (e.g. the dev holding server) — not a throwaway probe — so there is
|
|
16712
|
+
* no time-of-check / time-of-use race between probing and listening.
|
|
16713
|
+
*
|
|
16714
|
+
* Design doc: 21-dev-server.md §"Default Port and Auto-Bump".
|
|
16715
|
+
*/
|
|
16716
|
+
/** Default port used by `timber dev` and `timber preview`. */
|
|
16717
|
+
var DEFAULT_PORT = 3e3;
|
|
16718
|
+
/**
|
|
16719
|
+
* Pure: compute the starting port from config / env / defaults.
|
|
16720
|
+
*
|
|
16721
|
+
* Performs no I/O — pair with {@link bindWithBump} to actually listen.
|
|
16722
|
+
*/
|
|
16723
|
+
function resolveStartPort(input) {
|
|
16724
|
+
const defaultPort = input.defaultPort ?? 3e3;
|
|
16725
|
+
if (typeof input.configPort === "number" && Number.isFinite(input.configPort)) return {
|
|
16726
|
+
port: input.configPort,
|
|
16727
|
+
explicit: true
|
|
16728
|
+
};
|
|
16729
|
+
if (input.envPort != null && input.envPort !== "") {
|
|
16730
|
+
const parsed = Number(input.envPort);
|
|
16731
|
+
if (Number.isFinite(parsed) && parsed > 0) return {
|
|
16732
|
+
port: parsed,
|
|
16733
|
+
explicit: true
|
|
16734
|
+
};
|
|
16735
|
+
}
|
|
16736
|
+
return {
|
|
16737
|
+
port: defaultPort,
|
|
16738
|
+
explicit: false
|
|
16739
|
+
};
|
|
16740
|
+
}
|
|
16741
|
+
/**
|
|
16742
|
+
* Bind a server starting at `startPort`, optionally bumping the port
|
|
16743
|
+
* on `EADDRINUSE` until a free one is found.
|
|
16744
|
+
*
|
|
16745
|
+
* Use this with the actual server you intend to keep listening (e.g.
|
|
16746
|
+
* the dev holding server). Pairing the probe with the real listen
|
|
16747
|
+
* eliminates the TOCTOU race that a throwaway probe would introduce.
|
|
16748
|
+
*/
|
|
16749
|
+
async function bindWithBump(listen, options) {
|
|
16750
|
+
const maxAttempts = options.autoBump ? options.maxAttempts ?? 100 : 1;
|
|
16751
|
+
let lastErr = null;
|
|
16752
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
16753
|
+
const port = options.startPort + i;
|
|
16754
|
+
try {
|
|
16755
|
+
return {
|
|
16756
|
+
port: await listen(port),
|
|
16757
|
+
bumped: i > 0
|
|
16758
|
+
};
|
|
16759
|
+
} catch (err) {
|
|
16760
|
+
lastErr = err;
|
|
16761
|
+
if (!isAddrInUse(err)) throw err;
|
|
16762
|
+
}
|
|
16763
|
+
}
|
|
16764
|
+
throw lastErr ?? /* @__PURE__ */ new Error(`Could not bind to a free port starting at ${options.startPort}`);
|
|
16765
|
+
}
|
|
16766
|
+
/** True if `err` is a Node `EADDRINUSE` error from `server.listen()`. */
|
|
16767
|
+
function isAddrInUse(err) {
|
|
16768
|
+
return typeof err === "object" && err !== null && err.code === "EADDRINUSE";
|
|
16769
|
+
}
|
|
16770
|
+
/**
|
|
16771
|
+
* Run the full dev-server port resolution + holding-server bind sequence.
|
|
16772
|
+
*
|
|
16773
|
+
* Resolves the port from config / env / default, attempts to bind the
|
|
16774
|
+
* holding server (auto-bumping when the port came from the default),
|
|
16775
|
+
* and logs the chosen URL. On a clean failure for an explicit port, it
|
|
16776
|
+
* warns and falls back to the requested port so Vite can surface the
|
|
16777
|
+
* conflict via `strictPort: true`.
|
|
16778
|
+
*
|
|
16779
|
+
* Extracted from `index.ts` so the rootSync `config()` hook stays
|
|
16780
|
+
* focused on plugin assembly.
|
|
16781
|
+
*/
|
|
16782
|
+
async function startDevServerPort(input) {
|
|
16783
|
+
const log = input.log ?? ((msg) => console.log(msg));
|
|
16784
|
+
const warn = input.warn ?? ((msg) => console.warn(msg));
|
|
16785
|
+
const start = resolveStartPort({
|
|
16786
|
+
configPort: input.configPort,
|
|
16787
|
+
envPort: input.envPort,
|
|
16788
|
+
defaultPort: DEFAULT_PORT
|
|
16789
|
+
});
|
|
16790
|
+
try {
|
|
16791
|
+
const result = await bindWithBump(input.listen, {
|
|
16792
|
+
startPort: start.port,
|
|
16793
|
+
autoBump: !start.explicit
|
|
16794
|
+
});
|
|
16795
|
+
if (result.bumped) log(`\n \x1b[33m[timber]\x1b[0m Port ${start.port} in use, using ${result.port}\n`);
|
|
16796
|
+
log(`\n \x1b[2m\u{1FAB5} timber.js dev server starting at\x1b[0m \x1b[36mhttp://localhost:${result.port}\x1b[0m\n`);
|
|
16797
|
+
return {
|
|
16798
|
+
port: result.port,
|
|
16799
|
+
explicit: start.explicit,
|
|
16800
|
+
bound: true
|
|
16801
|
+
};
|
|
16802
|
+
} catch (err) {
|
|
16803
|
+
if (start.explicit && isAddrInUse(err)) warn(`\n \x1b[33m[timber]\x1b[0m Port ${start.port} is already in use. Set PORT (or remove the override) to pick another port.\n`);
|
|
16804
|
+
return {
|
|
16805
|
+
port: start.port,
|
|
16806
|
+
explicit: start.explicit,
|
|
16807
|
+
bound: false
|
|
16808
|
+
};
|
|
16809
|
+
}
|
|
16810
|
+
}
|
|
16811
|
+
//#endregion
|
|
16704
16812
|
//#region src/plugin-context.ts
|
|
16705
16813
|
/**
|
|
16706
16814
|
* Plugin context — internal types and helpers for timber sub-plugins.
|
|
@@ -16942,7 +17050,7 @@ function timber(config) {
|
|
|
16942
17050
|
const earlyFileConfig = loadTimberConfigFile(process.cwd());
|
|
16943
17051
|
const rootSync = {
|
|
16944
17052
|
name: "timber-root-sync",
|
|
16945
|
-
config(userConfig, { command }) {
|
|
17053
|
+
async config(userConfig, { command, isPreview }) {
|
|
16946
17054
|
const viteRoot = resolve(userConfig.root ?? process.cwd());
|
|
16947
17055
|
ctx.timer.start("config-load");
|
|
16948
17056
|
const fileConfig = loadTimberConfigFile(viteRoot);
|
|
@@ -16960,19 +17068,26 @@ function timber(config) {
|
|
|
16960
17068
|
const hadCompilerInEarly = !config?.reactCompiler && earlyFileConfig?.reactCompiler;
|
|
16961
17069
|
if (hasCompilerInReloaded && !hadCompilerInEarly) console.warn("[timber] reactCompiler is set in timber.config.ts but could not be registered because the config file is in a non-cwd root directory. Move reactCompiler to the inline timber() config in vite.config.ts, or run vite from the project root directory.");
|
|
16962
17070
|
}
|
|
16963
|
-
|
|
16964
|
-
|
|
16965
|
-
|
|
16966
|
-
|
|
16967
|
-
|
|
16968
|
-
|
|
16969
|
-
|
|
16970
|
-
|
|
16971
|
-
|
|
16972
|
-
|
|
16973
|
-
|
|
16974
|
-
|
|
16975
|
-
|
|
17071
|
+
let resolvedDevPort = null;
|
|
17072
|
+
let resolvedDevPortExplicit = false;
|
|
17073
|
+
if (command === "serve" && !isPreview) {
|
|
17074
|
+
ctx.holdingServer = createHoldingServer();
|
|
17075
|
+
const holdingRef = ctx.holdingServer;
|
|
17076
|
+
const result = await startDevServerPort({
|
|
17077
|
+
configPort: typeof userConfig.server?.port === "number" ? userConfig.server.port : void 0,
|
|
17078
|
+
envPort: process.env.PORT,
|
|
17079
|
+
listen: (p) => holdingRef.listen(p)
|
|
17080
|
+
});
|
|
17081
|
+
resolvedDevPort = result.port;
|
|
17082
|
+
resolvedDevPortExplicit = result.explicit;
|
|
17083
|
+
if (!result.bound) ctx.holdingServer = null;
|
|
17084
|
+
} else if (command === "serve" && isPreview) {
|
|
17085
|
+
const start = resolveStartPort({
|
|
17086
|
+
configPort: typeof userConfig.preview?.port === "number" ? userConfig.preview.port : typeof userConfig.server?.port === "number" ? userConfig.server.port : void 0,
|
|
17087
|
+
envPort: process.env.PORT
|
|
17088
|
+
});
|
|
17089
|
+
resolvedDevPort = start.port;
|
|
17090
|
+
resolvedDevPortExplicit = start.explicit;
|
|
16976
17091
|
}
|
|
16977
17092
|
const buildOutDir = timberBuildDir ? timberBuildDir : viteOutDir && viteOutDir !== "dist" ? viteOutDir : DEFAULT_BUILD_DIR;
|
|
16978
17093
|
const envOutDirs = {};
|
|
@@ -16986,9 +17101,21 @@ function timber(config) {
|
|
|
16986
17101
|
environments: envOutDirs,
|
|
16987
17102
|
oxc: { jsx: { development: false } }
|
|
16988
17103
|
};
|
|
17104
|
+
const serverConfig = {};
|
|
17105
|
+
const previewConfig = {};
|
|
17106
|
+
if (resolvedDevPort != null) {
|
|
17107
|
+
if (!isPreview) {
|
|
17108
|
+
serverConfig.port = resolvedDevPort;
|
|
17109
|
+
serverConfig.strictPort = true;
|
|
17110
|
+
}
|
|
17111
|
+
previewConfig.port = resolvedDevPort;
|
|
17112
|
+
previewConfig.strictPort = resolvedDevPortExplicit;
|
|
17113
|
+
}
|
|
16989
17114
|
return {
|
|
16990
17115
|
build: { outDir: buildOutDir },
|
|
16991
|
-
environments: envOutDirs
|
|
17116
|
+
environments: envOutDirs,
|
|
17117
|
+
server: serverConfig,
|
|
17118
|
+
preview: previewConfig
|
|
16992
17119
|
};
|
|
16993
17120
|
},
|
|
16994
17121
|
configResolved(resolved) {
|