@vistagenic/vista 0.3.6 → 0.3.8

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 (68) hide show
  1. package/bin/flashpack-agent-playground.mjs +134 -0
  2. package/bin/flashpack-boot.mjs +504 -0
  3. package/bin/flashpack-ssr-loader.mjs +42 -0
  4. package/bin/flashpack-ssr.mjs +81 -0
  5. package/bin/flashpack-vendor.mjs +97 -0
  6. package/bin/vista.js +56 -42
  7. package/dist/ai/providers/base.js +7 -1
  8. package/dist/bin/build-rsc.js +16 -0
  9. package/dist/bin/build.js +17 -11
  10. package/dist/bin/deploy.js +7 -0
  11. package/dist/bin/dev-client-runtime.d.ts +6 -0
  12. package/dist/bin/dev-client-runtime.js +99 -0
  13. package/dist/bin/dev-error-overlay-snippet.js +79 -21
  14. package/dist/bin/dev-error-overlay-snippet.ts +840 -0
  15. package/dist/bin/devtools-indicator-snippet.js +29 -13
  16. package/dist/bin/file-scanner.js +7 -24
  17. package/dist/bin/generate.js +94 -2
  18. package/dist/bin/webpack.config.js +3 -1
  19. package/dist/build/rsc/compiler.d.ts +14 -0
  20. package/dist/build/rsc/compiler.js +66 -6
  21. package/dist/build/rsc/server-manifest.js +5 -4
  22. package/dist/config.d.ts +5 -0
  23. package/dist/config.js +15 -1
  24. package/dist/deploy/adapters/render.js +2 -2
  25. package/dist/deploy/index.d.ts +1 -0
  26. package/dist/deploy/index.js +3 -0
  27. package/dist/deploy/runtime-pack.js +22 -1
  28. package/dist/deploy/utils.js +16 -6
  29. package/dist/flashpack/command.d.ts +1 -0
  30. package/dist/flashpack/command.js +42 -76
  31. package/dist/flashpack/runtime.d.ts +12 -0
  32. package/dist/flashpack/runtime.js +124 -22
  33. package/dist/metadata/generate.d.ts +1 -0
  34. package/dist/metadata/generate.js +117 -21
  35. package/dist/metadata/index.d.ts +5 -0
  36. package/dist/metadata/index.js +15 -1
  37. package/dist/metadata/json-ld.d.ts +15 -0
  38. package/dist/metadata/json-ld.js +19 -0
  39. package/dist/metadata/merge.d.ts +17 -0
  40. package/dist/metadata/merge.js +161 -0
  41. package/dist/metadata/routes.d.ts +66 -0
  42. package/dist/metadata/routes.js +143 -0
  43. package/dist/metadata/types.d.ts +1 -1
  44. package/dist/metadata/types.js +3 -1
  45. package/dist/og/image-response.d.ts +38 -0
  46. package/dist/og/image-response.js +88 -0
  47. package/dist/og/index.d.ts +2 -0
  48. package/dist/og/index.js +5 -0
  49. package/dist/rust/index.d.ts +13 -0
  50. package/dist/rust/index.js +132 -0
  51. package/dist/server/engine.js +13 -3
  52. package/dist/server/logger.js +2 -1
  53. package/dist/server/project-alias-resolver.d.ts +7 -0
  54. package/dist/server/project-alias-resolver.js +70 -0
  55. package/dist/server/rsc-engine.js +93 -16
  56. package/dist/server/static-generator.js +4 -3
  57. package/dist/server/typed-api-runtime.js +92 -0
  58. package/docs/ai.md +69 -0
  59. package/docs/app.md +30 -0
  60. package/docs/cli.md +29 -0
  61. package/docs/index.md +18 -0
  62. package/docs/rag.md +54 -0
  63. package/native/win32-x64/flashpack-cli.exe +0 -0
  64. package/package.json +196 -178
  65. package/skills/README.md +9 -0
  66. package/skills/vista-agent/SKILL.md +28 -0
  67. package/skills/vista-dev-loop/SKILL.md +25 -0
  68. package/skills/vista-rag/SKILL.md +26 -0
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Runs a real vista/ai agent stream for the marketing playground.
3
+ * Used by Flashpack POST /_flashpack/agent-playground (and readable as a CLI).
4
+ *
5
+ * Prefers the model from the request when the matching API key exists.
6
+ * Otherwise falls back to OpenRouter / GROQ / OpenAI / NVIDIA env, then mock:echo.
7
+ */
8
+ import fs from 'node:fs';
9
+ import { createRequire } from 'node:module';
10
+ import path from 'node:path';
11
+ import { stdin as input } from 'node:process';
12
+
13
+ function readArg(name) {
14
+ const index = process.argv.indexOf(name);
15
+ if (index === -1 || index + 1 >= process.argv.length) return '';
16
+ return process.argv[index + 1];
17
+ }
18
+
19
+ function loadEnvFile(filePath) {
20
+ if (!fs.existsSync(filePath)) return;
21
+ const text = fs.readFileSync(filePath, 'utf8');
22
+ for (const rawLine of text.split(/\r?\n/)) {
23
+ const line = rawLine.trim();
24
+ if (!line || line.startsWith('#')) continue;
25
+ const eq = line.indexOf('=');
26
+ if (eq <= 0) continue;
27
+ const key = line.slice(0, eq).trim();
28
+ let value = line.slice(eq + 1).trim();
29
+ if (
30
+ (value.startsWith('"') && value.endsWith('"')) ||
31
+ (value.startsWith("'") && value.endsWith("'"))
32
+ ) {
33
+ value = value.slice(1, -1);
34
+ }
35
+ if (!process.env[key]) process.env[key] = value;
36
+ }
37
+ }
38
+
39
+ async function readStdin() {
40
+ const chunks = [];
41
+ for await (const chunk of input) chunks.push(chunk);
42
+ return Buffer.concat(chunks).toString('utf8');
43
+ }
44
+
45
+ function providerOf(model) {
46
+ const colon = String(model || '').indexOf(':');
47
+ return colon === -1 ? '' : model.slice(0, colon).toLowerCase();
48
+ }
49
+
50
+ function hasKeyFor(model) {
51
+ const provider = providerOf(model);
52
+ if (provider === 'mock') return true;
53
+ if (provider === 'openrouter') return Boolean(process.env.OPENROUTER_API_KEY);
54
+ if (provider === 'groq') return Boolean(process.env.GROQ_API_KEY);
55
+ if (provider === 'openai') return Boolean(process.env.OPENAI_API_KEY);
56
+ if (provider === 'anthropic') return Boolean(process.env.ANTHROPIC_API_KEY);
57
+ if (provider === 'gemini') return Boolean(process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY);
58
+ if (provider === 'nvidia' || provider === 'nim') {
59
+ return Boolean(process.env.NVIDIA_API_KEY || process.env.NIM_API_KEY);
60
+ }
61
+ if (provider === 'ollama') return true;
62
+ return false;
63
+ }
64
+
65
+ function pickModel(requested) {
66
+ if (requested && hasKeyFor(requested)) return { model: requested, mode: requested.startsWith('mock:') ? 'demo' : 'live' };
67
+ if (process.env.OPENROUTER_API_KEY) {
68
+ return {
69
+ model: process.env.VISTA_AI_MODEL || 'openrouter:openai/gpt-4o-mini',
70
+ mode: 'live',
71
+ };
72
+ }
73
+ if (process.env.GROQ_API_KEY) return { model: 'groq:llama-3.1-8b-instant', mode: 'live' };
74
+ if (process.env.OPENAI_API_KEY) return { model: 'openai:gpt-4o-mini', mode: 'live' };
75
+ if (process.env.NVIDIA_API_KEY || process.env.NIM_API_KEY) {
76
+ return { model: 'nvidia:meta/llama-3.1-8b-instruct', mode: 'live' };
77
+ }
78
+ if (process.env.ANTHROPIC_API_KEY) return { model: 'anthropic:claude-3-5-haiku-latest', mode: 'live' };
79
+ return { model: 'mock:echo', mode: 'demo' };
80
+ }
81
+
82
+ const cwd = path.resolve(readArg('--cwd') || process.cwd());
83
+ loadEnvFile(path.join(cwd, '.env'));
84
+ loadEnvFile(path.join(cwd, '.env.local'));
85
+ const raw = await readStdin();
86
+ let body = {};
87
+ try {
88
+ body = raw.trim() ? JSON.parse(raw) : {};
89
+ } catch {
90
+ process.stderr.write('invalid json body\n');
91
+ process.exit(1);
92
+ }
93
+
94
+ const prompt = typeof body.prompt === 'string' ? body.prompt.trim() : '';
95
+ if (!prompt) {
96
+ process.stderr.write('prompt is required\n');
97
+ process.exit(1);
98
+ }
99
+
100
+ const name = typeof body.name === 'string' && body.name ? body.name : 'support';
101
+ const systemPrompt =
102
+ typeof body.systemPrompt === 'string' && body.systemPrompt
103
+ ? body.systemPrompt
104
+ : 'You help ship Vista apps. Keep answers short and practical.';
105
+ const requestedModel =
106
+ typeof body.model === 'string' && body.model
107
+ ? body.model
108
+ : process.env.VISTA_AI_MODEL || '';
109
+ const { model, mode } = pickModel(requestedModel);
110
+
111
+ const require = createRequire(path.join(cwd, 'package.json'));
112
+ const { agent } = require('vista/ai');
113
+
114
+ const playgroundAgent = agent({
115
+ name,
116
+ model,
117
+ systemPrompt,
118
+ memory: false,
119
+ maxSteps: 3,
120
+ });
121
+
122
+ process.stdout.write(`meta: ${JSON.stringify({ mode, model, name })}\n`);
123
+
124
+ const stream = playgroundAgent.stream({ prompt, sessionId: body.sessionId });
125
+ try {
126
+ for await (const chunk of stream) {
127
+ process.stdout.write(`data: ${JSON.stringify(chunk)}\n\n`);
128
+ }
129
+ process.stdout.write('data: [DONE]\n\n');
130
+ } catch (error) {
131
+ const message = error?.message || String(error);
132
+ process.stdout.write(`data: ${JSON.stringify({ type: 'error', error: message })}\n\n`);
133
+ process.exitCode = 1;
134
+ }
@@ -0,0 +1,504 @@
1
+ import { createRequire } from 'node:module';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ const cwd = path.resolve(process.argv[2] || '');
6
+ const requestPath = process.argv[3] || '/';
7
+ const outfile = path.resolve(process.argv[4] || '');
8
+ const generation = process.argv[5] || '1';
9
+
10
+ function loadEsbuild() {
11
+ const bases = [cwd, path.resolve(cwd, '../..'), path.resolve(cwd, '../../..')];
12
+ for (const base of bases) {
13
+ try {
14
+ return createRequire(path.join(base, 'package.json'))('esbuild');
15
+ } catch {
16
+ // try the next root
17
+ }
18
+ }
19
+ const pnpm = path.resolve(cwd, '../../node_modules/.pnpm');
20
+ if (!fs.existsSync(pnpm)) throw new Error('esbuild is not installed');
21
+ const versions = fs.readdirSync(pnpm).filter((name) => name.startsWith('esbuild@')).sort();
22
+ const version = versions.at(-1);
23
+ if (!version) throw new Error('esbuild is not installed');
24
+ return createRequire(path.join(pnpm, version, 'node_modules', 'esbuild', 'package.json'))('esbuild');
25
+ }
26
+
27
+ function walk(dir, found = []) {
28
+ if (!fs.existsSync(dir)) return found;
29
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
30
+ const full = path.join(dir, entry.name);
31
+ if (entry.isDirectory()) walk(full, found);
32
+ else if (entry.name.endsWith('.js')) found.push(full);
33
+ }
34
+ return found;
35
+ }
36
+
37
+ function moduleUrl(appRel, relative) {
38
+ return `/_flashpack/modules/${appRel}/${relative.replaceAll('\\', '/')}?v=${generation}`;
39
+ }
40
+
41
+ function partsOf(pathname) {
42
+ return String(pathname || '/').split('?')[0].split('#')[0].split('/').filter(Boolean);
43
+ }
44
+
45
+ function matchSegments(segments, parts) {
46
+ const params = {};
47
+ let pi = 0;
48
+ let staticCount = 0;
49
+ let dynamicCount = 0;
50
+ for (const seg of segments) {
51
+ if (seg.startsWith('(') && seg.endsWith(')')) continue;
52
+ if (seg.startsWith('[...') && seg.endsWith(']')) {
53
+ params[seg.slice(4, -1)] = parts.slice(pi);
54
+ dynamicCount += 1;
55
+ pi = parts.length;
56
+ continue;
57
+ }
58
+ if (seg.startsWith('[') && seg.endsWith(']')) {
59
+ if (pi >= parts.length) return null;
60
+ params[seg.slice(1, -1)] = parts[pi];
61
+ dynamicCount += 1;
62
+ pi += 1;
63
+ continue;
64
+ }
65
+ if (pi >= parts.length || parts[pi] !== seg) return null;
66
+ staticCount += 1;
67
+ pi += 1;
68
+ }
69
+ if (pi !== parts.length) return null;
70
+ return { params, staticCount, dynamicCount };
71
+ }
72
+
73
+ function collect() {
74
+ const modulesDir = path.join(cwd, '.flash', 'dev', 'modules');
75
+ const pages = [];
76
+ const layouts = [];
77
+ let root = null;
78
+ for (const appRel of ['app', 'src/app']) {
79
+ const dir = path.join(modulesDir, appRel);
80
+ if (!fs.existsSync(dir)) continue;
81
+ for (const file of walk(dir)) {
82
+ const relative = path.relative(dir, file).replaceAll('\\', '/');
83
+ const name = path.basename(relative);
84
+ const segments = relative.split('/').slice(0, -1);
85
+ const url = moduleUrl(appRel, relative);
86
+ if (!root && name.startsWith('root.') && segments.length === 0) root = url.split('?')[0];
87
+ if (name.startsWith('page.') || name.startsWith('index.')) pages.push({ module: url.split('?')[0], segments });
88
+ else if (name.startsWith('layout.')) layouts.push({ module: url.split('?')[0], segments });
89
+ }
90
+ }
91
+ return { root: root || '/_flashpack/modules/app/root.tsx.js', pages, layouts };
92
+ }
93
+
94
+ function layoutApplies(segments, parts) {
95
+ let pi = 0;
96
+ for (const seg of segments || []) {
97
+ if (seg.startsWith('(') && seg.endsWith(')')) continue;
98
+ if (seg.startsWith('[...')) return true;
99
+ if (seg.startsWith('[') && seg.endsWith(']')) {
100
+ if (pi >= parts.length) return false;
101
+ pi += 1;
102
+ continue;
103
+ }
104
+ if (parts[pi] !== seg) return false;
105
+ pi += 1;
106
+ }
107
+ return true;
108
+ }
109
+
110
+ const table = collect();
111
+ const parts = partsOf(requestPath);
112
+ let best = null;
113
+ for (const page of table.pages) {
114
+ const matched = matchSegments(page.segments || [], parts);
115
+ if (!matched) continue;
116
+ const candidate = { page, ...matched };
117
+ if (
118
+ !best ||
119
+ candidate.staticCount > best.staticCount ||
120
+ (candidate.staticCount === best.staticCount && candidate.dynamicCount < best.dynamicCount)
121
+ ) {
122
+ best = candidate;
123
+ }
124
+ }
125
+ const layouts = (table.layouts || [])
126
+ .filter((layout) => layoutApplies(layout.segments, parts))
127
+ .sort((a, b) => (a.segments || []).length - (b.segments || []).length);
128
+
129
+ const imports = [
130
+ `import { hydrateRoot, createRoot } from "/_flashpack/mod/react-dom/client";`,
131
+ `import * as React from "/_flashpack/mod/react";`,
132
+ `import * as ReactDOM from "/_flashpack/mod/react-dom";`,
133
+ `import * as ReactDOMClient from "/_flashpack/mod/react-dom/client";`,
134
+ `import { jsx } from "/_flashpack/mod/react/jsx-runtime";`,
135
+ `import * as JsxRuntime from "/_flashpack/mod/react/jsx-runtime";`,
136
+ `import * as JsxDevRuntime from "/_flashpack/mod/react/jsx-dev-runtime";`,
137
+ `import Root from ${JSON.stringify(table.root)};`,
138
+ ];
139
+ if (best) imports.push(`import Page from ${JSON.stringify(best.page.module)};`);
140
+ layouts.forEach((layout, index) => {
141
+ imports.push(`import Layout${index} from ${JSON.stringify(layout.module)};`);
142
+ });
143
+
144
+ const params = JSON.stringify(best ? best.params : {});
145
+ const layoutWrap = layouts
146
+ .map((_, index) => `node = jsx(Layout${index}, { params, children: node });`)
147
+ .join('\n');
148
+
149
+ const source = `${imports.join('\n')}
150
+ // Claim the soft-HMR slot immediately so the status poll never hard-reloads
151
+ // while this boot script is still hydrating.
152
+ window.__VISTA_SOFT_HMR__ = Object.assign(async function pendingSoftHmr() {
153
+ throw new Error("flashpack soft HMR still booting");
154
+ }, { __pending: true });
155
+ const params = ${params};
156
+ const mount = document.getElementById("root");
157
+ const target = mount || document;
158
+ if (target.querySelectorAll) {
159
+ target.querySelectorAll("[data-cursor-ref]").forEach((node) => node.removeAttribute("data-cursor-ref"));
160
+ }
161
+ let node = ${best ? 'jsx(Page, { params })' : 'jsx("p", { children: "This page could not be found." })'};
162
+ ${layoutWrap}
163
+ const tree = jsx(Root, { children: node });
164
+ window.__VISTA_REACT__ = React;
165
+ window.__VISTA_REACT_DOM__ = ReactDOM;
166
+ window.__VISTA_REACT_DOM_CLIENT__ = ReactDOMClient;
167
+ window.__VISTA_JSX__ = JsxRuntime;
168
+ window.__VISTA_JSX_DEV__ = JsxDevRuntime;
169
+ let reactRoot;
170
+ try {
171
+ if (mount) {
172
+ if (mount.childNodes.length > 0) reactRoot = hydrateRoot(mount, tree);
173
+ else {
174
+ reactRoot = createRoot(mount);
175
+ reactRoot.render(tree);
176
+ }
177
+ } else {
178
+ reactRoot = hydrateRoot(document, tree);
179
+ }
180
+ } catch (error) {
181
+ console.error(error);
182
+ reactRoot = createRoot(mount || document.body);
183
+ reactRoot.render(tree);
184
+ }
185
+ function reveal() {
186
+ document.documentElement.setAttribute("data-vista-ready", "");
187
+ // After first paint, drop BOOT_HOLD entirely. Soft HMR re-renders <html> and
188
+ // would otherwise clear data-vista-ready and hide the body again.
189
+ try {
190
+ const hold = document.getElementById("__vista-boot-hold");
191
+ if (hold) hold.remove();
192
+ document.querySelectorAll("style").forEach((node) => {
193
+ const text = node.textContent || "";
194
+ if (text.includes("data-vista-ready") && text.includes("visibility:hidden")) node.remove();
195
+ });
196
+ } catch (_) {}
197
+ }
198
+ reveal();
199
+
200
+ let hmrGeneration = ${JSON.stringify(generation)};
201
+ let routes = ${JSON.stringify({ root: table.root, pages: table.pages, layouts: table.layouts })};
202
+ function versioned(url) {
203
+ if (!url) return url;
204
+ try {
205
+ const parsed = new URL(url, location.origin);
206
+ parsed.searchParams.set("v", String(hmrGeneration));
207
+ return parsed.pathname + parsed.search;
208
+ } catch {
209
+ if (url.includes("?v=") || url.includes("&v=")) {
210
+ return url.replace(/([?&])v=[^&#]*/, "$1v=" + hmrGeneration);
211
+ }
212
+ return url + (url.includes("?") ? "&" : "?") + "v=" + hmrGeneration;
213
+ }
214
+ }
215
+ function bustStylesheet(nextGeneration) {
216
+ const links = Array.from(document.querySelectorAll('link[rel="stylesheet"][href*="/_flashpack/app.css"]'));
217
+ for (const link of links) {
218
+ const parsed = new URL(link.href, location.href);
219
+ parsed.searchParams.set("v", String(nextGeneration));
220
+ const nextHref = parsed.pathname + parsed.search;
221
+ if ((link.getAttribute("href") || "").includes("v=" + nextGeneration)) continue;
222
+ const next = link.cloneNode(false);
223
+ next.setAttribute("href", nextHref);
224
+ next.onload = () => { try { link.remove(); } catch (_) {} };
225
+ next.onerror = () => { try { next.remove(); } catch (_) {} };
226
+ if (link.parentNode) link.parentNode.insertBefore(next, link.nextSibling);
227
+ }
228
+ }
229
+ function exportDefault(mod) {
230
+ if (!mod) return null;
231
+ if (mod.default != null) return mod.default;
232
+ if (typeof mod === "function") return mod;
233
+ return null;
234
+ }
235
+ async function softHmr(nextGeneration) {
236
+ // In-place refresh: keep document + React root; no BOOT_HOLD / location.reload.
237
+ hmrGeneration = String(nextGeneration);
238
+ try {
239
+ const fresh = await fetch("/_flashpack/routes").then((res) => res.json());
240
+ if (fresh && fresh.root) routes = fresh;
241
+ } catch (_) {}
242
+ bustStylesheet(hmrGeneration);
243
+ try {
244
+ await show(location.pathname);
245
+ } finally {
246
+ reveal();
247
+ }
248
+ try {
249
+ const indicator = window.__VISTA_DEVTOOLS_INDICATOR__;
250
+ if (indicator && typeof indicator.pulse === "function") indicator.pulse("hmr", 420);
251
+ } catch (_) {}
252
+ }
253
+ // Expose immediately so the status poll never falls back to location.reload
254
+ // before boot finishes wiring the real handler.
255
+ window.__VISTA_SOFT_HMR__ = softHmr;
256
+ window.__VISTA_SOFT_HMR__.__pending = false;
257
+ function partsOf(pathname) {
258
+ return String(pathname || "/").split("?")[0].split("#")[0].split("/").filter(Boolean);
259
+ }
260
+ function matchSegments(segments, parts) {
261
+ const params = {};
262
+ let pi = 0;
263
+ let staticCount = 0;
264
+ let dynamicCount = 0;
265
+ for (const seg of segments || []) {
266
+ if (seg.startsWith("(") && seg.endsWith(")")) continue;
267
+ if (seg.startsWith("[...") && seg.endsWith("]")) {
268
+ params[seg.slice(4, -1)] = parts.slice(pi);
269
+ dynamicCount += 1;
270
+ pi = parts.length;
271
+ continue;
272
+ }
273
+ if (seg.startsWith("[") && seg.endsWith("]")) {
274
+ if (pi >= parts.length) return null;
275
+ params[seg.slice(1, -1)] = parts[pi];
276
+ dynamicCount += 1;
277
+ pi += 1;
278
+ continue;
279
+ }
280
+ if (pi >= parts.length || parts[pi] !== seg) return null;
281
+ staticCount += 1;
282
+ pi += 1;
283
+ }
284
+ if (pi !== parts.length) return null;
285
+ return { params, staticCount, dynamicCount };
286
+ }
287
+ function pickPage(pages, parts) {
288
+ let best = null;
289
+ for (const page of pages || []) {
290
+ const matched = matchSegments(page.segments || [], parts);
291
+ if (!matched) continue;
292
+ const candidate = { page, ...matched };
293
+ if (!best || candidate.staticCount > best.staticCount || (candidate.staticCount === best.staticCount && candidate.dynamicCount < best.dynamicCount)) best = candidate;
294
+ }
295
+ return best;
296
+ }
297
+ function layoutApplies(segments, parts) {
298
+ let pi = 0;
299
+ for (const seg of segments || []) {
300
+ if (seg.startsWith("(") && seg.endsWith(")")) continue;
301
+ if (seg.startsWith("[...")) return true;
302
+ if (seg.startsWith("[") && seg.endsWith("]")) {
303
+ if (pi >= parts.length) return false;
304
+ pi += 1;
305
+ continue;
306
+ }
307
+ if (parts[pi] !== seg) return false;
308
+ pi += 1;
309
+ }
310
+ return true;
311
+ }
312
+ async function renderNode(Component, props) {
313
+ if (typeof Component === "function" && Component.constructor && Component.constructor.name === "AsyncFunction") return await Component(props);
314
+ return jsx(Component, props);
315
+ }
316
+ async function show(pathname) {
317
+ const parts = partsOf(pathname);
318
+ const match = pickPage(routes.pages, parts);
319
+ const RootComponent = exportDefault(await import(versioned(routes.root)));
320
+ if (typeof RootComponent !== "function") {
321
+ throw new Error("flashpack soft HMR: root module has no default export");
322
+ }
323
+ let tree;
324
+ if (!match) {
325
+ tree = jsx(RootComponent, { children: jsx("p", { children: "This page could not be found." }) });
326
+ } else {
327
+ const PageComponent = exportDefault(await import(versioned(match.page.module)));
328
+ if (typeof PageComponent !== "function") {
329
+ throw new Error("flashpack soft HMR: page module has no default export");
330
+ }
331
+ let next = await renderNode(PageComponent, { params: match.params });
332
+ const nested = (routes.layouts || []).filter((layout) => layoutApplies(layout.segments, parts)).sort((a, b) => (a.segments || []).length - (b.segments || []).length);
333
+ for (const layout of nested) {
334
+ const LayoutComponent = exportDefault(await import(versioned(layout.module)));
335
+ if (typeof LayoutComponent !== "function") continue;
336
+ next = await renderNode(LayoutComponent, { params: match.params, children: next });
337
+ }
338
+ tree = jsx(RootComponent, { children: next });
339
+ }
340
+ if (!reactRoot) {
341
+ const el = document.getElementById("root");
342
+ reactRoot = el ? createRoot(el) : createRoot(document.body);
343
+ }
344
+ ReactDOM.flushSync(() => {
345
+ reactRoot.render(tree);
346
+ });
347
+ reveal();
348
+ }
349
+ let navQueue = Promise.resolve();
350
+ function navigate(href, options) {
351
+ const opts = options || {};
352
+ navQueue = navQueue.then(async () => {
353
+ const url = new URL(href, location.href);
354
+ if (url.origin !== location.origin) {
355
+ location.assign(url.href);
356
+ return;
357
+ }
358
+ const next = url.pathname + url.search + url.hash;
359
+ const current = location.pathname + location.search + location.hash;
360
+ if (next === current) return;
361
+ if (opts.replace) history.replaceState({}, "", next);
362
+ else history.pushState({}, "", next);
363
+ try {
364
+ await show(url.pathname);
365
+ } finally {
366
+ reveal();
367
+ }
368
+ if (opts.scroll !== false) window.scrollTo(0, 0);
369
+ }).catch((error) => {
370
+ console.error(error);
371
+ reveal();
372
+ });
373
+ }
374
+ function isInternalNavigation(anchor, event) {
375
+ if (!anchor || event.defaultPrevented || event.button !== 0) return false;
376
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return false;
377
+ if (anchor.target && anchor.target !== "_self") return false;
378
+ if (anchor.hasAttribute("download")) return false;
379
+ const href = anchor.getAttribute("href");
380
+ if (!href || href.startsWith("#")) return false;
381
+ try { return new URL(href, location.href).origin === location.origin; } catch { return false; }
382
+ }
383
+ window.__VISTA_NAVIGATE__ = navigate;
384
+ document.addEventListener("click", (event) => {
385
+ const anchor = event.target && event.target.closest ? event.target.closest("a") : null;
386
+ if (!isInternalNavigation(anchor, event)) return;
387
+ event.preventDefault();
388
+ navigate(anchor.getAttribute("href"));
389
+ }, true);
390
+ window.addEventListener("popstate", () => {
391
+ navQueue = navQueue.then(async () => {
392
+ try {
393
+ await show(location.pathname);
394
+ } finally {
395
+ reveal();
396
+ }
397
+ }).catch((error) => {
398
+ console.error(error);
399
+ reveal();
400
+ });
401
+ });
402
+ `;
403
+
404
+ function isInside(file, dir) {
405
+ const relative = path.relative(dir, file);
406
+ return relative === '' || (relative && !relative.startsWith('..') && !path.isAbsolute(relative));
407
+ }
408
+
409
+ function inlineFsShim() {
410
+ const files = {};
411
+ const root = path.join(cwd, 'content');
412
+ const walkFiles = (dir) => {
413
+ if (!fs.existsSync(dir)) return;
414
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
415
+ const full = path.join(dir, entry.name);
416
+ if (entry.isDirectory()) walkFiles(full);
417
+ else if (entry.name.endsWith('.md')) {
418
+ const relative = path.relative(cwd, full).replaceAll('\\', '/');
419
+ const text = fs.readFileSync(full, 'utf8');
420
+ files[`/${relative}`] = text;
421
+ files[`/_flashpack/modules/${relative}`] = text;
422
+ }
423
+ }
424
+ };
425
+ walkFiles(root);
426
+ return `const files = ${JSON.stringify(files)};
427
+ function keyOf(target) {
428
+ const href = typeof target === "string" ? target : (target && target.href) || String(target);
429
+ try { return new URL(href, "http://flashpack.local").pathname; } catch { return String(href); }
430
+ }
431
+ export function readFileSync(target) {
432
+ const key = keyOf(target);
433
+ if (Object.prototype.hasOwnProperty.call(files, key)) return files[key];
434
+ const error = new Error("ENOENT: " + key);
435
+ error.code = "ENOENT";
436
+ throw error;
437
+ }
438
+ export function existsSync(target) {
439
+ return Object.prototype.hasOwnProperty.call(files, keyOf(target));
440
+ }
441
+ export default { readFileSync, existsSync };
442
+ `;
443
+ }
444
+
445
+ const esbuild = loadEsbuild();
446
+ const modulesDir = path.join(cwd, '.flash', 'dev', 'modules');
447
+ const shimsDir = path.join(cwd, '.flash', 'dev', 'ssr');
448
+ const appRequire = createRequire(path.join(cwd, 'package.json'));
449
+
450
+ fs.mkdirSync(path.dirname(outfile), { recursive: true });
451
+ await esbuild.build({
452
+ absWorkingDir: cwd,
453
+ stdin: {
454
+ contents: source,
455
+ resolveDir: modulesDir,
456
+ sourcefile: 'flashpack-boot.js',
457
+ },
458
+ bundle: true,
459
+ format: 'iife',
460
+ platform: 'browser',
461
+ target: ['es2022'],
462
+ outfile,
463
+ logLevel: 'silent',
464
+ define: { 'process.env.NODE_ENV': '"development"' },
465
+ mainFields: ['browser', 'module', 'main'],
466
+ plugins: [
467
+ {
468
+ name: 'flashpack-boot',
469
+ setup(build) {
470
+ build.onResolve({ filter: /^\/_flashpack\/modules\// }, (args) => {
471
+ const rel = args.path.slice('/_flashpack/modules/'.length).split('?')[0];
472
+ return { path: path.join(modulesDir, rel) };
473
+ });
474
+ build.onResolve({ filter: /^\/_flashpack\/shims\// }, (args) => {
475
+ const rel = args.path.slice('/_flashpack/shims/'.length).split('?')[0];
476
+ if (rel === 'fs.js') return { path: 'fs.js', namespace: 'flashpack-fs' };
477
+ return { path: path.join(shimsDir, rel) };
478
+ });
479
+ build.onLoad({ filter: /.*/, namespace: 'flashpack-fs' }, () => ({
480
+ contents: inlineFsShim(),
481
+ loader: 'js',
482
+ }));
483
+ build.onLoad({ filter: /\.js$/ }, (args) => {
484
+ if (!isInside(args.path, modulesDir)) return null;
485
+ const contents = fs.readFileSync(args.path, 'utf8');
486
+ if (!contents.includes('import.meta.url')) return null;
487
+ const relative = path.relative(modulesDir, args.path).replaceAll('\\', '/');
488
+ const rewritten = contents.replaceAll(
489
+ 'import.meta.url',
490
+ `(location.origin + ${JSON.stringify(`/_flashpack/modules/${relative}`)})`
491
+ );
492
+ return { contents: rewritten, loader: 'js' };
493
+ });
494
+ build.onResolve({ filter: /^\/_flashpack\/mod\// }, (args) => {
495
+ const spec = args.path.slice('/_flashpack/mod/'.length).split('?')[0];
496
+ return { path: appRequire.resolve(spec) };
497
+ });
498
+ build.onResolve({ filter: /^\/_flashpack\/empty\.js/ }, () => ({
499
+ path: path.join(shimsDir, 'empty.js'),
500
+ }));
501
+ },
502
+ },
503
+ ],
504
+ });
@@ -0,0 +1,42 @@
1
+ import path from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+
4
+ let modulesDir = '';
5
+ let shimsDir = '';
6
+
7
+ export function initialize(data) {
8
+ modulesDir = data.modulesDir;
9
+ shimsDir = data.shimsDir;
10
+ }
11
+
12
+ export async function resolve(specifier, context, nextResolve) {
13
+ if (specifier.startsWith('/_flashpack/modules/')) {
14
+ const rel = specifier.slice('/_flashpack/modules/'.length).split('?')[0];
15
+ return {
16
+ url: pathToFileURL(path.join(modulesDir, ...rel.split('/'))).href,
17
+ shortCircuit: true,
18
+ };
19
+ }
20
+
21
+ if (specifier.startsWith('/_flashpack/shims/')) {
22
+ const name = specifier.slice('/_flashpack/shims/'.length).split('?')[0];
23
+ return {
24
+ url: pathToFileURL(path.join(shimsDir, name)).href,
25
+ shortCircuit: true,
26
+ };
27
+ }
28
+
29
+ if (specifier === '/_flashpack/empty.js') {
30
+ return {
31
+ url: pathToFileURL(path.join(shimsDir, 'empty.js')).href,
32
+ shortCircuit: true,
33
+ };
34
+ }
35
+
36
+ if (specifier.startsWith('/_flashpack/mod/')) {
37
+ const spec = decodeURIComponent(specifier.slice('/_flashpack/mod/'.length));
38
+ return nextResolve(spec, context);
39
+ }
40
+
41
+ return nextResolve(specifier, context);
42
+ }