@weasel-js/forge 1.16.2 → 1.17.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.
@@ -110,7 +110,7 @@ function knobPaths(schema) {
110
110
  if (key.startsWith(INTERNAL_PREFIX) || key.includes(".")) continue;
111
111
  const path = at === "" ? key : `${at}.${key}`;
112
112
  if (isConfigBranch(entry)) walk(entry.children, path);
113
- else if (codecOf(entry) !== null) out.push({ path, leaf: entry });
113
+ else if (codecOf(entry) !== null && isKnobParam(path)) out.push({ path, leaf: entry });
114
114
  }
115
115
  };
116
116
  walk(schema.nodes, "");
@@ -133,7 +133,7 @@ function paramsToKnobs(schema, params) {
133
133
  const knobs = {};
134
134
  for (const { path, leaf } of knobPaths(schema)) {
135
135
  const raw = params[path];
136
- if (raw === void 0 || !isKnobParam(path)) continue;
136
+ if (raw === void 0) continue;
137
137
  const decoded = decodeKnob(raw, leaf);
138
138
  if (decoded) knobs[path] = decoded.value;
139
139
  }
@@ -170,5 +170,5 @@ function paramsToPins(schema, params) {
170
170
  }
171
171
 
172
172
  export { PLAYHEAD_PARAM, RESERVED_PARAMS, decodeKnob, encodeKnob, formatRoute, knobParams, knobPaths, knobsToParams, paramsToKnobs, paramsToPins, parseRoute, pinParams, reservedParams, sameKnob, storyHref, withoutPins };
173
- //# sourceMappingURL=chunk-SFKDHXVE.js.map
174
- //# sourceMappingURL=chunk-SFKDHXVE.js.map
173
+ //# sourceMappingURL=chunk-MUBZSDOI.js.map
174
+ //# sourceMappingURL=chunk-MUBZSDOI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/route/url.ts"],"names":[],"mappings":"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","file":"chunk-MUBZSDOI.js","sourcesContent":["import { type ConfigSchema, type ConfigShape, isConfigBranch, valueAtPath } from '@weasel-js/labkit/config';\nimport { isPlainObject } from '@weasel-js/core';\nimport { type Globals, stableStringify } from '../protocol/messages';\nimport { GLOBALS_KEY } from '../shell/globals';\n\n/**\n * Forge's URL, as pure functions: `#/<story id>?<params>`. The story and its params share the hash, so the page's\n * own query string stays the host's. A param is a knob — a story arg, addressed by its dotted config path\n * (`look.px`) — unless its name is reserved for the workshop itself.\n */\n\n/** The param a paused playhead is held in, in seconds. */\nexport const PLAYHEAD_PARAM = 't';\n\n/** Param names that are never knobs. */\nexport const RESERVED_PARAMS: ReadonlySet<string> = new Set([PLAYHEAD_PARAM]);\n\n/** Config keys forge keeps for itself, such as the `$globals` pins, start with this and never reach the URL. */\nconst INTERNAL_PREFIX = '$';\n\n/** What a forge hash holds: the story it names, and every param after it, knobs and reserved alike, in order. */\nexport interface ForgeRoute {\n story: string | null;\n params: Readonly<Record<string, string>>;\n}\n\n/** Reads a hash (`location.hash`, with or without its `#`). Anything not in the `#/<id>` form names no story. */\nexport function parseRoute(hash: string): ForgeRoute {\n const body = hash.startsWith('#') ? hash.slice(1) : hash;\n if (!body.startsWith('/')) return { story: null, params: {} };\n const at = body.indexOf('?');\n const path = at === -1 ? body.slice(1) : body.slice(1, at);\n let story: string | null;\n try {\n story = decodeURIComponent(path) || null;\n } catch {\n story = null;\n }\n const params: Record<string, string> = {};\n if (at !== -1) for (const [key, value] of new URLSearchParams(body.slice(at + 1))) params[key] = value;\n return { story, params };\n}\n\n/** The hash for `route`, with its leading `#`. The inverse of `parseRoute`. */\nexport function formatRoute(route: ForgeRoute): string {\n const query = new URLSearchParams(Object.entries(route.params)).toString();\n return `#/${encodeURIComponent(route.story ?? '')}${query ? `?${query}` : ''}`;\n}\n\n/** Whether a param name can be a knob: not reserved and not one of forge's own config keys. */\nexport function isKnobParam(name: string): boolean {\n return name !== '' && !RESERVED_PARAMS.has(name) && !name.startsWith(INTERNAL_PREFIX);\n}\n\n/** The params that are knobs, with the reserved ones left out. */\nexport function knobParams(params: Readonly<Record<string, string>>): Record<string, string> {\n return Object.fromEntries(Object.entries(params).filter(([name]) => isKnobParam(name)));\n}\n\n/** The params that are not knobs. Kept as they are when the knobs are rewritten. */\nexport function reservedParams(params: Readonly<Record<string, string>>): Record<string, string> {\n return Object.fromEntries(Object.entries(params).filter(([name]) => !isKnobParam(name)));\n}\n\n/** What a knob's value is decoded against: its schema leaf's declared kind, its default, and its choices. */\nexport interface KnobLeaf {\n kind: string | null;\n default: unknown;\n annotations?: { options?: readonly { value: string }[] };\n}\n\ntype Codec = 'number' | 'boolean' | 'string' | 'enum' | 'json';\n\n/** How a leaf's value travels: by its declared kind first, then by the type of its default. */\nfunction codecOf(leaf: KnobLeaf): Codec | null {\n switch (leaf.kind) {\n case 'number':\n case 'boolean':\n case 'string':\n case 'enum':\n return leaf.kind;\n case 'color':\n return 'string';\n case 'list':\n case 'json':\n return 'json';\n }\n const d = leaf.default;\n if (typeof d === 'number' || typeof d === 'boolean' || typeof d === 'string') return typeof d as Codec;\n return Array.isArray(d) || isPlainObject(d) ? 'json' : null;\n}\n\n/** A value as one param. Undefined for a value no param can carry, such as a function. */\nexport function encodeKnob(value: unknown): string | undefined {\n switch (typeof value) {\n case 'string':\n return value;\n case 'number':\n case 'boolean':\n return String(value);\n case 'object':\n return value !== null && (Array.isArray(value) || isPlainObject(value)) ? JSON.stringify(value) : undefined;\n default:\n return undefined;\n }\n}\n\n/** Whether `value` is something a leaf of codec `codec` could hold, so a well-formed param of the wrong type is refused. */\nfunction fits(codec: Codec, value: unknown, leaf: KnobLeaf): boolean {\n if (codec !== 'json') return true;\n const d = leaf.default;\n if (Array.isArray(d)) {\n if (!Array.isArray(value)) return false;\n return leaf.kind !== 'list' || value.every((item) => typeof item === 'string');\n }\n if (isPlainObject(d)) return isPlainObject(value);\n return true;\n}\n\n/**\n * One param read as `leaf`'s type: `{ value }`, or undefined for a param that does not parse as one — a number that\n * is not a number, an option the enum does not list, malformed JSON. A bare boolean param (`?dark`) reads as true.\n */\nexport function decodeKnob(raw: string, leaf: KnobLeaf): { value: unknown } | undefined {\n const codec = codecOf(leaf);\n switch (codec) {\n case 'number': {\n const n = raw.trim() === '' ? Number.NaN : Number(raw);\n return Number.isNaN(n) ? undefined : { value: n };\n }\n case 'boolean':\n if (raw === '' || raw === 'true' || raw === '1') return { value: true };\n if (raw === 'false' || raw === '0') return { value: false };\n return undefined;\n case 'string':\n return { value: raw };\n case 'enum': {\n const options = leaf.annotations?.options;\n return !options || options.some((o) => o.value === raw) ? { value: raw } : undefined;\n }\n case 'json': {\n try {\n const value: unknown = JSON.parse(raw);\n return fits(codec, value, leaf) ? { value } : undefined;\n } catch {\n return undefined;\n }\n }\n default:\n return undefined;\n }\n}\n\n/** One leaf a URL can address: its dotted path, and what its value decodes against. */\nexport interface KnobPath {\n path: string;\n leaf: KnobLeaf;\n}\n\n/** Every leaf of `schema` a param can carry, in schema order, reserved names left out, with branches flattened into dotted paths. */\nexport function knobPaths(schema: Pick<ConfigSchema<unknown>, 'nodes'>): KnobPath[] {\n const out: KnobPath[] = [];\n const walk = (shape: ConfigShape, at: string): void => {\n for (const [key, entry] of Object.entries(shape)) {\n if (key.startsWith(INTERNAL_PREFIX) || key.includes('.')) continue;\n const path = at === '' ? key : `${at}.${key}`;\n if (isConfigBranch(entry)) walk(entry.children, path);\n else if (codecOf(entry) !== null && isKnobParam(path)) out.push({ path, leaf: entry });\n }\n };\n walk(schema.nodes, '');\n return out;\n}\n\n/** Whether two knob values are the same value, compared as data. */\nexport function sameKnob(a: unknown, b: unknown): boolean {\n return Object.is(a, b) || stableStringify(a) === stableStringify(b);\n}\n\n/**\n * The knob params for `config`: one per leaf whose value differs from its default, keyed by dotted path, in schema\n * order. A leaf absent from `config` is at its default.\n */\nexport function knobsToParams(schema: Pick<ConfigSchema<unknown>, 'nodes'>, config: unknown): Record<string, string> {\n const params: Record<string, string> = {};\n for (const { path, leaf } of knobPaths(schema)) {\n const value = valueAtPath(config, path);\n if (value === undefined || sameKnob(value, leaf.default)) continue;\n const encoded = encodeKnob(value);\n if (encoded !== undefined) params[path] = encoded;\n }\n return params;\n}\n\n/**\n * The knob values `params` sets, by dotted path, each coerced to its leaf's type. A param naming no leaf, a reserved\n * param, and one that does not parse as its leaf's type are left out rather than failing the rest.\n */\nexport function paramsToKnobs(\n schema: Pick<ConfigSchema<unknown>, 'nodes'>,\n params: Readonly<Record<string, string>>,\n): Record<string, unknown> {\n const knobs: Record<string, unknown> = {};\n for (const { path, leaf } of knobPaths(schema)) {\n const raw = params[path];\n if (raw === undefined) continue;\n const decoded = decodeKnob(raw, leaf);\n if (decoded) knobs[path] = decoded.value;\n }\n return knobs;\n}\n\n/**\n * A link to a story at a given state, for a host that has no schema to hand: each knob is a dotted path and a value,\n * encoded by its own type. `params` adds workshop params, such as `t`.\n */\nexport function storyHref(\n story: string,\n knobs: Readonly<Record<string, unknown>> = {},\n params: Readonly<Record<string, string>> = {},\n): string {\n const encoded: Record<string, string> = {};\n for (const [path, value] of Object.entries(knobs)) {\n const raw = encodeKnob(value);\n if (raw !== undefined && isKnobParam(path)) encoded[path] = raw;\n }\n return formatRoute({ story, params: { ...encoded, ...reservedParams(params) } });\n}\n\n/** A param pinning one of the trial's globals is named by the pin's config path, `$globals.<key>`. */\nconst PIN_PREFIX = `${GLOBALS_KEY}.`;\n\n/** The params pinning a trial's globals to `globals`. Not knobs: they are read once, as the story opens, and dropped. */\nexport function pinParams(globals: Globals): Record<string, string> {\n const params: Record<string, string> = {};\n for (const [key, value] of Object.entries(globals)) if (typeof value === 'string') params[`${PIN_PREFIX}${key}`] = value;\n return params;\n}\n\n/** `params` without the ones pinning globals. */\nexport function withoutPins(params: Readonly<Record<string, string>>): Record<string, string> {\n return Object.fromEntries(Object.entries(params).filter(([name]) => !name.startsWith(PIN_PREFIX)));\n}\n\n/** The pins `params` sets, by config path: each a value its global's leaf in `schema` offers; any other is left out. */\nexport function paramsToPins(\n schema: Pick<ConfigSchema<unknown>, 'nodes'>,\n params: Readonly<Record<string, string>>,\n): Record<string, string> {\n const group = (schema.nodes as ConfigShape)[GLOBALS_KEY];\n if (!group || !isConfigBranch(group)) return {};\n const pins: Record<string, string> = {};\n for (const [key, leaf] of Object.entries(group.children)) {\n const raw = params[`${PIN_PREFIX}${key}`];\n if (raw === undefined || isConfigBranch(leaf)) continue;\n const decoded = decodeKnob(raw, leaf);\n if (typeof decoded?.value === 'string') pins[`${PIN_PREFIX}${key}`] = decoded.value;\n }\n return pins;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -167,7 +167,7 @@ interface KnobPath {
167
167
  path: string;
168
168
  leaf: KnobLeaf;
169
169
  }
170
- /** Every leaf of `schema` a param can carry, in schema order, with branches flattened into dotted paths. */
170
+ /** Every leaf of `schema` a param can carry, in schema order, reserved names left out, with branches flattened into dotted paths. */
171
171
  declare function knobPaths(schema: Pick<ConfigSchema<unknown>, 'nodes'>): KnobPath[];
172
172
  /**
173
173
  * The knob params for `config`: one per leaf whose value differs from its default, keyed by dotted path, in schema
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { RESERVED_PARAMS, decodeKnob, encodeKnob, formatRoute, knobParams, knobPaths, knobsToParams, paramsToKnobs, parseRoute, reservedParams, storyHref } from './chunk-SFKDHXVE.js';
1
+ export { RESERVED_PARAMS, decodeKnob, encodeKnob, formatRoute, knobParams, knobPaths, knobsToParams, paramsToKnobs, parseRoute, reservedParams, storyHref } from './chunk-MUBZSDOI.js';
2
2
  export { GALLERY_TAG, LAB_TAG } from './chunk-VUPN73JP.js';
3
3
  export { FOLLOW_APP } from './chunk-FPF2JSWB.js';
4
4
  export { meta, story, usePlayhead, useTimeline } from './chunk-TT4ZOYB6.js';
@@ -1,4 +1,4 @@
1
- import { parseRoute, formatRoute, paramsToKnobs, knobPaths, sameKnob, paramsToPins, knobsToParams, PLAYHEAD_PARAM, pinParams, withoutPins, reservedParams } from '../chunk-SFKDHXVE.js';
1
+ import { parseRoute, formatRoute, paramsToKnobs, knobPaths, sameKnob, paramsToPins, knobsToParams, PLAYHEAD_PARAM, pinParams, withoutPins, reservedParams } from '../chunk-MUBZSDOI.js';
2
2
  import { DEFAULT_TITLE, ISSUES_PATH, CAPTURE_PATH, issueLabel, messageOf, MIN_DPR, MAX_DPR } from '../chunk-7YVJS4QS.js';
3
3
  import { TITLE_LIBRARIES, loadIndexBundle, librariesFrom, LibrariesContext, LAB_DENSITY, createKitRoot, schemaFromDescription, LibraryBadge, Description, IndexPage, indexEnvOf, LabBadge, openChannel } from '../chunk-5B4RDEF3.js';
4
4
  import { isIndexId, indexId, indexEntries } from '../chunk-ZGELUE2H.js';
@@ -122,19 +122,18 @@ var fileOfKey = (key) => key.slice(0, key.lastIndexOf("#"));
122
122
  var SCRIPT = /\.[cm]?[jt]sx?$/;
123
123
  var DEFAULT_LOCAL = "*default*";
124
124
  var TYPE_KEYS = /* @__PURE__ */ new Set(["typeAnnotation", "typeParameters", "typeArguments", "returnType", "superTypeParameters", "implements"]);
125
+ function readModule(read, file) {
126
+ try {
127
+ return moduleInfo(read(file), file);
128
+ } catch {
129
+ return null;
130
+ }
131
+ }
125
132
  function createDepGraph({ root, read }) {
126
133
  const modules = /* @__PURE__ */ new Map();
127
134
  const resolutions = /* @__PURE__ */ new Map();
128
135
  const info = (file) => {
129
- if (!modules.has(file)) {
130
- let parsed = null;
131
- try {
132
- parsed = moduleInfo(read(file), file);
133
- } catch {
134
- parsed = null;
135
- }
136
- modules.set(file, parsed);
137
- }
136
+ if (!modules.has(file)) modules.set(file, readModule(read, file));
138
137
  return modules.get(file) ?? null;
139
138
  };
140
139
  return {
@@ -146,6 +145,7 @@ function createDepGraph({ root, read }) {
146
145
  },
147
146
  reset() {
148
147
  resolutions.clear();
148
+ for (const [file, parsed] of modules) if (parsed === null) modules.delete(file);
149
149
  },
150
150
  async build(entries, resolve6) {
151
151
  const resolveFrom = (spec, importer) => {
@@ -737,6 +737,7 @@ function indexProgram(program, file, autoTitle2, extra, foreign) {
737
737
  }
738
738
  }
739
739
  if (!metaNode && !program.body.some(isDefaultExport)) return [];
740
+ const assigned2 = assignedProps(program);
740
741
  const meta = objectOf(metaNode, bindings, wrappers);
741
742
  const title = stringOf(prop(meta, "title"), bindings) ?? autoTitle2;
742
743
  const include = storyFilter(meta, "includeStories");
@@ -747,7 +748,7 @@ function indexProgram(program, file, autoTitle2, extra, foreign) {
747
748
  const metaTags = tagsOf(meta);
748
749
  const native = wrappers.has(calleeOf(metaNode, bindings) ?? "");
749
750
  return exported.filter(({ name }) => name !== "__namedExportsOrder").filter(({ name }) => (!include || include(name)) && (!exclude || !exclude(name))).filter((e) => !native || (e.from ? foreign?.reexport(e.name) ?? true : mayBeStory(e.node, { bindings, wrappers, foreign }))).map(({ name, node, doc }) => {
750
- const spec = objectOf(node, bindings, wrappers);
751
+ const spec = withAssigned(objectOf(node, bindings, wrappers), assigned2.get(name));
751
752
  const isolate = isolateOf(spec, bindings, wrappers, name) ?? metaIsolate;
752
753
  const tags = combineTags(metaTags, tagsOf(spec));
753
754
  const viewport = native ? viewportOf(spec, bindings, wrappers) : void 0;
@@ -766,6 +767,23 @@ function indexProgram(program, file, autoTitle2, extra, foreign) {
766
767
  };
767
768
  });
768
769
  }
770
+ function assignedProps(program) {
771
+ const out = /* @__PURE__ */ new Map();
772
+ for (const stmt of program.body) {
773
+ const e = stmt.type === "ExpressionStatement" ? stmt.expression : void 0;
774
+ if (e?.type !== "AssignmentExpression" || e.operator !== "=" || e.left.type !== "MemberExpression") continue;
775
+ const { object, property, computed } = e.left;
776
+ if (object.type !== "Identifier" || computed || property.type !== "Identifier") continue;
777
+ const props = out.get(object.name) ?? [];
778
+ props.push({ type: "ObjectProperty", key: property, value: e.right, computed: false, shorthand: false });
779
+ out.set(object.name, props);
780
+ }
781
+ return out;
782
+ }
783
+ function withAssigned(spec, props) {
784
+ if (!props) return spec;
785
+ return { type: "ObjectExpression", properties: [...[...props].reverse(), ...spec?.properties ?? []] };
786
+ }
769
787
  function isolateOf(obj, bindings, wrappers, exportName) {
770
788
  const parameters = objectOf(prop(obj, "parameters"), bindings, wrappers);
771
789
  const forge2 = objectOf(prop(parameters, "forge"), bindings, wrappers);
@@ -2190,6 +2208,23 @@ function watchSources(server, { files, depGraph, reindex, redeps, logError }) {
2190
2208
  });
2191
2209
  }
2192
2210
  }
2211
+ function coalesced(run, onError) {
2212
+ let running = false;
2213
+ let next = null;
2214
+ const start = (arg) => {
2215
+ running = true;
2216
+ run(arg).catch(onError).finally(() => {
2217
+ running = false;
2218
+ const queued = next;
2219
+ next = null;
2220
+ if (queued) start(queued.arg);
2221
+ });
2222
+ };
2223
+ return (arg) => {
2224
+ if (running) next = { arg };
2225
+ else start(arg);
2226
+ };
2227
+ }
2193
2228
  function companions(options, files) {
2194
2229
  const { storybookShims: shims, propControls: controls, api } = options;
2195
2230
  const storyFiles = () => [...files().keys()];
@@ -2212,7 +2247,6 @@ function forge(options) {
2212
2247
  let logger;
2213
2248
  let deps = null;
2214
2249
  let depsSent = null;
2215
- let depsPending = null;
2216
2250
  const wrappersOf = /* @__PURE__ */ new Map();
2217
2251
  let settledAll = null;
2218
2252
  const glob = () => {
@@ -2285,20 +2319,17 @@ function forge(options) {
2285
2319
  }
2286
2320
  }
2287
2321
  };
2288
- function redeps(server) {
2289
- if (depsSent === null || depsPending) return;
2290
- const resolve6 = async (spec, importer) => (await server.environments.client.pluginContainer.resolveId(spec, importer))?.id ?? null;
2291
- depsPending = Promise.resolve().then(async () => {
2292
- const graph = await graphOf(resolve6);
2293
- const json3 = JSON.stringify(graph);
2294
- if (json3 === depsSent) return;
2295
- depsSent = json3;
2296
- invalidate(server, ["deps.js"]);
2297
- server.ws.send({ type: "custom", event: "forge:deps", data: graph });
2298
- }).catch(logError).finally(() => {
2299
- depsPending = null;
2300
- });
2301
- }
2322
+ const rebuild = coalesced(async (server) => {
2323
+ const graph = await graphOf(async (spec, importer) => (await server.environments.client.pluginContainer.resolveId(spec, importer))?.id ?? null);
2324
+ const json3 = JSON.stringify(graph);
2325
+ if (json3 === depsSent) return;
2326
+ depsSent = json3;
2327
+ invalidate(server, ["deps.js"]);
2328
+ server.ws.send({ type: "custom", event: "forge:deps", data: graph });
2329
+ }, logError);
2330
+ const redeps = (server) => {
2331
+ if (depsSent !== null) rebuild(server);
2332
+ };
2302
2333
  const storiesReaching = (changed) => {
2303
2334
  const current = files();
2304
2335
  const seen = /* @__PURE__ */ new Set();