rastack 0.0.24 → 0.0.25

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 (47) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/rastack-design.d.ts +17 -0
  3. package/dist/rastack-design.js +134 -0
  4. package/dist/rastack-tokens.d.ts +15 -0
  5. package/dist/rastack-tokens.js +43 -0
  6. package/dist/rastack.d.ts +2 -0
  7. package/dist/rastack.js +12 -0
  8. package/dist/tokens/color.d.ts +22 -0
  9. package/dist/tokens/color.js +70 -0
  10. package/dist/tokens/compile.d.ts +34 -0
  11. package/dist/tokens/compile.js +103 -0
  12. package/dist/tokens/css.d.ts +30 -0
  13. package/dist/tokens/css.js +102 -0
  14. package/dist/tokens/define.d.ts +71 -0
  15. package/dist/tokens/define.js +98 -0
  16. package/dist/tokens/index.d.ts +17 -0
  17. package/dist/tokens/index.js +33 -0
  18. package/dist/tokens/resolve.d.ts +40 -0
  19. package/dist/tokens/resolve.js +135 -0
  20. package/dist/tokens/studio.d.ts +21 -0
  21. package/dist/tokens/studio.js +328 -0
  22. package/dist/tokens/theme.d.ts +55 -0
  23. package/dist/tokens/theme.js +139 -0
  24. package/dist/tokens/ts.d.ts +15 -0
  25. package/dist/tokens/ts.js +73 -0
  26. package/dist/tokens/types.d.ts +92 -0
  27. package/dist/tokens/types.js +35 -0
  28. package/jest.config.cjs +6 -0
  29. package/package.json +3 -2
  30. package/src/rastack-design.ts +117 -0
  31. package/src/rastack-tokens.ts +46 -0
  32. package/src/rastack.ts +12 -0
  33. package/src/tokens/color.ts +74 -0
  34. package/src/tokens/compile.ts +85 -0
  35. package/src/tokens/css.ts +138 -0
  36. package/src/tokens/define.ts +128 -0
  37. package/src/tokens/index.ts +18 -0
  38. package/src/tokens/resolve.ts +170 -0
  39. package/src/tokens/studio.ts +357 -0
  40. package/src/tokens/theme.ts +180 -0
  41. package/src/tokens/ts.ts +80 -0
  42. package/src/tokens/types.ts +125 -0
  43. package/test/tokens.spec.ts +302 -0
  44. package/theme/index.ts +9 -0
  45. package/theme/provider.tsx +157 -0
  46. package/tokens.ts +9 -0
  47. package/tsconfig.json +3 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.0.25](https://github.com/theserverkid/reactapistack/compare/v0.0.24...v0.0.25) (2026-07-10)
6
+
5
7
  ### [0.0.24](https://github.com/theserverkid/reactapistack/compare/v0.0.23...v0.0.24) (2026-07-09)
6
8
 
7
9
 
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `rastack design` (aka `ras design`) — a live design-system studio.
4
+ *
5
+ * rastack design [input] [--port <n>] [--prefix <p>]
6
+ *
7
+ * Boots a tiny, dependency-free HTTP server that serves an interactive page
8
+ * showcasing the design system — colour swatches, the type ramp, spacing,
9
+ * radii, shadows, and component previews built from the tokens themselves — and
10
+ * lets you edit token values in place with a live preview. Editing a `.json`
11
+ * source and hitting **Save** writes it back and recompiles `tokens.css` /
12
+ * `tokens.ts`, so the rest of the app picks the change up on reload.
13
+ *
14
+ * In keeping with the local-first ethos (the whole API runs in the browser via
15
+ * WASM), the studio needs no build step and no external dependencies.
16
+ */
17
+ export {};
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * `rastack design` (aka `ras design`) — a live design-system studio.
5
+ *
6
+ * rastack design [input] [--port <n>] [--prefix <p>]
7
+ *
8
+ * Boots a tiny, dependency-free HTTP server that serves an interactive page
9
+ * showcasing the design system — colour swatches, the type ramp, spacing,
10
+ * radii, shadows, and component previews built from the tokens themselves — and
11
+ * lets you edit token values in place with a live preview. Editing a `.json`
12
+ * source and hitting **Save** writes it back and recompiles `tokens.css` /
13
+ * `tokens.ts`, so the rest of the app picks the change up on reload.
14
+ *
15
+ * In keeping with the local-first ethos (the whole API runs in the browser via
16
+ * WASM), the studio needs no build step and no external dependencies.
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ const http = __importStar(require("http"));
53
+ const path = __importStar(require("path"));
54
+ const compile_1 = require("./tokens/compile");
55
+ const studio_1 = require("./tokens/studio");
56
+ const DEFAULT_INPUT = "tokens/theme.tokens.json";
57
+ const DEFAULT_OUT = ".rastack";
58
+ function parseArgs(argv) {
59
+ const positional = [];
60
+ let port = 4321;
61
+ let prefix = "";
62
+ let outDir = DEFAULT_OUT;
63
+ for (let i = 0; i < argv.length; i++) {
64
+ const a = argv[i];
65
+ if (a === "--port")
66
+ port = Number(argv[++i]) || port;
67
+ else if (a === "--prefix")
68
+ prefix = argv[++i] ?? "";
69
+ else if (a === "--out")
70
+ outDir = argv[++i] ?? outDir;
71
+ else
72
+ positional.push(a);
73
+ }
74
+ return { input: positional[0] || DEFAULT_INPUT, port, prefix, outDir };
75
+ }
76
+ function main(argv) {
77
+ const args = parseArgs(argv);
78
+ const isJson = args.input.endsWith(".json");
79
+ let themed;
80
+ try {
81
+ themed = (0, compile_1.loadTokens)(args.input);
82
+ }
83
+ catch (err) {
84
+ console.error(` ✗ ${err.message}`);
85
+ process.exit(1);
86
+ return;
87
+ }
88
+ const server = http.createServer((req, res) => {
89
+ // Only edits to a JSON source can be written back safely (a module source
90
+ // is code, not data).
91
+ if (req.method === "POST" && req.url === "/save") {
92
+ if (!isJson) {
93
+ res.writeHead(400).end("Editing is only supported for .json token sources.");
94
+ return;
95
+ }
96
+ let body = "";
97
+ req.on("data", (c) => (body += c));
98
+ req.on("end", () => {
99
+ try {
100
+ const next = JSON.parse(body);
101
+ require("fs").writeFileSync(path.resolve(args.input), JSON.stringify(next, null, 2) + "\n");
102
+ themed = next;
103
+ // Recompile so tokens.css / tokens.ts reflect the save immediately.
104
+ (0, compile_1.compileTokens)(args.input, args.outDir, { prefix: args.prefix });
105
+ res.writeHead(200, { "Content-Type": "application/json" }).end(`{"ok":true}`);
106
+ console.log(` ↳ saved ${args.input} & recompiled`);
107
+ }
108
+ catch (err) {
109
+ res.writeHead(500).end(err.message);
110
+ }
111
+ });
112
+ return;
113
+ }
114
+ if (req.method === "GET" && (req.url === "/" || req.url === "/index.html")) {
115
+ const html = (0, studio_1.renderStudio)(themed, {
116
+ prefix: args.prefix,
117
+ editable: isJson,
118
+ title: "Design System",
119
+ });
120
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }).end(html);
121
+ return;
122
+ }
123
+ res.writeHead(404).end("Not found");
124
+ });
125
+ server.listen(args.port, () => {
126
+ console.log(`\n rastack design — studio for ${args.input}\n` +
127
+ ` ▸ http://localhost:${args.port}\n` +
128
+ (isJson ? ` Edits Save back to ${args.input}.\n` : ` Read-only (module source).\n`) +
129
+ `\n Press Ctrl+C to stop.\n`);
130
+ });
131
+ }
132
+ if (require.main === module) {
133
+ main(process.argv.slice(2));
134
+ }
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `rastack tokens` — compile a design-token source into shippable artifacts.
4
+ *
5
+ * rastack tokens [input] [outDir] [--prefix <p>]
6
+ *
7
+ * `input` a DTCG `.json` file, or a compiled module that default-exports
8
+ * `defineTokens(...)` (default: `tokens/theme.tokens.json`).
9
+ * `outDir` where to write `tokens.{rastack.json,css,ts}` (default: `.rastack`).
10
+ *
11
+ * Mirrors `rastack compile`: TypeScript-authored source → canonical JSON +
12
+ * ready-to-use outputs, with alias/cycle errors surfaced before anything is
13
+ * written.
14
+ */
15
+ export {};
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * `rastack tokens` — compile a design-token source into shippable artifacts.
5
+ *
6
+ * rastack tokens [input] [outDir] [--prefix <p>]
7
+ *
8
+ * `input` a DTCG `.json` file, or a compiled module that default-exports
9
+ * `defineTokens(...)` (default: `tokens/theme.tokens.json`).
10
+ * `outDir` where to write `tokens.{rastack.json,css,ts}` (default: `.rastack`).
11
+ *
12
+ * Mirrors `rastack compile`: TypeScript-authored source → canonical JSON +
13
+ * ready-to-use outputs, with alias/cycle errors surfaced before anything is
14
+ * written.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ const compile_1 = require("./tokens/compile");
18
+ const DEFAULT_INPUT = "tokens/theme.tokens.json";
19
+ const DEFAULT_OUT = ".rastack";
20
+ function main(argv) {
21
+ const positional = [];
22
+ let prefix = "";
23
+ for (let i = 0; i < argv.length; i++) {
24
+ if (argv[i] === "--prefix")
25
+ prefix = argv[++i] ?? "";
26
+ else
27
+ positional.push(argv[i]);
28
+ }
29
+ const input = positional[0] || DEFAULT_INPUT;
30
+ const outDir = positional[1] || DEFAULT_OUT;
31
+ try {
32
+ const { themed, files } = (0, compile_1.compileTokens)(input, outDir, { prefix });
33
+ const modes = Object.keys(themed.modes ?? {});
34
+ console.log(`✓ compiled design tokens${modes.length ? ` (${modes.length} mode${modes.length > 1 ? "s" : ""}: ${modes.join(", ")})` : ""} → ${files.join(", ")}`);
35
+ }
36
+ catch (err) {
37
+ console.error(` ✗ ${err.message}`);
38
+ process.exit(1);
39
+ }
40
+ }
41
+ if (require.main === module) {
42
+ main(process.argv.slice(2));
43
+ }
package/dist/rastack.d.ts CHANGED
@@ -8,6 +8,8 @@
8
8
  * rastack list [resourcesDir] Print resources, fields and relations
9
9
  * rastack urls [resourcesDir] Print the generated /api/{app}/v1/{model}/ routes
10
10
  * rastack generate [--schema-only] Generate typed hooks from OpenAPI
11
+ * rastack tokens [input] [outDir] Compile design tokens → CSS vars + typed theme
12
+ * rastack design [input] [--port n] Live design-system studio (showcase + edit)
11
13
  * rastack serve [--warehouse dir] Run the Rust API over an Iceberg warehouse
12
14
  * rastack admin [--warehouse dir] Run the Rust admin (Django-admin-style DB browser)
13
15
  * rastack wasm Compile the API to WebAssembly (in-browser local dev)
package/dist/rastack.js CHANGED
@@ -9,6 +9,8 @@
9
9
  * rastack list [resourcesDir] Print resources, fields and relations
10
10
  * rastack urls [resourcesDir] Print the generated /api/{app}/v1/{model}/ routes
11
11
  * rastack generate [--schema-only] Generate typed hooks from OpenAPI
12
+ * rastack tokens [input] [outDir] Compile design tokens → CSS vars + typed theme
13
+ * rastack design [input] [--port n] Live design-system studio (showcase + edit)
12
14
  * rastack serve [--warehouse dir] Run the Rust API over an Iceberg warehouse
13
15
  * rastack admin [--warehouse dir] Run the Rust admin (Django-admin-style DB browser)
14
16
  * rastack wasm Compile the API to WebAssembly (in-browser local dev)
@@ -70,6 +72,14 @@ switch (command) {
70
72
  // Compile the API to WebAssembly for in-browser local dev.
71
73
  run("rastack-wasm-build.js", rest);
72
74
  break;
75
+ case "tokens":
76
+ // Design tokens → CSS custom properties + typed theme.
77
+ run("rastack-tokens.js", rest);
78
+ break;
79
+ case "design":
80
+ // Live design-system studio (showcase + inspect + edit).
81
+ run("rastack-design.js", rest);
82
+ break;
73
83
  case "scan":
74
84
  run("scan.js");
75
85
  break;
@@ -87,6 +97,8 @@ switch (command) {
87
97
  ` rastack compile [resourcesDir] [outDir]\n` +
88
98
  ` rastack check | list | urls [resourcesDir]\n` +
89
99
  ` rastack generate [--schema-only]\n` +
100
+ ` rastack tokens [input] [outDir] [--prefix p] Compile design tokens → CSS vars + typed theme\n` +
101
+ ` rastack design [input] [--port n] Live design-system studio (showcase + edit)\n` +
90
102
  ` rastack serve | admin [--warehouse dir]\n` +
91
103
  ` rastack wasm Compile the API to WebAssembly (browser local dev)\n` +
92
104
  ` rastack scan [files...] [--fail-on-pii]\n` +
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Tiny, dependency-free colour maths — just enough to turn one brand colour into
3
+ * a full tint/shade scale and to pick a readable on-colour. Used by
4
+ * {@link defineTheme} so a non-designer specifies a single hex and gets a
5
+ * complete, sensible palette.
6
+ */
7
+ interface Rgb {
8
+ r: number;
9
+ g: number;
10
+ b: number;
11
+ }
12
+ /** Parse `#rgb` / `#rrggbb` (with or without `#`) into 0–255 channels. */
13
+ export declare function parseHex(hex: string): Rgb;
14
+ /** Blend two colours; `amount` is how far from `a` toward `b` (0–1). */
15
+ export declare function mix(a: string, b: string, amount: number): string;
16
+ /** Relative luminance (0 dark – 1 light), for contrast decisions. */
17
+ export declare function luminance(hex: string): number;
18
+ /** Black or white — whichever is readable on `hex`. */
19
+ export declare function readableOn(hex: string): string;
20
+ /** Build a `{ 50…950 }` colour scale from a single seed colour. */
21
+ export declare function scale(seed: string): Record<string, string>;
22
+ export {};
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /**
3
+ * Tiny, dependency-free colour maths — just enough to turn one brand colour into
4
+ * a full tint/shade scale and to pick a readable on-colour. Used by
5
+ * {@link defineTheme} so a non-designer specifies a single hex and gets a
6
+ * complete, sensible palette.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.parseHex = parseHex;
10
+ exports.mix = mix;
11
+ exports.luminance = luminance;
12
+ exports.readableOn = readableOn;
13
+ exports.scale = scale;
14
+ /** Parse `#rgb` / `#rrggbb` (with or without `#`) into 0–255 channels. */
15
+ function parseHex(hex) {
16
+ let h = hex.trim().replace(/^#/, "");
17
+ if (h.length === 3)
18
+ h = h.split("").map((c) => c + c).join("");
19
+ if (!/^[0-9a-fA-F]{6}$/.test(h)) {
20
+ throw new Error(`Not a hex colour: "${hex}" (use e.g. "#4F6BFF").`);
21
+ }
22
+ return {
23
+ r: parseInt(h.slice(0, 2), 16),
24
+ g: parseInt(h.slice(2, 4), 16),
25
+ b: parseInt(h.slice(4, 6), 16),
26
+ };
27
+ }
28
+ function toHex({ r, g, b }) {
29
+ const h = (n) => ("0" + Math.round(Math.max(0, Math.min(255, n))).toString(16)).slice(-2);
30
+ return `#${h(r)}${h(g)}${h(b)}`.toUpperCase();
31
+ }
32
+ /** Blend two colours; `amount` is how far from `a` toward `b` (0–1). */
33
+ function mix(a, b, amount) {
34
+ const x = parseHex(a);
35
+ const y = parseHex(b);
36
+ return toHex({
37
+ r: x.r + (y.r - x.r) * amount,
38
+ g: x.g + (y.g - x.g) * amount,
39
+ b: x.b + (y.b - x.b) * amount,
40
+ });
41
+ }
42
+ /** Relative luminance (0 dark – 1 light), for contrast decisions. */
43
+ function luminance(hex) {
44
+ const { r, g, b } = parseHex(hex);
45
+ const lin = (c) => {
46
+ const s = c / 255;
47
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
48
+ };
49
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
50
+ }
51
+ /** Black or white — whichever is readable on `hex`. */
52
+ function readableOn(hex) {
53
+ return luminance(hex) > 0.45 ? "#0B1020" : "#FFFFFF";
54
+ }
55
+ /**
56
+ * How much to mix a seed toward white (tints) or black (shades) at each step of
57
+ * a Tailwind-style 50–950 scale. `500` is the seed itself.
58
+ */
59
+ const TINT = { 50: 0.95, 100: 0.9, 200: 0.78, 300: 0.62, 400: 0.34 };
60
+ const SHADE = { 600: 0.12, 700: 0.28, 800: 0.44, 900: 0.6, 950: 0.74 };
61
+ /** Build a `{ 50…950 }` colour scale from a single seed colour. */
62
+ function scale(seed) {
63
+ const out = {};
64
+ for (const [step, amt] of Object.entries(TINT))
65
+ out[step] = mix(seed, "#FFFFFF", amt);
66
+ out["500"] = toHex(parseHex(seed));
67
+ for (const [step, amt] of Object.entries(SHADE))
68
+ out[step] = mix(seed, "#000000", amt);
69
+ return out;
70
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Token compilation with file IO — the engine behind `rastack tokens`.
3
+ *
4
+ * Loads a token source (canonical DTCG `.json`, or a `.js`/compiled module that
5
+ * default-exports a {@link ThemedTokens}), resolves it, and writes three
6
+ * artifacts:
7
+ *
8
+ * - `tokens.rastack.json` — the canonical, resolved DTCG document (the manifest
9
+ * analogue: one file that fully describes the design system).
10
+ * - `tokens.css` — CSS custom properties with per-mode selector scopes.
11
+ * - `tokens.ts` — the typed theme tree + `TokenPath` union.
12
+ *
13
+ * Kept separate from the pure core (`./index.ts`) so the browser runtime never
14
+ * pulls in `fs`.
15
+ */
16
+ import { CssOptions } from "./css";
17
+ import { ThemedTokens } from "./types";
18
+ export interface TokenCompileOptions extends CssOptions {
19
+ }
20
+ export interface TokenCompileResult {
21
+ themed: ThemedTokens;
22
+ files: string[];
23
+ }
24
+ /**
25
+ * Load a token source into a normalised {@link ThemedTokens}. `.json` is parsed
26
+ * as DTCG; any other extension is `require`d and its default export (or the
27
+ * module itself) is taken — so a `defineTokens(...)` module works once compiled
28
+ * to JS (or when a `.ts` loader such as ts-node is registered).
29
+ */
30
+ export declare function loadTokens(file: string): ThemedTokens;
31
+ /** The canonical, resolved DTCG document written to `tokens.rastack.json`. */
32
+ export declare function canonicalDocument(themed: ThemedTokens): unknown;
33
+ /** Compile a token source file to `tokens.{rastack.json,css,ts}` in `outDir`. */
34
+ export declare function compileTokens(file: string, outDir: string, options?: TokenCompileOptions): TokenCompileResult;
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ /**
3
+ * Token compilation with file IO — the engine behind `rastack tokens`.
4
+ *
5
+ * Loads a token source (canonical DTCG `.json`, or a `.js`/compiled module that
6
+ * default-exports a {@link ThemedTokens}), resolves it, and writes three
7
+ * artifacts:
8
+ *
9
+ * - `tokens.rastack.json` — the canonical, resolved DTCG document (the manifest
10
+ * analogue: one file that fully describes the design system).
11
+ * - `tokens.css` — CSS custom properties with per-mode selector scopes.
12
+ * - `tokens.ts` — the typed theme tree + `TokenPath` union.
13
+ *
14
+ * Kept separate from the pure core (`./index.ts`) so the browser runtime never
15
+ * pulls in `fs`.
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.loadTokens = loadTokens;
52
+ exports.canonicalDocument = canonicalDocument;
53
+ exports.compileTokens = compileTokens;
54
+ const fs = __importStar(require("fs"));
55
+ const path = __importStar(require("path"));
56
+ const define_1 = require("./define");
57
+ const css_1 = require("./css");
58
+ const resolve_1 = require("./resolve");
59
+ const ts_1 = require("./ts");
60
+ /**
61
+ * Load a token source into a normalised {@link ThemedTokens}. `.json` is parsed
62
+ * as DTCG; any other extension is `require`d and its default export (or the
63
+ * module itself) is taken — so a `defineTokens(...)` module works once compiled
64
+ * to JS (or when a `.ts` loader such as ts-node is registered).
65
+ */
66
+ function loadTokens(file) {
67
+ const abs = path.resolve(file);
68
+ if (!fs.existsSync(abs))
69
+ throw new Error(`No such tokens file: ${file}`);
70
+ if (abs.endsWith(".json")) {
71
+ const raw = JSON.parse(fs.readFileSync(abs, "utf8"));
72
+ // Accept either the themed shape or a bare DTCG tree.
73
+ return (0, define_1.defineTokens)(raw);
74
+ }
75
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
76
+ const mod = require(abs);
77
+ const exported = mod?.default ?? mod;
78
+ return (0, define_1.defineTokens)(exported);
79
+ }
80
+ /** The canonical, resolved DTCG document written to `tokens.rastack.json`. */
81
+ function canonicalDocument(themed) {
82
+ return {
83
+ $description: "Compiled by `rastack tokens`. The resolved design system.",
84
+ ...themed.tokens,
85
+ ...(themed.modes && Object.keys(themed.modes).length
86
+ ? { $extensions: { "com.rastack.modes": themed.modes, "com.rastack.defaultMode": themed.defaultMode } }
87
+ : {}),
88
+ };
89
+ }
90
+ /** Compile a token source file to `tokens.{rastack.json,css,ts}` in `outDir`. */
91
+ function compileTokens(file, outDir, options = {}) {
92
+ const themed = loadTokens(file);
93
+ // Resolve eagerly so alias/cycle errors surface here, before writing anything.
94
+ (0, resolve_1.resolveTheme)(themed);
95
+ fs.mkdirSync(outDir, { recursive: true });
96
+ const jsonPath = path.join(outDir, "tokens.rastack.json");
97
+ const cssPath = path.join(outDir, "tokens.css");
98
+ const tsPath = path.join(outDir, "tokens.ts");
99
+ fs.writeFileSync(jsonPath, JSON.stringify(canonicalDocument(themed), null, 2) + "\n");
100
+ fs.writeFileSync(cssPath, (0, css_1.emitCss)(themed, options));
101
+ fs.writeFileSync(tsPath, (0, ts_1.emitTs)((0, resolve_1.resolveTheme)(themed).base, options));
102
+ return { themed, files: [jsonPath, cssPath, tsPath] };
103
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * CSS custom-property emitter — the primary, framework-agnostic output.
3
+ *
4
+ * Design tokens become CSS variables under `:root`, and each **mode** becomes a
5
+ * selector scope that overrides only the tokens it changes. A running app then
6
+ * switches theme by toggling one attribute (`<html data-theme="dark">`), and
7
+ * every `var(--…)` reference recascades for free. Aliases are preserved as
8
+ * `var(--target)` rather than inlined, so overriding a base token
9
+ * automatically flows through everything that references it.
10
+ */
11
+ import { ResolvedTheme } from "./resolve";
12
+ import { ThemedTokens, ResolvedToken } from "./types";
13
+ export interface CssOptions {
14
+ /** Prefix every variable: `prefix: "rs"` → `--rs-color-brand-500`. */
15
+ prefix?: string;
16
+ /**
17
+ * How a mode maps to a selector. Given the mode name, return the selector
18
+ * whose block holds that mode's overrides. Default: `[data-theme="<mode>"]`,
19
+ * with the default mode also written to `:root`.
20
+ */
21
+ modeSelector?: (mode: string) => string;
22
+ /** Root selector for the base tokens. Default `:root`. */
23
+ rootSelector?: string;
24
+ }
25
+ /** Format a resolved token's literal value as a CSS declaration value. */
26
+ export declare function formatCssValue(token: ResolvedToken): string;
27
+ /** Emit CSS custom properties for a resolved theme (base + mode scopes). */
28
+ export declare function emitCssFromResolved(resolved: ResolvedTheme, options?: CssOptions): string;
29
+ /** Emit CSS custom properties for a themed token document. */
30
+ export declare function emitCss(themed: ThemedTokens, options?: CssOptions): string;
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ /**
3
+ * CSS custom-property emitter — the primary, framework-agnostic output.
4
+ *
5
+ * Design tokens become CSS variables under `:root`, and each **mode** becomes a
6
+ * selector scope that overrides only the tokens it changes. A running app then
7
+ * switches theme by toggling one attribute (`<html data-theme="dark">`), and
8
+ * every `var(--…)` reference recascades for free. Aliases are preserved as
9
+ * `var(--target)` rather than inlined, so overriding a base token
10
+ * automatically flows through everything that references it.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.formatCssValue = formatCssValue;
14
+ exports.emitCssFromResolved = emitCssFromResolved;
15
+ exports.emitCss = emitCss;
16
+ const resolve_1 = require("./resolve");
17
+ /** Render one CSS number/length; bare numbers stay unitless. */
18
+ function dim(v) {
19
+ return typeof v === "number" ? `${v}px` : v;
20
+ }
21
+ /** Quote a font-family entry only when it contains whitespace. */
22
+ function fontEntry(name) {
23
+ return /\s/.test(name) && !/^["']/.test(name) ? `"${name}"` : name;
24
+ }
25
+ function shadow(s) {
26
+ const parts = [
27
+ s.inset ? "inset" : "",
28
+ dim(s.offsetX),
29
+ dim(s.offsetY),
30
+ s.blur != null ? dim(s.blur) : "",
31
+ s.spread != null ? dim(s.spread) : "",
32
+ s.color,
33
+ ];
34
+ return parts.filter(Boolean).join(" ");
35
+ }
36
+ /** Format a resolved token's literal value as a CSS declaration value. */
37
+ function formatCssValue(token) {
38
+ const { type, value } = token;
39
+ switch (type) {
40
+ case "fontFamily":
41
+ return (Array.isArray(value) ? value : [value])
42
+ .map(fontEntry)
43
+ .join(", ");
44
+ case "cubicBezier":
45
+ return `cubic-bezier(${value.join(", ")})`;
46
+ case "shadow":
47
+ return (Array.isArray(value) ? value : [value])
48
+ .map(shadow)
49
+ .join(", ");
50
+ case "dimension":
51
+ return dim(value);
52
+ case "number":
53
+ case "fontWeight":
54
+ case "color":
55
+ case "duration":
56
+ return String(value);
57
+ default:
58
+ return typeof value === "object" ? JSON.stringify(value) : String(value);
59
+ }
60
+ }
61
+ /** The right-hand side of a token's CSS declaration: `var(--alias)` or a literal. */
62
+ function declValue(token, prefix) {
63
+ if (token.aliasOf)
64
+ return `var(${(0, resolve_1.cssVarName)(token.aliasOf, prefix)})`;
65
+ return formatCssValue(token);
66
+ }
67
+ function block(selector, tokens, prefix, indent = " ") {
68
+ const lines = tokens.map((tok) => `${indent}${(0, resolve_1.cssVarName)(tok.path, prefix)}: ${declValue(tok, prefix)};`);
69
+ return `${selector} {\n${lines.join("\n")}\n}`;
70
+ }
71
+ /** Only the tokens whose emitted declaration differs from the base. */
72
+ function changed(base, mode, prefix) {
73
+ const baseDecl = new Map(base.map((t) => [t.path, declValue(t, prefix)]));
74
+ return mode.filter((t) => baseDecl.get(t.path) !== declValue(t, prefix));
75
+ }
76
+ /** Emit CSS custom properties for a resolved theme (base + mode scopes). */
77
+ function emitCssFromResolved(resolved, options = {}) {
78
+ const prefix = options.prefix ?? "";
79
+ const root = options.rootSelector ?? ":root";
80
+ const modeSelector = options.modeSelector ?? ((m) => `[data-theme="${m}"]`);
81
+ const blocks = [
82
+ `/* Generated by \`rastack tokens\` — do not edit by hand. */`,
83
+ ];
84
+ // Base + the default mode both live on :root, so an app with no attribute set
85
+ // still gets a complete, sensible theme.
86
+ const defaultMode = resolved.defaultMode;
87
+ const rootTokens = defaultMode && resolved.modes[defaultMode]
88
+ ? resolved.modes[defaultMode]
89
+ : resolved.base;
90
+ blocks.push(block(root, rootTokens, prefix));
91
+ for (const [name, tokens] of Object.entries(resolved.modes)) {
92
+ const diff = changed(resolved.base, tokens, prefix);
93
+ if (!diff.length)
94
+ continue;
95
+ blocks.push(block(modeSelector(name), diff, prefix));
96
+ }
97
+ return blocks.join("\n\n") + "\n";
98
+ }
99
+ /** Emit CSS custom properties for a themed token document. */
100
+ function emitCss(themed, options = {}) {
101
+ return emitCssFromResolved((0, resolve_1.resolveTheme)(themed), options);
102
+ }