@theme-registry/refract 0.1.1 → 0.1.3
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 +21 -5
- package/dist/_errors-be768b25.cjs.js.map +1 -1
- package/dist/_errors-d75a5c74.esm.js.map +1 -1
- package/dist/build/createCommand.d.ts +47 -0
- package/dist/build/createInterview.d.ts +56 -0
- package/dist/build/index.d.ts +4 -0
- package/dist/build/init.d.ts +42 -0
- package/dist/build/prompt.d.ts +38 -0
- package/dist/build/scaffold.d.ts +146 -0
- package/dist/build.cjs.js +1 -1
- package/dist/build.cjs.js.map +1 -1
- package/dist/build.esm.js +1 -1
- package/dist/build.esm.js.map +1 -1
- package/dist/cli.js +993 -38
- package/dist/cli.js.map +1 -1
- package/dist/core/assertRawTheme.d.ts +2 -0
- package/dist/core/errors.d.ts +5 -2
- package/dist/core/index.d.ts +1 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/skills/adapter-scaffold/SKILL.md +2 -2
- package/skills/adapter-usage/SKILL.md +1 -1
- package/skills/colors/SKILL.md +1 -1
- package/skills/layout/SKILL.md +1 -1
- package/skills/theme-authoring/SKILL.md +4 -1
- package/skills/theme-scaffold/SKILL.md +130 -0
- package/skills/typography/SKILL.md +1 -1
- package/skills/visual-effects/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,21 @@ npm install @theme-registry/refract
|
|
|
47
47
|
`styled-components` and `typescript` are **optional** peers — only needed if you use the
|
|
48
48
|
styled-components adapter or the `.ts` build config, respectively.
|
|
49
49
|
|
|
50
|
+
## Scaffold a theme
|
|
51
|
+
|
|
52
|
+
Don't start from a blank file. One seed colour becomes a full theme — palettes with tonal ladders,
|
|
53
|
+
semantic colours, a type scale with derived leading, a spacing ramp — with every colour checked
|
|
54
|
+
against **WCAG contrast before the file is written**:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx refract create # in an existing project → theme.raw.ts
|
|
58
|
+
npm create refract-theme my-theme # from nothing → a publishable theme package
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The generator runs **once**; what it writes is an ordinary theme file you own and edit. It emits
|
|
62
|
+
**tokens only** — no recipes, so nothing composes into a class list yet. That's the next step, and
|
|
63
|
+
it's design work: see [Recipes](https://theme-registry.github.io/refract/r-recipes).
|
|
64
|
+
|
|
50
65
|
## Quick start
|
|
51
66
|
|
|
52
67
|
`createTheme(raw, { adapter })` builds the theme. The `adapter` is **required** — core
|
|
@@ -104,7 +119,7 @@ const raw = {
|
|
|
104
119
|
Property values can be **literals** (`"#4c6ef5"`, `16`), **references** to other tokens
|
|
105
120
|
(`color: "brand.text"`), and carry **responsive / variant / target** overrides keyed on
|
|
106
121
|
your breakpoints. Recipes group reusable rule-sets with **states** (`hover`, `disabled`, …).
|
|
107
|
-
Full field-by-field walkthrough: **[docs/authoring.md](docs/authoring.md)**.
|
|
122
|
+
Full field-by-field walkthrough: **[docs/authoring.md](https://github.com/theme-registry/refract/blob/main/docs/authoring.md)**.
|
|
108
123
|
|
|
109
124
|
## Adapters — the multi-format thesis
|
|
110
125
|
|
|
@@ -121,12 +136,13 @@ Core ships **zero** adapters; each is its own installable package (`npm i @theme
|
|
|
121
136
|
|
|
122
137
|
Write your own with `defineAdapter(spec)` — you fill four primitives (`recipeName`,
|
|
123
138
|
`renderRecipe`, `renderVariables`, `join`) and core supplies the rest. See
|
|
124
|
-
**[docs/extending.md](docs/extending.md)** or run the `adapter-scaffold` skill.
|
|
139
|
+
**[docs/extending.md](https://github.com/theme-registry/refract/blob/main/docs/extending.md)** or run the `adapter-scaffold` skill.
|
|
125
140
|
|
|
126
141
|
## Build to disk (CLI)
|
|
127
142
|
|
|
128
143
|
```bash
|
|
129
|
-
npx refract
|
|
144
|
+
npx refract create # design a theme.raw.(ts|js|json) from one seed colour
|
|
145
|
+
npx refract init # scaffold a theme.config.(ts|js|mjs) — imports theme.raw.* if present
|
|
130
146
|
npx refract build # load the config → write every target's files to its outDir
|
|
131
147
|
npx refract tokens # export theme.tokens as a DTCG tokens.json (adapter-free)
|
|
132
148
|
```
|
|
@@ -182,8 +198,8 @@ const doc = toDTCG(theme); // built theme's tokens → DTC
|
|
|
182
198
|
|
|
183
199
|
## Documentation
|
|
184
200
|
|
|
185
|
-
- **[docs/authoring.md](docs/authoring.md)** — author a theme + consume its output (the user guide).
|
|
186
|
-
- **[docs/extending.md](docs/extending.md)** — write a subsystem or an adapter against the frozen contract.
|
|
201
|
+
- **[docs/authoring.md](https://github.com/theme-registry/refract/blob/main/docs/authoring.md)** — author a theme + consume its output (the user guide).
|
|
202
|
+
- **[docs/extending.md](https://github.com/theme-registry/refract/blob/main/docs/extending.md)** — write a subsystem or an adapter against the frozen contract.
|
|
187
203
|
- **[AGENTS.md](AGENTS.md)** — orientation for AI coding agents.
|
|
188
204
|
|
|
189
205
|
## License
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_errors-be768b25.cjs.js","sources":["../src/core/errors.ts"],"sourcesContent":["/**\n * Stable, machine-readable error codes (§P2-3). Every error refract throws for a theme authoring /\n * build problem — everything reachable from {@link createTheme} (subsystem normalization, token\n * derivation, DTCG parsing) — is a {@link RefractError} carrying a `code` from {@link RefractErrorCode}:\n * a stable identifier an agent (or a catch block) can branch on without string-matching the human\n * message. The message text stays human-first and unchanged; the code is additive.\n *\n * Scope: authoring/build errors. Pure CLI-tooling failures in `src/build/*` (bad arguments, a missing\n * file, an unknown command) stay plain `Error`s — they're usage errors from `refract <cmd>`, not a\n * problem with the theme an agent is editing.\n *\n * Dependency-free leaf on purpose, so any module (including the standalone `adapter-kit`) can throw one\n * without pulling in the core graph.\n */\nexport type RefractErrorCode =\n | \"REFRACT_E_COLOR_INPUT\" // a colour value wasn't a derivable colour\n | \"REFRACT_E_COLOR_TUPLE\" // an [r,g,b] tuple was malformed\n | \"REFRACT_E_STEPS\" // colors.<name>.steps out of range / non-numeric\n | \"REFRACT_E_VARIANT\" // a derivation-spec variant / modifier chain was malformed\n | \"REFRACT_E_VARIANT_REF\" // a variant referenced an unknown source\n | \"REFRACT_E_CYCLE\" // a cyclic reference (colour variant, token, recipe, DTCG alias)\n | \"REFRACT_E_ADJUST\" // an adjust dial was out of range\n | \"REFRACT_E_HARMONY\" // an invalid harmony scheme\n | \"REFRACT_E_VALIDATION\" // aggregate: one or more post-build ref-validation failures (see `failures`)\n | \"REFRACT_E_RECIPE_PROPERTY\" // a recipe declaration key is neither a known CSS property nor a reserved key\n | \"REFRACT_E_TOKEN_PATH\" // a token path is unknown / malformed / uses an unknown derivation fn\n | \"REFRACT_E_BREAKPOINT\" // a responsive entry omitted or named an unknown breakpoint\n | \"REFRACT_E_STATE\" // a recipe state entry named a state the adapter doesn't know\n | \"REFRACT_E_CONTAINER\" // a container-query entry named an unknown container / size / unsupported axis\n | \"REFRACT_E_RESPONSIVE\" // a responsive/mode override was malformed (variant+target conflict, unknown ref)\n | \"REFRACT_E_MEDIA\" // an invalid media / container query range (min > max)\n | \"REFRACT_E_MODE\" // an appearance-mode override with no base to override\n | \"REFRACT_E_PRESET\" // an unknown globals preset name\n | \"REFRACT_E_EFFECTS\" // a malformed effects value (shadow / transition layer)\n | \"REFRACT_E_LAYOUT\" // a malformed layout scale (ratio+step, bad rung)\n | \"REFRACT_E_UNITS\" // a malformed units config\n | \"REFRACT_E_PROPERTY\" // a property couldn't be normalized (no resolvable base value)\n | \"REFRACT_E_REFERENCE\" // a composition / rule-set reference didn't resolve\n | \"REFRACT_E_NAMING\" // a naming override / default path produced a collision\n | \"REFRACT_E_DTCG_VERSION\" // an unreadable DTCG refract-extension version\n | \"REFRACT_E_AUDIT\"
|
|
1
|
+
{"version":3,"file":"_errors-be768b25.cjs.js","sources":["../src/core/errors.ts"],"sourcesContent":["/**\n * Stable, machine-readable error codes (§P2-3). Every error refract throws for a theme authoring /\n * build problem — everything reachable from {@link createTheme} (subsystem normalization, token\n * derivation, DTCG parsing) — is a {@link RefractError} carrying a `code` from {@link RefractErrorCode}:\n * a stable identifier an agent (or a catch block) can branch on without string-matching the human\n * message. The message text stays human-first and unchanged; the code is additive.\n *\n * Scope: authoring/build errors. Pure CLI-tooling failures in `src/build/*` (bad arguments, a missing\n * file, an unknown command) stay plain `Error`s — they're usage errors from `refract <cmd>`, not a\n * problem with the theme an agent is editing. The one build-layer exception is `REFRACT_E_RAW_SHAPE`:\n * a value handed to `diff` / `validate` (or the MCP server) where a `RawTheme` is required but the\n * value isn't theme-shaped — that IS a theme problem, and a governance tool must fail loud + coded on\n * it (see {@link assertRawTheme}) rather than emit a nonsense \"everything removed\" diff at exit 0.\n *\n * Dependency-free leaf on purpose, so any module (including the standalone `adapter-kit`) can throw one\n * without pulling in the core graph.\n */\nexport type RefractErrorCode =\n | \"REFRACT_E_COLOR_INPUT\" // a colour value wasn't a derivable colour\n | \"REFRACT_E_COLOR_TUPLE\" // an [r,g,b] tuple was malformed\n | \"REFRACT_E_STEPS\" // colors.<name>.steps out of range / non-numeric\n | \"REFRACT_E_VARIANT\" // a derivation-spec variant / modifier chain was malformed\n | \"REFRACT_E_VARIANT_REF\" // a variant referenced an unknown source\n | \"REFRACT_E_CYCLE\" // a cyclic reference (colour variant, token, recipe, DTCG alias)\n | \"REFRACT_E_ADJUST\" // an adjust dial was out of range\n | \"REFRACT_E_HARMONY\" // an invalid harmony scheme\n | \"REFRACT_E_VALIDATION\" // aggregate: one or more post-build ref-validation failures (see `failures`)\n | \"REFRACT_E_RECIPE_PROPERTY\" // a recipe declaration key is neither a known CSS property nor a reserved key\n | \"REFRACT_E_TOKEN_PATH\" // a token path is unknown / malformed / uses an unknown derivation fn\n | \"REFRACT_E_BREAKPOINT\" // a responsive entry omitted or named an unknown breakpoint\n | \"REFRACT_E_STATE\" // a recipe state entry named a state the adapter doesn't know\n | \"REFRACT_E_CONTAINER\" // a container-query entry named an unknown container / size / unsupported axis\n | \"REFRACT_E_RESPONSIVE\" // a responsive/mode override was malformed (variant+target conflict, unknown ref)\n | \"REFRACT_E_MEDIA\" // an invalid media / container query range (min > max)\n | \"REFRACT_E_MODE\" // an appearance-mode override with no base to override\n | \"REFRACT_E_PRESET\" // an unknown globals preset name\n | \"REFRACT_E_EFFECTS\" // a malformed effects value (shadow / transition layer)\n | \"REFRACT_E_LAYOUT\" // a malformed layout scale (ratio+step, bad rung)\n | \"REFRACT_E_UNITS\" // a malformed units config\n | \"REFRACT_E_PROPERTY\" // a property couldn't be normalized (no resolvable base value)\n | \"REFRACT_E_REFERENCE\" // a composition / rule-set reference didn't resolve\n | \"REFRACT_E_NAMING\" // a naming override / default path produced a collision\n | \"REFRACT_E_DTCG_VERSION\" // an unreadable DTCG refract-extension version\n | \"REFRACT_E_AUDIT\" // a strict contrast audit failed\n | \"REFRACT_E_RAW_SHAPE\"; // a value passed where a RawTheme is required isn't theme-shaped (e.g. a defineConfig, an array, null)\n\n/**\n * An authoring / build error with a stable {@link RefractErrorCode}. For an aggregate (collect-all)\n * error, `failures` lists each individual message so a caller can report them all at once.\n */\nexport class RefractError extends Error {\n readonly code: RefractErrorCode;\n readonly failures?: readonly string[];\n\n constructor(code: RefractErrorCode, message: string, failures?: readonly string[]) {\n super(message);\n this.name = \"RefractError\";\n this.code = code;\n if (failures) this.failures = failures;\n // Keep `instanceof RefractError` working after transpilation to ES5-ish targets.\n Object.setPrototypeOf(this, RefractError.prototype);\n }\n}\n"],"names":["RefractError","Error","constructor","code","message","failures","super","this","name","Object","setPrototypeOf","prototype"],"mappings":"aAkDM,MAAOA,UAAqBC,MAIhC,WAAAC,CAAYC,EAAwBC,EAAiBC,GACnDC,MAAMF,GACNG,KAAKC,KAAO,eACZD,KAAKJ,KAAOA,EACRE,IAAUE,KAAKF,SAAWA,GAE9BI,OAAOC,eAAeH,KAAMP,EAAaW,UAC1C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_errors-d75a5c74.esm.js","sources":["../src/core/errors.ts"],"sourcesContent":["/**\n * Stable, machine-readable error codes (§P2-3). Every error refract throws for a theme authoring /\n * build problem — everything reachable from {@link createTheme} (subsystem normalization, token\n * derivation, DTCG parsing) — is a {@link RefractError} carrying a `code` from {@link RefractErrorCode}:\n * a stable identifier an agent (or a catch block) can branch on without string-matching the human\n * message. The message text stays human-first and unchanged; the code is additive.\n *\n * Scope: authoring/build errors. Pure CLI-tooling failures in `src/build/*` (bad arguments, a missing\n * file, an unknown command) stay plain `Error`s — they're usage errors from `refract <cmd>`, not a\n * problem with the theme an agent is editing.\n *\n * Dependency-free leaf on purpose, so any module (including the standalone `adapter-kit`) can throw one\n * without pulling in the core graph.\n */\nexport type RefractErrorCode =\n | \"REFRACT_E_COLOR_INPUT\" // a colour value wasn't a derivable colour\n | \"REFRACT_E_COLOR_TUPLE\" // an [r,g,b] tuple was malformed\n | \"REFRACT_E_STEPS\" // colors.<name>.steps out of range / non-numeric\n | \"REFRACT_E_VARIANT\" // a derivation-spec variant / modifier chain was malformed\n | \"REFRACT_E_VARIANT_REF\" // a variant referenced an unknown source\n | \"REFRACT_E_CYCLE\" // a cyclic reference (colour variant, token, recipe, DTCG alias)\n | \"REFRACT_E_ADJUST\" // an adjust dial was out of range\n | \"REFRACT_E_HARMONY\" // an invalid harmony scheme\n | \"REFRACT_E_VALIDATION\" // aggregate: one or more post-build ref-validation failures (see `failures`)\n | \"REFRACT_E_RECIPE_PROPERTY\" // a recipe declaration key is neither a known CSS property nor a reserved key\n | \"REFRACT_E_TOKEN_PATH\" // a token path is unknown / malformed / uses an unknown derivation fn\n | \"REFRACT_E_BREAKPOINT\" // a responsive entry omitted or named an unknown breakpoint\n | \"REFRACT_E_STATE\" // a recipe state entry named a state the adapter doesn't know\n | \"REFRACT_E_CONTAINER\" // a container-query entry named an unknown container / size / unsupported axis\n | \"REFRACT_E_RESPONSIVE\" // a responsive/mode override was malformed (variant+target conflict, unknown ref)\n | \"REFRACT_E_MEDIA\" // an invalid media / container query range (min > max)\n | \"REFRACT_E_MODE\" // an appearance-mode override with no base to override\n | \"REFRACT_E_PRESET\" // an unknown globals preset name\n | \"REFRACT_E_EFFECTS\" // a malformed effects value (shadow / transition layer)\n | \"REFRACT_E_LAYOUT\" // a malformed layout scale (ratio+step, bad rung)\n | \"REFRACT_E_UNITS\" // a malformed units config\n | \"REFRACT_E_PROPERTY\" // a property couldn't be normalized (no resolvable base value)\n | \"REFRACT_E_REFERENCE\" // a composition / rule-set reference didn't resolve\n | \"REFRACT_E_NAMING\" // a naming override / default path produced a collision\n | \"REFRACT_E_DTCG_VERSION\" // an unreadable DTCG refract-extension version\n | \"REFRACT_E_AUDIT\"
|
|
1
|
+
{"version":3,"file":"_errors-d75a5c74.esm.js","sources":["../src/core/errors.ts"],"sourcesContent":["/**\n * Stable, machine-readable error codes (§P2-3). Every error refract throws for a theme authoring /\n * build problem — everything reachable from {@link createTheme} (subsystem normalization, token\n * derivation, DTCG parsing) — is a {@link RefractError} carrying a `code` from {@link RefractErrorCode}:\n * a stable identifier an agent (or a catch block) can branch on without string-matching the human\n * message. The message text stays human-first and unchanged; the code is additive.\n *\n * Scope: authoring/build errors. Pure CLI-tooling failures in `src/build/*` (bad arguments, a missing\n * file, an unknown command) stay plain `Error`s — they're usage errors from `refract <cmd>`, not a\n * problem with the theme an agent is editing. The one build-layer exception is `REFRACT_E_RAW_SHAPE`:\n * a value handed to `diff` / `validate` (or the MCP server) where a `RawTheme` is required but the\n * value isn't theme-shaped — that IS a theme problem, and a governance tool must fail loud + coded on\n * it (see {@link assertRawTheme}) rather than emit a nonsense \"everything removed\" diff at exit 0.\n *\n * Dependency-free leaf on purpose, so any module (including the standalone `adapter-kit`) can throw one\n * without pulling in the core graph.\n */\nexport type RefractErrorCode =\n | \"REFRACT_E_COLOR_INPUT\" // a colour value wasn't a derivable colour\n | \"REFRACT_E_COLOR_TUPLE\" // an [r,g,b] tuple was malformed\n | \"REFRACT_E_STEPS\" // colors.<name>.steps out of range / non-numeric\n | \"REFRACT_E_VARIANT\" // a derivation-spec variant / modifier chain was malformed\n | \"REFRACT_E_VARIANT_REF\" // a variant referenced an unknown source\n | \"REFRACT_E_CYCLE\" // a cyclic reference (colour variant, token, recipe, DTCG alias)\n | \"REFRACT_E_ADJUST\" // an adjust dial was out of range\n | \"REFRACT_E_HARMONY\" // an invalid harmony scheme\n | \"REFRACT_E_VALIDATION\" // aggregate: one or more post-build ref-validation failures (see `failures`)\n | \"REFRACT_E_RECIPE_PROPERTY\" // a recipe declaration key is neither a known CSS property nor a reserved key\n | \"REFRACT_E_TOKEN_PATH\" // a token path is unknown / malformed / uses an unknown derivation fn\n | \"REFRACT_E_BREAKPOINT\" // a responsive entry omitted or named an unknown breakpoint\n | \"REFRACT_E_STATE\" // a recipe state entry named a state the adapter doesn't know\n | \"REFRACT_E_CONTAINER\" // a container-query entry named an unknown container / size / unsupported axis\n | \"REFRACT_E_RESPONSIVE\" // a responsive/mode override was malformed (variant+target conflict, unknown ref)\n | \"REFRACT_E_MEDIA\" // an invalid media / container query range (min > max)\n | \"REFRACT_E_MODE\" // an appearance-mode override with no base to override\n | \"REFRACT_E_PRESET\" // an unknown globals preset name\n | \"REFRACT_E_EFFECTS\" // a malformed effects value (shadow / transition layer)\n | \"REFRACT_E_LAYOUT\" // a malformed layout scale (ratio+step, bad rung)\n | \"REFRACT_E_UNITS\" // a malformed units config\n | \"REFRACT_E_PROPERTY\" // a property couldn't be normalized (no resolvable base value)\n | \"REFRACT_E_REFERENCE\" // a composition / rule-set reference didn't resolve\n | \"REFRACT_E_NAMING\" // a naming override / default path produced a collision\n | \"REFRACT_E_DTCG_VERSION\" // an unreadable DTCG refract-extension version\n | \"REFRACT_E_AUDIT\" // a strict contrast audit failed\n | \"REFRACT_E_RAW_SHAPE\"; // a value passed where a RawTheme is required isn't theme-shaped (e.g. a defineConfig, an array, null)\n\n/**\n * An authoring / build error with a stable {@link RefractErrorCode}. For an aggregate (collect-all)\n * error, `failures` lists each individual message so a caller can report them all at once.\n */\nexport class RefractError extends Error {\n readonly code: RefractErrorCode;\n readonly failures?: readonly string[];\n\n constructor(code: RefractErrorCode, message: string, failures?: readonly string[]) {\n super(message);\n this.name = \"RefractError\";\n this.code = code;\n if (failures) this.failures = failures;\n // Keep `instanceof RefractError` working after transpilation to ES5-ish targets.\n Object.setPrototypeOf(this, RefractError.prototype);\n }\n}\n"],"names":["RefractError","Error","constructor","code","message","failures","super","this","name","Object","setPrototypeOf","prototype"],"mappings":"AAkDM,MAAOA,UAAqBC,MAIhC,WAAAC,CAAYC,EAAwBC,EAAiBC,GACnDC,MAAMF,GACNG,KAAKC,KAAO,eACZD,KAAKJ,KAAOA,EACRE,IAAUE,KAAKF,SAAWA,GAE9BI,OAAOC,eAAeH,KAAMP,EAAaW,UAC1C"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type ScaffoldAnswers, type ScaffoldReport } from "./scaffold";
|
|
2
|
+
import type { RawTheme } from "../core";
|
|
3
|
+
/** The file flavours `create` can emit. */
|
|
4
|
+
export type RawFormat = "ts" | "js" | "json";
|
|
5
|
+
export declare const DEFAULT_RAW_BASENAME = "theme.raw";
|
|
6
|
+
export interface CreateOptions extends ScaffoldAnswers {
|
|
7
|
+
/** Project dir to write into (default `process.cwd()`). */
|
|
8
|
+
readonly cwd?: string;
|
|
9
|
+
/** Which flavour to write (default `"ts"`). */
|
|
10
|
+
readonly format?: RawFormat;
|
|
11
|
+
/** Output filename, relative to `cwd` (default `theme.raw.<format>`). */
|
|
12
|
+
readonly out?: string;
|
|
13
|
+
/** Overwrite an existing file instead of refusing (default `false`). */
|
|
14
|
+
readonly force?: boolean;
|
|
15
|
+
/** Package name the `.ts`/`.js` type import points at (default: this package's own `name`). */
|
|
16
|
+
readonly packageName?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface CreateResult {
|
|
19
|
+
/** Absolute path of the written file. */
|
|
20
|
+
readonly path: string;
|
|
21
|
+
readonly format: RawFormat;
|
|
22
|
+
/** The generated theme, so callers can compile it without re-reading the file. */
|
|
23
|
+
readonly raw: RawTheme;
|
|
24
|
+
/** What the generator derived — the CLI prints this as its summary. */
|
|
25
|
+
readonly report: ScaffoldReport;
|
|
26
|
+
/** Count of emitted top-level subsystem slices. */
|
|
27
|
+
readonly sections: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Serialize a generated theme to source text.
|
|
31
|
+
*
|
|
32
|
+
* `ts` and `js` share one body and differ only in how the type is attached (a real `satisfies` vs a
|
|
33
|
+
* JSDoc cast), so a JS user gets the same editor completion without adding a compile step. `json`
|
|
34
|
+
* carries no types and no shared `ladder` const — it is the portable flavour, and for a scaffolded
|
|
35
|
+
* theme it is exactly as faithful, because nothing here is a function or a recipe.
|
|
36
|
+
*/
|
|
37
|
+
export declare function renderRawTheme(raw: RawTheme, options: {
|
|
38
|
+
format: RawFormat;
|
|
39
|
+
packageName: string;
|
|
40
|
+
seed: string;
|
|
41
|
+
detail: string;
|
|
42
|
+
}): string;
|
|
43
|
+
/**
|
|
44
|
+
* Generate and write the raw theme. Throws when the target exists and `force` is not set — the same
|
|
45
|
+
* guard `init` and `import` use, so no command in this CLI can silently destroy authored work.
|
|
46
|
+
*/
|
|
47
|
+
export declare function runCreate(options: CreateOptions): CreateResult;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `refract create` interview (Node-only) — the question flow, separated from both CLIs that run it.
|
|
3
|
+
*
|
|
4
|
+
* `refract create` asks it to write a theme into an existing project; `create-refract-theme` asks it
|
|
5
|
+
* while scaffolding a whole new package. Two entry points, one script of questions — because an
|
|
6
|
+
* interview duplicated across two packages is an interview that drifts.
|
|
7
|
+
*
|
|
8
|
+
* Every answer can be supplied up front (a flag, or a caller's default), and every prompt is
|
|
9
|
+
* non-interactive-safe, so the same function serves a human at a terminal and a scripted run.
|
|
10
|
+
*/
|
|
11
|
+
import type { Prompter } from "./prompt";
|
|
12
|
+
import { type ScaffoldAnswers } from "./scaffold";
|
|
13
|
+
import type { CreateResult, RawFormat } from "./createCommand";
|
|
14
|
+
/** What each harmony scheme is for, in one line — the names mean nothing to most people. */
|
|
15
|
+
export declare const SCHEME_HINTS: Readonly<Record<string, string>>;
|
|
16
|
+
/**
|
|
17
|
+
* Ratio choices, tightest to most dramatic, each showing the ladder it produces from 16px. The ratio
|
|
18
|
+
* is the most consequential typographic choice in the theme and its name carries no information, so
|
|
19
|
+
* the numbers do the explaining.
|
|
20
|
+
*/
|
|
21
|
+
export declare const TYPE_SCALE_CHOICES: ReadonlyArray<readonly [string, string]>;
|
|
22
|
+
/** Answers a caller already has — from CLI flags, or a host tool's own defaults. */
|
|
23
|
+
export interface InterviewGiven {
|
|
24
|
+
readonly seed?: string;
|
|
25
|
+
/** Brand count in auto mode, or a comma-separated colour list in manual mode. */
|
|
26
|
+
readonly colors?: string;
|
|
27
|
+
readonly scheme?: string;
|
|
28
|
+
readonly manual?: boolean;
|
|
29
|
+
readonly feel?: string;
|
|
30
|
+
readonly ratio?: string;
|
|
31
|
+
readonly baseSize?: string | number;
|
|
32
|
+
readonly contrast?: string;
|
|
33
|
+
readonly reset?: string;
|
|
34
|
+
readonly format?: string;
|
|
35
|
+
readonly noSemantics?: boolean;
|
|
36
|
+
readonly noNeutral?: boolean;
|
|
37
|
+
readonly noShadows?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** The fully-resolved answer set, ready for `runCreate`. */
|
|
40
|
+
export type InterviewAnswers = ScaffoldAnswers & {
|
|
41
|
+
readonly format: RawFormat;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Run the interview. Anything present in `given` is taken as answered and never asked.
|
|
45
|
+
*
|
|
46
|
+
* The scheme question only appears when the brand count admits more than one rotation set — which is
|
|
47
|
+
* only at three colours. Two is a complement, four a tetradic, five a pentadic: asking would be a
|
|
48
|
+
* prompt with a single option.
|
|
49
|
+
*/
|
|
50
|
+
export declare function promptCreateAnswers(p: Prompter, given?: InterviewGiven): Promise<InterviewAnswers>;
|
|
51
|
+
/**
|
|
52
|
+
* The post-generation summary: what the contrast pass did, and what landed. Returned as lines rather
|
|
53
|
+
* than printed so each host can frame them — the standalone command and the project scaffolder put
|
|
54
|
+
* different things around the same facts.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createReportLines(result: CreateResult, variableCount: number): string[];
|
package/dist/build/index.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export * from "./emitTheme";
|
|
|
12
12
|
export * from "./emit";
|
|
13
13
|
export * from "./config";
|
|
14
14
|
export * from "./init";
|
|
15
|
+
export * from "./scaffold";
|
|
16
|
+
export * from "./createCommand";
|
|
17
|
+
export * from "./createInterview";
|
|
18
|
+
export * from "./prompt";
|
|
15
19
|
export * from "./importCommand";
|
|
16
20
|
export * from "./buildCommand";
|
|
17
21
|
export * from "./tokensCommand";
|
package/dist/build/init.d.ts
CHANGED
|
@@ -13,6 +13,11 @@ export interface InitOptions {
|
|
|
13
13
|
readonly force?: boolean;
|
|
14
14
|
/** Package name to import from in the scaffold (default: this package's own `name`). */
|
|
15
15
|
readonly packageName?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Override the raw-theme detection: a {@link DetectedRawTheme} to wire up regardless of what's on
|
|
18
|
+
* disk, or `null` to force the self-contained starter config. Omit to detect (the normal path).
|
|
19
|
+
*/
|
|
20
|
+
readonly rawTheme?: DetectedRawTheme | null;
|
|
16
21
|
}
|
|
17
22
|
export interface InitResult {
|
|
18
23
|
/** Absolute path of the written config. */
|
|
@@ -20,9 +25,46 @@ export interface InitResult {
|
|
|
20
25
|
readonly variant: ConfigVariant;
|
|
21
26
|
/** The package name the scaffold imports from. */
|
|
22
27
|
readonly packageName: string;
|
|
28
|
+
/** The raw theme the config was wired to, or `undefined` when it carries its own starter palette. */
|
|
29
|
+
readonly rawTheme?: DetectedRawTheme;
|
|
23
30
|
}
|
|
24
31
|
/** Read this package's own `name` (so the scaffold imports the currently-installed package name). */
|
|
25
32
|
export declare function readOwnPackageName(): string;
|
|
33
|
+
/** A raw theme found next to the config, and how a config should reach it. */
|
|
34
|
+
export interface DetectedRawTheme {
|
|
35
|
+
/** Absolute path of the file found. */
|
|
36
|
+
readonly path: string;
|
|
37
|
+
/** Its basename, e.g. `theme.raw.json`. */
|
|
38
|
+
readonly filename: string;
|
|
39
|
+
/** The extension, including the dot. */
|
|
40
|
+
readonly ext: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Look for an authored raw theme in `fromDir`.
|
|
44
|
+
*
|
|
45
|
+
* This is the seam between `create` and `init`: `create` designs the theme, `init` wires the build.
|
|
46
|
+
* When a theme is already there, `init` must not invent a second one — a project with two sources of
|
|
47
|
+
* truth for its tokens is worse than a project with none.
|
|
48
|
+
*/
|
|
49
|
+
export declare const findRawTheme: (fromDir?: string) => DetectedRawTheme | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* The import line (or lines) a config uses to reach a detected raw theme.
|
|
52
|
+
*
|
|
53
|
+
* `.ts` is graph-compiled alongside the config, so an extensionless specifier resolves and the
|
|
54
|
+
* transformer rewrites it. `.mjs`/`.js` are imported by Node directly, so they keep their extension.
|
|
55
|
+
* `.json` is read rather than imported: an import attribute (`with { type: "json" }`) would work on
|
|
56
|
+
* current Node but pins the scaffolded config to a version floor for no benefit, and `readFileSync`
|
|
57
|
+
* behaves identically in every flavour.
|
|
58
|
+
*/
|
|
59
|
+
export declare function rawThemeImport(detected: DetectedRawTheme): {
|
|
60
|
+
head: string;
|
|
61
|
+
expression: string;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* The config body when a raw theme already exists — it imports that theme instead of carrying one.
|
|
65
|
+
* Deliberately short: everything about the design lives in the theme file, and this is only wiring.
|
|
66
|
+
*/
|
|
67
|
+
export declare function scaffoldConfigForRaw(packageName: string, detected: DetectedRawTheme): string;
|
|
26
68
|
/** The scaffolded config body — one shared ESM template across ts/js/mjs. */
|
|
27
69
|
export declare function scaffoldConfig(packageName: string): string;
|
|
28
70
|
/**
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare const bold: (s: string) => string;
|
|
2
|
+
export declare const dim: (s: string) => string;
|
|
3
|
+
export declare const cyan: (s: string) => string;
|
|
4
|
+
export declare const green: (s: string) => string;
|
|
5
|
+
export declare const yellow: (s: string) => string;
|
|
6
|
+
/** One choice in a `select` / `multiselect`. */
|
|
7
|
+
export interface Choice<T> {
|
|
8
|
+
readonly value: T;
|
|
9
|
+
readonly label: string;
|
|
10
|
+
/** Trailing grey note — what this option means. */
|
|
11
|
+
readonly hint?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A prompt session. Holds one readline interface for the whole interview so stdin is opened and
|
|
15
|
+
* closed exactly once; `interactive` is false when there's no TTY, and every ask short-circuits to
|
|
16
|
+
* its default.
|
|
17
|
+
*/
|
|
18
|
+
export declare class Prompter {
|
|
19
|
+
private rl;
|
|
20
|
+
readonly interactive: boolean;
|
|
21
|
+
constructor(interactive?: boolean);
|
|
22
|
+
private get io();
|
|
23
|
+
close(): void;
|
|
24
|
+
private question;
|
|
25
|
+
/** Print a line to stdout. Kept on the prompter so command code never touches `process.stdout`. */
|
|
26
|
+
write(line?: string): void;
|
|
27
|
+
/** Free-text, with a default shown in the prompt. Blank input takes the default. */
|
|
28
|
+
text(label: string, fallback: string, validate?: (v: string) => string | undefined): Promise<string>;
|
|
29
|
+
/** A number, validated as finite. */
|
|
30
|
+
number(label: string, fallback: number, validate?: (v: number) => string | undefined): Promise<number>;
|
|
31
|
+
/** Pick one. Answers are 1-based indices; blank takes the default (the first choice unless given). */
|
|
32
|
+
select<T>(label: string, choices: readonly Choice<T>[], defaultIndex?: number): Promise<T>;
|
|
33
|
+
/**
|
|
34
|
+
* Pick any. Answers are comma-separated indices; blank keeps the pre-selected set, and an explicit
|
|
35
|
+
* `none` clears it — otherwise there'd be no way to deselect everything with a line-based prompt.
|
|
36
|
+
*/
|
|
37
|
+
multiselect<T>(label: string, choices: readonly Choice<T>[], preselected: readonly number[]): Promise<T[]>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guided raw-theme generator (Node-only, pure) — the engine behind `refract create`.
|
|
3
|
+
*
|
|
4
|
+
* Turns a seed colour plus a handful of answers into a complete `RawTheme`. Deliberately split from
|
|
5
|
+
* the prompting (`cli.ts`) and from the file writing (`createCommand.ts`) so the whole generator is
|
|
6
|
+
* testable without a TTY and reusable programmatically.
|
|
7
|
+
*
|
|
8
|
+
* The governing rule for what lands in the output:
|
|
9
|
+
*
|
|
10
|
+
* **Bake a literal** where the value came from an opinion the engine does not hold — the harmony
|
|
11
|
+
* rotation, the contrast nudge, the leading/tracking curves. Those are this module's taste, and a
|
|
12
|
+
* scaffolded theme is a style guide the user then owns, so it must hold still.
|
|
13
|
+
*
|
|
14
|
+
* **Write the declaration** where the engine already synthesizes — `fontSize.ratio`,
|
|
15
|
+
* `layout.spacing.step`. Baking those would throw the intent away: re-tuning a scale should stay a
|
|
16
|
+
* one-word edit, not a regeneration of eight numbers.
|
|
17
|
+
*
|
|
18
|
+
* Nothing here changes the palette model. It calls the colour helpers the package already ships
|
|
19
|
+
* (`rotateHue`/`darken`, and `audit` for the contrast gate) and writes down what they return, once.
|
|
20
|
+
* Callers who want a companion that *re-derives* on `override()` should use `colors.harmony` instead —
|
|
21
|
+
* that is the other tool, for the other job.
|
|
22
|
+
*/
|
|
23
|
+
import type { RawTheme } from "../core";
|
|
24
|
+
import type { WcagLevel } from "../subsystems/colors/audit";
|
|
25
|
+
/**
|
|
26
|
+
* Hue rotations per scheme. Aligned with the colours subsystem's own `HARMONY_SCHEMES` where they
|
|
27
|
+
* overlap, so a scaffolded literal and an authored `harmony:` key agree. `pentadic` is local — the
|
|
28
|
+
* subsystem has no five-way scheme, and the scaffolder needs one to reach five brand colours.
|
|
29
|
+
*/
|
|
30
|
+
declare const SCHEME_ROTATIONS: Readonly<Record<string, readonly number[]>>;
|
|
31
|
+
export type Feel = "neutral" | "compact" | "editorial" | "technical";
|
|
32
|
+
interface FeelPreset {
|
|
33
|
+
readonly label: string;
|
|
34
|
+
readonly blurb: string;
|
|
35
|
+
/** `fontFamily` base + the `mono` variant (system stacks only — a CLI can't install fonts). */
|
|
36
|
+
readonly fontFamily: {
|
|
37
|
+
readonly base: string;
|
|
38
|
+
readonly mono: string;
|
|
39
|
+
};
|
|
40
|
+
/** Multiplier map for the linear spacing curve (`step: 4`). */
|
|
41
|
+
readonly spacing: Readonly<Record<string, number>>;
|
|
42
|
+
/** Radius base + variants. */
|
|
43
|
+
readonly radius: {
|
|
44
|
+
readonly base: number;
|
|
45
|
+
readonly variants: Readonly<Record<string, number | string>>;
|
|
46
|
+
};
|
|
47
|
+
/** Suggested type ratio when the user takes the default. */
|
|
48
|
+
readonly ratio: string;
|
|
49
|
+
}
|
|
50
|
+
export declare const FEEL_PRESETS: Readonly<Record<Feel, FeelPreset>>;
|
|
51
|
+
export type BrandScheme = keyof typeof SCHEME_ROTATIONS;
|
|
52
|
+
export type ResetPreset = "preflight" | "normalize" | "none";
|
|
53
|
+
export type ContrastTarget = "AA" | "AAA" | "none";
|
|
54
|
+
export interface ScaffoldAnswers {
|
|
55
|
+
/** The seed colour — any form `parseColor` accepts (hex, `rgb()`, `oklch()`). */
|
|
56
|
+
readonly seed: string;
|
|
57
|
+
/** `auto` derives colours 2…N by hue rotation; `manual` takes them verbatim from `extraColors`. */
|
|
58
|
+
readonly mode?: "auto" | "manual";
|
|
59
|
+
/** Total brand colours including the primary, 1–5. Default 2. */
|
|
60
|
+
readonly brandCount?: number;
|
|
61
|
+
/** Which rotation set to use. Ignored unless `mode: "auto"`; defaulted from `brandCount`. */
|
|
62
|
+
readonly scheme?: BrandScheme;
|
|
63
|
+
/** `mode: "manual"` — the colours after the primary, in order. */
|
|
64
|
+
readonly extraColors?: readonly string[];
|
|
65
|
+
/** Add success / info / warning / danger. Default true. */
|
|
66
|
+
readonly semantics?: boolean;
|
|
67
|
+
/** Add the neutral ramp. Default true. */
|
|
68
|
+
readonly neutral?: boolean;
|
|
69
|
+
/** Add shadow ink + alpha tints, and the effects ramp that references them. Default true. */
|
|
70
|
+
readonly shadows?: boolean;
|
|
71
|
+
/** Contrast bar for the text-on-base pass. Default `"AA"`; `"none"` skips the pass entirely. */
|
|
72
|
+
readonly contrast?: ContrastTarget;
|
|
73
|
+
/** Base font size in px. Default 16. */
|
|
74
|
+
readonly baseFontSize?: number;
|
|
75
|
+
/** Named type ratio. Defaults to the feel preset's suggestion. */
|
|
76
|
+
readonly ratio?: string;
|
|
77
|
+
/** Type family + density + corners, moved together. Default `"neutral"`. */
|
|
78
|
+
readonly feel?: Feel;
|
|
79
|
+
/** Which normalization layer `globals.preset` gets. Default `"preflight"`. */
|
|
80
|
+
readonly reset?: ResetPreset;
|
|
81
|
+
}
|
|
82
|
+
/** What the contrast pass did to one palette — surfaced so the CLI can report it honestly. */
|
|
83
|
+
export interface ContrastAdjustment {
|
|
84
|
+
readonly name: string;
|
|
85
|
+
/** The colour before the pass. */
|
|
86
|
+
readonly seed: string;
|
|
87
|
+
/** The colour written to the file. */
|
|
88
|
+
readonly final: string;
|
|
89
|
+
/** OKLCH lightness points removed (0 when untouched). */
|
|
90
|
+
readonly nudge: number;
|
|
91
|
+
/** Ratio before, and after. */
|
|
92
|
+
readonly ratioBefore: number;
|
|
93
|
+
readonly ratioAfter: number;
|
|
94
|
+
readonly levelAfter: WcagLevel;
|
|
95
|
+
/** True when the pass ran out of room without reaching the bar. */
|
|
96
|
+
readonly unresolved: boolean;
|
|
97
|
+
}
|
|
98
|
+
export interface ScaffoldReport {
|
|
99
|
+
/** The seed's OKLCH lightness, 0–100. */
|
|
100
|
+
readonly seedLightness: number;
|
|
101
|
+
/** The ladder label the seed sits nearest — `base` stays canonical, this is orientation only. */
|
|
102
|
+
readonly nearestStep: number;
|
|
103
|
+
/** Brand palettes in order, with the hue rotation that produced each (0 for the primary/manual). */
|
|
104
|
+
readonly brand: ReadonlyArray<{
|
|
105
|
+
readonly name: string;
|
|
106
|
+
readonly hex: string;
|
|
107
|
+
readonly rotation: number;
|
|
108
|
+
}>;
|
|
109
|
+
/** Every palette the contrast pass looked at. Empty when `contrast: "none"`. */
|
|
110
|
+
readonly contrast: readonly ContrastAdjustment[];
|
|
111
|
+
/** Derived per-step leading and tracking, for the CLI's summary line. */
|
|
112
|
+
readonly type: ReadonlyArray<{
|
|
113
|
+
readonly step: string;
|
|
114
|
+
readonly px: number;
|
|
115
|
+
readonly leading: number;
|
|
116
|
+
readonly tracking: string;
|
|
117
|
+
}>;
|
|
118
|
+
/** The resolved spacing ramp in px, for the CLI's summary line. */
|
|
119
|
+
readonly spacing: ReadonlyArray<{
|
|
120
|
+
readonly step: string;
|
|
121
|
+
readonly px: number;
|
|
122
|
+
}>;
|
|
123
|
+
}
|
|
124
|
+
export interface ScaffoldResult {
|
|
125
|
+
readonly raw: RawTheme;
|
|
126
|
+
readonly report: ScaffoldReport;
|
|
127
|
+
}
|
|
128
|
+
/** Leading for a size, from the curve above. */
|
|
129
|
+
export declare const deriveLeading: (sizePx: number) => number;
|
|
130
|
+
/** Tracking (em) for a size, from the curve above. */
|
|
131
|
+
export declare const deriveTracking: (sizePx: number) => string;
|
|
132
|
+
/**
|
|
133
|
+
* The ladder label a colour's lightness sits nearest. `base` stays the brand colour — this exists so
|
|
134
|
+
* the CLI can tell you which stops are your hover and active shades without moving the hex you typed.
|
|
135
|
+
*/
|
|
136
|
+
export declare const nearestLadderStep: (lightness: number) => number;
|
|
137
|
+
/** Which scheme a brand count implies when the caller doesn't name one. */
|
|
138
|
+
export declare const defaultSchemeFor: (brandCount: number) => BrandScheme | undefined;
|
|
139
|
+
/** Schemes that produce exactly `brandCount − 1` members — the valid choices at that count. */
|
|
140
|
+
export declare const schemesFor: (brandCount: number) => readonly BrandScheme[];
|
|
141
|
+
/**
|
|
142
|
+
* Build a complete `RawTheme` from the interview answers. Pure — no filesystem, no prompting, no
|
|
143
|
+
* randomness — so the same answers always produce the same theme.
|
|
144
|
+
*/
|
|
145
|
+
export declare function scaffoldTheme(answers: ScaffoldAnswers): ScaffoldResult;
|
|
146
|
+
export {};
|