@codepress/design-kit 0.2.2 → 0.4.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/README.md +127 -15
- package/package.json +1 -1
- package/schema/design-kit.config.schema.json +11 -1
- package/skills/design-kit-setup/SKILL.md +14 -10
- package/skills/storybook-setup/SKILL.md +389 -0
- package/src/capture-stories.mjs +1 -1
- package/src/ci.mjs +463 -0
- package/src/cli.mjs +52 -2
- package/src/init.mjs +224 -41
- package/src/templates/design-kit-skill.md +16 -5
package/src/init.mjs
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* `design-kit init`: sets a design kit up in a repository. It looks at the
|
|
3
3
|
* project (Storybook, framework, package manager, dev script, Playwright,
|
|
4
4
|
* theme) and writes design-kit.config.json, a daily GitHub Actions workflow
|
|
5
|
-
* that
|
|
6
|
-
* one line in AGENTS.md or
|
|
7
|
-
* --force and never writes a
|
|
5
|
+
* that rebuilds the kit when the watched UI files change and uploads it, a
|
|
6
|
+
* design-kit skill for the repository's agents and one line in AGENTS.md or
|
|
7
|
+
* CLAUDE.md. It never overwrites a file without --force and never writes a
|
|
8
|
+
* secret. `--workflow-only` rewrites just the workflow.
|
|
8
9
|
*/
|
|
9
10
|
import {
|
|
10
11
|
existsSync,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
} from "node:fs";
|
|
21
22
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
22
23
|
|
|
24
|
+
import { DEFAULT_MAX_AGE_DAYS } from "./ci.mjs";
|
|
23
25
|
import { CONFIG_NAME } from "./config.mjs";
|
|
24
26
|
|
|
25
27
|
const PACKAGE = JSON.parse(
|
|
@@ -34,6 +36,8 @@ export const WORKFLOW_PATH = ".github/workflows/design-kit.yml";
|
|
|
34
36
|
export const SKILL_DIR = ".claude/skills/design-kit";
|
|
35
37
|
export const AGENTS_SKILL_DIR = ".agents/skills/design-kit";
|
|
36
38
|
const TODO = "TODO";
|
|
39
|
+
/** How long a kit artifact is kept: the most a public repository allows. */
|
|
40
|
+
export const RETENTION_DAYS = 90;
|
|
37
41
|
|
|
38
42
|
const IGNORED_DIRS = new Set([
|
|
39
43
|
"node_modules",
|
|
@@ -549,12 +553,117 @@ function posix(path) {
|
|
|
549
553
|
return path.split(sep).join("/");
|
|
550
554
|
}
|
|
551
555
|
|
|
556
|
+
/** Folders at a package root that hold what the UI renders or loads. */
|
|
557
|
+
const ROOT_UI_DIRS = new Set([
|
|
558
|
+
".storybook",
|
|
559
|
+
"app",
|
|
560
|
+
"assets",
|
|
561
|
+
"components",
|
|
562
|
+
"design-tokens",
|
|
563
|
+
"lib",
|
|
564
|
+
"pages",
|
|
565
|
+
"public",
|
|
566
|
+
"src",
|
|
567
|
+
"stories",
|
|
568
|
+
"styles",
|
|
569
|
+
"theme",
|
|
570
|
+
"tokens",
|
|
571
|
+
]);
|
|
572
|
+
/** Global styles and tokens that can sit at the repository root. */
|
|
573
|
+
const ROOT_STYLE_DIRS = new Set(["design-tokens", "styles", "theme", "tokens"]);
|
|
574
|
+
const UI_CONFIG_FILE =
|
|
575
|
+
/^(tailwind|postcss|vite|next|astro|remix|svelte|nuxt|uno|panda)\.config\.[cm]?[jt]s$/;
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* What to watch for one package folder: the folder itself, or, when the
|
|
579
|
+
* package is the repository root, its UI folders and config files (watching
|
|
580
|
+
* the whole root would rebuild on every commit).
|
|
581
|
+
*/
|
|
582
|
+
function packageWatch(dir, repoRoot) {
|
|
583
|
+
if (dir !== repoRoot) return [posix(relative(repoRoot, dir))];
|
|
584
|
+
const paths = [];
|
|
585
|
+
for (const entry of safeReaddir(dir)) {
|
|
586
|
+
if (entry.isDirectory() && ROOT_UI_DIRS.has(entry.name))
|
|
587
|
+
paths.push(entry.name);
|
|
588
|
+
else if (
|
|
589
|
+
entry.isFile() &&
|
|
590
|
+
(UI_CONFIG_FILE.test(entry.name) ||
|
|
591
|
+
entry.name === "package.json" ||
|
|
592
|
+
entry.name === "index.html")
|
|
593
|
+
)
|
|
594
|
+
paths.push(entry.name);
|
|
595
|
+
}
|
|
596
|
+
return paths.sort();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** The workspace packages `dirs` depend on, directly or through each other. */
|
|
600
|
+
function workspaceDependencies(dirs, found) {
|
|
601
|
+
const all = [
|
|
602
|
+
...new Set([...workspacePackages(found.repoRoot), ...found.workspaces]),
|
|
603
|
+
];
|
|
604
|
+
const byName = new Map();
|
|
605
|
+
for (const dir of all) {
|
|
606
|
+
const name = readJson(join(dir, "package.json"))?.name;
|
|
607
|
+
if (name && !byName.has(name)) byName.set(name, dir);
|
|
608
|
+
}
|
|
609
|
+
const seen = new Set(dirs);
|
|
610
|
+
const queue = [...dirs];
|
|
611
|
+
const dependencies = [];
|
|
612
|
+
while (queue.length) {
|
|
613
|
+
const dir = queue.shift();
|
|
614
|
+
for (const name of Object.keys(deps(readJson(join(dir, "package.json"))))) {
|
|
615
|
+
const dep = byName.get(name);
|
|
616
|
+
if (dep && !seen.has(dep)) {
|
|
617
|
+
seen.add(dep);
|
|
618
|
+
dependencies.push(dep);
|
|
619
|
+
queue.push(dep);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return dependencies;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* The config's `watch` list: the paths, relative to the repository root,
|
|
628
|
+
* whose change rebuilds the kit. The Storybook and app folders, the
|
|
629
|
+
* workspace packages they depend on (shared UI usually lives there), global
|
|
630
|
+
* styles and tokens at the root, the config, the lockfile and the workflow.
|
|
631
|
+
*/
|
|
632
|
+
export function watchDefaults(found) {
|
|
633
|
+
const rel = (path) => posix(relative(found.repoRoot, path));
|
|
634
|
+
const roots = [
|
|
635
|
+
...(found.storybook ? [found.storybook.dir] : []),
|
|
636
|
+
found.framework?.dir ?? found.project,
|
|
637
|
+
];
|
|
638
|
+
const packages = [...new Set(roots)];
|
|
639
|
+
const paths = [];
|
|
640
|
+
for (const dir of [...packages, ...workspaceDependencies(packages, found)])
|
|
641
|
+
paths.push(...packageWatch(dir, found.repoRoot));
|
|
642
|
+
for (const entry of safeReaddir(found.repoRoot))
|
|
643
|
+
if (
|
|
644
|
+
(entry.isDirectory() && ROOT_STYLE_DIRS.has(entry.name)) ||
|
|
645
|
+
(entry.isFile() && UI_CONFIG_FILE.test(entry.name))
|
|
646
|
+
)
|
|
647
|
+
paths.push(entry.name);
|
|
648
|
+
paths.push(rel(join(found.project, CONFIG_NAME)));
|
|
649
|
+
const pm = found.packageManager;
|
|
650
|
+
if (pm.lockfile) paths.push(rel(join(pm.installDir, pm.lockfile)));
|
|
651
|
+
paths.push(WORKFLOW_PATH);
|
|
652
|
+
const unique = [...new Set(paths.filter(Boolean))];
|
|
653
|
+
// A path inside another watched folder adds nothing.
|
|
654
|
+
return unique.filter(
|
|
655
|
+
(path) =>
|
|
656
|
+
!unique.some((other) => other !== path && path.startsWith(`${other}/`)),
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
552
660
|
/** design-kit.config.json, as an object. */
|
|
553
661
|
export function buildConfig(found) {
|
|
554
662
|
const config = {
|
|
555
663
|
$schema: `https://unpkg.com/${PACKAGE.name}@${PACKAGE.version}/schema/design-kit.config.schema.json`,
|
|
556
664
|
version: 1,
|
|
557
665
|
out: "design-kit",
|
|
666
|
+
watch: watchDefaults(found),
|
|
558
667
|
theme: {
|
|
559
668
|
attribute: found.theme.attribute,
|
|
560
669
|
dark: found.theme.dark,
|
|
@@ -634,13 +743,18 @@ export function buildWorkflow(found) {
|
|
|
634
743
|
|
|
635
744
|
return `name: Design kit
|
|
636
745
|
|
|
637
|
-
# Captures this repository's design kit
|
|
638
|
-
#
|
|
639
|
-
#
|
|
640
|
-
#
|
|
641
|
-
#
|
|
746
|
+
# Captures this repository's design kit: ${found.storybook ? "every Storybook story and " : ""}the pages
|
|
747
|
+
# listed in ${at(CONFIG_NAME)}, as static HTML with the CSS and fonts they
|
|
748
|
+
# load. It uploads the result as the \`design-kit\` artifact, which agents
|
|
749
|
+
# download to draw mockups from the app's real markup.
|
|
750
|
+
#
|
|
751
|
+
# It runs daily, but builds only when a path in the config's "watch" list
|
|
752
|
+
# changed since the newest kit's commit, when there is no kit yet, or when
|
|
753
|
+
# the newest kit is DESIGN_KIT_MAX_AGE_DAYS old, so a kit never expires
|
|
754
|
+
# unreplaced. After an upload it deletes the branch's older kits.
|
|
642
755
|
#
|
|
643
|
-
# Written by \`design-kit init\` (${PACKAGE.name})
|
|
756
|
+
# Written by \`design-kit init\` (${PACKAGE.name}); \`init --workflow-only
|
|
757
|
+
# --force\` rewrites just this file. Edit it freely.
|
|
644
758
|
|
|
645
759
|
on:
|
|
646
760
|
schedule:
|
|
@@ -648,11 +762,11 @@ on:
|
|
|
648
762
|
workflow_dispatch:
|
|
649
763
|
inputs:
|
|
650
764
|
force:
|
|
651
|
-
description: Build even when
|
|
765
|
+
description: Build even when no watched file changed since the newest kit
|
|
652
766
|
type: boolean
|
|
653
767
|
default: false
|
|
654
768
|
package:
|
|
655
|
-
description: "The ${PACKAGE.name} to
|
|
769
|
+
description: "The ${PACKAGE.name} to build with, as an npm package spec (default: the pinned version below). Always builds."
|
|
656
770
|
type: string
|
|
657
771
|
default: ""
|
|
658
772
|
|
|
@@ -667,30 +781,46 @@ env:
|
|
|
667
781
|
DESIGN_KIT_PACKAGE: "${PACKAGE.name}@${PACKAGE.version}"
|
|
668
782
|
PLAYWRIGHT_PACKAGE: "@playwright/test@${playwrightPin()}"
|
|
669
783
|
CONFIG: \${{ github.workspace }}/${at(CONFIG_NAME)}
|
|
784
|
+
# Rebuild a kit this many days old even with no UI change. Kits are kept
|
|
785
|
+
# ${RETENTION_DAYS} days (retention-days below), so this refreshes the kit before it
|
|
786
|
+
# expires.
|
|
787
|
+
DESIGN_KIT_MAX_AGE_DAYS: "${DEFAULT_MAX_AGE_DAYS}"
|
|
670
788
|
|
|
671
789
|
jobs:
|
|
672
790
|
check:
|
|
673
|
-
name:
|
|
791
|
+
name: Did the watched files change?
|
|
674
792
|
runs-on: ubuntu-latest
|
|
675
|
-
timeout-minutes:
|
|
793
|
+
timeout-minutes: 10
|
|
676
794
|
permissions:
|
|
795
|
+
contents: read
|
|
677
796
|
actions: read
|
|
678
797
|
outputs:
|
|
679
|
-
build: \${{ steps.
|
|
798
|
+
build: \${{ steps.check.outputs.build }}
|
|
680
799
|
steps:
|
|
681
|
-
|
|
800
|
+
# Only the config is checked out and no file contents are fetched: the
|
|
801
|
+
# check compares the git tree and blob ids of the watched paths at the
|
|
802
|
+
# newest kit's commit and at this one.
|
|
803
|
+
- uses: actions/checkout@v5
|
|
804
|
+
with:
|
|
805
|
+
fetch-depth: 1
|
|
806
|
+
filter: blob:none
|
|
807
|
+
sparse-checkout: ${at(CONFIG_NAME)}
|
|
808
|
+
sparse-checkout-cone-mode: false
|
|
809
|
+
|
|
810
|
+
- uses: actions/setup-node@v5
|
|
811
|
+
with:
|
|
812
|
+
node-version: "22"
|
|
813
|
+
|
|
814
|
+
- id: check
|
|
682
815
|
env:
|
|
683
816
|
GH_TOKEN: \${{ github.token }}
|
|
684
817
|
FORCE: \${{ inputs.force || 'false' }}
|
|
818
|
+
PACKAGE_INPUT: \${{ inputs.package || '' }}
|
|
685
819
|
run: |
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
if [
|
|
689
|
-
|
|
690
|
-
echo "build=false" >> "$GITHUB_OUTPUT"
|
|
691
|
-
else
|
|
692
|
-
echo "build=true" >> "$GITHUB_OUTPUT"
|
|
693
|
-
fi
|
|
820
|
+
args=()
|
|
821
|
+
if [ "$FORCE" = "true" ]; then args+=(--force); fi
|
|
822
|
+
if [ -n "$PACKAGE_INPUT" ]; then args+=(--package "$PACKAGE_INPUT"); fi
|
|
823
|
+
npx --yes -p "$DESIGN_KIT_PACKAGE" design-kit ci-check --config "$CONFIG" "\${args[@]}"
|
|
694
824
|
|
|
695
825
|
build:
|
|
696
826
|
name: Build the design kit
|
|
@@ -698,6 +828,8 @@ jobs:
|
|
|
698
828
|
if: needs.check.outputs.build == 'true'
|
|
699
829
|
runs-on: ubuntu-latest
|
|
700
830
|
timeout-minutes: 60
|
|
831
|
+
outputs:
|
|
832
|
+
artifact-id: \${{ steps.upload.outputs.artifact-id }}
|
|
701
833
|
steps:
|
|
702
834
|
- uses: actions/checkout@v5
|
|
703
835
|
with:
|
|
@@ -745,13 +877,37 @@ ${storiesStep}
|
|
|
745
877
|
run: |
|
|
746
878
|
echo "::warning title=Design kit::Nothing to capture yet, so no kit was uploaded: ${at(CONFIG_NAME)} has no stories and its pages still have ${TODO} settings. Fill in the pages (or add Storybook) and run this workflow again."
|
|
747
879
|
|
|
748
|
-
-
|
|
880
|
+
- id: upload
|
|
881
|
+
if: hashFiles('${at("design-kit")}/manifest.json') != ''
|
|
749
882
|
uses: actions/upload-artifact@v5
|
|
750
883
|
with:
|
|
751
884
|
name: design-kit
|
|
752
885
|
path: ${at("design-kit")}
|
|
753
|
-
|
|
886
|
+
# The most a public repository allows. A private repository may
|
|
887
|
+
# raise it to 400 (and DESIGN_KIT_MAX_AGE_DAYS with it).
|
|
888
|
+
retention-days: ${RETENTION_DAYS}
|
|
754
889
|
if-no-files-found: error
|
|
890
|
+
|
|
891
|
+
# Deletes this branch's older kits once the new one is uploaded. The only
|
|
892
|
+
# job that may delete artifacts; a failed delete is a warning.
|
|
893
|
+
cleanup:
|
|
894
|
+
name: Delete older kits
|
|
895
|
+
needs: build
|
|
896
|
+
if: needs.build.outputs.artifact-id != ''
|
|
897
|
+
runs-on: ubuntu-latest
|
|
898
|
+
timeout-minutes: 5
|
|
899
|
+
permissions:
|
|
900
|
+
actions: write
|
|
901
|
+
steps:
|
|
902
|
+
- uses: actions/setup-node@v5
|
|
903
|
+
with:
|
|
904
|
+
node-version: "22"
|
|
905
|
+
|
|
906
|
+
- continue-on-error: true
|
|
907
|
+
env:
|
|
908
|
+
GH_TOKEN: \${{ github.token }}
|
|
909
|
+
KEEP: \${{ needs.build.outputs.artifact-id }}
|
|
910
|
+
run: npx --yes -p "$DESIGN_KIT_PACKAGE" design-kit ci-cleanup --keep "$KEEP"
|
|
755
911
|
`;
|
|
756
912
|
}
|
|
757
913
|
|
|
@@ -759,19 +915,11 @@ ${storiesStep}
|
|
|
759
915
|
export function buildSkill(found) {
|
|
760
916
|
const pm = found.packageManager;
|
|
761
917
|
const projectDir = posix(relative(found.repoRoot, found.project)) || ".";
|
|
762
|
-
const watched = found.storybook
|
|
763
|
-
? [...new Set([found.storybook.dir, found.framework?.dir ?? found.project])]
|
|
764
|
-
: [found.framework?.dir ?? found.project];
|
|
765
|
-
const watchedPaths =
|
|
766
|
-
watched
|
|
767
|
-
.map((dir) => posix(relative(found.repoRoot, dir)) || ".")
|
|
768
|
-
.join(" ") || ".";
|
|
769
918
|
const cli = `npx --yes -p ${PACKAGE.name}@${PACKAGE.version} -p @playwright/test@${playwrightPin()} design-kit`;
|
|
770
919
|
const values = {
|
|
771
920
|
CLI: cli,
|
|
772
921
|
CONFIG: projectDir === "." ? CONFIG_NAME : `${projectDir}/${CONFIG_NAME}`,
|
|
773
922
|
PROJECT_DIR: projectDir,
|
|
774
|
-
WATCHED_PATHS: watchedPaths,
|
|
775
923
|
STORIES_LINE: found.storybook
|
|
776
924
|
? `$DK capture stories --config "$CONFIG" --out "$KIT" # builds Storybook first`
|
|
777
925
|
: `# (no Storybook: this kit has pages only)`,
|
|
@@ -902,6 +1050,7 @@ function describe(found) {
|
|
|
902
1050
|
? `${found.playwright.name}${found.playwright.version ? ` ${found.playwright.version}` : ""}`
|
|
903
1051
|
: "none (the workflow installs its own)",
|
|
904
1052
|
],
|
|
1053
|
+
["Rebuilds on", watchDefaults(found).join(", ")],
|
|
905
1054
|
[
|
|
906
1055
|
"Theme",
|
|
907
1056
|
found.theme.evidence
|
|
@@ -916,11 +1065,11 @@ function todo(found) {
|
|
|
916
1065
|
const sb = found.storybook;
|
|
917
1066
|
if (!sb)
|
|
918
1067
|
items.push(
|
|
919
|
-
"No Storybook: the config captures pages only. Decide whether to add Storybook with stories for the components the main screens use, capture real pages, or both.",
|
|
1068
|
+
"No Storybook: the config captures pages only. Decide whether to add Storybook with stories for the components the main screens use (`npx --yes @codepress/design-kit storybook-guide` prints a guide for that), capture real pages, or both.",
|
|
920
1069
|
);
|
|
921
|
-
else if (sb.major && (sb.major < 7 || sb.major >
|
|
1070
|
+
else if (sb.major && (sb.major < 7 || sb.major > 9))
|
|
922
1071
|
items.push(
|
|
923
|
-
`Storybook ${sb.version}: captures are supported for Storybook 7 and
|
|
1072
|
+
`Storybook ${sb.version}: captures are supported for Storybook 7, 8 and 9 static builds; check that \`capture stories\` works, or use page capture.`,
|
|
924
1073
|
);
|
|
925
1074
|
items.push(
|
|
926
1075
|
`Pages: replace the ${TODO} selectors in ${CONFIG_NAME} (pages[].ready and slots.main), add the app's main screens, and check startCommand and waitUrl. Until then the workflow skips page capture.`,
|
|
@@ -942,10 +1091,10 @@ function todo(found) {
|
|
|
942
1091
|
return items;
|
|
943
1092
|
}
|
|
944
1093
|
|
|
945
|
-
function summary(found, plan, { dryRun }) {
|
|
1094
|
+
function summary(found, plan, { dryRun, workflowOnly, notes }) {
|
|
946
1095
|
const rel = (path) => posix(relative(found.repoRoot, path)) || ".";
|
|
947
1096
|
const lines = [
|
|
948
|
-
`design-kit init${dryRun ? " --dry-run" : ""}: ${found.project}`,
|
|
1097
|
+
`design-kit init${workflowOnly ? " --workflow-only" : ""}${dryRun ? " --dry-run" : ""}: ${found.project}`,
|
|
949
1098
|
"",
|
|
950
1099
|
"Found",
|
|
951
1100
|
];
|
|
@@ -968,24 +1117,58 @@ function summary(found, plan, { dryRun }) {
|
|
|
968
1117
|
lines.push(` ${rel(write.path)}${note}`);
|
|
969
1118
|
}
|
|
970
1119
|
lines.push("", "Still to decide");
|
|
971
|
-
for (const item of todo(found)) lines.push(` - ${item}`);
|
|
1120
|
+
for (const item of notes ?? todo(found)) lines.push(` - ${item}`);
|
|
972
1121
|
return lines.join("\n");
|
|
973
1122
|
}
|
|
974
1123
|
|
|
975
1124
|
// ── the command ─────────────────────────────────────────────────────────────
|
|
976
1125
|
|
|
1126
|
+
/**
|
|
1127
|
+
* `init --workflow-only`: rewrites .github/workflows/design-kit.yml from
|
|
1128
|
+
* this version and nothing else. The config is left alone; when it has no
|
|
1129
|
+
* `watch` list, the summary prints the one init would write.
|
|
1130
|
+
*/
|
|
1131
|
+
function initWorkflowOnly(found, { dryRun, force, log }) {
|
|
1132
|
+
const configFile = join(found.project, CONFIG_NAME);
|
|
1133
|
+
if (!exists(configFile))
|
|
1134
|
+
throw new InitError(
|
|
1135
|
+
`${configFile} does not exist, so there is no kit to build yet. Run init without --workflow-only.`,
|
|
1136
|
+
);
|
|
1137
|
+
const path = join(found.repoRoot, WORKFLOW_PATH);
|
|
1138
|
+
if (!force && exists(path))
|
|
1139
|
+
throw new InitError(
|
|
1140
|
+
`${WORKFLOW_PATH} already exists. Nothing was written; pass --force with --workflow-only to replace it.`,
|
|
1141
|
+
);
|
|
1142
|
+
const plan = {
|
|
1143
|
+
writes: [{ path, kind: "file", content: buildWorkflow(found) }],
|
|
1144
|
+
};
|
|
1145
|
+
if (!dryRun) apply(plan.writes[0]);
|
|
1146
|
+
const notes = [];
|
|
1147
|
+
const raw = readJson(configFile);
|
|
1148
|
+
if (!Array.isArray(raw?.watch) || !raw.watch.length)
|
|
1149
|
+
notes.push(
|
|
1150
|
+
`${CONFIG_NAME} has no "watch" list, so the kit rebuilds after any commit. To rebuild only when the UI changes, add:\n "watch": ${JSON.stringify(watchDefaults(found))}`,
|
|
1151
|
+
);
|
|
1152
|
+
else notes.push(`Nothing: ${CONFIG_NAME} already has a "watch" list.`);
|
|
1153
|
+
log(summary(found, plan, { dryRun, workflowOnly: true, notes }));
|
|
1154
|
+
return 0;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
977
1157
|
/**
|
|
978
1158
|
* Runs init. Options: `root` (the project folder, default the working
|
|
979
|
-
* directory), `dryRun`, `force`, `
|
|
980
|
-
*
|
|
1159
|
+
* directory), `dryRun`, `force`, `workflowOnly` (write just the workflow, for
|
|
1160
|
+
* a repository set up by an older version), `log`. Returns the exit code: 0
|
|
1161
|
+
* done, 1 already set up or another file in the way.
|
|
981
1162
|
*/
|
|
982
1163
|
export function init({
|
|
983
1164
|
root = ".",
|
|
984
1165
|
dryRun = false,
|
|
985
1166
|
force = false,
|
|
1167
|
+
workflowOnly = false,
|
|
986
1168
|
log = console.log,
|
|
987
1169
|
} = {}) {
|
|
988
1170
|
const found = detectProject(root);
|
|
1171
|
+
if (workflowOnly) return initWorkflowOnly(found, { dryRun, force, log });
|
|
989
1172
|
const plan = planInit(found);
|
|
990
1173
|
if (!force) {
|
|
991
1174
|
const config = join(found.project, CONFIG_NAME);
|
|
@@ -16,12 +16,19 @@ The kit is configured in `{{CONFIG}}` and built by the `Design kit` workflow
|
|
|
16
16
|
(`.github/workflows/design-kit.yml`). The format and every command are in the
|
|
17
17
|
`{{PACKAGE}}` README.
|
|
18
18
|
|
|
19
|
+
When this repository has Storybook and you add a component or change what
|
|
20
|
+
one renders, add or update its story in the same pull request, covering its
|
|
21
|
+
default, every variant and size, and the empty, loading, error and long-text
|
|
22
|
+
states that apply, so the next kit captures it. A component without a story
|
|
23
|
+
is missing from every kit until it gets one.
|
|
24
|
+
|
|
19
25
|
## 1. Fetch the newest kit
|
|
20
26
|
|
|
21
27
|
The workflow runs once a day and on demand (`workflow_dispatch`), and uploads
|
|
22
|
-
the `design-kit` artifact (kept
|
|
23
|
-
|
|
24
|
-
the newest
|
|
28
|
+
the `design-kit` artifact (kept 90 days; older kits are deleted). A day when
|
|
29
|
+
none of the config's `watch` paths changed skips and uploads nothing, yet
|
|
30
|
+
still succeeds, so look up the newest artifact rather than the newest run,
|
|
31
|
+
then download it once per kit commit:
|
|
25
32
|
|
|
26
33
|
```bash
|
|
27
34
|
REPO=$(gh repo view --json nameWithOwner --jq .nameWithOwner)
|
|
@@ -45,11 +52,15 @@ working files in a scratch folder, never in the repository.
|
|
|
45
52
|
## 2. Check staleness
|
|
46
53
|
|
|
47
54
|
The kit is a snapshot of `manifest.sha`, up to a day behind. Check whether UI
|
|
48
|
-
code changed since
|
|
55
|
+
code changed since, over the same `watch` paths that make the workflow
|
|
56
|
+
rebuild (read from the config, so the two always agree):
|
|
49
57
|
|
|
50
58
|
```bash
|
|
51
59
|
KIT_SHA=$(node -p 'require(process.argv[1]).sha' "$KIT/manifest.json")
|
|
52
|
-
git
|
|
60
|
+
TOP=$(git rev-parse --show-toplevel)
|
|
61
|
+
git fetch -q origin "$BRANCH" &&
|
|
62
|
+
node -e 'const w = require(process.argv[1]).watch; process.stdout.write((w?.length ? w : ["."]).map((p) => `:(top)${p}`).join("\0"))' "$TOP/{{CONFIG}}" |
|
|
63
|
+
xargs -0 git log --oneline "$KIT_SHA..origin/$BRANCH" --
|
|
53
64
|
```
|
|
54
65
|
|
|
55
66
|
When the list is not empty, say so on the mockup: "Kit from `<sha7>`; N UI
|