@untestutils/nuxt 0.5.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 (70) hide show
  1. package/LICENSE +21 -0
  2. package/dist/config/index.d.mts +58 -0
  3. package/dist/config/index.d.ts +59 -0
  4. package/dist/config/index.d.ts.map +1 -0
  5. package/dist/config/index.mjs +323 -0
  6. package/dist/config/utils.d.mts +12 -0
  7. package/dist/config/utils.d.ts +13 -0
  8. package/dist/config/utils.d.ts.map +1 -0
  9. package/dist/config/utils.mjs +79 -0
  10. package/dist/environment/index.d.mts +22 -0
  11. package/dist/environment/index.d.ts +23 -0
  12. package/dist/environment/index.d.ts.map +1 -0
  13. package/dist/environment/index.mjs +129 -0
  14. package/dist/index.d.mts +36 -0
  15. package/dist/index.d.ts +65 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.mjs +355 -0
  18. package/dist/module/index.d.mts +3 -0
  19. package/dist/module/index.d.ts +4 -0
  20. package/dist/module/index.d.ts.map +1 -0
  21. package/dist/module/index.mjs +294 -0
  22. package/dist/runtime/browser-entry.d.mts +5 -0
  23. package/dist/runtime/browser-entry.d.ts +6 -0
  24. package/dist/runtime/browser-entry.d.ts.map +1 -0
  25. package/dist/runtime/browser-entry.mjs +2 -0
  26. package/dist/runtime/entry.d.mts +1 -0
  27. package/dist/runtime/entry.d.ts +2 -0
  28. package/dist/runtime/entry.d.ts.map +1 -0
  29. package/dist/runtime/entry.mjs +9 -0
  30. package/dist/runtime/index.d.mts +81 -0
  31. package/dist/runtime/index.d.ts +82 -0
  32. package/dist/runtime/index.d.ts.map +1 -0
  33. package/dist/runtime/index.mjs +156 -0
  34. package/dist/runtime/mocks/vue-devtools.d.mts +12 -0
  35. package/dist/runtime/mocks/vue-devtools.d.ts +13 -0
  36. package/dist/runtime/mocks/vue-devtools.d.ts.map +1 -0
  37. package/dist/runtime/mocks/vue-devtools.mjs +12 -0
  38. package/dist/runtime/nuxt-root.d.mts +3 -0
  39. package/dist/runtime/nuxt-root.d.ts +4 -0
  40. package/dist/runtime/nuxt-root.d.ts.map +1 -0
  41. package/dist/runtime/nuxt-root.mjs +29 -0
  42. package/dist/runtime/shared/environment.d.mts +52 -0
  43. package/dist/runtime/shared/environment.d.ts +53 -0
  44. package/dist/runtime/shared/environment.d.ts.map +1 -0
  45. package/dist/runtime/shared/environment.mjs +79 -0
  46. package/dist/runtime/shared/h3-v1.d.mts +7 -0
  47. package/dist/runtime/shared/h3-v1.d.ts +8 -0
  48. package/dist/runtime/shared/h3-v1.d.ts.map +1 -0
  49. package/dist/runtime/shared/h3-v1.mjs +50 -0
  50. package/dist/runtime/shared/h3-v2.d.mts +7 -0
  51. package/dist/runtime/shared/h3-v2.d.ts +8 -0
  52. package/dist/runtime/shared/h3-v2.d.ts.map +1 -0
  53. package/dist/runtime/shared/h3-v2.mjs +32 -0
  54. package/dist/runtime/shared/h3.d.mts +4 -0
  55. package/dist/runtime/shared/h3.d.ts +5 -0
  56. package/dist/runtime/shared/h3.d.ts.map +1 -0
  57. package/dist/runtime/shared/h3.mjs +3 -0
  58. package/dist/runtime/shared/nuxt.d.mts +2 -0
  59. package/dist/runtime/shared/nuxt.d.ts +3 -0
  60. package/dist/runtime/shared/nuxt.d.ts.map +1 -0
  61. package/dist/runtime/shared/nuxt.mjs +17 -0
  62. package/dist/runtime/shared/vue-wrapper-plugin.d.mts +12 -0
  63. package/dist/runtime/shared/vue-wrapper-plugin.d.ts +13 -0
  64. package/dist/runtime/shared/vue-wrapper-plugin.d.ts.map +1 -0
  65. package/dist/runtime/shared/vue-wrapper-plugin.mjs +42 -0
  66. package/dist/runtime/suspended.d.mts +45 -0
  67. package/dist/runtime/suspended.d.ts +46 -0
  68. package/dist/runtime/suspended.d.ts.map +1 -0
  69. package/dist/runtime/suspended.mjs +259 -0
  70. package/package.json +150 -0
@@ -0,0 +1,129 @@
1
+ import { defu } from "defu";
2
+ import { joinURL } from "ufo";
3
+ import { getPackageInfoSync } from "local-pkg";
4
+ import { indexedDB } from "fake-indexeddb";
5
+ import { setupWindow } from "../runtime/shared/environment.mjs";
6
+ const happyDomEnvironment = async (_global, { happyDom = {} }) => {
7
+ const { Window, GlobalWindow } = await import("happy-dom");
8
+ const window = new (GlobalWindow || Window)(happyDom);
9
+ return {
10
+ window,
11
+ teardown() {
12
+ window.happyDOM.abort();
13
+ }
14
+ };
15
+ };
16
+ const jsdomEnvironment = async (global, { jsdom = {} }) => {
17
+ const { CookieJar, JSDOM, ResourceLoader, VirtualConsole } = await import("jsdom");
18
+ const jsdomOptions = defu(jsdom, {
19
+ html: "<!DOCTYPE html>",
20
+ url: "http://localhost:3000",
21
+ contentType: "text/html",
22
+ pretendToBeVisual: true,
23
+ includeNodeLocations: false,
24
+ runScripts: "dangerously",
25
+ console: false,
26
+ cookieJar: false
27
+ });
28
+ const virtualConsole = jsdomOptions.console && global.console ? new VirtualConsole() : undefined;
29
+ const forwardConsole = virtualConsole ? forwardVirtualConsole(virtualConsole, global.console) : undefined;
30
+ const window = new JSDOM(jsdomOptions.html ?? "<!DOCTYPE html>", {
31
+ ...jsdomOptions,
32
+ resources: jsdomOptions.resources ?? (jsdomOptions.userAgent ? new ResourceLoader({ userAgent: jsdomOptions.userAgent }) : undefined),
33
+ virtualConsole: forwardConsole,
34
+ cookieJar: jsdomOptions.cookieJar ? new CookieJar() : undefined
35
+ }).window;
36
+ window.scrollTo = () => {};
37
+ return {
38
+ window,
39
+ teardown() {
40
+ window.close();
41
+ }
42
+ };
43
+ };
44
+ const environmentMap = {
45
+ "happy-dom": happyDomEnvironment,
46
+ jsdom: jsdomEnvironment
47
+ };
48
+ const vitestMajor = Number(getPackageInfoSync("vitest")?.version?.split(".")[0]);
49
+ class IntersectionObserverStub {
50
+ root = null;
51
+ rootMargin = "";
52
+ scrollMargin = "";
53
+ thresholds = [];
54
+ observe() {}
55
+ unobserve() {}
56
+ disconnect() {}
57
+ takeRecords() {
58
+ return [];
59
+ }
60
+ }
61
+ function forwardVirtualConsole(virtualConsole, console) {
62
+ if ("sendTo" in virtualConsole && typeof virtualConsole.sendTo === "function") {
63
+ return virtualConsole.sendTo(console);
64
+ }
65
+ return virtualConsole.forwardTo(console);
66
+ }
67
+ async function importVitestEnvironments() {
68
+ const sub = vitestMajor >= 5 ? "runtime" : "environments";
69
+ return import(
70
+ /* @vite-ignore */
71
+ `vitest/${sub}`
72
+ );
73
+ }
74
+ function resolveDomFactory(name) {
75
+ if (name === "jsdom") return environmentMap.jsdom;
76
+ return environmentMap["happy-dom"];
77
+ }
78
+ const environment = {
79
+ name: "untestutils",
80
+ viteEnvironment: "client",
81
+ async setup(global, environmentOptions) {
82
+ const { populateGlobal } = await importVitestEnvironments();
83
+ const url = joinURL(environmentOptions.nuxt?.url ?? "http://localhost:3000", environmentOptions.nuxtRuntimeConfig?.app?.baseURL || "/");
84
+ const { window: win, teardown } = await resolveDomFactory(environmentOptions.nuxt?.domEnvironment)(global, defu(environmentOptions, {
85
+ happyDom: { url },
86
+ jsdom: { url }
87
+ }));
88
+ if (environmentOptions.nuxt?.mock?.intersectionObserver) {
89
+ win.IntersectionObserver ||= IntersectionObserverStub;
90
+ }
91
+ if (environmentOptions.nuxt?.mock?.indexedDb) {
92
+ win.indexedDB = indexedDB;
93
+ }
94
+ const teardownWindow = await setupWindow(win, {
95
+ ...environmentOptions,
96
+ nuxt: {
97
+ rootId: environmentOptions.nuxt?.rootId,
98
+ h3Version: environmentOptions.nuxt?.h3Version,
99
+ url: environmentOptions.nuxt?.url,
100
+ domEnvironment: environmentOptions.nuxt?.domEnvironment,
101
+ mock: environmentOptions.nuxt?.mock
102
+ }
103
+ });
104
+ const { keys, originals } = populateGlobal(global, win, {
105
+ bindFunctions: true,
106
+ additionalKeys: ["fetch", "Request"]
107
+ });
108
+ return { teardown() {
109
+ keys.forEach((key) => {
110
+ Reflect.deleteProperty(global, key);
111
+ });
112
+ teardownWindow();
113
+ if (vitestMajor >= 5) {
114
+ originals.forEach((descriptor, k) => {
115
+ Object.defineProperty(global, k, descriptor);
116
+ });
117
+ } else {
118
+ originals.forEach((v, k) => {
119
+ Reflect.set(global, k, v);
120
+ });
121
+ }
122
+ if (!global.IntersectionObserver) {
123
+ global.IntersectionObserver = IntersectionObserverStub;
124
+ }
125
+ teardown();
126
+ } };
127
+ }
128
+ };
129
+ export default environment;
@@ -0,0 +1,36 @@
1
+ import { waitForHttpReady, type Recipe } from "@untestutils/core";
2
+ export type NuxtRun = "server" | "static" | "dev";
3
+ export interface NuxtOptions {
4
+ id?: string;
5
+ root: string;
6
+ run?: NuxtRun;
7
+ nuxtConfig?: Record<string, unknown>;
8
+ hashInputs?: string[];
9
+ /** Include nearby monorepo package src dirs in prepare hash (auto when workspace exists). */
10
+ workspaceDeps?: boolean | "auto";
11
+ /** Build/runtime env (e.g. STRATEGY=prefix for fixture variants). */
12
+ env?: Record<string, string>;
13
+ readyTimeoutMs?: number;
14
+ /**
15
+ * Nitro deploy preset (`node-server`, `azure`, `cloudflare_module`, …).
16
+ * Merged into `nuxtConfig.nitro.preset` and included in the prepare hash.
17
+ */
18
+ preset?: string;
19
+ }
20
+ export declare const _internals: NuxtInternals;
21
+ /**
22
+ * Nuxt Recipe factory.
23
+ * `run: 'server'` — buildNuxt + node nitro server
24
+ * `run: 'static'` — generate + static file server
25
+ * `run: 'dev'` — nuxi `_dev` only (HMR fixtures; never shared). Default path is still build → nitro.
26
+ */
27
+ export declare function nuxt(opts: NuxtOptions): Recipe;
28
+ export { waitForHttpReady };
29
+ export type MatrixVariant = Partial<Pick<NuxtOptions, "env" | "nuxtConfig" | "hashInputs" | "run" | "preset" | "readyTimeoutMs" | "workspaceDeps">>;
30
+ /**
31
+ * Expand one Nuxt recipe base into many recipes with distinct ids / identity.
32
+ * Variant key `default` keeps `base.id`; other keys become `${baseId}__${key}`.
33
+ */
34
+ export declare function matrix(base: NuxtOptions, variants: Record<string, MatrixVariant>): Record<string, Recipe>;
35
+ /** Nuxt-aware goto wait helpers are provided by vitest/playwright runners via playwright page. */
36
+ export default nuxt;
@@ -0,0 +1,65 @@
1
+ import { waitForHttpReady, type Recipe } from '@untestutils/core';
2
+ export type NuxtRun = 'server' | 'static' | 'dev';
3
+ export interface NuxtOptions {
4
+ id?: string;
5
+ root: string;
6
+ run?: NuxtRun;
7
+ nuxtConfig?: Record<string, unknown>;
8
+ hashInputs?: string[];
9
+ /** Include nearby monorepo package src dirs in prepare hash (auto when workspace exists). */
10
+ workspaceDeps?: boolean | 'auto';
11
+ /** Build/runtime env (e.g. STRATEGY=prefix for fixture variants). */
12
+ env?: Record<string, string>;
13
+ readyTimeoutMs?: number;
14
+ /**
15
+ * Nitro deploy preset (`node-server`, `azure`, `cloudflare_module`, …).
16
+ * Merged into `nuxtConfig.nitro.preset` and included in the prepare hash.
17
+ */
18
+ preset?: string;
19
+ }
20
+ declare function resolveKit(rootDir: string): string;
21
+ /**
22
+ * Resolve Nuxt CLI entry for `run: 'dev'`.
23
+ * Prefers legacy `nuxi/cli`; falls back to `nuxt/bin/nuxt.mjs` (Nuxt 4 / @nuxt/cli).
24
+ * Walks fixture → cwd → workspace root so bare fixture package.json still works.
25
+ */
26
+ declare function resolveNuxiEntry(rootDir: string): string;
27
+ declare function findWorkspaceRoot(from: string): string | undefined;
28
+ declare function workspacePackageSrcDirs(from: string): Promise<string[]>;
29
+ declare function resolveHashInputs(opts: NuxtOptions, root: string): Promise<string[]>;
30
+ declare function withEnv<T>(env: Record<string, string> | undefined, fn: () => Promise<T>): Promise<T>;
31
+ declare function buildNuxtApp(rootDir: string, outDir: string, nuxtConfig?: Record<string, unknown>, ssr?: boolean): Promise<void>;
32
+ declare function generateNuxtApp(rootDir: string, outDir: string, nuxtConfig?: Record<string, unknown>): Promise<void>;
33
+ declare function findServerEntry(outDir: string): string;
34
+ declare function findPublicDir(outDir: string): string;
35
+ /** @internal test seams */
36
+ export interface NuxtInternals {
37
+ resolveKit: typeof resolveKit;
38
+ resolveNuxiEntry: typeof resolveNuxiEntry;
39
+ buildNuxtApp: typeof buildNuxtApp;
40
+ generateNuxtApp: typeof generateNuxtApp;
41
+ findServerEntry: typeof findServerEntry;
42
+ findPublicDir: typeof findPublicDir;
43
+ findWorkspaceRoot: typeof findWorkspaceRoot;
44
+ workspacePackageSrcDirs: typeof workspacePackageSrcDirs;
45
+ resolveHashInputs: typeof resolveHashInputs;
46
+ withEnv: typeof withEnv;
47
+ }
48
+ export declare const _internals: NuxtInternals;
49
+ /**
50
+ * Nuxt Recipe factory.
51
+ * `run: 'server'` — buildNuxt + node nitro server
52
+ * `run: 'static'` — generate + static file server
53
+ * `run: 'dev'` — nuxi `_dev` only (HMR fixtures; never shared). Default path is still build → nitro.
54
+ */
55
+ export declare function nuxt(opts: NuxtOptions): Recipe;
56
+ export { waitForHttpReady };
57
+ export type MatrixVariant = Partial<Pick<NuxtOptions, 'env' | 'nuxtConfig' | 'hashInputs' | 'run' | 'preset' | 'readyTimeoutMs' | 'workspaceDeps'>>;
58
+ /**
59
+ * Expand one Nuxt recipe base into many recipes with distinct ids / identity.
60
+ * Variant key `default` keeps `base.id`; other keys become `${baseId}__${key}`.
61
+ */
62
+ export declare function matrix(base: NuxtOptions, variants: Record<string, MatrixVariant>): Record<string, Recipe>;
63
+ /** Nuxt-aware goto wait helpers are provided by vitest/playwright runners via playwright page. */
64
+ export default nuxt;
65
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.mjs ADDED
@@ -0,0 +1,355 @@
1
+ import { createRequire } from "node:module";
2
+ import { existsSync } from "node:fs";
3
+ import { readdir } from "node:fs/promises";
4
+ import { dirname, join, resolve } from "pathe";
5
+ import { pathToFileURL } from "node:url";
6
+ import { defineRecipe, loopbackUrl, waitForHttpReady, spawnManaged, withQuietLogger } from "@untestutils/core";
7
+ import { assertAppRoot, staticDir } from "@untestutils/core";
8
+ function resolveKit(rootDir) {
9
+
10
+ assertAppRoot(rootDir, "nuxt", { requirePackageJson: false });
11
+ try {
12
+ return createRequire(join(rootDir, "package.json")).resolve("@nuxt/kit");
13
+ } catch (e) {
14
+ throw new Error(`[untestutils/nuxt] cannot resolve @nuxt/kit from ${rootDir} (install nuxt or @nuxt/kit in the fixture/workspace)`, { cause: e });
15
+ }
16
+ }
17
+
18
+ function resolveNuxiEntry(rootDir) {
19
+ const roots = [
20
+ rootDir,
21
+ process.cwd(),
22
+ findWorkspaceRoot(rootDir)
23
+ ].filter((d) => Boolean(d));
24
+ const seen = new Set();
25
+ for (const dir of roots) {
26
+ const key = resolve(dir);
27
+ if (seen.has(key)) continue;
28
+ seen.add(key);
29
+ try {
30
+ return createRequire(join(dir, "package.json")).resolve("nuxi/cli");
31
+ } catch {}
32
+ }
33
+ for (const dir of roots) {
34
+ const key = resolve(dir);
35
+ try {
36
+ const nuxtPkg = createRequire(join(key, "package.json")).resolve("nuxt/package.json");
37
+ const bin = join(dirname(nuxtPkg), "bin/nuxt.mjs");
38
+ if (existsSync(bin)) return bin;
39
+ } catch {}
40
+ }
41
+ throw new Error(`[untestutils/nuxt] cannot resolve Nuxt CLI from ${rootDir} (tried nuxi/cli and nuxt/bin/nuxt.mjs)`);
42
+ }
43
+ function findWorkspaceRoot(from) {
44
+ let dir = resolve(from);
45
+ for (let i = 0; i < 12; i++) {
46
+ if (existsSync(join(dir, "pnpm-workspace.yaml")) || existsSync(join(dir, "pnpm-workspace.yml"))) {
47
+ return dir;
48
+ }
49
+ const parent = dirname(dir);
50
+ if (parent === dir) break;
51
+ dir = parent;
52
+ }
53
+ return undefined;
54
+ }
55
+ async function workspacePackageSrcDirs(from) {
56
+ const ws = findWorkspaceRoot(from);
57
+ if (!ws) return [];
58
+ const packagesDir = join(ws, "packages");
59
+ if (!existsSync(packagesDir)) return [];
60
+ const names = await readdir(packagesDir, { withFileTypes: true }).catch(() => []);
61
+ const out = [];
62
+ for (const e of names) {
63
+ if (!e.isDirectory()) continue;
64
+ const src = join(packagesDir, e.name, "src");
65
+ if (existsSync(src)) out.push(src);
66
+ }
67
+ return out;
68
+ }
69
+ async function resolveHashInputs(opts, root) {
70
+ const inputs = [...opts.hashInputs ?? [root]];
71
+ const wantWs = opts.workspaceDeps === true || opts.workspaceDeps !== false && Boolean(findWorkspaceRoot(root));
72
+ if (wantWs) {
73
+ inputs.push(...await workspacePackageSrcDirs(root));
74
+ }
75
+ if (opts.env && Object.keys(opts.env).length) {
76
+ inputs.push(`env:${JSON.stringify(opts.env)}`);
77
+ }
78
+ if (opts.nuxtConfig) {
79
+ inputs.push(`nuxtConfig:${JSON.stringify(opts.nuxtConfig)}`);
80
+ }
81
+ if (opts.preset) {
82
+ inputs.push(`preset:${opts.preset}`);
83
+ }
84
+ return inputs;
85
+ }
86
+ function mergeNuxtConfig(opts) {
87
+ const base = { ...opts.nuxtConfig ?? {} };
88
+ if (!opts.preset) return base;
89
+ const nitro = {
90
+ ...base.nitro ?? {},
91
+ preset: opts.preset
92
+ };
93
+ return {
94
+ ...base,
95
+ nitro
96
+ };
97
+ }
98
+ async function withEnv(env, fn) {
99
+ if (!env || !Object.keys(env).length) return fn();
100
+ const prev = {};
101
+ for (const [k, v] of Object.entries(env)) {
102
+ prev[k] = process.env[k];
103
+ process.env[k] = v;
104
+ }
105
+ try {
106
+ return await fn();
107
+ } finally {
108
+ for (const [k, v] of Object.entries(prev)) {
109
+ if (v === undefined) delete process.env[k];
110
+ else process.env[k] = v;
111
+ }
112
+ }
113
+ }
114
+ async function buildNuxtApp(rootDir, outDir, nuxtConfig, ssr = true) {
115
+ return withQuietLogger(async () => {
116
+ const kit = await import(pathToFileURL(_internals.resolveKit(rootDir)).href);
117
+ const nuxt = await kit.loadNuxt({
118
+ cwd: rootDir,
119
+ dev: false,
120
+ overrides: {
121
+ ...nuxtConfig ?? {},
122
+ ssr,
123
+ buildDir: join(outDir, ".nuxt"),
124
+ sourcemap: {
125
+ server: false,
126
+ client: false
127
+ },
128
+ vite: { build: { minify: false } },
129
+ nitro: {
130
+ output: { dir: join(outDir, "output") },
131
+ minify: false,
132
+ ...nuxtConfig?.nitro ?? {}
133
+ }
134
+ }
135
+ });
136
+ try {
137
+ if (!ssr) {}
138
+ await kit.buildNuxt(nuxt);
139
+ } finally {
140
+ await nuxt.close().catch(() => {});
141
+ }
142
+ });
143
+ }
144
+ async function generateNuxtApp(rootDir, outDir, nuxtConfig) {
145
+ return withQuietLogger(async () => {
146
+ const kit = await import(pathToFileURL(_internals.resolveKit(rootDir)).href);
147
+ const nuxt = await kit.loadNuxt({
148
+ cwd: rootDir,
149
+ dev: false,
150
+ overrides: {
151
+ ...nuxtConfig ?? {},
152
+ ssr: true,
153
+ buildDir: join(outDir, ".nuxt"),
154
+ nitro: {
155
+ output: { dir: join(outDir, "output") },
156
+ static: true,
157
+ prerender: { crawlLinks: true },
158
+ ...nuxtConfig?.nitro ?? {}
159
+ }
160
+ }
161
+ });
162
+ try {
163
+ await kit.buildNuxt(nuxt);
164
+ } finally {
165
+ await nuxt.close().catch(() => {});
166
+ }
167
+ });
168
+ }
169
+ function findServerEntry(outDir) {
170
+ const candidates = [join(outDir, "output", "server", "index.mjs"), join(outDir, "output", "server", "index.js")];
171
+ for (const c of candidates) {
172
+ if (existsSync(c)) return c;
173
+ }
174
+ throw new Error(`[untestutils/nuxt] server entry not found under ${outDir}/output/server`);
175
+ }
176
+ function findPublicDir(outDir) {
177
+ const candidates = [join(outDir, "output", "public"), join(outDir, "dist")];
178
+ for (const c of candidates) {
179
+ if (existsSync(c)) return c;
180
+ }
181
+ throw new Error(`[untestutils/nuxt] public dir not found under ${outDir}`);
182
+ }
183
+ export const _internals = {
184
+ resolveKit,
185
+ resolveNuxiEntry,
186
+ buildNuxtApp,
187
+ generateNuxtApp,
188
+ findServerEntry,
189
+ findPublicDir,
190
+ findWorkspaceRoot,
191
+ workspacePackageSrcDirs,
192
+ resolveHashInputs,
193
+ withEnv
194
+ };
195
+
196
+ export function nuxt(opts) {
197
+ const root = resolve(opts.root);
198
+ const runMode = opts.run ?? "server";
199
+ const id = opts.id ?? `nuxt-${runMode}-${root.split("/").pop()}`;
200
+ const startEnv = { ...opts.env ?? {} };
201
+ const ensureRoot = () => assertAppRoot(root, "nuxt", { requirePackageJson: false });
202
+ if (runMode === "dev") {
203
+ const recipe = defineRecipe({
204
+ id,
205
+ share: "never",
206
+
207
+ hashInputs: async () => _internals.resolveHashInputs(opts, root),
208
+ ready: async () => {},
209
+ start: async ({ port, host }) => {
210
+ ensureRoot();
211
+ return _internals.withEnv(opts.env, async () => {
212
+ const nuxi = _internals.resolveNuxiEntry(root);
213
+ const managed = spawnManaged(process.execPath, [nuxi, "_dev"], {
214
+ cwd: root,
215
+ env: {
216
+ ...process.env,
217
+ ...startEnv,
218
+ PORT: String(port),
219
+ HOST: host,
220
+ NODE_ENV: "development",
221
+
222
+ NUXT_IGNORE_LOCK: "1"
223
+ },
224
+ captureLogs: true
225
+ });
226
+ const url = loopbackUrl(port, "/", host);
227
+ try {
228
+ await waitForHttpReady(url, {
229
+ timeoutMs: opts.readyTimeoutMs ?? 18e4,
230
+ rejectBodyIncludes: ["__NUXT_LOADING__"]
231
+ });
232
+ } catch (e) {
233
+ await managed.stop();
234
+
235
+ throw new Error(`[untestutils/nuxt] ready failed:\n${e}\n--- logs ---\n${managed.logs().slice(-4e3)}`);
236
+ }
237
+ return {
238
+ kind: "url",
239
+ url,
240
+ stop: managed.stop,
241
+ pid: managed.pid
242
+ };
243
+ });
244
+ }
245
+ });
246
+ recipe.root = root;
247
+ return recipe;
248
+ }
249
+ if (runMode === "static") {
250
+ const recipe = defineRecipe({
251
+ id,
252
+ share: "always",
253
+ hashInputs: async () => _internals.resolveHashInputs(opts, root),
254
+ ready: async () => {},
255
+ prepare: async ({ outDir }) => {
256
+ ensureRoot();
257
+ await _internals.withEnv(opts.env, () => _internals.generateNuxtApp(root, outDir, mergeNuxtConfig(opts)));
258
+ },
259
+ start: async (ctx) => {
260
+ const publicDir = _internals.findPublicDir(ctx.outDir);
261
+ const serving = staticDir({
262
+ id: `${id}-static-serve`,
263
+ root: publicDir
264
+ });
265
+ return serving.start(ctx);
266
+ }
267
+ });
268
+ recipe.root = root;
269
+ return recipe;
270
+ }
271
+ const recipe = defineRecipe({
272
+ id,
273
+ share: "always",
274
+ hashInputs: async () => _internals.resolveHashInputs(opts, root),
275
+ ready: async () => {},
276
+ prepare: async ({ outDir }) => {
277
+ ensureRoot();
278
+ await _internals.withEnv(opts.env, () => _internals.buildNuxtApp(root, outDir, mergeNuxtConfig(opts), true));
279
+ },
280
+ start: async ({ port, host, outDir }) => {
281
+ ensureRoot();
282
+ const entry = _internals.findServerEntry(outDir);
283
+ const managed = spawnManaged(process.execPath, [entry], {
284
+ cwd: root,
285
+ env: {
286
+ ...process.env,
287
+ ...startEnv,
288
+ PORT: String(port),
289
+ HOST: host,
290
+ NODE_ENV: "production"
291
+ },
292
+ captureLogs: true
293
+ });
294
+ const url = loopbackUrl(port, "/", host);
295
+ try {
296
+ await waitForHttpReady(url, { timeoutMs: opts.readyTimeoutMs ?? 6e4 });
297
+ } catch (e) {
298
+ await managed.stop();
299
+ throw new Error(`[untestutils/nuxt] ready failed:\n${e}\n--- logs ---\n${managed.logs().slice(-4e3)}`);
300
+ }
301
+ return {
302
+ kind: "url+dir",
303
+ url,
304
+ dir: outDir,
305
+ stop: managed.stop,
306
+ pid: managed.pid
307
+ };
308
+ }
309
+ });
310
+ recipe.root = root;
311
+ return recipe;
312
+ }
313
+ export { waitForHttpReady };
314
+
315
+ export function matrix(base, variants) {
316
+ if (!base.root) {
317
+ throw new Error("[untestutils/nuxt] matrix() requires base.root");
318
+ }
319
+ const baseId = base.id ?? `nuxt-${base.run ?? "server"}`;
320
+ const out = {};
321
+ for (const [name, patch] of Object.entries(variants)) {
322
+ const id = name === "default" ? baseId : `${baseId}__${name}`;
323
+ if (out[id]) {
324
+ throw new Error(`[untestutils/nuxt] matrix() duplicate id "${id}"`);
325
+ }
326
+ const baseNitro = base.nuxtConfig?.nitro ?? {};
327
+ const patchNitro = patch.nuxtConfig?.nitro ?? {};
328
+ const merged = {
329
+ ...base,
330
+ ...patch,
331
+ id,
332
+ env: {
333
+ ...base.env ?? {},
334
+ ...patch.env ?? {}
335
+ },
336
+ nuxtConfig: {
337
+ ...base.nuxtConfig ?? {},
338
+ ...patch.nuxtConfig ?? {},
339
+ nitro: {
340
+ ...baseNitro,
341
+ ...patchNitro
342
+ }
343
+ },
344
+ hashInputs: [
345
+ ...base.hashInputs ?? [base.root],
346
+ ...patch.hashInputs ?? [],
347
+ `variant:${name}`
348
+ ]
349
+ };
350
+ out[id] = nuxt(merged);
351
+ }
352
+ return out;
353
+ }
354
+
355
+ export default nuxt;
@@ -0,0 +1,3 @@
1
+ import type { NuxtModule } from "@nuxt/schema";
2
+ declare const untestutilsModule: NuxtModule;
3
+ export default untestutilsModule;
@@ -0,0 +1,4 @@
1
+ import type { NuxtModule } from '@nuxt/schema';
2
+ declare const untestutilsModule: NuxtModule;
3
+ export default untestutilsModule;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/module/index.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAA8B,UAAU,EAAE,MAAM,cAAc,CAAC;AAgW3E,QAAA,MAAM,iBAAiB,EAAE,UA4DvB,CAAC;AAEH,eAAe,iBAAiB,CAAC"}