@codepress/design-kit 0.1.0 → 0.1.2
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 +47 -3
- package/package.json +6 -3
- package/src/capture-pages.mjs +22 -5
- package/src/cli.mjs +18 -1
- package/src/init.mjs +1017 -0
- package/src/kit.d.ts +81 -0
- package/src/kit.mjs +44 -12
- package/src/templates/design-kit-skill.md +156 -0
package/README.md
CHANGED
|
@@ -19,6 +19,47 @@ It runs on Node 20 or later. Playwright is a peer dependency, so the capture
|
|
|
19
19
|
drives the Playwright and Chromium your project already has. The `kit`
|
|
20
20
|
commands need neither.
|
|
21
21
|
|
|
22
|
+
## Set up in your repository
|
|
23
|
+
|
|
24
|
+
`design-kit init` sets a kit up as one reviewable change. Run it in the
|
|
25
|
+
repository's root, or in a monorepo with `--root <package>` (the config goes
|
|
26
|
+
there; the workflow, skill and instructions line go at the repository root):
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx @codepress/design-kit init --dry-run # what it found and would write
|
|
30
|
+
npx @codepress/design-kit init
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
It looks for Storybook (a `storybook` or `@storybook/*` dependency, or a
|
|
34
|
+
`.storybook` folder, in the project or its workspace packages) and its
|
|
35
|
+
version, the framework (Next.js, Vite, Remix, Create React App, Astro or
|
|
36
|
+
other), the package manager and lockfile, the dev or start script and the
|
|
37
|
+
app's local URL, an existing Playwright, and whether the app switches to dark
|
|
38
|
+
with a class or a `data-theme` attribute. Then it writes:
|
|
39
|
+
|
|
40
|
+
- `design-kit.config.json`: the `stories` section when Storybook is found, a
|
|
41
|
+
`pages` section with `TODO` selectors to fill in, and the `theme`.
|
|
42
|
+
- `.github/workflows/design-kit.yml`: a daily build (and `workflow_dispatch`)
|
|
43
|
+
on `ubuntu-latest`. It skips when there are no commits since the newest
|
|
44
|
+
`design-kit` artifact's commit; otherwise it installs dependencies, this
|
|
45
|
+
package at a pinned version and Chromium, builds Storybook and captures the
|
|
46
|
+
stories, captures the pages once no `TODO` is left in the config, and
|
|
47
|
+
uploads the `design-kit` artifact for 30 days. A run with nothing to
|
|
48
|
+
capture yet (no stories, pages still `TODO`) ends with a warning and
|
|
49
|
+
uploads nothing. The page capture gets
|
|
50
|
+
`DESIGN_KIT_USERNAME` and `DESIGN_KIT_PASSWORD` from repository secrets for
|
|
51
|
+
a sign-in module (`pages.auth.setup`).
|
|
52
|
+
- `.claude/skills/design-kit/SKILL.md`: how agents fetch the newest kit,
|
|
53
|
+
compose mockups from it and caption them, and `.agents/skills/design-kit` as
|
|
54
|
+
a symlink to it (a copy when the repository keeps real folders there).
|
|
55
|
+
- One line in `AGENTS.md`, or `CLAUDE.md` when only that exists (a new
|
|
56
|
+
`AGENTS.md` when neither does), pointing at the skill.
|
|
57
|
+
|
|
58
|
+
It prints what it found and what is still to decide: the page selectors, the
|
|
59
|
+
start command, sign-in, a missing Storybook. It never overwrites an existing
|
|
60
|
+
file without `--force` (a repository with a `design-kit.config.json` exits 1,
|
|
61
|
+
"already set up") and never writes a secret.
|
|
62
|
+
|
|
22
63
|
## Configuration
|
|
23
64
|
|
|
24
65
|
The commands read `design-kit.config.json` from the working directory or the
|
|
@@ -109,8 +150,9 @@ export returns (`async ({ browser, baseUrl, config }) => state`).
|
|
|
109
150
|
for apps that render a different frame at phone width; `layoutSlots[<that
|
|
110
151
|
value>]` then replaces `slots`.
|
|
111
152
|
- `forbidStrings`: values that must never reach the kit, such as a test
|
|
112
|
-
account's token.
|
|
113
|
-
HTML or JSON, or (for a token) any long segment of it,
|
|
153
|
+
account's token. When any page's output contains one, as written, escaped
|
|
154
|
+
for HTML or JSON, or (for a token) any long segment of it, the capture
|
|
155
|
+
fails (exit 1) and writes no page, so a CI job uploads nothing.
|
|
114
156
|
- `fontsPath`, `copyFonts`: the fonts the pages load are copied into the
|
|
115
157
|
kit's `fonts/` folder (`copyFonts: false` keeps them out); `fontsPath` says
|
|
116
158
|
which URL path holds them when not every font file is one.
|
|
@@ -122,6 +164,7 @@ value>]` then replaces `slots`.
|
|
|
122
164
|
## Commands
|
|
123
165
|
|
|
124
166
|
```bash
|
|
167
|
+
design-kit init [--root dir] [--dry-run] [--force]
|
|
125
168
|
design-kit capture stories [--config f] [--out dir] [--storybook-dir dir]
|
|
126
169
|
[--only id,... | --only-file f] [--base previous-kit]
|
|
127
170
|
design-kit capture pages [--config f] [--out dir] [--base-url url] [--only name,...]
|
|
@@ -136,7 +179,8 @@ byte. The two captures share one kit folder and keep each other's files: run
|
|
|
136
179
|
both with the same `--out`.
|
|
137
180
|
|
|
138
181
|
Exit codes: 0 done, 1 a capture could not run (no Storybook build, the app
|
|
139
|
-
did not start
|
|
182
|
+
did not start, a forbidden string) or `init` found the repository already set
|
|
183
|
+
up, 2 a config or usage error. A story or page that fails is a
|
|
140
184
|
`gaps.json` row and does not fail the run.
|
|
141
185
|
|
|
142
186
|
Operational knobs: `DESIGN_KIT_CONCURRENCY` (browser pages at once, default
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codepress/design-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Capture your Storybook and real app pages as a static design kit, and compose self-contained mockups from it",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"design-kit": "
|
|
7
|
+
"design-kit": "bin/design-kit.mjs"
|
|
8
8
|
},
|
|
9
9
|
"main": "./src/index.mjs",
|
|
10
10
|
"types": "./src/index.d.ts",
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
"types": "./src/index.d.ts",
|
|
14
14
|
"default": "./src/index.mjs"
|
|
15
15
|
},
|
|
16
|
-
"./kit":
|
|
16
|
+
"./kit": {
|
|
17
|
+
"types": "./src/kit.d.ts",
|
|
18
|
+
"default": "./src/kit.mjs"
|
|
19
|
+
},
|
|
17
20
|
"./schema.json": "./schema/design-kit.config.schema.json",
|
|
18
21
|
"./package.json": "./package.json"
|
|
19
22
|
},
|
package/src/capture-pages.mjs
CHANGED
|
@@ -376,8 +376,16 @@ function themeClassesOf(theme) {
|
|
|
376
376
|
* browser}: set `interrupted` to stop between pages; `browser` is set once
|
|
377
377
|
* launched so a signal handler can close it).
|
|
378
378
|
*
|
|
379
|
-
* Returns 0, or 130 when interrupted (nothing is written then).
|
|
379
|
+
* Returns 0, or 130 when interrupted (nothing is written then). Throws,
|
|
380
|
+
* writing nothing, when any page's markup carries a forbidStrings value.
|
|
380
381
|
*/
|
|
382
|
+
/** A page's markup carries a forbidStrings value. */
|
|
383
|
+
class ForbiddenStringError extends Error {
|
|
384
|
+
constructor() {
|
|
385
|
+
super("the page's markup carries a forbidStrings value");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
381
389
|
export async function capturePages(config, options = {}) {
|
|
382
390
|
const settings = config.pages;
|
|
383
391
|
if (!settings) {
|
|
@@ -442,6 +450,7 @@ export async function capturePages(config, options = {}) {
|
|
|
442
450
|
);
|
|
443
451
|
const timings = {};
|
|
444
452
|
const gaps = [];
|
|
453
|
+
const leaks = [];
|
|
445
454
|
const rows = [];
|
|
446
455
|
const files = new Map();
|
|
447
456
|
const fontUrls = new Map();
|
|
@@ -547,10 +556,7 @@ export async function capturePages(config, options = {}) {
|
|
|
547
556
|
].join("\n"),
|
|
548
557
|
needles,
|
|
549
558
|
);
|
|
550
|
-
if (found)
|
|
551
|
-
throw new Error(
|
|
552
|
-
"the page's markup carries a forbidStrings value; not written",
|
|
553
|
-
);
|
|
559
|
+
if (found) throw new ForbiddenStringError();
|
|
554
560
|
|
|
555
561
|
// CSS assets on the app origin (other than fonts) are inlined.
|
|
556
562
|
const inlined = new Map();
|
|
@@ -610,6 +616,11 @@ export async function capturePages(config, options = {}) {
|
|
|
610
616
|
`captured ${id} (${capture.layout}, slots ${capture.slots.join(",")}, ${Math.round(Buffer.byteLength(html) / 1024)} KB, ${((performance.now() - pageStart) / 1000).toFixed(1)}s)`,
|
|
611
617
|
);
|
|
612
618
|
} catch (error) {
|
|
619
|
+
if (error instanceof ForbiddenStringError) {
|
|
620
|
+
leaks.push(id);
|
|
621
|
+
log(`${id}: ${error.message}`);
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
613
624
|
const reason = String(error?.message ?? error)
|
|
614
625
|
.split("\n")
|
|
615
626
|
.slice(0, 3)
|
|
@@ -630,6 +641,12 @@ export async function capturePages(config, options = {}) {
|
|
|
630
641
|
fs.rmSync(shotsDir, { recursive: true, force: true });
|
|
631
642
|
}
|
|
632
643
|
|
|
644
|
+
// A leaked value fails the whole capture: no page of this run is written,
|
|
645
|
+
// so a CI job stops before it uploads anything.
|
|
646
|
+
if (leaks.length)
|
|
647
|
+
throw new Error(
|
|
648
|
+
`${leaks.join(", ")} carried a forbidStrings value; nothing was written`,
|
|
649
|
+
);
|
|
633
650
|
writePagesKit(out, {
|
|
634
651
|
rows,
|
|
635
652
|
files,
|
package/src/cli.mjs
CHANGED
|
@@ -13,6 +13,9 @@ const VERSION = JSON.parse(
|
|
|
13
13
|
|
|
14
14
|
const USAGE = `usage: design-kit <command> [options]
|
|
15
15
|
|
|
16
|
+
init set a kit up in this repository: the config, a daily
|
|
17
|
+
GitHub Actions build, and a skill for its agents
|
|
18
|
+
[--root <dir>] [--dry-run] [--force]
|
|
16
19
|
capture stories capture every story of a Storybook 7/8 static build
|
|
17
20
|
[--config <file>] [--out <dir>] [--storybook-dir <dir>]
|
|
18
21
|
[--only <id,...> | --only-file <file>] [--base <previous kit>]
|
|
@@ -27,7 +30,8 @@ const USAGE = `usage: design-kit <command> [options]
|
|
|
27
30
|
|
|
28
31
|
The config is the nearest design-kit.config.json at or above the working
|
|
29
32
|
directory unless --config names one. Exit codes: 0 done, 1 a capture could
|
|
30
|
-
not run, 2 a config or usage
|
|
33
|
+
not run (or init found the repository already set up), 2 a config or usage
|
|
34
|
+
error.`;
|
|
31
35
|
|
|
32
36
|
function parse(argv, spec) {
|
|
33
37
|
const options = {};
|
|
@@ -153,6 +157,19 @@ export async function main(argv = process.argv.slice(2), env = process.env) {
|
|
|
153
157
|
return 0;
|
|
154
158
|
}
|
|
155
159
|
if (command === "kit") return await kitCommand(argv.slice(1), env);
|
|
160
|
+
if (command === "init") {
|
|
161
|
+
const options = parse(argv.slice(1), {
|
|
162
|
+
root: "value",
|
|
163
|
+
"dry-run": "flag",
|
|
164
|
+
force: "flag",
|
|
165
|
+
});
|
|
166
|
+
const { init } = await import("./init.mjs");
|
|
167
|
+
return init({
|
|
168
|
+
root: options.root ?? ".",
|
|
169
|
+
dryRun: Boolean(options["dry-run"]),
|
|
170
|
+
force: Boolean(options.force),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
156
173
|
if (command === "validate") {
|
|
157
174
|
const options = parse(argv.slice(1), { config: "value" });
|
|
158
175
|
const config = loadConfig(options.config);
|