@brightspot/ui-builder 1.1.0 → 2.0.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.
@@ -1,3 +1,15 @@
1
+ import { createRequire } from 'node:module'
2
+
3
+ const require = createRequire(import.meta.url)
4
+ const plugins = ['prettier-plugin-organize-imports']
5
+
6
+ try {
7
+ require.resolve('prettier-plugin-svelte')
8
+ plugins.push('prettier-plugin-svelte')
9
+ } catch {
10
+ // prettier-plugin-svelte is an optional peer dep — skip if not installed
11
+ }
12
+
1
13
  /** @type {import('prettier').Config} */
2
14
  export default {
3
15
  arrowParens: 'avoid',
@@ -15,5 +27,5 @@ export default {
15
27
  tabWidth: 2,
16
28
  useTabs: false,
17
29
  organizeImportsSkipDestructiveCodeActions: true,
18
- plugins: ['prettier-plugin-organize-imports'],
30
+ plugins,
19
31
  }
@@ -0,0 +1,2 @@
1
+ import { type BuilderConfig } from '../lib/resolve-config.js';
2
+ export declare function loadOrExit(): BuilderConfig;
@@ -0,0 +1,23 @@
1
+ import { log } from '../lib/logger.js';
2
+ import { ConfigError, resolveConfig } from '../lib/resolve-config.js';
3
+ // CLI-side wrapper around resolveConfig: prints warnings + a one-line
4
+ // summary, and on ConfigError logs the message and exits 1. Lives next
5
+ // to commands so the lib module stays free of process termination.
6
+ export function loadOrExit() {
7
+ try {
8
+ const { config, warnings } = resolveConfig();
9
+ for (const w of warnings)
10
+ log.warn(w);
11
+ log.info(`basePath: ${config.basePath}`);
12
+ log.info(`entry: ${config.entry}`);
13
+ log.info(`format: ${config.format}${config.format === 'iife' && config.name ? ` (name: ${config.name})` : ''}`);
14
+ return config;
15
+ }
16
+ catch (e) {
17
+ if (e instanceof ConfigError) {
18
+ log.error(e.message);
19
+ process.exit(1);
20
+ }
21
+ throw e;
22
+ }
23
+ }
@@ -1,11 +1,11 @@
1
1
  import { build as viteBuild } from 'vite';
2
2
  import { log } from '../lib/logger.js';
3
- import { resolveConfig } from '../lib/resolve-config.js';
4
3
  import { createBuildConfig } from '../vite/vite-config.js';
4
+ import { loadOrExit } from './_load-config.js';
5
5
  export async function build() {
6
- log.info('Building IIFE bundle...');
7
- const { basePath } = resolveConfig();
8
- const config = createBuildConfig(basePath);
6
+ const builder = loadOrExit();
7
+ log.info(`Building ${builder.format.toUpperCase()} bundle...`);
8
+ const config = await createBuildConfig({ builder });
9
9
  await viteBuild(config);
10
- log.success('Build complete — output in dist/');
10
+ log.success(`Build complete — output in ${builder.output.dir}/`);
11
11
  }
@@ -1,14 +1,31 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
1
4
  import { createServer } from 'vite';
2
5
  import { log } from '../lib/logger.js';
3
- import { resolveConfig } from '../lib/resolve-config.js';
4
- import { resolveTarget } from '../lib/resolve-target.js';
5
6
  import { createDevConfig } from '../vite/vite-config.js';
7
+ import { loadOrExit } from './_load-config.js';
8
+ const LOCAL_URL_PATH = path.join(os.homedir(), '.brightspot', 'local-url');
9
+ const DEFAULT_TARGET = 'http://localhost';
6
10
  export async function dev(opts) {
7
- const { basePath } = resolveConfig();
8
- const target = opts.url ?? resolveTarget();
9
- const config = createDevConfig({ target, port: opts.port, basePath });
11
+ const builder = loadOrExit();
12
+ const target = opts.url ?? readLocalTarget();
13
+ const config = await createDevConfig({ builder, target, port: opts.port });
10
14
  const server = await createServer(config);
11
15
  await server.listen();
12
16
  server.printUrls();
13
17
  log.success('Dev server running — proxying to ' + target);
14
18
  }
19
+ function readLocalTarget() {
20
+ try {
21
+ const url = fs.readFileSync(LOCAL_URL_PATH, 'utf8').trim();
22
+ if (url) {
23
+ log.info(`Proxy target: ${url}`);
24
+ return url;
25
+ }
26
+ }
27
+ catch {
28
+ log.warn(`${LOCAL_URL_PATH} not found — falling back to ${DEFAULT_TARGET}`);
29
+ }
30
+ return DEFAULT_TARGET;
31
+ }
@@ -1,7 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { log } from '../lib/logger.js';
4
- import { resolveTarget } from '../lib/resolve-target.js';
5
4
  const cwd = process.cwd();
6
5
  export async function init() {
7
6
  log.info('Initializing Brightspot UI project...');
@@ -9,7 +8,6 @@ export async function init() {
9
8
  writeEslintConfig();
10
9
  patchPackageJson();
11
10
  scaffoldEntryPoint();
12
- resolveTarget();
13
11
  log.success('Project initialized. Run `brightspot-ui dev` to start developing.');
14
12
  }
15
13
  function writeTsConfig() {
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ program
18
18
  });
19
19
  program
20
20
  .command('build')
21
- .description('Build IIFE bundle for production')
21
+ .description('Build production bundle')
22
22
  .action(async () => {
23
23
  const { build } = await import('./commands/build.js');
24
24
  await build();
@@ -1,5 +1,19 @@
1
- interface BuilderConfig {
1
+ export interface BuilderConfig {
2
2
  basePath: string;
3
+ entry: string;
4
+ format: 'es' | 'iife';
5
+ name?: string;
6
+ output: {
7
+ dir: string;
8
+ jsName: string;
9
+ cssName: string;
10
+ };
3
11
  }
4
- export declare function resolveConfig(): BuilderConfig;
5
- export {};
12
+ export interface ResolveResult {
13
+ config: BuilderConfig;
14
+ warnings: string[];
15
+ }
16
+ export declare class ConfigError extends Error {
17
+ constructor(message: string);
18
+ }
19
+ export declare function resolveConfig(): ResolveResult;
@@ -1,25 +1,115 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { log } from './logger.js';
4
- const DEFAULT_BASE_PATH = '/_resource/dist/';
3
+ export class ConfigError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = 'ConfigError';
7
+ }
8
+ }
9
+ const DEFAULTS = {
10
+ entry: 'src/index.ts',
11
+ format: 'es',
12
+ output: {
13
+ dir: 'dist',
14
+ jsName: 'bundle.js',
15
+ cssName: 'style.css',
16
+ },
17
+ };
5
18
  export function resolveConfig() {
6
19
  const pkgPath = path.resolve(process.cwd(), 'package.json');
20
+ let pkg;
7
21
  try {
8
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
9
- const config = pkg['brightspot-ui'];
10
- if (config?.basePath) {
11
- const basePath = config.basePath;
12
- if (!basePath.startsWith('/') || !basePath.endsWith('/')) {
13
- log.warn(`basePath "${basePath}" must start and end with "/" — using default`);
14
- return { basePath: DEFAULT_BASE_PATH };
15
- }
16
- log.info(`basePath: ${basePath}`);
17
- return { basePath };
18
- }
22
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
19
23
  }
20
24
  catch {
21
- // package.json not found or unreadable — use defaults
25
+ throw new ConfigError(`Could not read ${pkgPath}.`);
26
+ }
27
+ const raw = pkg['brightspot-ui'] ?? {};
28
+ const warnings = [];
29
+ const pkgName = pkg.name;
30
+ const basePath = resolveBasePath(raw.basePath, pkgName, warnings);
31
+ // Normalize leading `./` so downstream consumers (the proxy regex,
32
+ // the dev-entry bootstrap import URL) get a canonical form.
33
+ const entry = (raw.entry ?? DEFAULTS.entry).replace(/^\.\//, '');
34
+ const format = resolveFormat(raw.format);
35
+ const name = resolveIifeName(raw.name, format, pkgName, warnings);
36
+ const output = {
37
+ dir: raw.output?.dir ?? DEFAULTS.output.dir,
38
+ jsName: raw.output?.jsName ?? DEFAULTS.output.jsName,
39
+ cssName: raw.output?.cssName ?? DEFAULTS.output.cssName,
40
+ };
41
+ return { config: { basePath, entry, format, name, output }, warnings };
42
+ }
43
+ function resolveBasePath(explicit, pkgName, warnings) {
44
+ if (explicit) {
45
+ if (!explicit.startsWith('/') || !explicit.endsWith('/')) {
46
+ throw new ConfigError(`basePath "${explicit}" must start and end with "/".`);
47
+ }
48
+ return explicit;
49
+ }
50
+ if (!pkgName) {
51
+ // BREAKING (2.0): pre-2.0 builds silently fell back to /_resource/dist/
52
+ // when both knobs were absent. We now exit so misconfigured monorepo
53
+ // packages don't ship to the wrong URL. Migration paths below.
54
+ throw new ConfigError('No basePath set in package.json#brightspot-ui.basePath, and no package.json#name to derive one from. ' +
55
+ 'To migrate from @brightspot/ui-builder 1.x: ' +
56
+ 'either set "name": "<slug>" in package.json (basePath will be inferred as /_resource/<slug>/cms/dist/), ' +
57
+ 'or set "brightspot-ui": { "basePath": "/_resource/<slug>/cms/dist/" } explicitly.');
58
+ }
59
+ const inferred = `/_resource/${stripScope(pkgName)}/cms/dist/`;
60
+ warnings.push(`basePath was not set — inferred "${inferred}" from package.json#name "${pkgName}". ` +
61
+ `Set "brightspot-ui": { "basePath": "..." } in package.json to silence this warning.`);
62
+ return inferred;
63
+ }
64
+ function resolveFormat(value) {
65
+ if (value === undefined)
66
+ return DEFAULTS.format;
67
+ if (value !== 'es' && value !== 'iife') {
68
+ throw new ConfigError(`format "${value}" must be "es" or "iife".`);
69
+ }
70
+ return value;
71
+ }
72
+ // Vite library mode requires `name` whenever formats include 'iife' or
73
+ // 'umd' — it's a config-time check, not a function-of-whether-the-entry-
74
+ // has-exports check. So when the consumer chooses iife and hasn't set
75
+ // name, we derive a PascalCase identifier from package.json#name
76
+ // (@brightspot/platform-tours -> PlatformTours) and warn. Returns
77
+ // undefined when format is es (name is irrelevant). Throws when iife
78
+ // is chosen but no valid identifier can be derived.
79
+ function resolveIifeName(explicit, format, pkgName, warnings) {
80
+ if (explicit !== undefined) {
81
+ if (!isValidJsIdentifier(explicit)) {
82
+ throw new ConfigError(`name "${explicit}" is not a valid JavaScript identifier — Vite library mode emits ` +
83
+ `\`var ${explicit} = ...\` for iife output, which would produce a syntax error. ` +
84
+ `Set "brightspot-ui": { "name": "..." } to a PascalCase identifier (e.g. "PlatformTours").`);
85
+ }
86
+ return explicit;
22
87
  }
23
- log.info(`basePath: ${DEFAULT_BASE_PATH} (default)`);
24
- return { basePath: DEFAULT_BASE_PATH };
88
+ if (format !== 'iife')
89
+ return undefined;
90
+ const derived = deriveIifeName(pkgName);
91
+ if (derived === null) {
92
+ throw new ConfigError(`format "iife" requires a "name" — could not derive a valid JavaScript identifier from package.json#name "${pkgName ?? '<unset>'}". ` +
93
+ `Set "brightspot-ui": { "name": "..." } in package.json (e.g. "PlatformTours").`);
94
+ }
95
+ warnings.push(`name was not set — derived "${derived}" from package.json#name "${pkgName}". ` +
96
+ `Set "brightspot-ui": { "name": "..." } in package.json to silence this warning.`);
97
+ return derived;
98
+ }
99
+ function deriveIifeName(pkgName) {
100
+ if (!pkgName)
101
+ return null;
102
+ const parts = stripScope(pkgName).split(/[-_.]/).filter(Boolean);
103
+ if (parts.length === 0)
104
+ return null;
105
+ const pascal = parts.map(p => p[0].toUpperCase() + p.slice(1)).join('');
106
+ return isValidJsIdentifier(pascal) ? pascal : null;
107
+ }
108
+ // ASCII-only by design: matches what minifiers and bundlers handle
109
+ // without surprises. ECMAScript permits a wider Unicode range.
110
+ function isValidJsIdentifier(s) {
111
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s);
112
+ }
113
+ function stripScope(pkgName) {
114
+ return pkgName.replace(/^@[^/]+\//, '');
25
115
  }
@@ -0,0 +1,2 @@
1
+ import type { PluginOption } from 'vite';
2
+ export declare function loadAutoPlugins(cwd: string): Promise<PluginOption[]>;
@@ -0,0 +1,41 @@
1
+ import path from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { resolve as resolveEsm } from 'import-meta-resolve';
4
+ import { log } from '../lib/logger.js';
5
+ export async function loadAutoPlugins(cwd) {
6
+ const candidates = await Promise.all([loadSveltePlugin(cwd)]);
7
+ return candidates.filter(p => p !== null);
8
+ }
9
+ // Resolve peer-optional plugins from the consumer's cwd, not the builder's
10
+ // own location. A bare `await import(specifier)` resolves relative to this
11
+ // file, which only works under npm flat hoisting. It breaks for yarn 1
12
+ // link, yarn 2+ workspaces, and pnpm strict — exactly the layouts
13
+ // consumers use during development. We use ESM resolution rooted at the
14
+ // consumer's package.json so the `import` condition is honored, unlike CJS
15
+ // require.resolve which trips on ESM-only packages with an `exports`
16
+ // block. Node's native import.meta.resolve has a 2-arg form that does
17
+ // exactly this, but it remains flag-gated behind
18
+ // --experimental-import-meta-resolve in Node 22; the import-meta-resolve
19
+ // polyfill mirrors the same semantics without the flag. Only
20
+ // module-not-found resolution failures become silent skips; other
21
+ // resolution failures (broken package.json#exports, malformed
22
+ // node_modules) and factory errors propagate so the consumer sees a real
23
+ // diagnostic instead of a confusing "plugin not installed".
24
+ async function loadOptionalPlugin(specifier, cwd, detectedMessage, invoke) {
25
+ const parent = pathToFileURL(path.join(cwd, 'package.json')).href;
26
+ let resolved;
27
+ try {
28
+ resolved = resolveEsm(specifier, parent);
29
+ }
30
+ catch (e) {
31
+ if (e.code === 'ERR_MODULE_NOT_FOUND')
32
+ return null;
33
+ throw e;
34
+ }
35
+ log.info(detectedMessage);
36
+ const mod = (await import(resolved));
37
+ return invoke(mod);
38
+ }
39
+ function loadSveltePlugin(cwd) {
40
+ return loadOptionalPlugin('@sveltejs/vite-plugin-svelte', cwd, 'Detected @sveltejs/vite-plugin-svelte — registering Svelte plugin.', mod => mod.svelte());
41
+ }
@@ -1,7 +1,16 @@
1
1
  import type { Plugin } from 'vite';
2
+ export interface DevEntryOptions {
3
+ basePath: string;
4
+ entry: string;
5
+ jsName: string;
6
+ cssName: string;
7
+ }
2
8
  /**
3
- * Serves a dev bootstrap script at the bundle.js path so that Vite's
9
+ * Serves a dev bootstrap script at the bundle path so that Vite's
4
10
  * HMR client and the real entry point are loaded as ES modules.
5
- * Also serves an empty style.css since Vite injects CSS via JS in dev.
11
+ * Also serves an empty stylesheet at the css path since Vite injects
12
+ * CSS via JS in dev. Both filenames mirror the build output knobs
13
+ * (output.jsName / output.cssName) so a consumer who renames them
14
+ * keeps dev/build parity.
6
15
  */
7
- export declare function devEntryPlugin(basePath: string, entry: string): Plugin;
16
+ export declare function devEntryPlugin({ basePath, entry, jsName, cssName }: DevEntryOptions): Plugin;
@@ -1,11 +1,14 @@
1
1
  /**
2
- * Serves a dev bootstrap script at the bundle.js path so that Vite's
2
+ * Serves a dev bootstrap script at the bundle path so that Vite's
3
3
  * HMR client and the real entry point are loaded as ES modules.
4
- * Also serves an empty style.css since Vite injects CSS via JS in dev.
4
+ * Also serves an empty stylesheet at the css path since Vite injects
5
+ * CSS via JS in dev. Both filenames mirror the build output knobs
6
+ * (output.jsName / output.cssName) so a consumer who renames them
7
+ * keeps dev/build parity.
5
8
  */
6
- export function devEntryPlugin(basePath, entry) {
7
- const bundlePath = `${basePath}bundle.js`;
8
- const cssPath = `${basePath}style.css`;
9
+ export function devEntryPlugin({ basePath, entry, jsName, cssName }) {
10
+ const bundlePath = `${basePath}${jsName}`;
11
+ const cssPath = `${basePath}${cssName}`;
9
12
  return {
10
13
  name: 'brightspot-dev-entry',
11
14
  configureServer(server) {
@@ -1,10 +1,12 @@
1
- import type { InlineConfig } from 'vite';
2
- interface DevConfigOptions {
1
+ import { type InlineConfig } from 'vite';
2
+ import type { BuilderConfig } from '../lib/resolve-config.js';
3
+ export interface DevConfigOptions {
4
+ builder: BuilderConfig;
3
5
  target: string;
4
6
  port?: number;
5
- basePath: string;
6
- entry?: string;
7
7
  }
8
- export declare function createDevConfig({ target, port, basePath, entry }: DevConfigOptions): InlineConfig;
9
- export declare function createBuildConfig(basePath: string): InlineConfig;
10
- export {};
8
+ export declare function createDevConfig({ builder, target, port }: DevConfigOptions): Promise<InlineConfig>;
9
+ export interface BuildConfigOptions {
10
+ builder: BuilderConfig;
11
+ }
12
+ export declare function createBuildConfig({ builder }: BuildConfigOptions): Promise<InlineConfig>;
@@ -1,20 +1,34 @@
1
1
  import path from 'node:path';
2
+ import { loadConfigFromFile, mergeConfig } from 'vite';
3
+ import { log } from '../lib/logger.js';
4
+ import { loadAutoPlugins } from './auto-plugins.js';
2
5
  import { devEntryPlugin } from './plugin-html-inject.js';
3
- export function createDevConfig({ target, port, basePath, entry = 'src/index.ts' }) {
6
+ export async function createDevConfig({ builder, target, port }) {
4
7
  const cwd = process.cwd();
5
- const escaped = basePath.replace(/^\//, '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
6
- const entryDir = entry.split('/')[0] + '/';
7
- return {
8
+ const { basePath, entry, output: { jsName, cssName }, } = builder;
9
+ const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
10
+ const escaped = escapeRegex(basePath.replace(/^\//, ''));
11
+ // entry.split('/')[0] yields the file itself (e.g. 'index.ts') for a
12
+ // root-level entry, which never matches a URL — skip the segment in
13
+ // that case so /index.ts isn't proxied to the upstream application.
14
+ const entryParts = entry.split('/');
15
+ const entryDirAlt = entryParts.length > 1 ? `|${escapeRegex(entryParts[0])}/` : '';
16
+ const [mkcert, autoPlugins] = await Promise.all([loadMkcertPlugin(), loadAutoPlugins(cwd)]);
17
+ const base = {
8
18
  root: cwd,
9
19
  base: '/',
10
- plugins: [mkcertPlugin(), devEntryPlugin(basePath, entry)],
20
+ plugins: [mkcert, devEntryPlugin({ basePath, entry, jsName, cssName }), ...autoPlugins],
11
21
  resolve: {
12
22
  preserveSymlinks: true,
23
+ // Vite 8 native: replaces the deprecated vite-tsconfig-paths plugin.
24
+ // Follows tsconfig `extends` chains and applies to all importers
25
+ // (including .svelte / .vue) without the plugin's loose-mode dance.
26
+ tsconfigPaths: true,
13
27
  },
14
28
  server: {
15
29
  port,
16
30
  proxy: {
17
- [`^/(?!(${escaped}|${entryDir}|@vite|@fs|node_modules))`]: {
31
+ [`^/(?!(${escaped}${entryDirAlt}|@vite|@fs|node_modules))`]: {
18
32
  target,
19
33
  autoRewrite: true,
20
34
  changeOrigin: true,
@@ -28,32 +42,77 @@ export function createDevConfig({ target, port, basePath, entry = 'src/index.ts'
28
42
  },
29
43
  },
30
44
  };
45
+ return applyUserConfig(base, cwd, 'serve');
31
46
  }
32
- export function createBuildConfig(basePath) {
47
+ export async function createBuildConfig({ builder }) {
33
48
  const cwd = process.cwd();
34
- return {
49
+ const { basePath, entry, format, name, output } = builder;
50
+ const autoPlugins = await loadAutoPlugins(cwd);
51
+ const base = {
35
52
  root: cwd,
36
- base: basePath,
53
+ plugins: [...autoPlugins],
37
54
  resolve: {
38
55
  preserveSymlinks: true,
56
+ // Vite 8 native: replaces the deprecated vite-tsconfig-paths plugin.
57
+ // Follows tsconfig `extends` chains and applies to all importers
58
+ // (including .svelte / .vue) without the plugin's loose-mode dance.
59
+ tsconfigPaths: true,
60
+ },
61
+ build: {
62
+ cssCodeSplit: false,
63
+ // Vite 7's `build.lib` implied a single bundle. Vite 8 dropped that
64
+ // default, so any consumer with `await import(...)` in its graph
65
+ // emits sidecar `*-<hash>.mjs` chunks that the toolkit's promised
66
+ // two-file output (bundle.js + style.css) doesn't account for —
67
+ // and that the gradle processResources task doesn't pattern-match
68
+ // against, so chunks 404 in production. Default-off here restores
69
+ // the contract; consumers who want code-splitting can opt back in
70
+ // via Layer 3 (vite.config.ts → build.rollupOptions.output.
71
+ // codeSplitting = true). Note: Rolldown deprecated the equivalent
72
+ // `inlineDynamicImports: true` in favor of `codeSplitting: false`.
73
+ rollupOptions: {
74
+ output: {
75
+ codeSplitting: false,
76
+ },
77
+ },
39
78
  },
79
+ };
80
+ const knobs = {
81
+ base: basePath,
40
82
  build: {
41
83
  lib: {
42
- entry: path.resolve(cwd, 'src/index.ts'),
43
- formats: ['iife'],
44
- name: 'BrightspotTheme',
45
- fileName: () => 'bundle.js',
84
+ entry: path.resolve(cwd, entry),
85
+ formats: [format],
86
+ fileName: () => output.jsName,
87
+ // Vite library mode requires `name` whenever format is 'iife'.
88
+ // resolveConfig guarantees this is set when format is iife (explicit
89
+ // or derived from package.json#name), so this guard is defensive
90
+ // against a manually-constructed BuilderConfig that violates the
91
+ // invariant — we propagate undefined to Vite rather than inject a
92
+ // silent default; Vite then surfaces its own clear error.
93
+ ...(format === 'iife' && name ? { name } : {}),
46
94
  },
95
+ outDir: output.dir,
47
96
  rollupOptions: {
48
97
  output: {
49
- assetFileNames: () => 'style.css',
98
+ assetFileNames: () => output.cssName,
50
99
  },
51
100
  },
52
- cssCodeSplit: false,
53
101
  },
54
102
  };
103
+ return applyUserConfig(mergeConfig(base, knobs), cwd, 'build');
104
+ }
105
+ async function applyUserConfig(merged, cwd, command) {
106
+ const loaded = await loadConfigFromFile({ command, mode: command === 'serve' ? 'development' : 'production' }, undefined, cwd);
107
+ if (loaded)
108
+ log.info(`Merging consumer Vite config from ${path.relative(cwd, loaded.path)}.`);
109
+ const base = loaded ? mergeConfig(merged, loaded.config) : merged;
110
+ // configFile: false prevents Vite from re-resolving + double-merging
111
+ // the consumer's vite.config when the InlineConfig is passed to
112
+ // viteBuild / createServer.
113
+ return { ...base, configFile: false };
55
114
  }
56
- async function mkcertPlugin() {
115
+ async function loadMkcertPlugin() {
57
116
  const mkcert = await import('vite-plugin-mkcert');
58
117
  return mkcert.default();
59
118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspot/ui-builder",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "description": "Zero-config build toolkit for Brightspot CMS front-end development.",
@@ -18,26 +18,49 @@
18
18
  ],
19
19
  "scripts": {
20
20
  "build": "tsc --noEmitOnError && chmod +x ./dist/index.js",
21
- "clean": "shx rm -rf ./dist/*"
21
+ "clean": "shx rm -rf ./dist/*",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest"
22
24
  },
23
25
  "dependencies": {
26
+ "@eslint/js": "^9.23.0",
27
+ "autoprefixer": "^10.0.0",
24
28
  "commander": "^13.0.0",
25
- "picocolors": "^1.1.0",
26
- "vite": "^7.0.0",
27
- "vite-plugin-mkcert": "^1.17.0",
28
29
  "eslint": "^9.39.0",
29
- "@eslint/js": "^9.23.0",
30
- "typescript-eslint": "^8.29.0",
31
- "eslint-plugin-prettier": "^5.5.0",
32
30
  "eslint-config-prettier": "^10.0.0",
31
+ "eslint-plugin-prettier": "^5.5.0",
32
+ "globals": "^16.0.0",
33
+ "import-meta-resolve": "^4.2.0",
34
+ "picocolors": "^1.1.0",
33
35
  "prettier": "^3.2.0",
34
36
  "prettier-plugin-organize-imports": "^4.3.0",
35
37
  "typescript": "^5.9.0",
36
- "autoprefixer": "^10.0.0",
37
- "globals": "^16.0.0"
38
+ "typescript-eslint": "^8.29.0",
39
+ "vite": "^8.0.0",
40
+ "vite-plugin-mkcert": "^1.17.0"
41
+ },
42
+ "peerDependencies": {
43
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
44
+ "prettier-plugin-svelte": "^3.0.0",
45
+ "svelte": "^5.0.0"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "@sveltejs/vite-plugin-svelte": {
49
+ "optional": true
50
+ },
51
+ "svelte": {
52
+ "optional": true
53
+ },
54
+ "prettier-plugin-svelte": {
55
+ "optional": true
56
+ }
38
57
  },
39
58
  "devDependencies": {
59
+ "@types/node": "^24.10.0",
40
60
  "shx": "^0.4.0",
41
- "@types/node": "^24.10.0"
61
+ "vitest": "^4.1.5"
62
+ },
63
+ "resolutions": {
64
+ "vite": "^8.0.0"
42
65
  }
43
66
  }