@nghitrum/dsforge 0.1.5-alpha.0 → 0.1.5-alpha.10

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/index.d.ts CHANGED
@@ -381,7 +381,19 @@ declare function hasRefs(value: string): boolean;
381
381
 
382
382
  declare function validateConfig(config: DesignSystemConfig, rules: RulesConfig): ValidationResult;
383
383
 
384
+ /**
385
+ * Density preset definitions.
386
+ *
387
+ * This is the single source of truth for compact / comfortable / spacious
388
+ * spacing and radius values. Imported by:
389
+ * - cli/commands/init.ts (initial config scaffolding)
390
+ * - cli/commands/generate.ts (re-applying preset on each generate run)
391
+ * - generators/tokens/css-vars.ts (emitting density.css for Pro)
392
+ * - generators/showcase/html.ts (embedding density CSS in showcase)
393
+ */
394
+
384
395
  type Preset = "compact" | "comfortable" | "spacious";
396
+
385
397
  declare function buildInitialConfig(name: string, preset?: Preset): DesignSystemConfig;
386
398
  declare function buildInitialRules(): RulesConfig;
387
399
 
package/dist/index.js CHANGED
@@ -1143,7 +1143,32 @@ var rl = readline.createInterface({
1143
1143
  output: process.stdout
1144
1144
  });
1145
1145
 
1146
- // src/cli/commands/init.ts
1146
+ // src/lib/license.ts
1147
+ import { readFileSync } from "fs";
1148
+ import { join } from "path";
1149
+ function readKeyFromDotEnv() {
1150
+ try {
1151
+ const content = readFileSync(join(process.cwd(), ".env"), "utf8");
1152
+ for (const raw of content.split("\n")) {
1153
+ const line = raw.trim();
1154
+ if (!line || line.startsWith("#")) continue;
1155
+ const eq = line.indexOf("=");
1156
+ if (eq === -1) continue;
1157
+ const key = line.slice(0, eq).trim();
1158
+ if (key !== "DSFORGE_KEY") continue;
1159
+ const val = line.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
1160
+ return val || void 0;
1161
+ }
1162
+ } catch {
1163
+ }
1164
+ return void 0;
1165
+ }
1166
+ function isProUnlocked() {
1167
+ const key = process.env["DSFORGE_KEY"] ?? readKeyFromDotEnv();
1168
+ return typeof key === "string" && key.length > 0;
1169
+ }
1170
+
1171
+ // src/presets/index.ts
1147
1172
  var SPACING_PRESETS = {
1148
1173
  compact: {
1149
1174
  "1": 2,
@@ -1181,6 +1206,31 @@ var RADIUS_PRESETS = {
1181
1206
  comfortable: { none: 0, sm: 2, md: 4, lg: 8, xl: 16, full: 9999 },
1182
1207
  spacious: { none: 0, sm: 3, md: 6, lg: 12, xl: 20, full: 9999 }
1183
1208
  };
1209
+ var CONTROL_SIZE_PRESETS = {
1210
+ compact: { sm: 12, md: 14, lg: 18 },
1211
+ comfortable: { sm: 14, md: 16, lg: 20 },
1212
+ spacious: { sm: 16, md: 18, lg: 24 }
1213
+ };
1214
+ var PRESET_BASE_UNITS = {
1215
+ compact: 2,
1216
+ comfortable: 4,
1217
+ spacious: 6
1218
+ };
1219
+ function buildSemanticSpacing(scale) {
1220
+ return {
1221
+ "component-padding-xs": `${scale["1"]}`,
1222
+ "component-padding-sm": `${scale["2"]}`,
1223
+ "component-padding-md": `${scale["4"]}`,
1224
+ "component-padding-lg": `${scale["5"]}`,
1225
+ "layout-gap-xs": `${scale["2"]}`,
1226
+ "layout-gap-sm": `${scale["3"]}`,
1227
+ "layout-gap-md": `${scale["5"]}`,
1228
+ "layout-gap-lg": `${scale["6"]}`,
1229
+ "layout-section": `${scale["7"]}`
1230
+ };
1231
+ }
1232
+
1233
+ // src/cli/commands/init.ts
1184
1234
  function buildInitialConfig(name, preset = "comfortable") {
1185
1235
  const spacing = SPACING_PRESETS[preset];
1186
1236
  const radius = RADIUS_PRESETS[preset];
@@ -1371,19 +1421,9 @@ function buildInitialConfig(name, preset = "comfortable") {
1371
1421
  }
1372
1422
  },
1373
1423
  spacing: {
1374
- baseUnit: preset === "compact" ? 2 : preset === "spacious" ? 6 : 4,
1424
+ baseUnit: PRESET_BASE_UNITS[preset],
1375
1425
  scale: spacing,
1376
- semantic: {
1377
- "component-padding-xs": `${spacing[1]}`,
1378
- "component-padding-sm": `${spacing[2]}`,
1379
- "component-padding-md": `${spacing[4]}`,
1380
- "component-padding-lg": `${spacing[5]}`,
1381
- "layout-gap-xs": `${spacing[2]}`,
1382
- "layout-gap-sm": `${spacing[3]}`,
1383
- "layout-gap-md": `${spacing[5]}`,
1384
- "layout-gap-lg": `${spacing[6]}`,
1385
- "layout-section": `${spacing[7]}`
1386
- }
1426
+ semantic: buildSemanticSpacing(spacing)
1387
1427
  },
1388
1428
  radius,
1389
1429
  elevation: {
@@ -1558,6 +1598,19 @@ function emitBaseCss(config, resolution) {
1558
1598
  if (typoEntries.length > 0) {
1559
1599
  sections.push(emitBlock(":root", typoEntries, "Typography"));
1560
1600
  }
1601
+ const currentPreset = config.philosophy?.density ?? "comfortable";
1602
+ const controlSizes = CONTROL_SIZE_PRESETS[currentPreset] ?? CONTROL_SIZE_PRESETS.comfortable;
1603
+ sections.push(
1604
+ emitBlock(
1605
+ ":root",
1606
+ [
1607
+ ["control-size-sm", `${controlSizes.sm}px`],
1608
+ ["control-size-md", `${controlSizes.md}px`],
1609
+ ["control-size-lg", `${controlSizes.lg}px`]
1610
+ ],
1611
+ "Control sizes"
1612
+ )
1613
+ );
1561
1614
  const radiusEntries = Object.entries(
1562
1615
  config.radius ?? {}
1563
1616
  ).filter(([, v]) => v !== void 0).map(([k, v]) => [`radius-${k}`, v === 9999 ? "9999px" : `${v}px`]);
@@ -2559,6 +2612,62 @@ function generateThemeProvider(config) {
2559
2612
  const themeNames = Object.keys(config.themes ?? { light: {}, dark: {} });
2560
2613
  const defaultTheme = themeNames.includes("light") ? "light" : themeNames[0] ?? "light";
2561
2614
  const themeType = themeNames.map((t) => `"${t}"`).join(" | ");
2615
+ const isPro = isProUnlocked();
2616
+ const defaultDensity = config.meta.preset ?? "comfortable";
2617
+ const densityImport = isPro ? `
2618
+ import "../tokens/density.css";` : "";
2619
+ const densityTypes = isPro ? `
2620
+ export type DensityName = "compact" | "comfortable" | "spacious";
2621
+ ` : "";
2622
+ const densityContextTypes = isPro ? `
2623
+ export interface DensityContextValue {
2624
+ density: DensityName;
2625
+ setDensity: (density: DensityName) => void;
2626
+ }
2627
+ ` : "";
2628
+ const densityContext = isPro ? `
2629
+ export const DensityContext = React.createContext<DensityContextValue>({
2630
+ density: "${defaultDensity}",
2631
+ setDensity: () => undefined,
2632
+ });
2633
+
2634
+ /**
2635
+ * Hook to read and change the current density.
2636
+ * Must be used inside a <ThemeProvider>.
2637
+ */
2638
+ export function useDensity(): DensityContextValue {
2639
+ return React.useContext(DensityContext);
2640
+ }
2641
+ ` : "";
2642
+ const densityProp = isPro ? `
2643
+ /** Component density. Requires density.css to be imported. Defaults to "${defaultDensity}". */
2644
+ density?: DensityName;` : "";
2645
+ const densityOnChangeProp = isPro ? `
2646
+ /** Called when setDensity is invoked. */
2647
+ onDensityChange?: (density: DensityName) => void;` : "";
2648
+ const densityState = isPro ? `
2649
+ const [density, setDensityState] = React.useState<DensityName>(initialDensity);
2650
+
2651
+ React.useEffect(() => {
2652
+ setDensityState(initialDensity);
2653
+ }, [initialDensity]);
2654
+
2655
+ const setDensity = React.useCallback(
2656
+ (next: DensityName) => {
2657
+ setDensityState(next);
2658
+ onDensityChange?.(next);
2659
+ },
2660
+ [onDensityChange],
2661
+ );
2662
+ ` : "";
2663
+ const densityDestructure = isPro ? `,
2664
+ density: initialDensity = "${defaultDensity}",
2665
+ onDensityChange,` : "";
2666
+ const densityProviderOpen = isPro ? `
2667
+ <DensityContext.Provider value={{ density, setDensity }}>` : "";
2668
+ const densityDataAttr = isPro ? ` data-density={density}` : "";
2669
+ const densityProviderClose = isPro ? `
2670
+ </DensityContext.Provider>` : "";
2562
2671
  return `/**
2563
2672
  * ThemeProvider \u2014 ${config.meta.name}
2564
2673
  *
@@ -2570,27 +2679,27 @@ function generateThemeProvider(config) {
2570
2679
  * import "@${config.meta.name}/tokens/light.css"; // or dark.css
2571
2680
  * import { ThemeProvider } from "@${config.meta.name}";
2572
2681
  *
2573
- * <ThemeProvider theme="light">
2682
+ * <ThemeProvider theme="light"${isPro ? ` density="${defaultDensity}"` : ""}>
2574
2683
  * <App />
2575
2684
  * </ThemeProvider>
2576
2685
  */
2577
2686
 
2578
- import React from "react";
2687
+ import React from "react";${densityImport}
2579
2688
 
2580
2689
  // \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2581
2690
 
2582
2691
  export type ThemeName = ${themeType};
2583
-
2692
+ ${densityTypes}
2584
2693
  export interface ThemeContextValue {
2585
2694
  theme: ThemeName;
2586
2695
  setTheme: (theme: ThemeName) => void;
2587
2696
  }
2588
-
2697
+ ${densityContextTypes}
2589
2698
  export interface ThemeProviderProps {
2590
2699
  /** Initial theme. Defaults to "${defaultTheme}". */
2591
2700
  theme?: ThemeName;
2592
2701
  /** Called when setTheme is invoked \u2014 use to persist theme preference. */
2593
- onThemeChange?: (theme: ThemeName) => void;
2702
+ onThemeChange?: (theme: ThemeName) => void;${densityProp}${densityOnChangeProp}
2594
2703
  children: React.ReactNode;
2595
2704
  }
2596
2705
 
@@ -2608,12 +2717,12 @@ export const ThemeContext = React.createContext<ThemeContextValue>({
2608
2717
  export function useTheme(): ThemeContextValue {
2609
2718
  return React.useContext(ThemeContext);
2610
2719
  }
2611
-
2720
+ ${densityContext}
2612
2721
  // \u2500\u2500\u2500 Provider \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2613
2722
 
2614
2723
  export function ThemeProvider({
2615
2724
  theme: initialTheme = "${defaultTheme}",
2616
- onThemeChange,
2725
+ onThemeChange,${densityDestructure}
2617
2726
  children,
2618
2727
  }: ThemeProviderProps) {
2619
2728
  const [theme, setThemeState] = React.useState<ThemeName>(initialTheme);
@@ -2629,13 +2738,13 @@ export function ThemeProvider({
2629
2738
  },
2630
2739
  [onThemeChange],
2631
2740
  );
2632
-
2633
- return (
2634
- <ThemeContext.Provider value={{ theme, setTheme }}>
2635
- <div data-theme={theme} style={{ display: "contents" }}>
2636
- {children}
2637
- </div>
2638
- </ThemeContext.Provider>
2741
+ ${densityState}
2742
+ return (${densityProviderOpen}
2743
+ <ThemeContext.Provider value={{ theme, setTheme }}>
2744
+ <div data-theme={theme}${densityDataAttr} style={{ display: "contents" }}>
2745
+ {children}
2746
+ </div>
2747
+ </ThemeContext.Provider>${densityProviderClose}
2639
2748
  );
2640
2749
  }
2641
2750
  `;
@@ -2652,9 +2761,9 @@ function generateComponentIndex(config, componentNames) {
2652
2761
  ""
2653
2762
  ];
2654
2763
  for (const name of componentNames) {
2655
- lines.push(`export * from "./${name}";`);
2764
+ lines.push(`export * from "./components/${name}/${name}";`);
2656
2765
  }
2657
- lines.push(`export * from "./ThemeProvider";`);
2766
+ lines.push(`export * from "./components/ThemeProvider/ThemeProvider";`);
2658
2767
  lines.push("");
2659
2768
  return lines.join("\n");
2660
2769
  }
@@ -3057,12 +3166,14 @@ function generatePackageJson(config, componentNames) {
3057
3166
  ),
3058
3167
  "./tokens": "./tokens/tokens.js",
3059
3168
  "./tailwind": "./tokens/tailwind.js",
3060
- "./metadata": "./metadata/index.json",
3061
3169
  ...Object.fromEntries(
3062
- componentNames.map((c) => [`./metadata/${c}`, `./metadata/${c}.json`])
3170
+ componentNames.map((c) => {
3171
+ const pascal = c.charAt(0).toUpperCase() + c.slice(1);
3172
+ return [`./components/${pascal}`, `./components/${pascal}/${pascal}.json`];
3173
+ })
3063
3174
  )
3064
3175
  },
3065
- files: ["dist", "tokens", "metadata", "docs", "CHANGELOG.md"],
3176
+ files: ["dist", "tokens", "components", "CHANGELOG.md"],
3066
3177
  scripts: {
3067
3178
  build: "tsc",
3068
3179
  prepublishOnly: "npm run build"
@@ -3102,7 +3213,7 @@ function generateTsConfig() {
3102
3213
  moduleResolution: "NodeNext",
3103
3214
  lib: ["ES2020", "DOM"],
3104
3215
  outDir: "./dist",
3105
- rootDir: "./src",
3216
+ rootDir: ".",
3106
3217
  declaration: true,
3107
3218
  declarationMap: true,
3108
3219
  sourceMap: true,
@@ -3111,7 +3222,7 @@ function generateTsConfig() {
3111
3222
  skipLibCheck: true,
3112
3223
  jsx: "react-jsx"
3113
3224
  },
3114
- include: ["src/**/*"],
3225
+ include: ["index.ts", "components/**/*"],
3115
3226
  exclude: ["node_modules", "dist"]
3116
3227
  },
3117
3228
  null,
@@ -3160,7 +3271,10 @@ function App() {
3160
3271
  ## Components
3161
3272
 
3162
3273
  ${componentNames.map(
3163
- (c) => `- **${c.charAt(0).toUpperCase() + c.slice(1)}** \u2014 see \`metadata/${c}.json\` for contract`
3274
+ (c) => {
3275
+ const pascal = c.charAt(0).toUpperCase() + c.slice(1);
3276
+ return `- **${pascal}** \u2014 see \`components/${pascal}/${pascal}.json\` for props and usage`;
3277
+ }
3164
3278
  ).join("\n")}
3165
3279
 
3166
3280
  ## Themes
@@ -3235,20 +3349,10 @@ every semantic and component token that references it.
3235
3349
 
3236
3350
  ## AI tool integration
3237
3351
 
3238
- The \`metadata/\` directory contains machine-readable component contracts.
3352
+ Each component ships with a machine-readable JSON contract (e.g. \`components/Button/Button.json\`).
3239
3353
  AI coding assistants (Copilot, Cursor, Claude Code) can read these to
3240
3354
  generate UI that respects your governance rules automatically.
3241
3355
 
3242
- \`\`\`json
3243
- // ${pkgName}/metadata/button.json
3244
- {
3245
- "component": "Button",
3246
- "allowedVariants": ["primary", "secondary", "danger", "ghost"],
3247
- "requiredProps": ["aria-label"],
3248
- "accessibilityContract": { "keyboard": true, "focusRing": true }
3249
- }
3250
- \`\`\`
3251
-
3252
3356
  ---
3253
3357
 
3254
3358
  Generated by [dsforge](https://github.com/nghitrum/dsforge) v${config.meta.version}.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nghitrum/dsforge",
3
- "version": "0.1.5-alpha.0",
3
+ "version": "0.1.5-alpha.10",
4
4
  "description": "AI-native design system generator — tokens → components → docs → npm",
5
5
  "keywords": [
6
6
  "design-system",