@theme-registry/refract 0.1.1 → 0.1.2

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/dist/cli.js CHANGED
@@ -6118,6 +6118,33 @@ const resolveReferences = (value, tokensByPath, visited = new Set()) => {
6118
6118
  return value;
6119
6119
  };
6120
6120
 
6121
+ /**
6122
+ * Fail-loud shape guard for a value that is *supposed* to be a {@link RawTheme}.
6123
+ *
6124
+ * `createTheme` accepts a bare object, and an empty `{}` is a valid (empty) theme — so a value of the
6125
+ * *wrong* shape doesn't necessarily throw during a build. That's the trap `refract diff` fell into: hand
6126
+ * it a `defineConfig({ raw, targets })` where it wanted the raw theme and it built the config as an
6127
+ * (effectively empty) theme, then reported a nonsense "every token removed" diff and exited 0. A
6128
+ * governance tool must not quietly mis-report. This guard is the loud, coded gate for that class of
6129
+ * mistake — used by `diff` (and available to `validate` / the MCP server) before anything is compared.
6130
+ *
6131
+ * It is deliberately narrow: it rejects only values that cannot be a RawTheme (non-objects, arrays,
6132
+ * `null`) or that are affirmatively something else (a `defineConfig`, spotted by its `targets` array).
6133
+ * A bare `{}` still passes — an empty theme is legitimately empty.
6134
+ */
6135
+ function assertRawTheme(value, source) {
6136
+ const at = source ? ` (${source})` : "";
6137
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
6138
+ const got = Array.isArray(value) ? "an array" : value === null ? "null" : typeof value;
6139
+ throw new RefractError("REFRACT_E_RAW_SHAPE", `Expected a RawTheme object${at}, got ${got}. Pass the raw theme (a module's default export, or a .json raw theme).`);
6140
+ }
6141
+ // The documented mistake: a defineConfig({ raw, targets }) passed where the bare RawTheme was wanted.
6142
+ // A RawTheme never has a top-level `targets`; a config always does.
6143
+ if (Array.isArray(value.targets)) {
6144
+ throw new RefractError("REFRACT_E_RAW_SHAPE", `Expected a RawTheme object${at}, but got what looks like a defineConfig({ raw, targets }). Pass its \`raw\` (or a module that default-exports the raw theme), not the whole config.`);
6145
+ }
6146
+ }
6147
+
6121
6148
  /**
6122
6149
  * `defineAdapter` — turns an `AdapterSpec` (identity + a `bind` returning the four
6123
6150
  * required primitives) into a full `ThemeAdapter` whose `bind` yields a complete
@@ -7171,8 +7198,10 @@ export default defineConfig({
7171
7198
  raw: {
7172
7199
  breakpoints: { sm: 576, md: 768, lg: 1024, xl: 1280 },
7173
7200
  colors: {
7174
- primary: { base: "#4dabf7", text: "#fff", variants: { dark: "#1c7ed6", light: "#a5d8ff" } },
7175
- neutral: { base: "#868e96", text: "#fff", variants: { light: "#f1f3f5", dark: "#343a40" } },
7201
+ // Starter palette passes its own contrast audit (WCAG AA): white text clears 4.5:1 on both
7202
+ // bases. Keep that when you retune the auditor is a shipped feature; the default should model it.
7203
+ primary: { base: "#1864ab", text: "#fff", variants: { dark: "#0b4a86", light: "#a5d8ff" } },
7204
+ neutral: { base: "#495057", text: "#fff", variants: { light: "#f1f3f5", dark: "#212529" } },
7176
7205
  recipes: {
7177
7206
  solid: {
7178
7207
  primary: { background: "primary", color: "primary.text" },
@@ -7745,26 +7774,35 @@ function diffThemes(base, candidate) {
7745
7774
  */
7746
7775
  const WCAG_RANK = { fail: 0, "AA-large": 1, AA: 2, AAA: 3 };
7747
7776
  let counter = 0;
7748
- /** Load a candidate raw theme from a module (default export) or a `.json` file. */
7777
+ /**
7778
+ * Load a candidate raw theme from a module (default export) or a `.json` file, and assert it's actually
7779
+ * theme-shaped before returning — a mis-shaped candidate (e.g. a `defineConfig`) must fail loud here,
7780
+ * not silently diff as "everything removed" (see {@link assertRawTheme}).
7781
+ */
7749
7782
  async function loadCandidate(path) {
7750
7783
  var _a, _b, _c, _d;
7751
7784
  if (!node_fs.existsSync(path))
7752
7785
  throw new Error(`candidate theme not found at "${path}".`);
7786
+ let candidate;
7753
7787
  if (node_path.extname(path) === ".json") {
7754
- return JSON.parse(node_fs.readFileSync(path, "utf8"));
7788
+ candidate = JSON.parse(node_fs.readFileSync(path, "utf8"));
7755
7789
  }
7756
- if (node_path.extname(path) === ".ts") {
7790
+ else if (node_path.extname(path) === ".ts") {
7757
7791
  const { entry, cleanup } = await compileTsConfigGraph(path);
7758
7792
  try {
7759
7793
  const mod = (await import(node_url.pathToFileURL(entry).href));
7760
- return ((_b = (_a = mod.default) !== null && _a !== void 0 ? _a : mod.raw) !== null && _b !== void 0 ? _b : mod);
7794
+ candidate = (_b = (_a = mod.default) !== null && _a !== void 0 ? _a : mod.raw) !== null && _b !== void 0 ? _b : mod;
7761
7795
  }
7762
7796
  finally {
7763
7797
  cleanup();
7764
7798
  }
7765
7799
  }
7766
- const mod = (await import(`${node_url.pathToFileURL(path).href}?v=${++counter}`));
7767
- return ((_d = (_c = mod.default) !== null && _c !== void 0 ? _c : mod.raw) !== null && _d !== void 0 ? _d : mod);
7800
+ else {
7801
+ const mod = (await import(`${node_url.pathToFileURL(path).href}?v=${++counter}`));
7802
+ candidate = (_d = (_c = mod.default) !== null && _c !== void 0 ? _c : mod.raw) !== null && _d !== void 0 ? _d : mod;
7803
+ }
7804
+ assertRawTheme(candidate, path);
7805
+ return candidate;
7768
7806
  }
7769
7807
  /** Does this built theme expose the class-emitting surface (CSS-style adapter)? */
7770
7808
  const emitsClasses = (theme) => typeof theme.renderRecipe === "function";
@@ -7868,6 +7906,21 @@ Commands:
7868
7906
  audit Score colour pairings for WCAG-2 contrast (+ advisory APCA). Reports; --strict fails.
7869
7907
  skills Install the bundled AI skills into your agent CLI(s) (claude/codex/…).
7870
7908
  `;
7909
+ /**
7910
+ * Uniform error reporter for a command's catch block. Surfaces a `RefractError`'s stable `code`
7911
+ * (`[REFRACT_E_…] message`) so an agent or CI log sees the machine-readable code, and lists each
7912
+ * collect-all `failure` — a plain `Error` just prints its message. Returns the exit code (1).
7913
+ */
7914
+ function reportError(cmd, err) {
7915
+ var _a;
7916
+ const e = err;
7917
+ const code = typeof e.code === "string" && e.code.startsWith("REFRACT_E_") ? `[${e.code}] ` : "";
7918
+ process.stderr.write(`refract ${cmd}: ${code}${(_a = e.message) !== null && _a !== void 0 ? _a : String(err)}\n`);
7919
+ if (e.failures)
7920
+ for (const f of e.failures)
7921
+ process.stderr.write(` - ${f}\n`);
7922
+ return 1;
7923
+ }
7871
7924
  async function cmdInit(argv) {
7872
7925
  const { values } = node_util.parseArgs({
7873
7926
  args: argv,
@@ -7890,8 +7943,7 @@ async function cmdInit(argv) {
7890
7943
  return 0;
7891
7944
  }
7892
7945
  catch (err) {
7893
- process.stderr.write(`refract init: ${err.message}\n`);
7894
- return 1;
7946
+ return reportError("init", err);
7895
7947
  }
7896
7948
  }
7897
7949
  async function cmdImport(argv) {
@@ -7935,8 +7987,7 @@ async function cmdImport(argv) {
7935
7987
  return 0;
7936
7988
  }
7937
7989
  catch (err) {
7938
- process.stderr.write(`refract import: ${err.message}\n`);
7939
- return 1;
7990
+ return reportError("import", err);
7940
7991
  }
7941
7992
  }
7942
7993
  async function cmdBuild(argv) {
@@ -7965,8 +8016,7 @@ async function cmdBuild(argv) {
7965
8016
  return 0;
7966
8017
  }
7967
8018
  catch (err) {
7968
- process.stderr.write(`refract build: ${err.message}\n`);
7969
- return 1;
8019
+ return reportError("build", err);
7970
8020
  }
7971
8021
  }
7972
8022
  async function cmdTokens(argv) {
@@ -7988,8 +8038,7 @@ async function cmdTokens(argv) {
7988
8038
  return 0;
7989
8039
  }
7990
8040
  catch (err) {
7991
- process.stderr.write(`refract tokens: ${err.message}\n`);
7992
- return 1;
8041
+ return reportError("tokens", err);
7993
8042
  }
7994
8043
  }
7995
8044
  const WCAG_LEVELS = new Set(["AAA", "AA", "AA-large"]);
@@ -8046,8 +8095,7 @@ async function cmdDiff(argv) {
8046
8095
  return 0;
8047
8096
  }
8048
8097
  catch (err) {
8049
- process.stderr.write(`refract diff: ${err.message}\n`);
8050
- return 1;
8098
+ return reportError("diff", err);
8051
8099
  }
8052
8100
  }
8053
8101
  async function cmdAudit(argv) {
@@ -8089,8 +8137,7 @@ async function cmdAudit(argv) {
8089
8137
  return 0;
8090
8138
  }
8091
8139
  catch (err) {
8092
- process.stderr.write(`${err.message}\n`);
8093
- return 1;
8140
+ return reportError("audit", err);
8094
8141
  }
8095
8142
  }
8096
8143
  /** Parse an `--agent` value (`"all"` or a comma list) into validated targets. */
@@ -8142,8 +8189,7 @@ async function cmdSkills(argv) {
8142
8189
  return 0;
8143
8190
  }
8144
8191
  catch (err) {
8145
- process.stderr.write(`refract skills list: ${err.message}\n`);
8146
- return 1;
8192
+ return reportError("skills list", err);
8147
8193
  }
8148
8194
  }
8149
8195
  const { values } = node_util.parseArgs({
@@ -8202,8 +8248,7 @@ async function cmdSkills(argv) {
8202
8248
  return 1;
8203
8249
  }
8204
8250
  catch (err) {
8205
- process.stderr.write(`refract skills: ${err.message}\n`);
8206
- return 1;
8251
+ return reportError("skills", err);
8207
8252
  }
8208
8253
  }
8209
8254
  /** Dispatch a parsed argv (without the node/script prefix). Returns a process exit code. */