@codepress/design-kit 0.0.0-stage → 0.1.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.
package/src/cli.mjs ADDED
@@ -0,0 +1,175 @@
1
+ /**
2
+ * The `design-kit` command line: capture a design kit from a Storybook build
3
+ * and a running app, and compose mockups from it. Run `design-kit --help`.
4
+ */
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { resolve } from "node:path";
7
+
8
+ import { ConfigError, loadConfig } from "./config.mjs";
9
+
10
+ const VERSION = JSON.parse(
11
+ readFileSync(new URL("../package.json", import.meta.url), "utf8"),
12
+ ).version;
13
+
14
+ const USAGE = `usage: design-kit <command> [options]
15
+
16
+ capture stories capture every story of a Storybook 7/8 static build
17
+ [--config <file>] [--out <dir>] [--storybook-dir <dir>]
18
+ [--only <id,...> | --only-file <file>] [--base <previous kit>]
19
+ capture pages capture the configured pages of a running app
20
+ [--config <file>] [--out <dir>] [--base-url <url>] [--only <name,...>]
21
+ [--ready <name>=<selector>] [--screenshots <dir>] [--timeout <ms>]
22
+ kit <info|find|show|compose> ...
23
+ look up and compose from a kit (\`design-kit kit --help\`)
24
+ [--kit <dir>] default: DESIGN_KIT_DIR, the config's "out", ./design-kit
25
+ validate check design-kit.config.json against the v1 schema
26
+ [--config <file>]
27
+
28
+ The config is the nearest design-kit.config.json at or above the working
29
+ directory unless --config names one. Exit codes: 0 done, 1 a capture could
30
+ not run, 2 a config or usage error.`;
31
+
32
+ function parse(argv, spec) {
33
+ const options = {};
34
+ for (let i = 0; i < argv.length; i += 1) {
35
+ const arg = argv[i];
36
+ if (arg === "--") continue;
37
+ const name = arg.replace(/^--/, "");
38
+ if (!arg.startsWith("--") || !(name in spec)) {
39
+ throw new ConfigError(`unknown option ${arg}\n\n${USAGE}`);
40
+ }
41
+ if (spec[name] === "flag") {
42
+ options[name] = true;
43
+ continue;
44
+ }
45
+ const value = argv[i + 1];
46
+ if (value === undefined) throw new ConfigError(`${arg} needs a value`);
47
+ i += 1;
48
+ if (spec[name] === "list") (options[name] ??= []).push(value);
49
+ else options[name] = value;
50
+ }
51
+ return options;
52
+ }
53
+
54
+ async function captureStories(argv) {
55
+ const options = parse(argv, {
56
+ config: "value",
57
+ out: "value",
58
+ "storybook-dir": "value",
59
+ only: "value",
60
+ "only-file": "value",
61
+ base: "value",
62
+ });
63
+ const config = loadConfig(options.config);
64
+ const { captureStories: run, parseOnly } =
65
+ await import("./capture-stories.mjs");
66
+ const only =
67
+ options.only !== undefined
68
+ ? parseOnly(options.only)
69
+ : options["only-file"] !== undefined
70
+ ? parseOnly(readFileSync(options["only-file"], "utf8"))
71
+ : undefined;
72
+ await run(config, {
73
+ out: options.out,
74
+ storybookDir: options["storybook-dir"],
75
+ only,
76
+ base: options.base ? resolve(options.base) : undefined,
77
+ });
78
+ return 0;
79
+ }
80
+
81
+ async function capturePagesCommand(argv) {
82
+ const options = parse(argv, {
83
+ config: "value",
84
+ out: "value",
85
+ "base-url": "value",
86
+ only: "value",
87
+ ready: "list",
88
+ screenshots: "value",
89
+ timeout: "value",
90
+ });
91
+ const config = loadConfig(options.config);
92
+ const ready = {};
93
+ for (const raw of options.ready ?? []) {
94
+ const at = raw.indexOf("=");
95
+ if (at < 1)
96
+ throw new ConfigError(`--ready takes <page>=<selector>, got ${raw}`);
97
+ ready[raw.slice(0, at)] = raw.slice(at + 1);
98
+ }
99
+ const timeoutMs =
100
+ options.timeout === undefined ? undefined : Number(options.timeout);
101
+ if (timeoutMs !== undefined && !(Number.isFinite(timeoutMs) && timeoutMs > 0))
102
+ throw new ConfigError("--timeout takes a positive number of milliseconds");
103
+ const { capturePages } = await import("./capture-pages.mjs");
104
+ const session = { interrupted: false, browser: null };
105
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
106
+ process.on(signal, () => {
107
+ session.interrupted = true;
108
+ });
109
+ }
110
+ return capturePages(config, {
111
+ out: options.out,
112
+ baseUrl: options["base-url"],
113
+ only: options.only?.split(",").filter(Boolean),
114
+ ready,
115
+ screenshots: options.screenshots ? resolve(options.screenshots) : undefined,
116
+ timeoutMs,
117
+ session,
118
+ });
119
+ }
120
+
121
+ async function kitCommand(argv, env) {
122
+ const { run } = await import("./kit.mjs");
123
+ let defaultDir = resolve("design-kit");
124
+ if (!argv.includes("--kit") && !env.DESIGN_KIT_DIR) {
125
+ try {
126
+ const config = loadConfig();
127
+ if (config.out) defaultDir = config.out;
128
+ } catch {
129
+ // No usable config: the kit folder comes from --kit or ./design-kit.
130
+ }
131
+ if (!existsSync(defaultDir)) defaultDir = process.cwd();
132
+ }
133
+ return run(argv, undefined, env, defaultDir);
134
+ }
135
+
136
+ export async function main(argv = process.argv.slice(2), env = process.env) {
137
+ const [command, sub, ...rest] = argv;
138
+ try {
139
+ if (!command) {
140
+ console.log(USAGE);
141
+ return 2;
142
+ }
143
+ // `kit --help` is the kit tool's own usage; anywhere else, this one.
144
+ if (
145
+ command !== "kit" &&
146
+ argv.some((arg) => arg === "--help" || arg === "-h")
147
+ ) {
148
+ console.log(USAGE);
149
+ return 0;
150
+ }
151
+ if (command === "--version") {
152
+ console.log(VERSION);
153
+ return 0;
154
+ }
155
+ if (command === "kit") return await kitCommand(argv.slice(1), env);
156
+ if (command === "validate") {
157
+ const options = parse(argv.slice(1), { config: "value" });
158
+ const config = loadConfig(options.config);
159
+ console.log(`${config.file}: valid (version 1)`);
160
+ return 0;
161
+ }
162
+ if (command === "capture" && sub === "stories")
163
+ return await captureStories(rest);
164
+ if (command === "capture" && sub === "pages")
165
+ return await capturePagesCommand(rest);
166
+ throw new ConfigError(`unknown command "${argv.join(" ")}"\n\n${USAGE}`);
167
+ } catch (error) {
168
+ if (error instanceof ConfigError) {
169
+ console.error(`design-kit: ${error.message}`);
170
+ return 2;
171
+ }
172
+ console.error(`design-kit: ${error?.message ?? error}`);
173
+ return 1;
174
+ }
175
+ }
package/src/config.mjs ADDED
@@ -0,0 +1,276 @@
1
+ /**
2
+ * design-kit.config.json: finding it, validating it against the v1 schema
3
+ * shipped in this package (schema/design-kit.config.schema.json), and
4
+ * resolving it into the shape the captures use. A config error names the
5
+ * field and exits 2.
6
+ */
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { dirname, join, resolve } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ export const CONFIG_NAME = "design-kit.config.json";
12
+ export const SCHEMA_PATH = fileURLToPath(
13
+ new URL("../schema/design-kit.config.schema.json", import.meta.url),
14
+ );
15
+ export const DEFAULT_FIXED_TIME = "2026-01-01T12:00:00.000Z";
16
+ export const DEFAULT_PAGE_WIDTHS = [
17
+ { width: 1280, height: 800 },
18
+ { width: 390, height: 844 },
19
+ ];
20
+
21
+ /** A problem with the config or the command line: exit 2. */
22
+ export class ConfigError extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.exitCode = 2;
26
+ }
27
+ }
28
+
29
+ let schemaCache = null;
30
+ export function configSchema() {
31
+ schemaCache ??= JSON.parse(readFileSync(SCHEMA_PATH, "utf8"));
32
+ return schemaCache;
33
+ }
34
+
35
+ // ── validation (the subset of JSON Schema the shipped schema uses) ──────────
36
+
37
+ function typeOf(value) {
38
+ if (Array.isArray(value)) return "array";
39
+ if (value === null) return "null";
40
+ if (typeof value === "number")
41
+ return Number.isInteger(value) ? "integer" : "number";
42
+ return typeof value;
43
+ }
44
+
45
+ function fieldName(path) {
46
+ return path.length ? path.join("").replace(/^\./, "") : "(the config)";
47
+ }
48
+
49
+ /**
50
+ * Every place `value` breaks `schema`, as "<field>: <problem>" strings, in
51
+ * document order.
52
+ */
53
+ export function validate(value, schema = configSchema(), root = schema) {
54
+ const errors = [];
55
+ const walk = (node, rule, path) => {
56
+ if (rule.$ref) {
57
+ const name = rule.$ref.replace(/^#\/\$defs\//, "");
58
+ walk(node, { ...root.$defs[name], ...rule, $ref: undefined }, path);
59
+ return;
60
+ }
61
+ const field = fieldName(path);
62
+ const actual = typeOf(node);
63
+ if (rule.type) {
64
+ const ok =
65
+ actual === rule.type ||
66
+ (rule.type === "number" && actual === "integer");
67
+ if (!ok) {
68
+ errors.push(`${field}: must be ${an(rule.type)}, not ${an(actual)}`);
69
+ return;
70
+ }
71
+ }
72
+ if (rule.enum && !rule.enum.includes(node)) {
73
+ errors.push(
74
+ `${field}: must be ${rule.enum.map((v) => JSON.stringify(v)).join(" or ")}`,
75
+ );
76
+ }
77
+ if (actual === "string") {
78
+ if (rule.minLength && node.length < rule.minLength)
79
+ errors.push(`${field}: must not be empty`);
80
+ if (rule.pattern && !new RegExp(rule.pattern).test(node))
81
+ errors.push(`${field}: must match ${rule.pattern}`);
82
+ }
83
+ if (
84
+ (actual === "integer" || actual === "number") &&
85
+ rule.minimum !== undefined
86
+ ) {
87
+ if (node < rule.minimum)
88
+ errors.push(`${field}: must be at least ${rule.minimum}`);
89
+ }
90
+ if (actual === "array") {
91
+ if (rule.minItems && node.length < rule.minItems)
92
+ errors.push(
93
+ `${field}: needs at least ${rule.minItems} item${rule.minItems === 1 ? "" : "s"}`,
94
+ );
95
+ if (rule.items)
96
+ node.forEach((item, index) =>
97
+ walk(item, rule.items, [...path, `[${index}]`]),
98
+ );
99
+ }
100
+ if (actual === "object") {
101
+ for (const key of rule.required ?? []) {
102
+ if (!(key in node))
103
+ errors.push(`${fieldName([...path, `.${key}`])}: is required`);
104
+ }
105
+ for (const [key, child] of Object.entries(node)) {
106
+ const childPath = [...path, `.${key}`];
107
+ if (rule.properties && key in rule.properties) {
108
+ walk(child, rule.properties[key], childPath);
109
+ } else if (rule.additionalProperties === false) {
110
+ errors.push(`${fieldName(childPath)}: unknown key`);
111
+ } else if (
112
+ rule.additionalProperties &&
113
+ typeof rule.additionalProperties === "object"
114
+ ) {
115
+ walk(child, rule.additionalProperties, childPath);
116
+ }
117
+ }
118
+ }
119
+ };
120
+ walk(value, schema, []);
121
+ return errors;
122
+ }
123
+
124
+ function an(type) {
125
+ return /^[aeiou]/.test(type) ? `an ${type}` : `a ${type}`;
126
+ }
127
+
128
+ // ── loading ─────────────────────────────────────────────────────────────────
129
+
130
+ /** The nearest design-kit.config.json at or above `from`, or null. */
131
+ export function findConfig(from = process.cwd()) {
132
+ let dir = resolve(from);
133
+ for (;;) {
134
+ const candidate = join(dir, CONFIG_NAME);
135
+ if (existsSync(candidate)) return candidate;
136
+ const parent = dirname(dir);
137
+ if (parent === dir) return null;
138
+ dir = parent;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Reads, validates and resolves a config. `file` defaults to the nearest
144
+ * design-kit.config.json above the working directory.
145
+ */
146
+ export function loadConfig(file) {
147
+ const path = file ? resolve(file) : findConfig();
148
+ if (!path) {
149
+ throw new ConfigError(
150
+ `no ${CONFIG_NAME} here or in any folder above; pass --config <file>`,
151
+ );
152
+ }
153
+ if (!existsSync(path)) throw new ConfigError(`${path}: no such file`);
154
+ let raw;
155
+ try {
156
+ raw = JSON.parse(readFileSync(path, "utf8"));
157
+ } catch (error) {
158
+ throw new ConfigError(`${path}: not valid JSON (${error.message})`);
159
+ }
160
+ return resolveConfig(raw, path);
161
+ }
162
+
163
+ /** Validates a parsed config and resolves its paths against `path`'s folder. */
164
+ export function resolveConfig(raw, path) {
165
+ const errors = validate(raw);
166
+ if (!errors.length && !raw.stories && !raw.pages) {
167
+ errors.push("(the config): needs a stories or a pages section");
168
+ }
169
+ raw.stories?.widths?.forEach((width, index) => {
170
+ try {
171
+ new RegExp(width.titles);
172
+ } catch (error) {
173
+ errors.push(
174
+ `stories.widths[${index}].titles: not a regular expression (${error.message})`,
175
+ );
176
+ }
177
+ });
178
+ for (const [key, section] of [
179
+ ["stories", raw.stories],
180
+ ["pages", raw.pages],
181
+ ]) {
182
+ if (section?.fixedTime && Number.isNaN(Date.parse(section.fixedTime)))
183
+ errors.push(`${key}.fixedTime: not an ISO 8601 date`);
184
+ }
185
+ if (raw.pages?.auth?.storageState && raw.pages?.auth?.setup) {
186
+ errors.push("pages.auth: give storageState or setup, not both");
187
+ }
188
+ if (raw.pages?.startCommand && !raw.pages?.waitUrl) {
189
+ errors.push("pages.waitUrl: is required with pages.startCommand");
190
+ }
191
+ const names = new Set();
192
+ raw.pages?.pages?.forEach((page, index) => {
193
+ if (names.has(page?.name))
194
+ errors.push(`pages.pages[${index}].name: "${page.name}" is used twice`);
195
+ names.add(page?.name);
196
+ });
197
+ if (errors.length) {
198
+ throw new ConfigError(
199
+ `${path}:\n${errors.map((error) => ` ${error}`).join("\n")}`,
200
+ );
201
+ }
202
+
203
+ const configDir = dirname(path);
204
+ const at = (value) =>
205
+ value === undefined ? undefined : resolve(configDir, value);
206
+ const config = {
207
+ file: path,
208
+ configDir,
209
+ raw,
210
+ out: at(raw.out),
211
+ theme: raw.theme ? normalizeTheme(raw.theme) : undefined,
212
+ };
213
+ if (raw.stories) {
214
+ const s = raw.stories;
215
+ const root = at(s.root ?? ".");
216
+ config.stories = {
217
+ root,
218
+ storybookDir: at(s.storybookDir),
219
+ buildCommand: s.buildCommand,
220
+ staticDir: s.staticDir ? resolve(root, s.staticDir) : undefined,
221
+ viewport: s.viewport ?? { width: 1280, height: 800 },
222
+ widths: (s.widths ?? []).map((w) => ({
223
+ titles: new RegExp(w.titles),
224
+ width: w.width,
225
+ height: w.height,
226
+ })),
227
+ fonts: s.fonts ? { dir: at(s.fonts.dir), faces: s.fonts.faces } : null,
228
+ fixedTime: s.fixedTime ?? DEFAULT_FIXED_TIME,
229
+ gaps: s.gaps
230
+ ? {
231
+ sourceRoot: s.gaps.sourceRoot ? at(s.gaps.sourceRoot) : root,
232
+ componentGlob: s.gaps.componentGlob,
233
+ }
234
+ : null,
235
+ };
236
+ }
237
+ if (raw.pages) {
238
+ const p = raw.pages;
239
+ config.pages = {
240
+ baseUrl: p.baseUrl,
241
+ startCommand: p.startCommand,
242
+ waitUrl: p.waitUrl,
243
+ cwd: at(p.cwd) ?? configDir,
244
+ startTimeoutMs: p.startTimeoutMs ?? 120_000,
245
+ auth: {
246
+ storageState: at(p.auth?.storageState),
247
+ setup: at(p.auth?.setup),
248
+ },
249
+ hooks: at(p.hooks),
250
+ widths: p.widths ?? DEFAULT_PAGE_WIDTHS,
251
+ fixedTime: p.fixedTime ?? DEFAULT_FIXED_TIME,
252
+ shellReady: p.shellReady,
253
+ layout: p.layout
254
+ ? { ...p.layout, default: p.layout.default ?? "default" }
255
+ : null,
256
+ fontsPath: p.fontsPath ?? null,
257
+ copyFonts: p.copyFonts ?? true,
258
+ timeoutMs: p.timeoutMs ?? 30_000,
259
+ forbidStrings: p.forbidStrings ?? [],
260
+ pages: p.pages,
261
+ };
262
+ }
263
+ return config;
264
+ }
265
+
266
+ /** The theme as the manifest records it, with its defaults filled in. */
267
+ export function normalizeTheme(theme) {
268
+ const attribute = theme.attribute ?? "class";
269
+ return {
270
+ attribute,
271
+ dark: theme.dark ?? "dark",
272
+ ...(theme.light ? { light: theme.light } : {}),
273
+ targets:
274
+ theme.targets ?? (attribute === "class" ? ["html", "body"] : ["html"]),
275
+ };
276
+ }
package/src/glob.mjs ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A small glob matcher for config paths: `**` (any number of folders), `*`
3
+ * and `?` (within one name), `[A-Z]` classes and `{a,b}` alternatives.
4
+ * Paths use `/`.
5
+ */
6
+
7
+ /** @param {string} glob */
8
+ export function globToRegExp(glob) {
9
+ let out = "";
10
+ for (let i = 0; i < glob.length; i += 1) {
11
+ const ch = glob[i];
12
+ if (ch === "*") {
13
+ if (glob[i + 1] === "*") {
14
+ const slash = glob[i + 2] === "/";
15
+ out += slash ? "(?:[^/]*/)*" : ".*";
16
+ i += slash ? 2 : 1;
17
+ } else out += "[^/]*";
18
+ } else if (ch === "?") out += "[^/]";
19
+ else if (ch === "[") {
20
+ const close = glob.indexOf("]", i + 1);
21
+ if (close < 0) out += "\\[";
22
+ else {
23
+ const body = glob.slice(i + 1, close).replace(/\\/g, "\\\\");
24
+ out += `[${body.startsWith("!") ? `^${body.slice(1)}` : body}]`;
25
+ i = close;
26
+ }
27
+ } else if (ch === "{") {
28
+ const close = glob.indexOf("}", i + 1);
29
+ if (close < 0) out += "\\{";
30
+ else {
31
+ out += `(?:${glob
32
+ .slice(i + 1, close)
33
+ .split(",")
34
+ .map((part) => globToRegExp(part).source.slice(1, -1))
35
+ .join("|")})`;
36
+ i = close;
37
+ }
38
+ } else out += ch.replace(/[.+^${}()|\\]/g, "\\$&");
39
+ }
40
+ return new RegExp(`^${out}$`);
41
+ }
42
+
43
+ /** @param {RegExp} pattern @param {string} path */
44
+ export function matchFile(pattern, path) {
45
+ return pattern.test(path.replace(/\\/g, "/"));
46
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,137 @@
1
+ /** Types for @codepress/design-kit's programmatic API. */
2
+
3
+ export const CONFIG_NAME: "design-kit.config.json";
4
+ export const SCHEMA_PATH: string;
5
+
6
+ export class ConfigError extends Error {
7
+ exitCode: 2;
8
+ }
9
+
10
+ export interface Size {
11
+ width: number;
12
+ height: number;
13
+ }
14
+
15
+ export interface PageConfig {
16
+ name: string;
17
+ title?: string;
18
+ path: string;
19
+ ready: string;
20
+ sourceFile?: string;
21
+ slots: { main: string[]; [slot: string]: string[] };
22
+ layoutSlots?: Record<string, { main: string[]; [slot: string]: string[] }>;
23
+ }
24
+
25
+ /** A loaded, validated config with its paths resolved. */
26
+ export interface DesignKitConfig {
27
+ file: string;
28
+ configDir: string;
29
+ raw: Record<string, unknown>;
30
+ out?: string;
31
+ theme?: {
32
+ attribute: string;
33
+ dark: string;
34
+ light?: string;
35
+ targets: ("html" | "body")[];
36
+ };
37
+ stories?: Record<string, unknown>;
38
+ pages?: {
39
+ baseUrl?: string;
40
+ fixedTime: string;
41
+ widths: Size[];
42
+ pages: PageConfig[];
43
+ [key: string]: unknown;
44
+ };
45
+ }
46
+
47
+ export function findConfig(from?: string): string | null;
48
+ export function loadConfig(file?: string): DesignKitConfig;
49
+ export function resolveConfig(raw: unknown, path: string): DesignKitConfig;
50
+ /** "<field>: <problem>" for every place `value` breaks the v1 schema. */
51
+ export function validate(value: unknown): string[];
52
+
53
+ export function playwrightVersion(from?: string): string;
54
+
55
+ export function captureStories(
56
+ config: DesignKitConfig,
57
+ options?: {
58
+ out?: string;
59
+ storybookDir?: string;
60
+ only?: string[];
61
+ base?: string;
62
+ log?: (message: string) => void;
63
+ },
64
+ ): Promise<{ out: string; rows: number; failed: number }>;
65
+
66
+ /** Returned by a `page` hook; both run inside the capture of one page. */
67
+ export interface PageHookResult {
68
+ /** The ready selector never appeared; may throw a clearer error. */
69
+ readyFailed?(): void | Promise<void>;
70
+ /** Runs just before serialization, in place of the optional screenshot. */
71
+ beforeSerialize?(info: { shotPath: string }): void | Promise<void>;
72
+ }
73
+
74
+ export interface CaptureHookInfo {
75
+ page: PageConfig;
76
+ viewport: Size;
77
+ }
78
+
79
+ export interface CapturePagesOptions {
80
+ out?: string;
81
+ only?: string[];
82
+ ready?: Record<string, string>;
83
+ screenshots?: string;
84
+ timeoutMs?: number;
85
+ baseUrl?: string;
86
+ /** A storageState file path or a Playwright storage state object. */
87
+ storageState?: string | object;
88
+ forbidStrings?: string[];
89
+ stripOrigins?: string[];
90
+ hooks?: {
91
+ /** Runs on each fresh context; may return a cleanup. */
92
+ context?(
93
+ context: unknown,
94
+ info: CaptureHookInfo,
95
+ ): void | (() => Promise<void>) | Promise<void | (() => Promise<void>)>;
96
+ /** Runs on each page before it navigates. */
97
+ page?(
98
+ page: unknown,
99
+ info: CaptureHookInfo,
100
+ ): void | PageHookResult | Promise<void | PageHookResult>;
101
+ };
102
+ /** The Playwright to drive; default the project's own. */
103
+ playwright?: { chromium: unknown };
104
+ log?: (message: string) => void;
105
+ /** Set `interrupted` to stop between pages; `browser` is set once launched. */
106
+ session?: { interrupted: boolean; browser: unknown };
107
+ }
108
+
109
+ /** 0, or 130 when `session.interrupted` stopped the run (nothing written). */
110
+ export function capturePages(
111
+ config: DesignKitConfig,
112
+ options?: CapturePagesOptions,
113
+ ): Promise<number>;
114
+
115
+ /** What to look for per forbidden value: as written, a token's long segments, and escaped. */
116
+ export function forbiddenNeedles(values: string[]): string[];
117
+ /** The first needle `text` contains, or null. */
118
+ export function findForbidden(text: string, needles: string[]): string | null;
119
+
120
+ export function loadKit(dir: string): unknown;
121
+ export function find(kit: unknown, query: string): unknown[];
122
+ export function compose(
123
+ kit: unknown,
124
+ options: {
125
+ frame: string;
126
+ slots?: { name: string; content: string }[];
127
+ theme?: "light" | "dark";
128
+ showNew?: boolean;
129
+ },
130
+ ): string;
131
+ /** Runs one `kit` command; returns its exit code. */
132
+ export function runKit(
133
+ argv: string[],
134
+ io?: { out(text: string): void; err(text: string): void },
135
+ env?: Record<string, string | undefined>,
136
+ defaultDir?: string,
137
+ ): number;
package/src/index.mjs ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @codepress/design-kit's programmatic API. The command line (`design-kit`)
3
+ * is a thin layer over these.
4
+ */
5
+ export {
6
+ CONFIG_NAME,
7
+ ConfigError,
8
+ SCHEMA_PATH,
9
+ findConfig,
10
+ loadConfig,
11
+ resolveConfig,
12
+ validate,
13
+ } from "./config.mjs";
14
+ export { captureStories, playwrightVersion } from "./capture-stories.mjs";
15
+ export { capturePages } from "./capture-pages.mjs";
16
+ export { findForbidden, forbiddenNeedles } from "./pages.mjs";
17
+ export { compose, find, loadKit, run as runKit } from "./kit.mjs";