justskill-design 0.0.1 → 3.2.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.
Files changed (67) hide show
  1. package/README.en.md +235 -0
  2. package/README.md +184 -11
  3. package/SKILL.md +435 -0
  4. package/canvas/app.mjs +2870 -0
  5. package/canvas/groups.mjs +204 -0
  6. package/canvas/index.html +141 -0
  7. package/canvas/styles.css +1032 -0
  8. package/canvas/tree.mjs +249 -0
  9. package/cordis.patch.yml +8 -0
  10. package/design.mjs +172 -0
  11. package/docs/dsh-workbench.png +0 -0
  12. package/dsh/client.js +864 -0
  13. package/dsh/index.js +354 -0
  14. package/dsh/scan.js +139 -0
  15. package/dsh/skill.js +41 -0
  16. package/lib/base-css.mjs +92 -0
  17. package/lib/detect.mjs +123 -0
  18. package/lib/diagnostics.mjs +382 -0
  19. package/lib/export-html.mjs +309 -0
  20. package/lib/export-svg.mjs +111 -0
  21. package/lib/fragment.mjs +702 -0
  22. package/lib/geometry.mjs +827 -0
  23. package/lib/icons.mjs +106 -0
  24. package/lib/levenshtein.mjs +133 -0
  25. package/lib/node/cli.mjs +387 -0
  26. package/lib/node/export-svg.mjs +75 -0
  27. package/lib/node/fs.mjs +309 -0
  28. package/lib/node/op-json.mjs +75 -0
  29. package/lib/node/raster-run.mjs +115 -0
  30. package/lib/node/server.mjs +1264 -0
  31. package/lib/node/static.mjs +206 -0
  32. package/lib/node/verbs/add.mjs +220 -0
  33. package/lib/node/verbs/export.mjs +161 -0
  34. package/lib/node/verbs/format.mjs +106 -0
  35. package/lib/node/verbs/import.mjs +126 -0
  36. package/lib/node/verbs/ls.mjs +98 -0
  37. package/lib/node/verbs/migrate.mjs +236 -0
  38. package/lib/node/verbs/move.mjs +76 -0
  39. package/lib/node/verbs/new.mjs +284 -0
  40. package/lib/node/verbs/remove.mjs +41 -0
  41. package/lib/node/verbs/rename.mjs +87 -0
  42. package/lib/node/verbs/serve.mjs +164 -0
  43. package/lib/node/verbs/set.mjs +150 -0
  44. package/lib/node/verbs/show.mjs +122 -0
  45. package/lib/node/verbs/tokens.mjs +167 -0
  46. package/lib/node/verbs/validate.mjs +130 -0
  47. package/lib/node/write-verbs.mjs +215 -0
  48. package/lib/normalize.mjs +288 -0
  49. package/lib/ops.mjs +2063 -0
  50. package/lib/parse.mjs +997 -0
  51. package/lib/raster.mjs +186 -0
  52. package/lib/readers/pen.mjs +1684 -0
  53. package/lib/receipt.mjs +203 -0
  54. package/lib/render.mjs +503 -0
  55. package/lib/serialize.mjs +441 -0
  56. package/lib/spans.mjs +177 -0
  57. package/lib/svg-path.mjs +483 -0
  58. package/lib/table.mjs +1047 -0
  59. package/lib/tokens.mjs +295 -0
  60. package/lib/validate.mjs +929 -0
  61. package/package.json +68 -6
  62. package/references/cookbook.md +156 -0
  63. package/references/images.md +171 -0
  64. package/references/product-flow.md +408 -0
  65. package/references/schema.md +90 -0
  66. package/references/styling.md +181 -0
  67. package/scripts/install.mjs +95 -0
package/lib/raster.mjs ADDED
@@ -0,0 +1,186 @@
1
+ /**
2
+ * lib/raster.mjs — find a browser that can turn HTML into pixels.
3
+ *
4
+ * Node cannot draw HTML. Every tool that exports an image has to borrow a
5
+ * renderer, and this one borrows a browser already on the machine: no download,
6
+ * no bundled Chromium, no dependency.
7
+ *
8
+ * Two paths, tried in order, and the order is deliberate:
9
+ *
10
+ * 1. a headless Chrome, Chromium or Edge, launched with `--screenshot`
11
+ * 2. the canvas, which is already a browser and already has the page mounted
12
+ *
13
+ * If neither is available the export FAILS and says so. It does not quietly fall
14
+ * back to another format: a PNG and an SVG are different deliverables, and
15
+ * changing what the user asked for without telling them is worse than refusing.
16
+ *
17
+ * Detection is split from invocation so the policy — which paths exist, what to
18
+ * say when none does — is testable without launching anything.
19
+ *
20
+ * ## No environment of its own
21
+ *
22
+ * This file is served to the canvas, so it may not import `node:fs` or read `process`.
23
+ * It used to do both: a top-level `existsSync` and a `process.platform` default, which
24
+ * meant the browser downloaded a module that threw on load. The three things it needs to
25
+ * look for a browser — does this path exist, what platform is this, where is `PATH` — are
26
+ * now arguments, and `lib/node/raster-run.mjs` is the one place that answers them from the
27
+ * machine.
28
+ */
29
+
30
+ /** Where a Chrome-family browser usually lives, per platform. */
31
+ export const BROWSER_CANDIDATES = {
32
+ darwin: [
33
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
34
+ "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
35
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
36
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
37
+ "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
38
+ "/Applications/Arc.app/Contents/MacOS/Arc",
39
+ ],
40
+ linux: [
41
+ "/usr/bin/google-chrome",
42
+ "/usr/bin/google-chrome-stable",
43
+ "/usr/bin/chromium",
44
+ "/usr/bin/chromium-browser",
45
+ "/usr/bin/microsoft-edge",
46
+ "/usr/bin/microsoft-edge-stable",
47
+ "/snap/bin/chromium",
48
+ ],
49
+ win32: [
50
+ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
51
+ "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
52
+ "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
53
+ "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
54
+ ],
55
+ };
56
+
57
+ /** Also look for a browser on PATH, because a homebrew or nix install lives there. */
58
+ export const PATH_NAMES = [
59
+ "google-chrome", "google-chrome-stable", "chromium", "chromium-browser",
60
+ "chrome", "msedge", "microsoft-edge", "brave-browser",
61
+ ];
62
+
63
+ /**
64
+ * The first Chrome-family browser on this machine, or null.
65
+ *
66
+ * Every answer comes from `deps`, and none of them has a default: a fallback here would
67
+ * have to reach for `node:fs` or `process`, which is the one thing this module may not do.
68
+ * `lib/node/raster-run.mjs` passes the machine's answers; a test passes its own.
69
+ *
70
+ * @param {object} deps `{ exists, platform, which }` — all three, no defaults.
71
+ * @returns {{ path: string, name: string, from: "candidate" | "path" } | null}
72
+ * @throws {RasterError} `raster/no-environment` when the environment is not supplied
73
+ */
74
+ export function findBrowser(deps = {}) {
75
+ const { exists, platform, which } = deps;
76
+ if (typeof exists !== "function" || typeof platform !== "string" || typeof which !== "function") {
77
+ throw new RasterError(
78
+ "raster/no-environment",
79
+ "findBrowser needs { exists, platform, which }.",
80
+ "lib/node/raster-run.mjs supplies them from the machine. A browser-side caller has no local Chrome to find.",
81
+ );
82
+ }
83
+
84
+ for (const candidate of BROWSER_CANDIDATES[platform] ?? []) {
85
+ if (exists(candidate)) return { path: candidate, name: nameOf(candidate), from: "candidate" };
86
+ }
87
+ for (const named of PATH_NAMES) {
88
+ const found = which(named);
89
+ if (found) return { path: found, name: nameOf(found), from: "path" };
90
+ }
91
+ return null;
92
+ }
93
+
94
+ function nameOf(target) {
95
+ const base = String(target).split(/[/\\]/).pop() ?? target;
96
+ if (/edge|msedge/i.test(base)) return "Microsoft Edge";
97
+ if (/chrom/i.test(base)) return "Chromium";
98
+ if (/brave/i.test(base)) return "Brave";
99
+ if (/arc/i.test(base)) return "Arc";
100
+ return "Google Chrome";
101
+ }
102
+
103
+ /**
104
+ * The command line that renders one page to one image.
105
+ *
106
+ * `--virtual-time-budget` is the load-bearing flag. Without it the screenshot is
107
+ * taken before a web font arrives, and the page is captured in the fallback
108
+ * font: the text wraps at different words and every box below it moves. That is
109
+ * the most common way an image export is quietly wrong.
110
+ */
111
+ export function screenshotArgs({ file, out, width, height, scale = 1, budgetMs = 6000 }) {
112
+ return [
113
+ "--headless=new",
114
+ "--disable-gpu",
115
+ "--hide-scrollbars",
116
+ "--no-first-run",
117
+ "--no-default-browser-check",
118
+ "--disable-extensions",
119
+ `--force-device-scale-factor=${scale}`,
120
+ `--window-size=${Math.round(width)},${Math.round(height)}`,
121
+ `--virtual-time-budget=${budgetMs}`,
122
+ `--screenshot=${out}`,
123
+ `file://${file}`,
124
+ ];
125
+ }
126
+
127
+ export class RasterError extends Error {
128
+ constructor(code, message, hint) {
129
+ super(message);
130
+ this.name = "RasterError";
131
+ this.code = code;
132
+ this.hint = hint;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Decide how to render, or refuse with three ways forward.
138
+ *
139
+ * @param {object} options `{ raster: "auto"|"chrome"|"canvas", canvasOnline: boolean }`
140
+ * @param {object} deps `{ findBrowser: () => browser | null }`
141
+ */
142
+ export function planRaster(options = {}, deps = {}) {
143
+ const find = deps.findBrowser ?? findBrowser;
144
+ const wanted = options.raster ?? "auto";
145
+
146
+ if (wanted === "chrome" || wanted === "auto") {
147
+ const browser = find();
148
+ if (browser) return { route: "chrome", browser };
149
+ if (wanted === "chrome") {
150
+ throw new RasterError(
151
+ "raster/no-browser",
152
+ "No Chrome-family browser was found, and --raster chrome asks for one.",
153
+ "Install Chrome, Chromium or Edge; or use --raster canvas with the canvas open; or use --format svg.",
154
+ );
155
+ }
156
+ }
157
+
158
+ if (wanted === "canvas" || wanted === "auto") {
159
+ if (options.canvasOnline) return { route: "canvas", browser: null };
160
+ if (wanted === "canvas") {
161
+ throw new RasterError(
162
+ "raster/no-canvas",
163
+ "No canvas is open, and --raster canvas asks for one.",
164
+ "Run `design.mjs serve <file>` and leave the tab open; or install a Chrome-family browser; or use --format svg.",
165
+ );
166
+ }
167
+ }
168
+
169
+ throw new RasterError(
170
+ "raster/no-route",
171
+ "Cannot turn HTML into pixels on this machine.",
172
+ "Install Chrome, Chromium or Edge; or run `design.mjs serve <file>` and leave the canvas open; or export --format svg instead.",
173
+ );
174
+ }
175
+
176
+ /** Formats this module can write, and what each needs. */
177
+ export const RASTER_FORMATS = {
178
+ png: { needsBrowser: true, transparent: true },
179
+ jpeg: { needsBrowser: true, transparent: false },
180
+ jpg: { needsBrowser: true, transparent: false },
181
+ webp: { needsBrowser: true, transparent: true },
182
+ };
183
+
184
+ export function isRasterFormat(format) {
185
+ return Object.prototype.hasOwnProperty.call(RASTER_FORMATS, String(format).toLowerCase());
186
+ }