webanvil 0.0.9 → 0.0.11

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/README.md CHANGED
@@ -56,17 +56,17 @@ wa typecheck # type-check the project
56
56
  What it includes
57
57
  ----------------
58
58
 
59
- | Project job | WebAnvil command | Tool |
60
- | -------------------------- | ------------------------------------------------------ | -------------------------------- |
61
- | Web builds and development | `wa build`, `wa dev`, `wa preview` | Vite |
62
- | Node builds and watch mode | `wa build`, `wa dev` | Rolldown |
63
- | Storybook development | `wa build --mode storybook`, `wa dev --mode storybook` | Storybook |
64
- | Tracked output cleanup | `wa clean` | WebAnvil |
65
- | Static checks | `wa check` | Oxfmt, Oxlint, TypeScript Native |
66
- | Tests | `wa test` | Vitest |
67
- | Linting | `wa lint` | Oxlint |
68
- | Formatting | `wa format` | Oxfmt |
69
- | Type checking | `wa typecheck` | TypeScript Native |
59
+ | Project job | WebAnvil command | Tool |
60
+ | -------------------------- | ---------------------------------- | -------------------------------- |
61
+ | Web builds and development | `wa build`, `wa dev`, `wa preview` | Vite |
62
+ | Node builds and watch mode | `wa build`, `wa dev` | Rolldown |
63
+ | Design-system Storybook | `wa build`, `wa dev`, `wa preview` | Storybook |
64
+ | Tracked output cleanup | `wa clean` | WebAnvil |
65
+ | Static checks | `wa check` | Oxfmt, Oxlint, TypeScript Native |
66
+ | Tests | `wa test` | Vitest |
67
+ | Linting | `wa lint` | Oxlint |
68
+ | Formatting | `wa format` | Oxfmt |
69
+ | Type checking | `wa typecheck` | TypeScript Native |
70
70
 
71
71
  Getting started
72
72
  ---------------
@@ -159,46 +159,41 @@ WebAnvil includes Storybook, the supported Vite framework adapters, Vitest's
159
159
  browser support, and Chromium. Add only a Storybook configuration and your
160
160
  project's normal framework dependencies.
161
161
 
162
- For example, a Svelte project can use this `.storybook/main.ts`:
163
-
164
- ```ts
165
- import { framework, type StorybookConfig } from "webanvil/storybook/svelte"
166
-
167
- export default {
168
- framework,
169
- stories: ["../src/**/*.stories.@(js|ts|svelte)"]
170
- } satisfies StorybookConfig
171
- ```
172
-
173
- Use `webanvil/storybook/react`, `webanvil/storybook/vue`, or
174
- `webanvil/storybook/web-components` for those frameworks. The WebAnvil wrapper
175
- uses the framework plugins already declared in `webanvil.config.*`.
176
-
177
- Set Storybook as the project's build mode when it is the primary development
178
- target:
162
+ For a Node design-system project, configure Storybook beside the package build:
179
163
 
180
164
  ```ts
165
+ import { svelte } from "@sveltejs/vite-plugin-svelte"
181
166
  import { defineConfig } from "webanvil"
182
167
 
183
168
  export default defineConfig({
184
- build: { mode: "storybook" }
169
+ build: { mode: "node", entries: { ".": "src/index.ts" }, outDir: "dist" },
170
+ storybook: { framework: "svelte", port: 6006, outDir: "storybook-static" },
171
+ vite: { plugins: [svelte()] }
185
172
  })
186
173
  ```
187
174
 
188
- Or select it for one command:
175
+ Then keep `.storybook/main.ts` focused on stories and addons:
189
176
 
190
- ```sh
191
- wa dev --mode storybook
192
- wa build --mode storybook
177
+ ```ts
178
+ import type { StorybookConfig } from "webanvil/storybook/svelte"
179
+
180
+ export default {
181
+ stories: ["../src/**/*.stories.@(js|ts|svelte)"],
182
+ addons: ["@storybook/addon-a11y"]
183
+ } satisfies StorybookConfig
193
184
  ```
194
185
 
195
- The build writes to `storybook-static` unless `storybook.outDir` in
196
- `webanvil.config.*` or `--out-dir` selects another directory. `wa test`
197
- automatically runs Storybook stories, including `play` functions, when it finds
198
- `.storybook/main.*`. Set `storybook.test: false` to exclude them. Chromium is
199
- downloaded by `@playwright/browser-chromium` when your package manager runs
200
- install scripts. If the project declares Vitest, Storybook tests require
201
- version `4.1.10` to match the bundled browser provider.
186
+ `wa dev` starts the package watcher, waits for its first successful build, then
187
+ starts Storybook. `wa build` creates the package output and static Storybook.
188
+ `wa preview` serves the static Storybook output. `wa clean` removes both sets of
189
+ tracked files. `--host` and `--port` on `wa dev` configure Storybook. The
190
+ package build still owns `--out-dir`.
191
+
192
+ Set `storybook.test: false` to exclude Storybook stories, including `play`
193
+ functions, from `wa test`. Chromium is downloaded by
194
+ `@playwright/browser-chromium` when your package manager runs install scripts.
195
+ If the project declares Vitest, Storybook tests require version `4.1.10` to
196
+ match the bundled browser provider.
202
197
 
203
198
  ### A Node project
204
199
 
@@ -534,16 +529,16 @@ That lets a project standardize on `wa` now and move settings into `webanvil.con
534
529
  Command reference
535
530
  -----------------
536
531
 
537
- | Command | Description | Options |
538
- | ------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
539
- | `wa build [entry]` | Builds with Vite in web mode, Rolldown in Node mode, or Storybook. | `--mode`, `--out-dir`, `--copy`, `--bundle`, `--no-bundle`, `--formats`, `--declaration`, `--sourcemap`, `--minify`, `--platform`, `--target` |
540
- | `wa clean` | Removes files emitted by prior WebAnvil builds. | No options |
541
- | `wa check` | Checks formatting, linting, and types, stopping on the first failure. | `--fix` |
542
- | `wa dev [entry]` | Starts Vite, a Node build watcher, or Storybook. | `--mode`, `--out-dir`, `--host`, `--port`, `--copy`, `--bundle`, `--no-bundle`, `--formats`, `--declaration`, `--sourcemap`, `--minify`, `--platform`, `--target` |
543
- | `wa preview` | Serves a Vite production build. | `--out-dir`, `--host`, `--port`, `--open` |
544
- | `wa test [filters...]` | Runs Vitest once, in watch mode, with coverage, or UI. | `--environment`, `--watch`, `--coverage`, `--ui`, `--ui-port` |
545
- | `wa lint [paths...]` | Runs Oxlint and treats warnings as failures. | `--fix` |
546
- | `wa format [paths...]` | Formats with Oxfmt. | `--check` |
547
- | `wa typecheck [paths...]` | Type-checks with TypeScript Native. | No options |
532
+ | Command | Description | Options |
533
+ | ------------------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
534
+ | `wa build [entry]` | Builds with Vite in web mode or Rolldown in Node mode. A configured Storybook builds with a Node project. | `--mode`, `--out-dir`, `--copy`, `--bundle`, `--no-bundle`, `--formats`, `--declaration`, `--sourcemap`, `--minify`, `--platform`, `--target` |
535
+ | `wa clean` | Removes files emitted by prior WebAnvil builds. | No options |
536
+ | `wa check` | Checks formatting, linting, and types, stopping on the first failure. | `--fix` |
537
+ | `wa dev [entry]` | Starts Vite or a Node build watcher. A configured Storybook starts with the Node watcher. | `--mode`, `--out-dir`, `--host`, `--port`, `--copy`, `--bundle`, `--no-bundle`, `--formats`, `--declaration`, `--sourcemap`, `--minify`, `--platform`, `--target` |
538
+ | `wa preview` | Serves a Vite production build or configured static Storybook output. | `--out-dir`, `--host`, `--port`, `--open` |
539
+ | `wa test [filters...]` | Runs Vitest once, in watch mode, with coverage, or UI. | `--environment`, `--watch`, `--coverage`, `--ui`, `--ui-port` |
540
+ | `wa lint [paths...]` | Runs Oxlint and treats warnings as failures. | `--fix` |
541
+ | `wa format [paths...]` | Formats with Oxfmt. | `--check` |
542
+ | `wa typecheck [paths...]` | Type-checks with TypeScript Native. | No options |
548
543
 
549
544
  Run `wa <command> --help` for the complete reference for a command.
@@ -1,9 +1,9 @@
1
- import { assertSyntaxTarget, loadConfig, resolveEffectiveBuildConfig, resolveRolldownPlugins, resolveVitePlugins, syntaxTargetSchema, withConfig } from "./config.mjs";
1
+ import { assertSyntaxTarget, effectiveUserConfigSchema, hasOxcConfig, hasStorybookConfig, hasToolConfig, loadConfig, resolveEffectiveBuildConfig, resolveRolldownPlugins, resolveVitePlugins, syntaxTargetSchema, withConfig } from "./config.mjs";
2
2
  import { Module, createRequire, isBuiltin } from "node:module";
3
3
  import { defineArgument, defineCommand, defineOption } from "cmdore";
4
4
  import { dirname, isAbsolute, join, relative, resolve } from "pathe";
5
5
  import { glob } from "tinyglobby";
6
- import { access, copyFile, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, rmdir, writeFile } from "node:fs/promises";
6
+ import { access, copyFile, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, rmdir, symlink, writeFile } from "node:fs/promises";
7
7
  import { z } from "zod";
8
8
  import { dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1 } from "node:path";
9
9
  import { getTsconfig, readTsconfig } from "get-tsconfig";
@@ -31,30 +31,6 @@ const paths = defineArgument({
31
31
  description: "Files or directories to check.",
32
32
  variadic: true
33
33
  });
34
- const hasFile = async (path) => access(path).then(() => true).catch(() => false);
35
- const hasToolConfig = async (name, cwd = process.cwd()) => {
36
- const files = await readdir(cwd).catch(() => []);
37
- return [
38
- "js",
39
- "mjs",
40
- "cjs",
41
- "ts",
42
- "mts",
43
- "cts"
44
- ].some((extension) => files.includes(`${name}.config.${extension}`));
45
- };
46
- const hasOxcConfig = (name, cwd = process.cwd()) => hasFile(join(cwd, name === "oxfmt" ? ".oxfmtrc.json" : ".oxlintrc.json"));
47
- const hasStorybookConfig = async (configDir = ".storybook", cwd = process.cwd()) => {
48
- const files = await readdir(resolve(cwd, configDir)).catch(() => []);
49
- return [
50
- "js",
51
- "mjs",
52
- "cjs",
53
- "ts",
54
- "mts",
55
- "cts"
56
- ].some((extension) => files.includes(`main.${extension}`));
57
- };
58
34
  const path = (cwd) => resolve(cwd, ".webanvil", "buildinfo.json");
59
35
  const relativeOutput = (file, cwd) => {
60
36
  const output = relative(cwd, resolve(cwd, file));
@@ -1109,15 +1085,68 @@ const useToolExecutable = async (name, toolchain = new Toolchain(process.cwd()))
1109
1085
  return tool.executable;
1110
1086
  };
1111
1087
  const storybookVitestVersion = "4.1.10";
1088
+ const relativeModule = (from, to) => {
1089
+ const path = relative(from, to).replaceAll("\\", "/");
1090
+ return path.startsWith(".") ? path : `./${path}`;
1091
+ };
1092
+ const storybookMain = async (configDirectory) => {
1093
+ const files = await readdir(configDirectory).catch(() => []);
1094
+ const file = [
1095
+ "js",
1096
+ "mjs",
1097
+ "cjs",
1098
+ "ts",
1099
+ "mts",
1100
+ "cts"
1101
+ ].map((extension) => `main.${extension}`).find((name) => files.includes(name));
1102
+ if (file === void 0) throw new Error(`Could not find a Storybook main configuration in ${configDirectory}`);
1103
+ return resolve(configDirectory, file);
1104
+ };
1105
+ const frameworkDirectory = (framework) => fileURLToPath(new URL(`../storybook/${framework}/`, import.meta.url));
1106
+ const prepareStorybookConfig = async (config = {}) => {
1107
+ if (config.framework === void 0) return void 0;
1108
+ const sourceDirectory = resolve(process.cwd(), config.configDir ?? ".storybook");
1109
+ const main = await storybookMain(sourceDirectory);
1110
+ const directory = await mkdtemp(resolve(dirname(sourceDirectory), ".webanvil-storybook-"));
1111
+ try {
1112
+ for (const entry of await readdir(sourceDirectory, { withFileTypes: true })) {
1113
+ if (resolve(sourceDirectory, entry.name) === main) continue;
1114
+ await symlink(resolve(sourceDirectory, entry.name), resolve(directory, entry.name), entry.isDirectory() ? "junction" : "file");
1115
+ }
1116
+ await mkdir(directory, { recursive: true });
1117
+ await writeFile(resolve(directory, "main.ts"), `import config from ${JSON.stringify(relativeModule(directory, main))}\n\nexport default { ...config, framework: ${JSON.stringify(frameworkDirectory(config.framework))} }\n`);
1118
+ return {
1119
+ config: {
1120
+ ...config,
1121
+ configDir: directory
1122
+ },
1123
+ cleanup: () => rm(directory, {
1124
+ force: true,
1125
+ recursive: true
1126
+ })
1127
+ };
1128
+ } catch (error) {
1129
+ await rm(directory, {
1130
+ force: true,
1131
+ recursive: true
1132
+ });
1133
+ throw error;
1134
+ }
1135
+ };
1112
1136
  const storybookOutputDir = (config = {}, options = {}) => options.outDir ?? config.outDir ?? "storybook-static";
1113
1137
  const exitWithError = (action, exitCode) => {
1114
1138
  throw new Error(`storybook ${action} exited with code ${exitCode ?? "unknown"}`);
1115
1139
  };
1116
- const runStorybook = async (action, config = {}, options = {}, toolchain = new Toolchain(process.cwd())) => {
1140
+ const startStorybook = async (action, config = {}, options = {}, toolchain = new Toolchain(process.cwd())) => {
1117
1141
  const executable = await useToolExecutable("storybook", toolchain);
1118
- const configDir = options.configDir ?? config.configDir;
1119
- const outDir = storybookOutputDir(config, options);
1120
- const result = await execa(executable, [
1142
+ const prepared = await prepareStorybookConfig({
1143
+ ...config,
1144
+ configDir: options.configDir ?? config.configDir
1145
+ });
1146
+ const effective = prepared?.config ?? config;
1147
+ const configDir = effective.configDir;
1148
+ const outDir = storybookOutputDir(effective, options);
1149
+ const child = execa(executable, [
1121
1150
  action,
1122
1151
  ...configDir === void 0 ? [] : ["--config-dir", configDir],
1123
1152
  ...action === "dev" && options.host !== void 0 ? ["--host", options.host] : [],
@@ -1127,8 +1156,16 @@ const runStorybook = async (action, config = {}, options = {}, toolchain = new T
1127
1156
  reject: false,
1128
1157
  stdio: "inherit"
1129
1158
  });
1130
- if (result.exitCode !== 0) exitWithError(action, result.exitCode);
1159
+ return {
1160
+ completed: child.then((result) => {
1161
+ if (result.exitCode !== 0) exitWithError(action, result.exitCode);
1162
+ }, (error) => {
1163
+ throw error;
1164
+ }).finally(() => prepared?.cleanup()),
1165
+ stop: () => child.kill("SIGTERM")
1166
+ };
1131
1167
  };
1168
+ const runStorybook = async (action, config = {}, options = {}, toolchain = new Toolchain(process.cwd())) => (await startStorybook(action, config, options, toolchain)).completed;
1132
1169
  const createStorybookTestProject = async (config = {}, vitestVersion = storybookVitestVersion) => {
1133
1170
  if (vitestVersion !== storybookVitestVersion) throw new Error(`Storybook tests require Vitest ${storybookVitestVersion}; Webanvil bundles @vitest/browser-playwright ${storybookVitestVersion}`);
1134
1171
  const previousVitest = process.env.VITEST;
@@ -1217,13 +1254,9 @@ const minify = defineOption({
1217
1254
  });
1218
1255
  const mode = defineOption({
1219
1256
  name: "mode",
1220
- description: "Build mode: web uses Vite, node uses Rolldown, and storybook runs Storybook.",
1257
+ description: "Build mode: web uses Vite and node uses Rolldown.",
1221
1258
  arity: 1,
1222
- schema: z.enum([
1223
- "web",
1224
- "node",
1225
- "storybook"
1226
- ])
1259
+ schema: z.enum(["web", "node"])
1227
1260
  });
1228
1261
  const open = defineOption({
1229
1262
  name: "open",
@@ -1287,24 +1320,6 @@ const noBundle$1 = defineOption({
1287
1320
  description: "Emit the reachable Node graph with preserveModules, overriding configuration that enables bundling.",
1288
1321
  arity: 0
1289
1322
  });
1290
- const assertStorybookArguments$1 = (explicit) => {
1291
- const option = [
1292
- "bundle",
1293
- "copy",
1294
- "declaration",
1295
- "entry",
1296
- "formats",
1297
- "minify",
1298
- "no-bundle",
1299
- "platform",
1300
- "sourcemap",
1301
- "target"
1302
- ].find((name) => {
1303
- const value = explicit[name];
1304
- return name === "bundle" || name === "no-bundle" ? value === true : value !== void 0;
1305
- });
1306
- if (option !== void 0) throw new Error(`--${option} is not available in Storybook mode`);
1307
- };
1308
1323
  const outputFiles = (result, outDir) => {
1309
1324
  if ("on" in result) throw new Error("Web builds cannot use watch mode");
1310
1325
  return (Array.isArray(result) ? result : [result]).flatMap((output) => output.output.map((file) => resolve(outDir, file.fileName)));
@@ -1380,22 +1395,27 @@ build.publicOutputFiles = async ({ outDir, publicDir }) => publicDir ? (await gl
1380
1395
  dot: true
1381
1396
  })).map((file) => resolve(outDir, file)) : [];
1382
1397
  build.web = async (web) => [...outputFiles(await web.vite.build(web.config), web.outDir), ...await build.publicOutputFiles(web)];
1398
+ const outputContains = (directory, path) => {
1399
+ const location = relative(directory, path);
1400
+ return location === "" || location !== ".." && !location.startsWith("../");
1401
+ };
1402
+ const assertSeparateStorybookOutput = (outDir, storybook) => {
1403
+ const output = resolve(process.cwd(), outDir);
1404
+ const storybookOutput = resolve(process.cwd(), storybookOutputDir(storybook));
1405
+ if (outputContains(output, storybookOutput) || outputContains(storybookOutput, output)) throw new Error("storybook.outDir must not overlap build.outDir");
1406
+ };
1407
+ const buildStorybook = async (storybook, toolchain) => {
1408
+ const outputDirectory = storybookOutputDir(storybook);
1409
+ const existing = await removeOutputsIn(outputDirectory);
1410
+ await runStorybook("build", storybook, {}, toolchain);
1411
+ const output = await glob("**/*", {
1412
+ cwd: resolve(process.cwd(), outputDirectory),
1413
+ dot: true,
1414
+ onlyFiles: true
1415
+ });
1416
+ await writeBuildInfo([...existing.output, ...output.map((file) => resolve(outputDirectory, file))]);
1417
+ };
1383
1418
  const commandRun$1 = (toolchain) => withConfig((config) => config.build, ({ copy, declaration, formats, minify, mode, entry, "out-dir": outDir, platform, sourcemap, target }, buildConfig, resolvedConfig, explicit) => {
1384
- if (mode === "storybook") {
1385
- assertStorybookArguments$1(explicit);
1386
- const storybookOptions = { outDir: explicit["out-dir"] === void 0 ? void 0 : outDir };
1387
- const outputDirectory = storybookOutputDir(resolvedConfig.storybook, storybookOptions);
1388
- return (async () => {
1389
- const existing = await removeOutputsIn(outputDirectory);
1390
- await runStorybook("build", resolvedConfig.storybook, storybookOptions, toolchain);
1391
- const output = await glob("**/*", {
1392
- cwd: resolve(process.cwd(), outputDirectory),
1393
- dot: true,
1394
- onlyFiles: true
1395
- });
1396
- await writeBuildInfo([...existing.output, ...output.map((file) => resolve(outputDirectory, file))]);
1397
- })();
1398
- }
1399
1419
  if (explicit.bundle && explicit["no-bundle"]) throw new Error("--bundle and --no-bundle cannot be used together");
1400
1420
  const effective = resolveEffectiveBuildConfig(resolvedConfig, {
1401
1421
  bundle: explicit["no-bundle"] ? false : explicit.bundle ? true : buildConfig.bundle,
@@ -1413,13 +1433,17 @@ const commandRun$1 = (toolchain) => withConfig((config) => config.build, ({ copy
1413
1433
  }, explicit.entry !== void 0);
1414
1434
  const executableMode = effective.mode;
1415
1435
  if (executableMode !== "web" && executableMode !== "node") throw new Error("Expected a web or Node build mode");
1416
- return build(executableMode, effective.entry, effective.outDir, effective, resolvedConfig.plugins ?? [], resolvedConfig.vite, resolvedConfig.rolldown, toolchain, {
1417
- ...explicit.entry === void 0 ? {} : { entry },
1418
- ...explicit["out-dir"] === void 0 ? {} : { outDir },
1419
- ...explicit.minify === void 0 ? {} : { minify },
1420
- ...explicit.sourcemap === void 0 ? {} : { sourcemap },
1421
- ...explicit.target === void 0 ? {} : { target }
1422
- });
1436
+ return (async () => {
1437
+ if (resolvedConfig.storybook !== void 0) assertSeparateStorybookOutput(effective.outDir, resolvedConfig.storybook);
1438
+ await build(executableMode, effective.entry, effective.outDir, effective, resolvedConfig.plugins ?? [], resolvedConfig.vite, resolvedConfig.rolldown, toolchain, {
1439
+ ...explicit.entry === void 0 ? {} : { entry },
1440
+ ...explicit["out-dir"] === void 0 ? {} : { outDir },
1441
+ ...explicit.minify === void 0 ? {} : { minify },
1442
+ ...explicit.sourcemap === void 0 ? {} : { sourcemap },
1443
+ ...explicit.target === void 0 ? {} : { target }
1444
+ });
1445
+ if (resolvedConfig.storybook !== void 0) await buildStorybook(resolvedConfig.storybook, toolchain);
1446
+ })();
1423
1447
  });
1424
1448
  var build_default = defineCommand({
1425
1449
  name: "build",
@@ -1438,11 +1462,11 @@ var build_default = defineCommand({
1438
1462
  target
1439
1463
  ],
1440
1464
  run: async (arguments_) => {
1441
- const config = arguments_.mode === void 0 ? (await loadConfig()).config : void 0;
1442
- const selectedMode = arguments_.mode ?? config?.build?.mode;
1465
+ const configured = arguments_.mode === void 0 ? (await loadConfig()).config : void 0;
1443
1466
  const toolchain = new Toolchain(process.cwd());
1444
- if (selectedMode === "storybook") await toolchain.resolve("storybook");
1445
- else await Promise.all([toolchain.resolve("vite"), toolchain.resolve("rolldown")]);
1467
+ await Promise.all([toolchain.resolve("vite"), toolchain.resolve("rolldown")]);
1468
+ const config = configured ?? (await loadConfig()).config;
1469
+ if (config.storybook !== void 0) await toolchain.resolve("storybook");
1446
1470
  return commandRun$1(toolchain)(arguments_, config);
1447
1471
  }
1448
1472
  });
@@ -1628,25 +1652,6 @@ const noBundle = defineOption({
1628
1652
  description: "Emit the reachable Node graph with preserveModules, overriding configuration that enables bundling.",
1629
1653
  arity: 0
1630
1654
  });
1631
- const assertStorybookArguments = (explicit) => {
1632
- const option = [
1633
- "bundle",
1634
- "copy",
1635
- "declaration",
1636
- "entry",
1637
- "formats",
1638
- "minify",
1639
- "no-bundle",
1640
- "out-dir",
1641
- "platform",
1642
- "sourcemap",
1643
- "target"
1644
- ].find((name) => {
1645
- const value = explicit[name];
1646
- return name === "bundle" || name === "no-bundle" ? value === true : value !== void 0;
1647
- });
1648
- if (option !== void 0) throw new Error(`--${option} is not available in Storybook mode`);
1649
- };
1650
1655
  const dev = async (mode, entry, outDir, host, port, plugins = [], options = {}, viteConfig = {}, rolldownConfig = {}, toolchain = new Toolchain(process.cwd())) => {
1651
1656
  assertSyntaxTarget(options.target);
1652
1657
  if (mode === "web" && options.platform !== void 0) throw new Error("Web development does not accept platform; platform applies only to Node builds");
@@ -1678,7 +1683,7 @@ dev.web = async (host, port, plugins = [], waitForTermination = untilTerminated,
1678
1683
  await server.close();
1679
1684
  }
1680
1685
  };
1681
- dev.node = async (entry, outDir, plugins = [], waitForTermination = untilTerminated, options = {}, rolldownConfig = {}, toolchain = new Toolchain(process.cwd())) => {
1686
+ dev.node = async (entry, outDir, plugins = [], waitForTermination = untilTerminated, options = {}, rolldownConfig = {}, toolchain = new Toolchain(process.cwd()), onBuild) => {
1682
1687
  assertSyntaxTarget(options.target);
1683
1688
  const rolldown = await useToolApi("rolldown", void 0, toolchain);
1684
1689
  const plan = await createNodeBuildPlan(entry, outDir, options, resolveRolldownPlugins(plugins), rolldownConfig, toolchain);
@@ -1700,7 +1705,10 @@ dev.node = async (entry, outDir, plugins = [], waitForTermination = untilTermina
1700
1705
  }
1701
1706
  if (event.code === "BUNDLE_END") await event.result.close();
1702
1707
  if (event.code === "END" && !failed) try {
1703
- if (await lifecycle.complete() !== void 0) logger.success(`Built ${entry} to ${outDir}`);
1708
+ if (await lifecycle.complete() !== void 0) {
1709
+ logger.success(`Built ${entry} to ${outDir}`);
1710
+ await onBuild?.();
1711
+ }
1704
1712
  } catch (error) {
1705
1713
  logger.error(error);
1706
1714
  }
@@ -1718,14 +1726,31 @@ dev.node = async (entry, outDir, plugins = [], waitForTermination = untilTermina
1718
1726
  await watcher.close();
1719
1727
  }
1720
1728
  };
1721
- const commandRun = (toolchain) => withConfig((config) => config.build, ({ copy, declaration, formats, minify, mode, entry, "out-dir": outDir, host, platform, port, sourcemap, target }, buildConfig, resolvedConfig, explicit) => {
1722
- if (mode === "storybook") {
1723
- assertStorybookArguments(explicit);
1724
- return runStorybook("dev", resolvedConfig.storybook, {
1725
- host,
1726
- port
1729
+ const devWithStorybook = async (entry, outDir, storybook, host, port, plugins = [], options = {}, rolldownConfig = {}, toolchain = new Toolchain(process.cwd()), waitForTermination = untilTerminated) => {
1730
+ let stopNodeWatcher = () => {};
1731
+ const nodeStopped = new Promise((resolve) => {
1732
+ stopNodeWatcher = resolve;
1733
+ });
1734
+ let initialBuild = () => {};
1735
+ const built = new Promise((resolve) => {
1736
+ initialBuild = resolve;
1737
+ });
1738
+ const node = dev.node(entry, outDir, plugins, () => nodeStopped, options, rolldownConfig, toolchain, initialBuild);
1739
+ let storybookProcess;
1740
+ try {
1741
+ await Promise.race([built, node]);
1742
+ storybookProcess = await startStorybook("dev", storybook, {
1743
+ host: host ?? storybook.host,
1744
+ port: port ?? storybook.port
1727
1745
  }, toolchain);
1746
+ if (await Promise.race([waitForTermination().then(() => "terminated"), storybookProcess.completed.then(() => "storybook")]) === "storybook") throw new Error("Storybook development stopped");
1747
+ } finally {
1748
+ stopNodeWatcher();
1749
+ storybookProcess?.stop();
1750
+ await Promise.allSettled([node, ...storybookProcess === void 0 ? [] : [storybookProcess.completed]]);
1728
1751
  }
1752
+ };
1753
+ const commandRun = (toolchain) => withConfig((config) => config.build, ({ copy, declaration, formats, minify, mode, entry, "out-dir": outDir, host, platform, port, sourcemap, target }, buildConfig, resolvedConfig, explicit) => {
1729
1754
  if (explicit.bundle && explicit["no-bundle"]) throw new Error("--bundle and --no-bundle cannot be used together");
1730
1755
  const effective = resolveEffectiveBuildConfig(resolvedConfig, {
1731
1756
  bundle: explicit["no-bundle"] ? false : explicit.bundle ? true : buildConfig.bundle,
@@ -1743,6 +1768,7 @@ const commandRun = (toolchain) => withConfig((config) => config.build, ({ copy,
1743
1768
  }, explicit.entry !== void 0);
1744
1769
  const executableMode = effective.mode;
1745
1770
  if (executableMode !== "web" && executableMode !== "node") throw new Error("Expected a web or Node build mode");
1771
+ if (resolvedConfig.storybook !== void 0) return devWithStorybook(effective.entry, effective.outDir, resolvedConfig.storybook, explicit.host === void 0 ? void 0 : host, explicit.port === void 0 ? void 0 : port, resolvedConfig.plugins ?? [], effective, resolvedConfig.rolldown, toolchain);
1746
1772
  return dev(executableMode, effective.entry, effective.outDir, host, port, resolvedConfig.plugins ?? [], effective, resolvedConfig.vite, resolvedConfig.rolldown, toolchain);
1747
1773
  });
1748
1774
  var dev_default = defineCommand({
@@ -1764,11 +1790,11 @@ var dev_default = defineCommand({
1764
1790
  target
1765
1791
  ],
1766
1792
  run: async (arguments_) => {
1767
- const config = arguments_.mode === void 0 ? (await loadConfig()).config : void 0;
1768
- const selectedMode = arguments_.mode ?? config?.build?.mode;
1793
+ const configured = arguments_.mode === void 0 ? (await loadConfig()).config : void 0;
1769
1794
  const toolchain = new Toolchain(process.cwd());
1770
- if (selectedMode === "storybook") await toolchain.resolve("storybook");
1771
- else await Promise.all([toolchain.resolve("vite"), toolchain.resolve("rolldown")]);
1795
+ await Promise.all([toolchain.resolve("vite"), toolchain.resolve("rolldown")]);
1796
+ const config = configured ?? (await loadConfig()).config;
1797
+ if (config.storybook !== void 0) await toolchain.resolve("storybook");
1772
1798
  return commandRun(toolchain)(arguments_, config);
1773
1799
  }
1774
1800
  });
@@ -1808,7 +1834,9 @@ var preview_default = defineCommand({
1808
1834
  run: async ({ "out-dir": outDir, host, port, open }) => {
1809
1835
  await useTool("vite");
1810
1836
  const { config } = await loadConfig();
1811
- return preview(outDir ?? config.build?.outDir ?? "dist", host, port, outDir !== void 0, untilTerminated, open, config.vite);
1837
+ resolveEffectiveBuildConfig(config, {}, false);
1838
+ const storybook = config.storybook;
1839
+ return preview(outDir ?? (storybook === void 0 ? config.build?.outDir ?? "dist" : storybookOutputDir(storybook)), host, port, outDir !== void 0 || storybook !== void 0, untilTerminated, open, config.vite);
1812
1840
  }
1813
1841
  });
1814
1842
  const test = async (filters, config = {}, options = {}, waitForTermination = untilTerminated, storybook = {}) => {
@@ -1821,57 +1849,65 @@ const test = async (filters, config = {}, options = {}, waitForTermination = unt
1821
1849
  const nativeConfig = hasVitestConfig ? {} : config;
1822
1850
  const nativeCoverage = typeof nativeConfig.coverage === "object" && nativeConfig.coverage !== null ? nativeConfig.coverage : {};
1823
1851
  const nativeApi = typeof nativeConfig.api === "object" && nativeConfig.api !== null ? nativeConfig.api : {};
1824
- const storybookProject = storybook.test === false || !await hasStorybookConfig(storybook.configDir) ? void 0 : await createStorybookTestProject(storybook, vitestTool.version);
1825
- const vitestOptions = {
1826
- ...nativeConfig,
1827
- passWithNoTests: true,
1828
- run: !persistent,
1829
- watch: persistent,
1830
- ...options.environment === void 0 ? {} : { environment: options.environment },
1831
- ...options.coverage ? { coverage: {
1832
- ...nativeCoverage,
1833
- enabled: true,
1834
- provider: "v8"
1835
- } } : {},
1836
- ...options.ui ? { ui: true } : {},
1837
- ...options.uiPort === void 0 ? {} : { api: {
1838
- ...nativeApi,
1839
- host: "127.0.0.1",
1840
- port: options.uiPort,
1841
- strictPort: true
1842
- } }
1843
- };
1844
- const vitests = hasVitestConfig && storybookProject !== void 0 ? [await startVitest("test", filters, vitestOptions), await startVitest("test", filters, {
1845
- ...vitestOptions,
1846
- ...options.ui ? {
1847
- api: false,
1848
- ui: false
1849
- } : {},
1850
- projects: [storybookProject]
1851
- })] : [await startVitest("test", filters, {
1852
- ...vitestOptions,
1853
- ...storybookProject === void 0 ? {} : { projects: [...nativeConfig.projects ?? [], storybookProject] }
1854
- })];
1855
- if (persistent) {
1856
- try {
1857
- await waitForTermination();
1858
- } finally {
1859
- await Promise.all(vitests.map((vitest) => vitest.close()));
1852
+ const preparedStorybook = storybook.framework === void 0 || storybook.test === false || !await hasStorybookConfig(storybook.configDir) ? void 0 : await prepareStorybookConfig(storybook);
1853
+ try {
1854
+ const storybookProject = preparedStorybook === void 0 ? void 0 : await createStorybookTestProject(preparedStorybook.config, vitestTool.version);
1855
+ const vitestOptions = {
1856
+ ...nativeConfig,
1857
+ passWithNoTests: true,
1858
+ run: !persistent,
1859
+ watch: persistent,
1860
+ ...options.environment === void 0 ? {} : { environment: options.environment },
1861
+ ...options.coverage ? { coverage: {
1862
+ ...nativeCoverage,
1863
+ enabled: true,
1864
+ provider: "v8"
1865
+ } } : {},
1866
+ ...options.ui ? { ui: true } : {},
1867
+ ...options.uiPort === void 0 ? {} : { api: {
1868
+ ...nativeApi,
1869
+ host: "127.0.0.1",
1870
+ port: options.uiPort,
1871
+ strictPort: true
1872
+ } }
1873
+ };
1874
+ const vitests = hasVitestConfig && storybookProject !== void 0 ? [await startVitest("test", filters, vitestOptions), await startVitest("test", filters, {
1875
+ ...vitestOptions,
1876
+ ...options.ui ? {
1877
+ api: false,
1878
+ ui: false
1879
+ } : {},
1880
+ projects: [storybookProject]
1881
+ })] : [await startVitest("test", filters, {
1882
+ ...vitestOptions,
1883
+ ...storybookProject === void 0 ? {} : { projects: [...nativeConfig.projects ?? [], storybookProject] }
1884
+ })];
1885
+ if (persistent) {
1886
+ try {
1887
+ await waitForTermination();
1888
+ } finally {
1889
+ await Promise.all(vitests.map((vitest) => vitest.close()));
1890
+ }
1891
+ return;
1860
1892
  }
1861
- return;
1893
+ const failed = vitests.some((vitest) => vitest.state.getFiles().some((file) => file.result?.state === "fail") || vitest.state.getUnhandledErrors().length > 0);
1894
+ await Promise.all(vitests.map((vitest) => vitest.close()));
1895
+ if (failed) throw new Error("Tests failed");
1896
+ logger.success("Tests passed");
1897
+ } finally {
1898
+ await preparedStorybook?.cleanup();
1862
1899
  }
1863
- const failed = vitests.some((vitest) => vitest.state.getFiles().some((file) => file.result?.state === "fail") || vitest.state.getUnhandledErrors().length > 0);
1864
- await Promise.all(vitests.map((vitest) => vitest.close()));
1865
- if (failed) throw new Error("Tests failed");
1866
- logger.success("Tests passed");
1867
- };
1868
- const runTest = withConfig((config) => config.test, ({ filters, environment, coverage, ui, "ui-port": uiPort, watch }, config, resolvedConfig, explicitArguments) => test(filters, config, {
1869
- coverage,
1870
- environment: explicitArguments.environment === void 0 ? void 0 : environment,
1871
- ui,
1872
- uiPort,
1873
- watch
1874
- }, untilTerminated, resolvedConfig.storybook));
1900
+ };
1901
+ const runTest = withConfig((config) => config.test, ({ filters, environment, coverage, ui, "ui-port": uiPort, watch }, config, resolvedConfig, explicitArguments) => {
1902
+ effectiveUserConfigSchema.parse(resolvedConfig);
1903
+ return test(filters, config, {
1904
+ coverage,
1905
+ environment: explicitArguments.environment === void 0 ? void 0 : environment,
1906
+ ui,
1907
+ uiPort,
1908
+ watch
1909
+ }, untilTerminated, resolvedConfig.storybook);
1910
+ });
1875
1911
  var test_default = defineCommand({
1876
1912
  name: "test",
1877
1913
  arguments: [filters],
@@ -1,6 +1,32 @@
1
+ import { join, resolve } from "pathe";
2
+ import { access, readdir } from "node:fs/promises";
1
3
  import { loadConfig } from "c12";
2
4
  import { defu } from "defu";
3
5
  import { z } from "zod";
6
+ const hasFile = async (path) => access(path).then(() => true).catch(() => false);
7
+ const hasToolConfig = async (name, cwd = process.cwd()) => {
8
+ const files = await readdir(cwd).catch(() => []);
9
+ return [
10
+ "js",
11
+ "mjs",
12
+ "cjs",
13
+ "ts",
14
+ "mts",
15
+ "cts"
16
+ ].some((extension) => files.includes(`${name}.config.${extension}`));
17
+ };
18
+ const hasOxcConfig = (name, cwd = process.cwd()) => hasFile(join(cwd, name === "oxfmt" ? ".oxfmtrc.json" : ".oxlintrc.json"));
19
+ const hasStorybookConfig = async (configDir = ".storybook", cwd = process.cwd()) => {
20
+ const files = await readdir(resolve(cwd, configDir)).catch(() => []);
21
+ return [
22
+ "js",
23
+ "mjs",
24
+ "cjs",
25
+ "ts",
26
+ "mts",
27
+ "cts"
28
+ ].some((extension) => files.includes(`main.${extension}`));
29
+ };
4
30
  const NODE_PLUGIN_ERROR = "Node builds require plugins created with definePlugin()";
5
31
  const definePlugin = (plugin, options) => ({
6
32
  rolldown: () => plugin.rolldown(options),
@@ -33,11 +59,7 @@ const syntaxTargetSchema = z.union([z.string().min(1), z.array(z.string().min(1)
33
59
  const nativeConfigSchema = () => z.custom((value) => typeof value === "object" && value !== null && !Array.isArray(value), "Expected a configuration object");
34
60
  const buildConfigSchema = z.strictObject({
35
61
  bundle: z.boolean().optional(),
36
- mode: z.enum([
37
- "web",
38
- "node",
39
- "storybook"
40
- ]).optional(),
62
+ mode: z.enum(["web", "node"]).optional(),
41
63
  entry: z.string().min(1).optional(),
42
64
  entries: z.record(z.string().min(1), z.string().min(1)).optional(),
43
65
  outDir: z.string().min(1).optional(),
@@ -60,7 +82,15 @@ const testConfigSchema = nativeConfigSchema();
60
82
  const viteConfigSchema = nativeConfigSchema();
61
83
  const storybookConfigSchema = z.strictObject({
62
84
  configDir: z.string().min(1).optional(),
85
+ framework: z.enum([
86
+ "react",
87
+ "svelte",
88
+ "vue",
89
+ "web-components"
90
+ ]).optional(),
91
+ host: z.string().min(1).optional(),
63
92
  outDir: z.string().min(1).optional(),
93
+ port: z.number().int().min(1).max(65535).optional(),
64
94
  test: z.boolean().optional()
65
95
  });
66
96
  const pluginSchema = z.custom(isWebAnvilPlugin, "Expected a Vite plugin or a WebAnvil plugin created with definePlugin()");
@@ -93,6 +123,18 @@ const effectiveUserConfigSchema = userConfigSchema.superRefine((config, context)
93
123
  message: NODE_PLUGIN_ERROR
94
124
  });
95
125
  }
126
+ if (config.storybook !== void 0) {
127
+ if (build.mode !== "node") context.addIssue({
128
+ code: "custom",
129
+ path: ["storybook"],
130
+ message: "storybook is available for Node projects; set build.mode to \"node\""
131
+ });
132
+ if (config.storybook.framework === void 0) context.addIssue({
133
+ code: "custom",
134
+ path: ["storybook", "framework"],
135
+ message: "storybook.framework selects the Storybook framework adapter"
136
+ });
137
+ }
96
138
  });
97
139
  const defaultConfig = {
98
140
  build: {
@@ -137,4 +179,4 @@ const withConfig = (select, run) => async (arguments_, resolvedConfig) => {
137
179
  ...defined(arguments_)
138
180
  }, selectedConfig, config, arguments_);
139
181
  };
140
- export { assertSyntaxTarget, buildConfigSchema, copyMappingSchema, defaultConfig, defineConfig, definePlugin, effectiveUserConfigSchema, formatConfigSchema, isUnpluginAdapter, isWebAnvilPlugin, lintConfigSchema, loadConfig$1 as loadConfig, resolveEffectiveBuildConfig, resolveRolldownPlugins, resolveVitePlugins, rolldownConfigSchema, storybookConfigSchema, syntaxTargetSchema, testConfigSchema, userConfigSchema, viteConfigSchema, withConfig };
182
+ export { assertSyntaxTarget, buildConfigSchema, copyMappingSchema, defaultConfig, defineConfig, definePlugin, effectiveUserConfigSchema, formatConfigSchema, hasOxcConfig, hasStorybookConfig, hasToolConfig, isUnpluginAdapter, isWebAnvilPlugin, lintConfigSchema, loadConfig$1 as loadConfig, resolveEffectiveBuildConfig, resolveRolldownPlugins, resolveVitePlugins, rolldownConfigSchema, storybookConfigSchema, syntaxTargetSchema, testConfigSchema, userConfigSchema, viteConfigSchema, withConfig };
@@ -1,9 +1,11 @@
1
- import { loadConfig, resolveVitePlugins } from "./config.mjs";
2
- const withProjectVitePlugins = async (config) => {
1
+ import { hasToolConfig, loadConfig, resolveVitePlugins } from "./config.mjs";
2
+ import { resolve } from "node:path";
3
+ const withProjectVitePlugins = async (config, storybookConfigDirectory = ".storybook") => {
3
4
  const { config: webanvilConfig } = await loadConfig();
5
+ const plugins = [...resolveVitePlugins(webanvilConfig.plugins ?? []), ...await hasToolConfig("vite", resolve(storybookConfigDirectory, "..")) ? [] : webanvilConfig.vite?.plugins ?? []];
4
6
  return {
5
7
  ...config,
6
- plugins: [...resolveVitePlugins(webanvilConfig.plugins ?? []), ...config.plugins ?? []]
8
+ plugins: [...plugins, ...config.plugins ?? []]
7
9
  };
8
10
  };
9
11
  export { withProjectVitePlugins };
package/dist/index.d.mts CHANGED
@@ -103,7 +103,6 @@ declare const buildConfigSchema: z.ZodObject<{
103
103
  mode: z.ZodOptional<z.ZodEnum<{
104
104
  web: "web";
105
105
  node: "node";
106
- storybook: "storybook";
107
106
  }>>;
108
107
  entry: z.ZodOptional<z.ZodString>;
109
108
  entries: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
@@ -140,7 +139,15 @@ declare const testConfigSchema: z.ZodCustom<TestUserConfig, TestUserConfig>;
140
139
  declare const viteConfigSchema: z.ZodCustom<UserConfig$1, UserConfig$1>;
141
140
  declare const storybookConfigSchema: z.ZodObject<{
142
141
  configDir: z.ZodOptional<z.ZodString>;
142
+ framework: z.ZodOptional<z.ZodEnum<{
143
+ react: "react";
144
+ svelte: "svelte";
145
+ vue: "vue";
146
+ "web-components": "web-components";
147
+ }>>;
148
+ host: z.ZodOptional<z.ZodString>;
143
149
  outDir: z.ZodOptional<z.ZodString>;
150
+ port: z.ZodOptional<z.ZodNumber>;
144
151
  test: z.ZodOptional<z.ZodBoolean>;
145
152
  }, z.core.$strict>;
146
153
  declare const userConfigSchema: z.ZodObject<{
@@ -149,7 +156,6 @@ declare const userConfigSchema: z.ZodObject<{
149
156
  mode: z.ZodOptional<z.ZodEnum<{
150
157
  web: "web";
151
158
  node: "node";
152
- storybook: "storybook";
153
159
  }>>;
154
160
  entry: z.ZodOptional<z.ZodString>;
155
161
  entries: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
@@ -177,7 +183,15 @@ declare const userConfigSchema: z.ZodObject<{
177
183
  rolldown: z.ZodOptional<z.ZodCustom<RolldownConfig, RolldownConfig>>;
178
184
  storybook: z.ZodOptional<z.ZodObject<{
179
185
  configDir: z.ZodOptional<z.ZodString>;
186
+ framework: z.ZodOptional<z.ZodEnum<{
187
+ react: "react";
188
+ svelte: "svelte";
189
+ vue: "vue";
190
+ "web-components": "web-components";
191
+ }>>;
192
+ host: z.ZodOptional<z.ZodString>;
180
193
  outDir: z.ZodOptional<z.ZodString>;
194
+ port: z.ZodOptional<z.ZodNumber>;
181
195
  test: z.ZodOptional<z.ZodBoolean>;
182
196
  }, z.core.$strict>>;
183
197
  test: z.ZodOptional<z.ZodCustom<TestUserConfig, TestUserConfig>>;
@@ -190,7 +204,6 @@ declare const effectiveUserConfigSchema: z.ZodObject<{
190
204
  mode: z.ZodOptional<z.ZodEnum<{
191
205
  web: "web";
192
206
  node: "node";
193
- storybook: "storybook";
194
207
  }>>;
195
208
  entry: z.ZodOptional<z.ZodString>;
196
209
  entries: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
@@ -218,7 +231,15 @@ declare const effectiveUserConfigSchema: z.ZodObject<{
218
231
  rolldown: z.ZodOptional<z.ZodCustom<RolldownConfig, RolldownConfig>>;
219
232
  storybook: z.ZodOptional<z.ZodObject<{
220
233
  configDir: z.ZodOptional<z.ZodString>;
234
+ framework: z.ZodOptional<z.ZodEnum<{
235
+ react: "react";
236
+ svelte: "svelte";
237
+ vue: "vue";
238
+ "web-components": "web-components";
239
+ }>>;
240
+ host: z.ZodOptional<z.ZodString>;
221
241
  outDir: z.ZodOptional<z.ZodString>;
242
+ port: z.ZodOptional<z.ZodNumber>;
222
243
  test: z.ZodOptional<z.ZodBoolean>;
223
244
  }, z.core.$strict>>;
224
245
  test: z.ZodOptional<z.ZodCustom<TestUserConfig, TestUserConfig>>;
@@ -276,12 +297,11 @@ declare const build: {
276
297
  };
277
298
  declare const _default: import("cmdore").Command<readonly [{
278
299
  readonly name: "mode";
279
- readonly description: "Build mode: web uses Vite, node uses Rolldown, and storybook runs Storybook.";
300
+ readonly description: "Build mode: web uses Vite and node uses Rolldown.";
280
301
  readonly arity: 1;
281
302
  readonly schema: import("zod").ZodEnum<{
282
303
  web: "web";
283
304
  node: "node";
284
- storybook: "storybook";
285
305
  }>;
286
306
  }, {
287
307
  readonly name: "out-dir";
@@ -364,16 +384,15 @@ declare const _default$2: import("cmdore").Command<readonly import("cmdore").Opt
364
384
  declare const dev: {
365
385
  (mode: "web" | "node", entry: string, outDir: string, host?: string, port?: number, plugins?: WebAnvilPlugin[], options?: NodeBuildOptions, viteConfig?: UserConfig$1, rolldownConfig?: RolldownConfig, toolchain?: Toolchain): Promise<void>;
366
386
  web(host?: string, port?: number, plugins?: WebAnvilPlugin[], waitForTermination?: () => Promise<void>, viteConfig?: UserConfig$1, toolchain?: Toolchain): Promise<void>;
367
- node(entry: string, outDir: string, plugins?: WebAnvilPlugin[], waitForTermination?: () => Promise<void>, options?: NodeBuildOptions, rolldownConfig?: RolldownConfig, toolchain?: Toolchain): Promise<void>;
387
+ node(entry: string, outDir: string, plugins?: WebAnvilPlugin[], waitForTermination?: () => Promise<void>, options?: NodeBuildOptions, rolldownConfig?: RolldownConfig, toolchain?: Toolchain, onBuild?: () => void | Promise<void>): Promise<void>;
368
388
  };
369
389
  declare const _default$3: import("cmdore").Command<readonly [{
370
390
  readonly name: "mode";
371
- readonly description: "Build mode: web uses Vite, node uses Rolldown, and storybook runs Storybook.";
391
+ readonly description: "Build mode: web uses Vite and node uses Rolldown.";
372
392
  readonly arity: 1;
373
393
  readonly schema: import("zod").ZodEnum<{
374
394
  web: "web";
375
395
  node: "node";
376
- storybook: "storybook";
377
396
  }>;
378
397
  }, {
379
398
  readonly name: "out-dir";
@@ -598,12 +617,11 @@ declare const minify$1: {
598
617
  };
599
618
  declare const mode: {
600
619
  readonly name: "mode";
601
- readonly description: "Build mode: web uses Vite, node uses Rolldown, and storybook runs Storybook.";
620
+ readonly description: "Build mode: web uses Vite and node uses Rolldown.";
602
621
  readonly arity: 1;
603
622
  readonly schema: z.ZodEnum<{
604
623
  web: "web";
605
624
  node: "node";
606
- storybook: "storybook";
607
625
  }>;
608
626
  };
609
627
  declare const open: {
@@ -1,3 +1,6 @@
1
- import { StorybookConfig } from "@storybook/react-vite/node";
1
+ import { StorybookConfig as StorybookConfig$1 } from "@storybook/react-vite/node";
2
+ type StorybookConfig = Omit<StorybookConfig$1, "framework"> & {
3
+ framework?: StorybookConfig$1["framework"];
4
+ };
2
5
  declare const framework: string;
3
- export { type StorybookConfig, framework };
6
+ export { StorybookConfig, framework };
@@ -4,5 +4,5 @@ import { fileURLToPath } from "node:url";
4
4
  import { viteFinal as viteFinal$1 } from "@storybook/react-vite/preset";
5
5
  export * from "@storybook/react-vite/preset";
6
6
  const addons = [resolve(fileURLToPath(new URL("..", import.meta.url)), "test")];
7
- const viteFinal = async (...arguments_) => viteFinal$1(await withProjectVitePlugins(arguments_[0]), arguments_[1]);
7
+ const viteFinal = async (...arguments_) => viteFinal$1(await withProjectVitePlugins(arguments_[0], arguments_[1]?.configDir), arguments_[1]);
8
8
  export { addons, viteFinal };
@@ -4,5 +4,5 @@ import { fileURLToPath } from "node:url";
4
4
  import { viteFinal as viteFinal$1 } from "@storybook/react-vite/preset";
5
5
  export * from "@storybook/react-vite/preset";
6
6
  const addons = [resolve(fileURLToPath(new URL("..", import.meta.url)), "test")];
7
- const viteFinal = async (...arguments_) => viteFinal$1(await withProjectVitePlugins(arguments_[0]), arguments_[1]);
7
+ const viteFinal = async (...arguments_) => viteFinal$1(await withProjectVitePlugins(arguments_[0], arguments_[1]?.configDir), arguments_[1]);
8
8
  export { addons, viteFinal };
@@ -1,3 +1,6 @@
1
- import { StorybookConfig } from "@storybook/svelte-vite/node";
1
+ import { StorybookConfig as StorybookConfig$1 } from "@storybook/svelte-vite/node";
2
+ type StorybookConfig = Omit<StorybookConfig$1, "framework"> & {
3
+ framework?: StorybookConfig$1["framework"];
4
+ };
2
5
  declare const framework: string;
3
- export { type StorybookConfig, framework };
6
+ export { StorybookConfig, framework };
@@ -4,5 +4,5 @@ import { fileURLToPath } from "node:url";
4
4
  import { viteFinal as viteFinal$1 } from "@storybook/svelte-vite/preset";
5
5
  export * from "@storybook/svelte-vite/preset";
6
6
  const addons = [resolve(fileURLToPath(new URL("..", import.meta.url)), "test")];
7
- const viteFinal = async (...arguments_) => viteFinal$1(await withProjectVitePlugins(arguments_[0]), arguments_[1]);
7
+ const viteFinal = async (...arguments_) => withProjectVitePlugins(await viteFinal$1(arguments_[0], arguments_[1]), arguments_[1]?.configDir);
8
8
  export { addons, viteFinal };
@@ -4,5 +4,5 @@ import { fileURLToPath } from "node:url";
4
4
  import { viteFinal as viteFinal$1 } from "@storybook/svelte-vite/preset";
5
5
  export * from "@storybook/svelte-vite/preset";
6
6
  const addons = [resolve(fileURLToPath(new URL("..", import.meta.url)), "test")];
7
- const viteFinal = async (...arguments_) => viteFinal$1(await withProjectVitePlugins(arguments_[0]), arguments_[1]);
7
+ const viteFinal = async (...arguments_) => withProjectVitePlugins(await viteFinal$1(arguments_[0], arguments_[1]), arguments_[1]?.configDir);
8
8
  export { addons, viteFinal };
@@ -1,3 +1,6 @@
1
- import { StorybookConfig } from "@storybook/vue3-vite/node";
1
+ import { StorybookConfig as StorybookConfig$1 } from "@storybook/vue3-vite/node";
2
+ type StorybookConfig = Omit<StorybookConfig$1, "framework"> & {
3
+ framework?: StorybookConfig$1["framework"];
4
+ };
2
5
  declare const framework: string;
3
- export { type StorybookConfig, framework };
6
+ export { StorybookConfig, framework };
@@ -4,5 +4,5 @@ import { fileURLToPath } from "node:url";
4
4
  import { viteFinal as viteFinal$1 } from "@storybook/vue3-vite/preset";
5
5
  export * from "@storybook/vue3-vite/preset";
6
6
  const addons = [resolve(fileURLToPath(new URL("..", import.meta.url)), "test")];
7
- const viteFinal = async (...arguments_) => viteFinal$1(await withProjectVitePlugins(arguments_[0]), arguments_[1]);
7
+ const viteFinal = async (...arguments_) => viteFinal$1(await withProjectVitePlugins(arguments_[0], arguments_[1]?.configDir), arguments_[1]);
8
8
  export { addons, viteFinal };
@@ -4,5 +4,5 @@ import { fileURLToPath } from "node:url";
4
4
  import { viteFinal as viteFinal$1 } from "@storybook/vue3-vite/preset";
5
5
  export * from "@storybook/vue3-vite/preset";
6
6
  const addons = [resolve(fileURLToPath(new URL("..", import.meta.url)), "test")];
7
- const viteFinal = async (...arguments_) => viteFinal$1(await withProjectVitePlugins(arguments_[0]), arguments_[1]);
7
+ const viteFinal = async (...arguments_) => viteFinal$1(await withProjectVitePlugins(arguments_[0], arguments_[1]?.configDir), arguments_[1]);
8
8
  export { addons, viteFinal };
@@ -1,3 +1,6 @@
1
- import { StorybookConfig } from "@storybook/web-components-vite/node";
1
+ import { StorybookConfig as StorybookConfig$1 } from "@storybook/web-components-vite/node";
2
+ type StorybookConfig = Omit<StorybookConfig$1, "framework"> & {
3
+ framework?: StorybookConfig$1["framework"];
4
+ };
2
5
  declare const framework: string;
3
- export { type StorybookConfig, framework };
6
+ export { StorybookConfig, framework };
@@ -3,5 +3,5 @@ import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  export * from "@storybook/web-components-vite/preset";
5
5
  const addons = [resolve(fileURLToPath(new URL("..", import.meta.url)), "test")];
6
- const viteFinal = withProjectVitePlugins;
6
+ const viteFinal = async (config, options) => withProjectVitePlugins(config, options.configDir);
7
7
  export { addons, viteFinal };
@@ -1,4 +1,8 @@
1
+ import { InlineConfig } from "vite";
1
2
  export * from "@storybook/web-components-vite/preset";
3
+ declare const withProjectVitePlugins: (config: InlineConfig, storybookConfigDirectory?: string) => Promise<InlineConfig>;
2
4
  declare const addons: string[];
3
- declare const viteFinal: (config: import("vite").InlineConfig) => Promise<import("vite").InlineConfig>;
5
+ declare const viteFinal: (config: Parameters<typeof withProjectVitePlugins>[0], options: {
6
+ configDir?: string;
7
+ }) => Promise<import("vite").InlineConfig>;
4
8
  export { addons, viteFinal };
@@ -3,5 +3,5 @@ import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  export * from "@storybook/web-components-vite/preset";
5
5
  const addons = [resolve(fileURLToPath(new URL("..", import.meta.url)), "test")];
6
- const viteFinal = withProjectVitePlugins;
6
+ const viteFinal = async (config, options) => withProjectVitePlugins(config, options.configDir);
7
7
  export { addons, viteFinal };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webanvil",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "A unified CLI for building, testing, linting, formatting, and type-checking JavaScript and TypeScript projects.",
5
5
  "keywords": [
6
6
  "build-tool",