@codepress/design-kit 0.1.0 → 0.1.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.
Files changed (3) hide show
  1. package/package.json +6 -3
  2. package/src/kit.d.ts +81 -0
  3. package/src/kit.mjs +44 -12
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@codepress/design-kit",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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": "./bin/design-kit.mjs"
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": "./src/kit.mjs",
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/kit.d.ts ADDED
@@ -0,0 +1,81 @@
1
+ /** Types for `@codepress/design-kit/kit`, the kit tool's functions. */
2
+
3
+ /** One row of `index.json` (stories) or `routes.json` (pages). */
4
+ export interface KitRow {
5
+ id: string;
6
+ kind?: "story" | "route";
7
+ title?: string;
8
+ component?: string;
9
+ file: string;
10
+ width?: number;
11
+ height?: number;
12
+ bytes?: number;
13
+ slots?: string[];
14
+ source_file?: string;
15
+ body_class?: string;
16
+ html_attrs?: Record<string, string>;
17
+ body_attrs?: Record<string, string>;
18
+ }
19
+
20
+ export interface KitManifest {
21
+ kit_version?: number;
22
+ sha?: string;
23
+ built_at?: string;
24
+ [key: string]: unknown;
25
+ }
26
+
27
+ /** A loaded kit: its folder, manifest and rows, sorted and by id. */
28
+ export interface Kit {
29
+ dir: string;
30
+ manifest: KitManifest | null;
31
+ rows: KitRow[];
32
+ byId: Map<string, KitRow>;
33
+ }
34
+
35
+ export function loadKit(dir: string): Kit;
36
+
37
+ /** Rows whose id, title, component or source file holds every word. */
38
+ export function find(kit: Kit, query: string): KitRow[];
39
+
40
+ /** Up to five candidates, closest first. */
41
+ export function nearMatches(wanted: string, candidates: string[]): string[];
42
+
43
+ /** A markup outline of at most `limit` bytes. */
44
+ export function outline(
45
+ html: string,
46
+ limit?: number,
47
+ ): { lines: string[]; truncated: boolean };
48
+
49
+ /**
50
+ * One self-contained page. Throws an Error whose message starts with
51
+ * `unknown ` (and names near matches) for an unknown frame, slot or snippet,
52
+ * and with `too large: ` when snippets grow the page past `maxBytes`.
53
+ */
54
+ export function compose(
55
+ kit: Kit,
56
+ options: {
57
+ frame: string;
58
+ slots?: { name: string; content: string }[];
59
+ theme?: "light" | "dark";
60
+ showNew?: boolean;
61
+ /** Bytes of markup the page may grow to through snippets. */
62
+ maxBytes?: number;
63
+ },
64
+ ): string;
65
+
66
+ /** The default `maxBytes` of `compose`: 32 MB. */
67
+ export const COMPOSE_MAX_BYTES: number;
68
+
69
+ export function sanitizeMarkup(html: string): string;
70
+ export function sanitizeCss(css: string): string;
71
+
72
+ /** Runs one command line; returns the exit code. */
73
+ export function run(
74
+ argv: string[],
75
+ io?: { out(text: string): void; err(text: string): void },
76
+ env?: Record<string, string | undefined>,
77
+ defaultDir?: string,
78
+ ): number;
79
+
80
+ /** Whether this file is the script node was started with. */
81
+ export function isMain(moduleUrl?: string, script?: string): boolean;
package/src/kit.mjs CHANGED
@@ -19,6 +19,12 @@ import { fileURLToPath, pathToFileURL } from "node:url";
19
19
  const FIND_LIMIT = 30;
20
20
  const SHOW_LIMIT = 2048;
21
21
  const SNIPPET_DEPTH = 8;
22
+ /**
23
+ * Bytes of markup one compose may read through <kit-snippet>s. Nesting is
24
+ * capped, but a file of many placeholders pointing at large rows would
25
+ * otherwise grow the page exponentially.
26
+ */
27
+ export const COMPOSE_MAX_BYTES = 32 * 1024 * 1024;
22
28
 
23
29
  class KitError extends Error {}
24
30
 
@@ -672,7 +678,7 @@ export function inlineAssets(kit, css, cssDir = join(kit.dir, "css")) {
672
678
 
673
679
  // ── compose ─────────────────────────────────────────────────────────────────
674
680
 
675
- function expandSnippets(kit, html, used, depth = 0) {
681
+ function expandSnippets(kit, html, used, budget, depth = 0) {
676
682
  const found = elements(html).filter((e) => e.name === "kit-snippet");
677
683
  if (!found.length) return html;
678
684
  if (depth >= SNIPPET_DEPTH) {
@@ -689,10 +695,16 @@ function expandSnippets(kit, html, used, depth = 0) {
689
695
  const row = requireRow(kit, id, "<kit-snippet id>");
690
696
  used.add(rowSource(row));
691
697
  let markup = readSnippet(kit, row);
698
+ budget.left -= markup.length;
699
+ if (budget.left < 0) {
700
+ throw new KitError(
701
+ `too large: the page grows past ${budget.max} bytes while expanding <kit-snippet> elements; use fewer or smaller snippets.`,
702
+ );
703
+ }
692
704
  if (rowSource(row) === "stories") markup = unwrapStoryRoot(markup);
693
705
  out +=
694
706
  html.slice(i, element.start) +
695
- expandSnippets(kit, markup, used, depth + 1);
707
+ expandSnippets(kit, markup, used, budget, depth + 1);
696
708
  i = element.end;
697
709
  }
698
710
  return out + html.slice(i);
@@ -815,7 +827,13 @@ function attributes(captured, extraClass, theme, choice, target, fixed) {
815
827
  */
816
828
  export function compose(
817
829
  kit,
818
- { frame, slots = [], theme = "light", showNew = false },
830
+ {
831
+ frame,
832
+ slots = [],
833
+ theme = "light",
834
+ showNew = false,
835
+ maxBytes = COMPOSE_MAX_BYTES,
836
+ },
819
837
  ) {
820
838
  const row = requireRow(kit, frame, "frame");
821
839
  if (theme !== "light" && theme !== "dark") {
@@ -823,7 +841,10 @@ export function compose(
823
841
  }
824
842
  const used = new Set([rowSource(row)]);
825
843
  const filled = fillSlots(kit, row, readSnippet(kit, row), slots);
826
- const body = expandSnippets(kit, filled, used);
844
+ const body = expandSnippets(kit, filled, used, {
845
+ left: maxBytes - filled.length,
846
+ max: maxBytes,
847
+ });
827
848
 
828
849
  const css = ["stories", "routes"]
829
850
  .filter((source) => used.has(source))
@@ -1069,16 +1090,27 @@ export function run(
1069
1090
  }
1070
1091
  }
1071
1092
 
1072
- /** Whether this file is the script node was started with (symlinks resolved). */
1073
- function isMain() {
1074
- if (!process.argv[1]) return false;
1093
+ /**
1094
+ * Whether this file is the script node was started with (symlinks resolved).
1095
+ * Only a file named `kit.mjs` can be: a bundler that inlines this module into
1096
+ * another program keeps `import.meta.url` pointing at that program, which is
1097
+ * also the script node started, and the program would then run the kit's
1098
+ * command line on its own arguments.
1099
+ */
1100
+ export function isMain(moduleUrl = import.meta.url, script = process.argv[1]) {
1101
+ if (!script) return false;
1102
+ let self;
1075
1103
  try {
1076
- return (
1077
- realpathSync(fileURLToPath(import.meta.url)) ===
1078
- realpathSync(resolve(process.argv[1]))
1079
- );
1104
+ self = fileURLToPath(moduleUrl);
1105
+ } catch {
1106
+ // Not a file URL: an evaluated bundle, never the script node started.
1107
+ return false;
1108
+ }
1109
+ if (basename(self) !== "kit.mjs") return false;
1110
+ try {
1111
+ return realpathSync(self) === realpathSync(resolve(script));
1080
1112
  } catch {
1081
- return import.meta.url === pathToFileURL(resolve(process.argv[1])).href;
1113
+ return moduleUrl === pathToFileURL(resolve(script)).href;
1082
1114
  }
1083
1115
  }
1084
1116