@astroscope/node 1.2.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +21 -0
  2. package/dist/boot.d.ts +0 -1
  3. package/dist/boot.d.ts.map +1 -1
  4. package/dist/{construct-DgB-jR0a.d.ts → construct-BGlPfWMF.d.ts} +1 -2
  5. package/dist/construct-BGlPfWMF.d.ts.map +1 -0
  6. package/dist/csrf-middleware-entrypoint.d.ts.map +1 -1
  7. package/dist/dev-middleware-entrypoint.d.ts +0 -1
  8. package/dist/dev-middleware-entrypoint.d.ts.map +1 -1
  9. package/dist/{events-CUzQ2_cp.d.ts → events-u7J3ezJR.d.ts} +1 -2
  10. package/dist/events-u7J3ezJR.d.ts.map +1 -0
  11. package/dist/{excludes-DLF3A_Cf.d.ts → excludes-BDiE3eyp.d.ts} +1 -2
  12. package/dist/excludes-BDiE3eyp.d.ts.map +1 -0
  13. package/dist/excludes.d.ts +1 -1
  14. package/dist/health.d.ts.map +1 -1
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +170 -2
  18. package/dist/index.js.map +1 -1
  19. package/dist/lifecycle/events.d.ts +1 -1
  20. package/dist/log/index.d.ts +33 -3
  21. package/dist/log/index.d.ts.map +1 -1
  22. package/dist/log/index.js +3 -2
  23. package/dist/{log-CSJlKaxY.js → log-B69HEBvg.js} +2 -2
  24. package/dist/{log-CSJlKaxY.js.map → log-B69HEBvg.js.map} +1 -1
  25. package/dist/{native-mount-hhwWdLtL.js → native-mount-DjYEnO4X.js} +4 -13
  26. package/dist/native-mount-DjYEnO4X.js.map +1 -0
  27. package/dist/native.d.ts +0 -1
  28. package/dist/native.d.ts.map +1 -1
  29. package/dist/native.js +1 -1
  30. package/dist/{prepare-DQEf2Bnt.js → prepare-CXZsyAVk.js} +6 -4
  31. package/dist/prepare-CXZsyAVk.js.map +1 -0
  32. package/dist/preview.d.ts +0 -1
  33. package/dist/preview.d.ts.map +1 -1
  34. package/dist/request-route-DcnZOOM4.js +92 -0
  35. package/dist/request-route-DcnZOOM4.js.map +1 -0
  36. package/dist/route-middleware-entrypoint.d.ts +3 -2
  37. package/dist/route-middleware-entrypoint.d.ts.map +1 -1
  38. package/dist/route-middleware-entrypoint.js +5 -11
  39. package/dist/route-middleware-entrypoint.js.map +1 -1
  40. package/dist/server.d.ts.map +1 -1
  41. package/dist/server.js +23 -8
  42. package/dist/server.js.map +1 -1
  43. package/dist/types-D0uMBi2M.d.ts.map +1 -1
  44. package/package.json +8 -7
  45. package/dist/construct-DgB-jR0a.d.ts.map +0 -1
  46. package/dist/events-CUzQ2_cp.d.ts.map +0 -1
  47. package/dist/excludes-DLF3A_Cf.d.ts.map +0 -1
  48. package/dist/native-mount-hhwWdLtL.js.map +0 -1
  49. package/dist/prepare-DQEf2Bnt.js.map +0 -1
  50. package/dist/store-BIUF4lqk.js +0 -29
  51. package/dist/store-BIUF4lqk.js.map +0 -1
package/README.md CHANGED
@@ -15,6 +15,7 @@ Opinionated, cloud-friendly Node adapter for Astro: boot lifecycle, health probe
15
15
  - **Native mounts** — http-native handlers (`oidc-provider`, ACME) mounted on the adapter's server
16
16
  - **Build tweaks** — SSR sourcemaps, SSR effect stripping
17
17
  - **Dev restart machinery** — changes to the boot file or entry seams restart the dev server behind a holding page
18
+ - **Dev island warmup** — `.astro` sources are scanned for `client:*` components; their deps are pre-optimized and their module graphs warmed at server start, preventing "504 Outdated Optimize Dep" hydration failures from vite's lazy dep discovery
18
19
  - **HTTPS for development** — `SERVER_CERT_PATH` / `SERVER_KEY_PATH` serve TLS directly for local runs of the built server ([HTTPS](#https)); in production, terminate TLS at the ingress
19
20
 
20
21
  ## What it does NOT do — beware
@@ -144,6 +145,26 @@ Entries logged before the logger is constructed (env loading, config, instrument
144
145
 
145
146
  Request logging happens at the native handler on `finish`/`close`: real status code, response size, `ttfb`, aborted-vs-completed, and the route pattern (fed back by an internal astro middleware). An incoming `x-request-id` header is passed through (and echoed on the response); otherwise a short id is generated.
146
147
 
148
+ ### Reporting the route yourself
149
+
150
+ The route comes from the route astro matched, which is wrong for a middleware that serves a request astro has no page for — one that rewrites via `next(url)`, or answers itself. Astro matches `/404` there, so the request is logged as `/404` despite its `200`, and every such request collapses into one `/404` bucket in the request duration metric. `overrideRequestRoute` lets the middleware report what it actually served:
151
+
152
+ ```typescript
153
+ import { overrideRequestRoute } from '@astroscope/node/log';
154
+
155
+ export const onRequest: MiddlewareHandler = (ctx, next) => {
156
+ const page = lookupPage(ctx.url.pathname);
157
+
158
+ if (!page) return next();
159
+
160
+ overrideRequestRoute('/cms/pages/[id]');
161
+
162
+ return next(`/cms/pages/${page.id}`);
163
+ };
164
+ ```
165
+
166
+ This fixes the log line, the metric and the server span name together. Pass a templated label, not a concrete path, so metric cardinality stays bounded. An overridden route always wins over the one astro matched, regardless of middleware order.
167
+
147
168
  The adapter itself emits exactly one info line on startup — `server ready { host, port, health, bootMs, warmupMs, totalMs }` — and `draining` / `shutdown complete { drainMs }` on the way down. Everything else is debug or error level.
148
169
 
149
170
  ## Telemetry
package/dist/boot.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { t as BootContext } from "./types-D0uMBi2M.js";
2
-
3
2
  //#region src/lifecycle/context.d.ts
4
3
  /**
5
4
  * The boot context of the server running in this process, or `undefined` when
@@ -1 +1 @@
1
- {"version":3,"file":"boot.d.ts","names":[],"sources":["../src/lifecycle/context.ts"],"mappings":";;;;AAiBA;;;;AAA6C;;;iBAA7B,cAAA,IAAkB,WAAW"}
1
+ {"version":3,"file":"boot.d.ts","names":[],"sources":["../src/lifecycle/context.ts"],"mappings":";;;;;;;;;;iBAiBgB,kBAAkB"}
@@ -1,5 +1,4 @@
1
1
  import { Logger, LoggerOptions } from "pino";
2
-
3
2
  //#region src/observability/log/construct.d.ts
4
3
  /**
5
4
  * Contract of the `src/log.ts` entry seam: pino logger options, or a factory
@@ -11,4 +10,4 @@ type LoggerOptionsFactory = LoggerOptions | ((ctx: {
11
10
  }) => LoggerOptions | Promise<LoggerOptions>);
12
11
  //#endregion
13
12
  export { LoggerOptionsFactory as t };
14
- //# sourceMappingURL=construct-DgB-jR0a.d.ts.map
13
+ //# sourceMappingURL=construct-BGlPfWMF.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"construct-BGlPfWMF.d.ts","names":[],"sources":["../src/observability/log/construct.ts"],"mappings":";;;;;;;KASY,uBAAuB,kBAAkB;EAAO;MAAmB,gBAAgB,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"csrf-middleware-entrypoint.d.ts","names":[],"sources":["../src/csrf/middleware-entrypoint.ts"],"mappings":";cAKa,SAAA,kBAAS,iBAA4D"}
1
+ {"version":3,"file":"csrf-middleware-entrypoint.d.ts","names":[],"sources":["../src/csrf/middleware-entrypoint.ts"],"mappings":";cAKa,2BAAS"}
@@ -1,5 +1,4 @@
1
1
  import { MiddlewareHandler } from "astro";
2
-
3
2
  //#region src/dev-mode/middleware-entrypoint.d.ts
4
3
  /**
5
4
  * Wrap every request so errors thrown by a stale render (one whose user
@@ -1 +1 @@
1
- {"version":3,"file":"dev-middleware-entrypoint.d.ts","names":[],"sources":["../src/dev-mode/middleware-entrypoint.ts"],"mappings":";;;;;AAWA;;;;AAcC;;cAdY,SAAA,EAAW,iBAcvB"}
1
+ {"version":3,"file":"dev-middleware-entrypoint.d.ts","names":[],"sources":["../src/dev-mode/middleware-entrypoint.ts"],"mappings":";;;;;;;;;;cAWa,WAAW"}
@@ -1,5 +1,4 @@
1
1
  import { t as BootContext } from "./types-D0uMBi2M.js";
2
-
3
2
  //#region src/lifecycle/events.d.ts
4
3
  type BootEventName = 'beforeOnStartup' | 'afterOnStartup' | 'beforeOnShutdown' | 'afterOnShutdown';
5
4
  type BootEventHandler = (context: BootContext) => Promise<void> | void;
@@ -17,4 +16,4 @@ declare function off(event: BootEventName, handler: BootEventHandler): void;
17
16
  declare function emit(event: BootEventName, context: BootContext): Promise<void>;
18
17
  //#endregion
19
18
  export { on as a, off as i, BootEventName as n, emit as r, BootEventHandler as t };
20
- //# sourceMappingURL=events-CUzQ2_cp.d.ts.map
19
+ //# sourceMappingURL=events-u7J3ezJR.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events-u7J3ezJR.d.ts","names":[],"sources":["../src/lifecycle/events.ts"],"mappings":";;KAEY;KAEA,oBAAoB,SAAS,gBAAgB;;;;iBAuBzC,GAAG,OAAO,eAAe,SAAS;;;;iBAelC,IAAI,OAAO,eAAe,SAAS;;;;iBAS7B,KAAK,OAAO,eAAe,SAAS,cAAc"}
@@ -1,6 +1,5 @@
1
1
  import { StringPattern } from "@entwico/dash/match";
2
2
  import { APIContext, MiddlewareHandler } from "astro";
3
-
4
3
  //#region src/excludes/excludes.d.ts
5
4
  /**
6
5
  * Patterns accepted by @astroscope/node exclude options. The serializable
@@ -60,4 +59,4 @@ declare function shouldExclude(ctx: APIContext, exclude: readonly StringPattern[
60
59
  declare function withExcluded(middleware: MiddlewareHandler, exclude: readonly StringPattern[] | ((context: APIContext) => boolean)): MiddlewareHandler;
61
60
  //#endregion
62
61
  export { STATIC_EXCLUDES as a, RECOMMENDED_EXCLUDES as i, DEV_EXCLUDES as n, shouldExclude as o, ExcludePattern as r, withExcluded as s, ASTRO_STATIC_EXCLUDES as t };
63
- //# sourceMappingURL=excludes-DLF3A_Cf.d.ts.map
62
+ //# sourceMappingURL=excludes-BDiE3eyp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excludes-BDiE3eyp.d.ts","names":[],"sources":["../src/excludes/excludes.ts"],"mappings":";;;;;;;;KAQY,iBAAiB,QAAQ,gBAAgB;;;;cAKxC,cAAc;;;;cAWd,uBAAuB;;;;cAKvB,iBAAiB;;;;;;;;;;;;;;;;cAwBjB,sBAAsB;;;;iBAKnB,cACd,KAAK,YACL,kBAAkB,oBAAoB,SAAS;;;;;;;;;;;;;;;;;;iBA4BjC,aACd,YAAY,mBACZ,kBAAkB,oBAAoB,SAAS,0BAC9C"}
@@ -1,2 +1,2 @@
1
- import { a as STATIC_EXCLUDES, i as RECOMMENDED_EXCLUDES, n as DEV_EXCLUDES, o as shouldExclude, r as ExcludePattern, s as withExcluded, t as ASTRO_STATIC_EXCLUDES } from "./excludes-DLF3A_Cf.js";
1
+ import { a as STATIC_EXCLUDES, i as RECOMMENDED_EXCLUDES, n as DEV_EXCLUDES, o as shouldExclude, r as ExcludePattern, s as withExcluded, t as ASTRO_STATIC_EXCLUDES } from "./excludes-BDiE3eyp.js";
2
2
  export { ASTRO_STATIC_EXCLUDES, DEV_EXCLUDES, ExcludePattern, RECOMMENDED_EXCLUDES, STATIC_EXCLUDES, shouldExclude, withExcluded };
@@ -1 +1 @@
1
- {"version":3,"file":"health.d.ts","names":[],"sources":["../src/health/health.ts"],"mappings":";UAEiB,iBAAA;EACf,MAAA;EACA,OAAA;EACA,KAAA;AAAA;AAAA,UAGe,qBAAA;EAJf;;;EAQA,IAAA;EAJe;;;;EAUf,KAAA,QAAa,OAAA,CAAQ,iBAAA,WAA4B,iBAAA;EAAA;;;;EAMjD,QAAA;EANa;;;;EAYb,OAAA;AAAA;AAAO;AAUT;;;;AAAqE;;AAV5D,iBAUO,mBAAA,CAAoB,UAAiC,EAArB,qBAAqB"}
1
+ {"version":3,"file":"health.d.ts","names":[],"sources":["../src/health/health.ts"],"mappings":";UAEiB;EACf;EACA;EACA;;UAGe;;;;EAIf;;;;;EAMA,aAAa,QAAQ,4BAA4B;;;;;EAMjD;;;;;EAMA;;;;;;;;;iBAUc,oBAAoB,YAAY"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { r as ExcludePattern } from "./excludes-DLF3A_Cf.js";
1
+ import { r as ExcludePattern } from "./excludes-BDiE3eyp.js";
2
+ import "./construct-BGlPfWMF.js";
2
3
  import { t as BootContext } from "./types-D0uMBi2M.js";
3
- import { n as BootEventName, t as BootEventHandler } from "./events-CUzQ2_cp.js";
4
+ import { n as BootEventName, t as BootEventHandler } from "./events-u7J3ezJR.js";
4
5
  import { AstroIntegration } from "astro";
5
-
6
6
  //#region src/types.d.ts
7
7
  interface NodeBootOptions {
8
8
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/integration/integration.ts","../src/platform/prepare.ts","../src/lifecycle/lifecycle.ts"],"mappings":";;;;;;UAEiB,eAAA;;;;;EAKf,KAAA;EALe;;;;AAYV;EAAL,KAAK;AAAA;;;;UAMU,gBAAA;EACf,IAAA;EACA,OAAA;EACA,KAAA;EACA,MAAA;AAAA;AAAA,UAGe,iBAAA;;;;;EAKf,IAAA;EAYQ;;AAAgB;AAG1B;EATE,IAAA;;;;;EAMA,KAAA,GAAQ,gBAAgB;AAAA;AAAA,UAGT,kBAAA;EAmBZ;AAGL;;;EAjBE,OAAA,GAAU,cAAc;EA4BpB;AAGN;;;;EAxBE,QAAA;EA6BU;;;;;EAtBV,GAAA;AAAA;AAAA,UAGe,qBAAA;;;;;EAKf,IAAA;EAqEmB;;;;EA/DnB,IAAI;AAAA;AAAA,UAGW,oBAAA;EA6Cf;;;;EAxCA,OAAA,GAAU,cAAA;EAuDD;;;;EAjDT,UAAA,GAAa,qBAAqB;EA8DnB;;;;ACzI+D;EDkF9E,GAAA;AAAA;AAAA,UAGe,WAAA;ECnCqB;;;;AAAmC;;;ED2CvE,IAAA,GAAO,eAAA;EE1GQ;;;;AACZ;;EFiHH,OAAA,GAAU,kBAAA;;AGrHZ;;;;;EH6HE,SAAA,GAAY,oBAAA;EG3H4B;;;;;EHkIxC,MAAA,GAAS,iBAAA;EGnI8B;;;;;;EH2IvC,IAAA;IAAS,OAAA,GAAU,cAAA;EAAA;;;;;EAMnB,aAAA;;;;;;EAOA,eAAA;AAAA;;;;;;AA9IK;AAMP;;iBCiDwB,IAAA,CAAK,OAAA,GAAS,WAAA,GAAmB,gBAAgB;;;UC/DxD,sBAAA;EACf,GAAG;AAAA;;;UCJY,UAAA;EACf,SAAA,KAAc,OAAA,EAAS,WAAA,KAAgB,OAAA;EACvC,UAAA,KAAe,OAAA,EAAS,WAAA,KAAgB,OAAA;AAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/integration/integration.ts","../src/platform/prepare.ts","../src/lifecycle/lifecycle.ts"],"mappings":";;;;;;UAEiB;;;;;EAKf;;;;;;EAOA;;;;;UAMe;EACf;EACA;EACA;EACA;;UAGe;;;;;EAKf;;;;;EAMA;;;;;EAMA,QAAQ;;UAGO;;;;;EAKf,UAAU;;;;;;EAOV;;;;;;EAOA;;UAGe;;;;;EAKf;;;;;EAMA;;UAGe;;;;;EAKf,UAAU;;;;;EAMV,aAAa;;;;;;EAOb;;UAGe;;;;;;;;EAQf,OAAO;;;;;;;EAQP,UAAU;;;;;;;EAQV,YAAY;;;;;;EAOZ,SAAS;;;;;;;EAQT;IAAS,UAAU;;;;;;EAMnB;;;;;;EAOA;;;;;;;;;;iBCtFsB,KAAK,UAAS,cAAmB;;;UChExC;EACf;;;;UCJe;EACf,cAAc,SAAS,gBAAgB;EACvC,eAAe,SAAS,gBAAgB"}
package/dist/index.js CHANGED
@@ -1,13 +1,175 @@
1
1
  import { n as setBootContext } from "./context-Bkg-FnnQ.js";
2
- import { a as runShutdown, i as createRequestInstrumentation, o as runStartup, t as preparePlatform } from "./prepare-DQEf2Bnt.js";
3
- import { n as dispatchNativeMount, t as clearNativeMounts } from "./native-mount-hhwWdLtL.js";
2
+ import { a as runShutdown, i as createRequestInstrumentation, o as runStartup, t as preparePlatform } from "./prepare-CXZsyAVk.js";
3
+ import { n as dispatchNativeMount, t as clearNativeMounts } from "./native-mount-DjYEnO4X.js";
4
4
  import { n as getCurrentGeneration, r as incrementGeneration, t as GEN_HEADER } from "./generation-Bp2IA0jf.js";
5
5
  import { r as RECOMMENDED_EXCLUDES } from "./excludes-pE23EbmQ.js";
6
6
  import fs, { readFileSync } from "node:fs";
7
7
  import path from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
+ import { parse } from "@astrojs/compiler-rs";
9
10
  import { Parser } from "acorn";
10
11
  import MagicString from "magic-string";
12
+ //#region src/dev-mode/island-warmup.ts
13
+ function isAstNode(value) {
14
+ return typeof value === "object" && value !== null && typeof value.type === "string";
15
+ }
16
+ function walk$1(node, visit) {
17
+ if (Array.isArray(node)) {
18
+ for (const item of node) walk$1(item, visit);
19
+ return;
20
+ }
21
+ if (typeof node !== "object" || node === null) return;
22
+ if (isAstNode(node)) visit(node);
23
+ for (const value of Object.values(node)) walk$1(value, visit);
24
+ }
25
+ /** local binding name → import specifier, from the frontmatter program */
26
+ function collectImports(program) {
27
+ const imports = /* @__PURE__ */ new Map();
28
+ walk$1(program, (node) => {
29
+ if (node.type !== "ImportDeclaration") return;
30
+ const decl = node;
31
+ if (decl.importKind === "type") return;
32
+ const specifier = typeof decl.source?.value === "string" ? decl.source.value : void 0;
33
+ if (!specifier) return;
34
+ for (const spec of decl.specifiers ?? []) {
35
+ if (spec.importKind === "type") continue;
36
+ if (typeof spec.local?.name === "string") imports.set(spec.local.name, specifier);
37
+ }
38
+ });
39
+ return imports;
40
+ }
41
+ /** the root identifier of a JSX tag: `Foo` → Foo, `Ns.Chart` → Ns */
42
+ function tagRootIdentifier(name) {
43
+ let current = name;
44
+ while (isAstNode(current) && current.type === "JSXMemberExpression") current = current.object;
45
+ if (isAstNode(current) && current.type === "JSXIdentifier") {
46
+ const identifier = current;
47
+ return typeof identifier.name === "string" ? identifier.name : void 0;
48
+ }
49
+ }
50
+ function hasClientDirective(attributes) {
51
+ if (!Array.isArray(attributes)) return false;
52
+ return attributes.some((attr) => {
53
+ if (!isAstNode(attr) || attr.type !== "JSXAttribute") return false;
54
+ const name = attr.name;
55
+ return typeof name?.name === "string" && name.name.startsWith("client:");
56
+ });
57
+ }
58
+ /**
59
+ * Extract the import specifiers of all hydrated (`client:*`) components from
60
+ * raw `.astro` source. Astro components can't hydrate, so `.astro` specifiers
61
+ * are skipped; dynamic tags without a frontmatter import are invisible here.
62
+ */
63
+ function scanAstroSource(source) {
64
+ const { ast } = parse(source);
65
+ const root = ast;
66
+ const imports = collectImports(root.frontmatter?.program);
67
+ if (imports.size === 0) return [];
68
+ const specifiers = /* @__PURE__ */ new Set();
69
+ walk$1(root.body, (node) => {
70
+ if (node.type !== "JSXOpeningElement") return;
71
+ const element = node;
72
+ if (!hasClientDirective(element.attributes)) return;
73
+ const rootName = tagRootIdentifier(element.name);
74
+ if (!rootName || /^[a-z]/.test(rootName)) return;
75
+ const specifier = imports.get(rootName);
76
+ if (specifier && !specifier.endsWith(".astro")) specifiers.add(specifier);
77
+ });
78
+ return [...specifiers];
79
+ }
80
+ /** scan all `.astro` files under `srcDir` for hydrated components */
81
+ async function scanProjectIslands(srcDir, logger) {
82
+ const islands = [];
83
+ let entries;
84
+ try {
85
+ entries = await fs.promises.readdir(srcDir, {
86
+ recursive: true,
87
+ withFileTypes: true
88
+ });
89
+ } catch {
90
+ return islands;
91
+ }
92
+ const files = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".astro")).map((entry) => path.join(entry.parentPath, entry.name));
93
+ await Promise.all(files.map(async (file) => {
94
+ try {
95
+ const source = await fs.promises.readFile(file, "utf8");
96
+ for (const specifier of scanAstroSource(source)) islands.push({
97
+ importer: file,
98
+ specifier
99
+ });
100
+ } catch (error) {
101
+ logger.debug(`island scan skipped ${file}: ${error instanceof Error ? error.message : String(error)}`);
102
+ }
103
+ }));
104
+ return islands;
105
+ }
106
+ function packageNameOf(specifier) {
107
+ const segments = specifier.split("/");
108
+ return specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0] ?? specifier;
109
+ }
110
+ /**
111
+ * Bare package specifiers resolvable from the project's `node_modules` — these
112
+ * go into `optimizeDeps.include`. Anything else (relative paths, tsconfig
113
+ * aliases, hoisted workspace deps) is resolved through vite at server start;
114
+ * misclassification is harmless, just slightly later discovery.
115
+ */
116
+ function selectBareSpecifiers(islands, root) {
117
+ const bare = /* @__PURE__ */ new Set();
118
+ for (const { specifier } of islands) {
119
+ if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("#")) continue;
120
+ if (fs.existsSync(path.join(root, "node_modules", packageNameOf(specifier)))) bare.add(specifier);
121
+ }
122
+ return [...bare];
123
+ }
124
+ function toRequestUrl(id, root) {
125
+ const normalized = id.split(path.sep).join("/");
126
+ const normalizedRoot = root.split(path.sep).join("/");
127
+ return normalized.startsWith(`${normalizedRoot}/`) ? normalized.slice(normalizedRoot.length) : `/@fs/${normalized}`;
128
+ }
129
+ function createIslandWarmup(options) {
130
+ const { root, srcDir, logger } = options;
131
+ let islands = [];
132
+ const warmIslands = async (server, batch, warmed) => {
133
+ const env = server.environments.client;
134
+ await Promise.all(batch.map(async ({ importer, specifier }) => {
135
+ try {
136
+ const resolved = await env.pluginContainer.resolveId(specifier, importer);
137
+ if (!resolved || resolved.external) return;
138
+ if (resolved.id.startsWith("\0") || resolved.id.includes("node_modules")) return;
139
+ if (warmed.has(resolved.id)) return;
140
+ warmed.add(resolved.id);
141
+ await env.warmupRequest(toRequestUrl(resolved.id, root));
142
+ } catch {}
143
+ }));
144
+ };
145
+ return {
146
+ name: "@astroscope/node/island-warmup",
147
+ async config() {
148
+ islands = await scanProjectIslands(srcDir, logger);
149
+ const include = selectBareSpecifiers(islands, root);
150
+ if (islands.length > 0) logger.info(`warming ${islands.length} island(s), pre-optimizing ${include.length} dependenc(ies)`);
151
+ return include.length > 0 ? { optimizeDeps: { include } } : void 0;
152
+ },
153
+ configureServer(server) {
154
+ const warmed = /* @__PURE__ */ new Set();
155
+ if (server.httpServer) server.httpServer.once("listening", () => void warmIslands(server, islands, warmed));
156
+ else warmIslands(server, islands, warmed);
157
+ const onWatcherEvent = (file) => {
158
+ if (!file.endsWith(".astro") || !file.startsWith(srcDir)) return;
159
+ fs.promises.readFile(file, "utf8").then((source) => {
160
+ const batch = scanAstroSource(source).map((specifier) => ({
161
+ importer: file,
162
+ specifier
163
+ }));
164
+ return warmIslands(server, batch, warmed);
165
+ }).catch(() => {});
166
+ };
167
+ server.watcher.on("add", onWatcherEvent);
168
+ server.watcher.on("change", onWatcherEvent);
169
+ }
170
+ };
171
+ }
172
+ //#endregion
11
173
  //#region src/dev-mode/serialize-error.ts
12
174
  function serializeError(error) {
13
175
  if (error instanceof Error) return error.stack ?? error.message;
@@ -620,6 +782,11 @@ function node(options = {}) {
620
782
  entrypoint: "@astroscope/node/dev-middleware",
621
783
  order: "pre"
622
784
  });
785
+ const islandWarmup = command === "dev" ? [createIslandWarmup({
786
+ root,
787
+ srcDir: fileURLToPath(config.srcDir),
788
+ logger
789
+ })] : [];
623
790
  updateConfig({
624
791
  build: { redirects: false },
625
792
  ...config.trailingSlash === "ignore" && { trailingSlash: "never" },
@@ -633,6 +800,7 @@ function node(options = {}) {
633
800
  } },
634
801
  vite: { plugins: [
635
802
  ...devMachinery,
803
+ ...islandWarmup,
636
804
  ssrSourcemapPlugin(),
637
805
  stripSsrEffectsPlugin(),
638
806
  {