poveste 0.12.3 → 0.13.1

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.
@@ -15,6 +15,7 @@ import { findAllStories } from './stories.js';
15
15
  import { chromeCssScopePlugin, entryCssMergerPlugin, userCssScopePlugin, } from './style-isolation/index.js';
16
16
  import { applyHeadTransform } from './util/head.js';
17
17
  import { wrapLogError } from './util/log.js';
18
+ import { viteMode } from './util/vite-mode.js';
18
19
  import { getViteConfigWithPlugins } from './vite.js';
19
20
  const PRELOAD_MODULES = [
20
21
  'vendor',
@@ -51,6 +52,10 @@ export async function build(ctx) {
51
52
  }
52
53
  const { viteConfig } = await getViteConfigWithPlugins(true, ctx);
53
54
  const server = await createViteServer(mergeViteConfig(viteConfig, {
55
+ // Same mode as the build below: `createServer` defaults to `development`,
56
+ // which would put one `.env` file in poveste.json and the other in the
57
+ // bundle built from it.
58
+ mode: viteMode(ctx.mode),
54
59
  optimizeDeps: { include: [], noDiscovery: true },
55
60
  }));
56
61
  await server.pluginContainer.buildStart({});
@@ -105,7 +110,9 @@ export async function build(ctx) {
105
110
  const emptyStoryCount = ctx.storyFiles.length - storyCount;
106
111
  const { viteConfig: buildViteConfigRaw } = await getViteConfigWithPlugins(false, ctx);
107
112
  const buildViteConfig = mergeViteConfig(buildViteConfigRaw, {
108
- mode: 'development',
113
+ // Vite picks the `.env` file from `mode`, not from NODE_ENV — this said
114
+ // `'development'` from histoire until #349.
115
+ mode: viteMode(ctx.mode),
109
116
  build: {
110
117
  lib: false,
111
118
  rollupOptions: {
@@ -207,6 +214,10 @@ export async function build(ctx) {
207
214
  emptyOutDir: true,
208
215
  // Re-merged per-entry by entry-css-merger.
209
216
  cssCodeSplit: true,
217
+ // Not an oversight: the source pane prints story code by reading
218
+ // functions back at runtime, and `Function.prototype.toString()`
219
+ // returns whatever the minifier left — `codegen-vue3.spec.ts` fails
220
+ // on three cases with this on (#329).
210
221
  minify: false,
211
222
  // Don't build in SSR mode
212
223
  ssr: false,
@@ -1,6 +1,7 @@
1
1
  import { cpus } from 'node:os';
2
2
  import { MessageChannel } from 'node:worker_threads';
3
3
  import Tinypool from '@akryum/tinypool';
4
+ import { escapeRegExp } from '@poveste/shared';
4
5
  import { createBirpc } from 'birpc';
5
6
  import path, { relative } from 'pathe';
6
7
  import pc from 'picocolors';
@@ -29,7 +30,7 @@ export function useCollectStories(options, ctx) {
29
30
  // @TODO temporary fix for https://github.com/histoire-dev/histoire/issues/409
30
31
  /vite\w*\/dist\/client\/(client|env).mjs/,
31
32
  ...ctx.config.viteNodeInlineDeps ?? [],
32
- new RegExp(path.resolve(TEMP_PATH, 'plugins')),
33
+ new RegExp(escapeRegExp(path.resolve(TEMP_PATH, 'plugins'))),
33
34
  ],
34
35
  fallbackCJS: true,
35
36
  },
@@ -1,10 +1,15 @@
1
1
  import { resolveConfig as resolveViteConfig } from 'vite';
2
2
  import { processConfig, resolveConfig } from './config.js';
3
+ import { viteCommand, viteMode } from './util/vite-mode.js';
3
4
  import { mergePovesteViteConfig } from './vite.js';
4
5
  export async function createContext(options) {
5
6
  const config = await resolveConfig(process.cwd(), options.mode, options.configFile);
6
- const command = options.mode === 'dev' ? 'serve' : 'build';
7
- const viteConfig = await resolveViteConfig({}, command);
7
+ const command = viteCommand(options.mode);
8
+ // The mode as well as the command: `resolveConfig` defaults to
9
+ // `'development'` whatever the command, and `base` from this resolution is
10
+ // what the hand-written index.html uses while the bundle uses the build's.
11
+ // A user config setting `base` by mode would otherwise disagree with itself.
12
+ const viteConfig = await resolveViteConfig({}, command, viteMode(options.mode));
8
13
  const supportPlugins = config.plugins.map(p => p.supportPlugin).filter(Boolean);
9
14
  const ctx = {
10
15
  root: viteConfig.root,
@@ -0,0 +1,13 @@
1
+ import type { ConfigMode } from '@poveste/shared';
2
+ /**
3
+ * poveste's `ConfigMode` in the vocabulary Vite's own API speaks.
4
+ *
5
+ * The two are different axes with overlapping words, and handing one straight
6
+ * to the other means a user config written the standard way — `mode ===
7
+ * 'production' ? … : …` — takes neither branch (#349).
8
+ *
9
+ * The public `vite(config, env)` hook takes it too: `env` is typed as Vite's own
10
+ * `ConfigEnv` in `@poveste/shared`, and its `command` was already translated.
11
+ */
12
+ export declare function viteMode(mode: ConfigMode): 'development' | 'production';
13
+ export declare function viteCommand(mode: ConfigMode): 'serve' | 'build';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * poveste's `ConfigMode` in the vocabulary Vite's own API speaks.
3
+ *
4
+ * The two are different axes with overlapping words, and handing one straight
5
+ * to the other means a user config written the standard way — `mode ===
6
+ * 'production' ? … : …` — takes neither branch (#349).
7
+ *
8
+ * The public `vite(config, env)` hook takes it too: `env` is typed as Vite's own
9
+ * `ConfigEnv` in `@poveste/shared`, and its `command` was already translated.
10
+ */
11
+ export function viteMode(mode) {
12
+ return mode === 'dev' ? 'development' : 'production';
13
+ }
14
+ export function viteCommand(mode) {
15
+ return mode === 'dev' ? 'serve' : 'build';
16
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Emitting values into generated modules.
3
+ *
4
+ * Every generator in this directory builds JavaScript source by interpolation,
5
+ * and most of the values come from somewhere the author of that source did not
6
+ * control — a plugin's id, a user's config key, the project's own path. Getting
7
+ * it wrong produces a syntax error inside a module nobody wrote and nobody can
8
+ * open, which is the symptom rather than a crash at the input (#609).
9
+ *
10
+ * There are two positions and they need different things.
11
+ */
12
+ /** A value in a string or key position. Valid JSON is valid JavaScript here. */
13
+ export declare function jsString(value: string): string;
14
+ /**
15
+ * A value in an *identifier* position — a binding name, an import name.
16
+ *
17
+ * Quoting cannot help here, so the only honest options are to reject the value
18
+ * or to rename it. These names are read back by consumers (`logos.square`,
19
+ * `generatedSetup.setupVue3`), so renaming would move the problem to whoever
20
+ * looks them up. It rejects, naming the value and where it came from.
21
+ */
22
+ export declare function jsIdentifier(value: string, what: string): string;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Emitting values into generated modules.
3
+ *
4
+ * Every generator in this directory builds JavaScript source by interpolation,
5
+ * and most of the values come from somewhere the author of that source did not
6
+ * control — a plugin's id, a user's config key, the project's own path. Getting
7
+ * it wrong produces a syntax error inside a module nobody wrote and nobody can
8
+ * open, which is the symptom rather than a crash at the input (#609).
9
+ *
10
+ * There are two positions and they need different things.
11
+ */
12
+ /** A value in a string or key position. Valid JSON is valid JavaScript here. */
13
+ export function jsString(value) {
14
+ return JSON.stringify(value);
15
+ }
16
+ /**
17
+ * A value in an *identifier* position — a binding name, an import name.
18
+ *
19
+ * Quoting cannot help here, so the only honest options are to reject the value
20
+ * or to rename it. These names are read back by consumers (`logos.square`,
21
+ * `generatedSetup.setupVue3`), so renaming would move the problem to whoever
22
+ * looks them up. It rejects, naming the value and where it came from.
23
+ */
24
+ export function jsIdentifier(value, what) {
25
+ if (!/^[A-Z_$][\w$]*$/i.test(value)) {
26
+ throw new Error(`${what} must be usable as a JavaScript identifier, and ${JSON.stringify(value)} is not. `
27
+ + 'It is emitted into a generated module as a binding name, where quoting cannot rescue it.');
28
+ }
29
+ return value;
30
+ }
@@ -1,3 +1,4 @@
1
+ import { jsIdentifier, jsString } from './codegen.js';
1
2
  const serializedFields = ['id', 'label', 'prompts', 'icon', 'searchText'];
2
3
  export function resolvedCommands(ctx) {
3
4
  const imports = [];
@@ -21,10 +22,10 @@ function getCommandFields(command, imports) {
21
22
  if (command.clientSetupFile) {
22
23
  const importedVar = `__setup${imports.length}__`;
23
24
  if (typeof command.clientSetupFile === 'string') {
24
- imports.push(`import ${importedVar} from '${command.clientSetupFile}'`);
25
+ imports.push(`import ${importedVar} from ${jsString(command.clientSetupFile)}`);
25
26
  }
26
27
  else {
27
- imports.push(`import { ${command.clientSetupFile.importName} as ${importedVar} } from '${command.clientSetupFile.file}'`);
28
+ imports.push(`import { ${jsIdentifier(command.clientSetupFile.importName, `clientSetupFile.importName '${command.clientSetupFile.importName}'`)} as ${importedVar} } from ${jsString(command.clientSetupFile.file)}`);
28
29
  }
29
30
  fields.push(`...${importedVar}`);
30
31
  }
@@ -1,10 +1,11 @@
1
+ import { jsIdentifier, jsString } from './codegen.js';
1
2
  export function resolvedConfig(ctx) {
2
3
  let js = `export const config = ${JSON.stringify(ctx.config)}\n`;
3
4
  if (ctx.config.theme?.logo) {
4
5
  for (const key in ctx.config.theme.logo) {
5
- js += `import Logo_${key} from '${ctx.config.theme.logo[key]}'\n`;
6
+ js += `import Logo_${jsIdentifier(key, `theme.logo key '${key}'`)} from ${jsString(ctx.config.theme.logo[key])}\n`;
6
7
  }
7
8
  }
8
- js += `export const logos = {${Object.keys(ctx.config.theme?.logo ?? {}).map(key => `${key}: Logo_${key}`).join(', ')}}\n`;
9
+ js += `export const logos = {${Object.keys(ctx.config.theme?.logo ?? {}).map(key => `${jsString(key)}: Logo_${key}`).join(', ')}}\n`;
9
10
  return js;
10
11
  }
@@ -1,4 +1,5 @@
1
1
  import { makeTree } from '../tree.js';
2
+ import { jsString } from './codegen.js';
2
3
  export function resolvedStories(ctx) {
3
4
  const resolvedStories = ctx.storyFiles.filter(s => !!s.story);
4
5
  const files = resolvedStories.map((file, index) => {
@@ -23,7 +24,7 @@ ${resolvedStories.map((file, index) => {
23
24
  }
24
25
  return supportPlugin.importStoryComponent(file, index);
25
26
  }).filter(Boolean).join('\n')}
26
- export let files = [${files.map(file => `{${JSON.stringify(file).slice(1, -1)}, component: Comp${file.index}, source: () => import('virtual:story-source:${file.story.id}')}`).join(',\n')}]
27
+ export let files = [${files.map(file => `{${JSON.stringify(file).slice(1, -1)}, component: Comp${file.index}, source: () => import(${jsString(`virtual:story-source:${file.story.id}`)})}`).join(',\n')}]
27
28
  export let tree = ${JSON.stringify(makeTree(ctx.config, resolvedStories))}
28
29
  const handlers = []
29
30
  export function onUpdate (cb) {
@@ -1,8 +1,9 @@
1
1
  import { createRequire } from 'node:module';
2
+ import { jsString } from './codegen.js';
2
3
  import { PLUGINS_HAVE_DEV } from './util.js';
3
4
  const require = createRequire(import.meta.url);
4
5
  export function resolvedSupportPluginsClient(ctx) {
5
- const plugins = ctx.supportPlugins.map(p => `'${p.id}': () => import(${JSON.stringify(require.resolve(`${p.moduleName}/client${process.env.POVESTE_DEV && PLUGINS_HAVE_DEV.includes(p.moduleName) ? '-dev' : ''}`, {
6
+ const plugins = ctx.supportPlugins.map(p => `${jsString(p.id)}: () => import(${JSON.stringify(require.resolve(`${p.moduleName}/client${process.env.POVESTE_DEV && PLUGINS_HAVE_DEV.includes(p.moduleName) ? '-dev' : ''}`, {
6
7
  paths: [ctx.root, import.meta.url],
7
8
  }))})`);
8
9
  return `export const clientSupportPlugins = {
@@ -1,8 +1,9 @@
1
1
  import { createRequire } from 'node:module';
2
+ import { jsString } from './codegen.js';
2
3
  import { PLUGINS_HAVE_DEV } from './util.js';
3
4
  const require = createRequire(import.meta.url);
4
5
  export function resolvedSupportPluginsCollect(ctx) {
5
- const plugins = ctx.supportPlugins.map(p => `'${p.id}': () => import(${JSON.stringify(require.resolve(`${p.moduleName}/collect${process.env.POVESTE_DEV && PLUGINS_HAVE_DEV.includes(p.moduleName) ? '-dev' : ''}`, {
6
+ const plugins = ctx.supportPlugins.map(p => `${jsString(p.id)}: () => import(${JSON.stringify(require.resolve(`${p.moduleName}/collect${process.env.POVESTE_DEV && PLUGINS_HAVE_DEV.includes(p.moduleName) ? '-dev' : ''}`, {
6
7
  paths: [ctx.root, import.meta.url],
7
8
  }))})`);
8
9
  return `export const collectSupportPlugins = {
@@ -1,3 +1,4 @@
1
+ import { jsIdentifier } from './codegen.js';
1
2
  export const ID_SEPARATOR = '__-__';
2
3
  /**
3
4
  * Names of every setup hook contributed by the enabled support plugins, e.g.
@@ -28,7 +29,7 @@ export function getSetupFnGroups(ctx) {
28
29
  * in every consumer build.
29
30
  */
30
31
  export function declareEmptySetupFns(ctx) {
31
- return getSetupFnNames(ctx).map(fnName => `export const ${fnName} = undefined`).join('\n');
32
+ return getSetupFnNames(ctx).map(fnName => `export const ${jsIdentifier(fnName, `support plugin setupFn '${fnName}'`)} = undefined`).join('\n');
32
33
  }
33
34
  export const PLUGINS_HAVE_DEV = [
34
35
  '@poveste/plugin-vue',
package/dist/node/vite.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { createRequire } from 'node:module';
3
+ import { escapeRegExp } from '@poveste/shared';
3
4
  import { lookup as lookupMime } from 'mrmime';
4
5
  import { dirname, join, relative } from 'pathe';
5
6
  import { loadConfigFromFile as loadViteConfigFromFile, mergeConfig as mergeViteConfig, searchForWorkspaceRoot, } from 'vite';
@@ -9,14 +10,15 @@ import { optimizeEntries } from './optimize-entries.js';
9
10
  import { notifyStoryChange } from './stories.js';
10
11
  import { chromeCssScopePlugin, globalStylesPlugin, userCssScopePlugin, } from './style-isolation/index.js';
11
12
  import { applyHeadTransform } from './util/head.js';
13
+ import { viteCommand, viteMode } from './util/vite-mode.js';
12
14
  import { createVirtualFilesPlugin } from './virtual/vite-plugin.js';
13
15
  const require = createRequire(import.meta.url);
14
16
  export async function mergePovesteViteConfig(viteConfig, ctx) {
15
17
  if (ctx.config.vite) {
16
- const command = ctx.mode === 'dev' ? 'serve' : 'build';
18
+ const command = viteCommand(ctx.mode);
17
19
  const overrides = typeof ctx.config.vite === 'function'
18
20
  ? await ctx.config.vite(viteConfig, {
19
- mode: ctx.mode,
21
+ mode: viteMode(ctx.mode),
20
22
  command,
21
23
  })
22
24
  : ctx.config.vite;
@@ -44,7 +46,7 @@ export async function mergePovesteViteConfig(viteConfig, ctx) {
44
46
  return viteConfig;
45
47
  }
46
48
  export async function getViteConfigWithPlugins(isServer, ctx) {
47
- const userViteConfigFile = await loadViteConfigFromFile({ command: ctx.mode === 'dev' ? 'serve' : 'build', mode: ctx.mode });
49
+ const userViteConfigFile = await loadViteConfigFromFile({ command: viteCommand(ctx.mode), mode: viteMode(ctx.mode) });
48
50
  const userViteConfig = mergeViteConfig(userViteConfigFile?.config ?? {}, { server: { port: 6006 } });
49
51
  const inlineConfig = await mergePovesteViteConfig(userViteConfig, ctx);
50
52
  const plugins = [];
@@ -298,7 +300,7 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
298
300
  if (/\.(?:vue|js)(?:$|\?)/.test(id)) {
299
301
  const original = code;
300
302
  for (const flag in flags) {
301
- code = code.replace(new RegExp(flag, 'g'), flags[flag]);
303
+ code = code.replace(new RegExp(escapeRegExp(flag), 'g'), flags[flag]);
302
304
  }
303
305
  if (original !== code)
304
306
  return code;
@@ -359,11 +361,11 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
359
361
  ['vue', 'vue'],
360
362
  ].reduce((acc, [name, entry]) => {
361
363
  acc.push({
362
- find: new RegExp(`^${name.replace(/\//g, '\\/')}$`),
364
+ find: new RegExp(`^${escapeRegExp(name)}$`),
363
365
  replacement: `@poveste/vendors/${entry}`,
364
366
  });
365
367
  acc.push({
366
- find: new RegExp(`^${name.replace(/\//g, '\\/')}\\/`),
368
+ find: new RegExp(`^${escapeRegExp(name)}/`),
367
369
  replacement: `@poveste/vendors/${entry}/`,
368
370
  });
369
371
  return acc;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "poveste",
3
3
  "type": "module",
4
- "version": "0.12.3",
4
+ "version": "0.13.1",
5
5
  "description": "Interactive component playgrounds for Vue, Nuxt, Svelte, SvelteKit and Quasar — a drop-in fork of histoire, with Quasar support histoire does not ship.",
6
6
  "author": {
7
7
  "name": "Sorin Gitlan",
@@ -73,10 +73,10 @@
73
73
  },
74
74
  "dependencies": {
75
75
  "@akryum/tinypool": "^0.3.1",
76
- "@poveste/app": "^0.12.3",
77
- "@poveste/controls": "^0.12.3",
78
- "@poveste/shared": "^0.12.3",
79
- "@poveste/vendors": "^0.12.3",
76
+ "@poveste/app": "^0.13.1",
77
+ "@poveste/controls": "^0.13.1",
78
+ "@poveste/shared": "^0.13.1",
79
+ "@poveste/vendors": "^0.13.1",
80
80
  "@types/markdown-it": "^14.1.2",
81
81
  "birpc": "^0.2.19",
82
82
  "change-case": "^5.4.4",