@nghitrum/dsforge 0.1.5-alpha.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -3,16 +3,16 @@ import {
3
3
  generateChangelog,
4
4
  generatePackageJson,
5
5
  generateTsConfig
6
- } from "../chunk-QHE35QQQ.js";
6
+ } from "../chunk-ZZRPNO6Z.js";
7
7
  import {
8
+ CONTROL_SIZE_PRESETS,
8
9
  PRESETS,
9
10
  PRESET_BASE_UNITS,
10
11
  RADIUS_PRESETS,
11
12
  SPACING_PRESETS,
12
13
  applyPreset,
13
- buildSemanticSpacing,
14
- isProUnlocked
15
- } from "../chunk-JUMR3N5J.js";
14
+ buildSemanticSpacing
15
+ } from "../chunk-5YT3VNE6.js";
16
16
 
17
17
  // src/cli/index.ts
18
18
  import { program } from "commander";
@@ -737,15 +737,7 @@ async function runInit(cwd, options) {
737
737
  }
738
738
  const name = rawName.replace(/\s+/g, "-").toLowerCase();
739
739
  let preset;
740
- if (!isProUnlocked()) {
741
- if (options.preset && options.preset !== "comfortable") {
742
- logger.hint(
743
- `Preset "${options.preset}" requires dsforge Pro`,
744
- `Set DSFORGE_KEY to unlock compact and spacious. Using comfortable.`
745
- );
746
- }
747
- preset = "comfortable";
748
- } else if (options.preset && VALID_PRESETS.includes(options.preset)) {
740
+ if (options.preset && VALID_PRESETS.includes(options.preset)) {
749
741
  preset = options.preset;
750
742
  } else {
751
743
  const answer = await ask(
@@ -1773,6 +1765,19 @@ function emitBaseCss(config, resolution) {
1773
1765
  if (typoEntries.length > 0) {
1774
1766
  sections.push(emitBlock(":root", typoEntries, "Typography"));
1775
1767
  }
1768
+ const currentPreset = config.philosophy?.density ?? "comfortable";
1769
+ const controlSizes = CONTROL_SIZE_PRESETS[currentPreset] ?? CONTROL_SIZE_PRESETS.comfortable;
1770
+ sections.push(
1771
+ emitBlock(
1772
+ ":root",
1773
+ [
1774
+ ["control-size-sm", `${controlSizes.sm}px`],
1775
+ ["control-size-md", `${controlSizes.md}px`],
1776
+ ["control-size-lg", `${controlSizes.lg}px`]
1777
+ ],
1778
+ "Control sizes"
1779
+ )
1780
+ );
1776
1781
  const radiusEntries = Object.entries(
1777
1782
  config.radius ?? {}
1778
1783
  ).filter(([, v]) => v !== void 0).map(([k, v]) => [`radius-${k}`, v === 9999 ? "9999px" : `${v}px`]);
@@ -1854,6 +1859,10 @@ function emitDensityCss(config) {
1854
1859
  for (const [key, value] of Object.entries(radius)) {
1855
1860
  entries.push([`radius-${key}`, value === 9999 ? "9999px" : `${value}px`]);
1856
1861
  }
1862
+ const controlSizes = CONTROL_SIZE_PRESETS[preset];
1863
+ entries.push(["control-size-sm", `${controlSizes.sm}px`]);
1864
+ entries.push(["control-size-md", `${controlSizes.md}px`]);
1865
+ entries.push(["control-size-lg", `${controlSizes.lg}px`]);
1857
1866
  lines.push(emitBlock(`[data-density="${preset}"]`, entries, `Preset: ${preset}`));
1858
1867
  lines.push("");
1859
1868
  }
@@ -1889,192 +1898,6 @@ function generateCssFiles(config, resolution) {
1889
1898
  return files;
1890
1899
  }
1891
1900
 
1892
- // src/generators/metadata/generator.ts
1893
- var COMPONENT_DEFAULTS = {
1894
- button: {
1895
- description: "Triggers an action or navigation. The primary interactive element.",
1896
- role: "action-trigger",
1897
- hierarchyLevel: "primary",
1898
- interactionModel: "synchronous",
1899
- layoutImpact: "inline",
1900
- destructive: false,
1901
- sizes: ["sm", "md", "lg"]
1902
- },
1903
- input: {
1904
- description: "Accepts user text input. Use with a label for accessibility.",
1905
- role: "data-entry",
1906
- hierarchyLevel: "primary",
1907
- interactionModel: "synchronous",
1908
- layoutImpact: "block",
1909
- destructive: false,
1910
- sizes: ["sm", "md", "lg"]
1911
- },
1912
- card: {
1913
- description: "Groups related content with optional header, body, and footer slots.",
1914
- role: "content-container",
1915
- hierarchyLevel: "utility",
1916
- interactionModel: "none",
1917
- layoutImpact: "block",
1918
- destructive: false
1919
- },
1920
- badge: {
1921
- description: "Compact label for status, categories, or counts. Display-only \u2014 not interactive.",
1922
- role: "status-indicator",
1923
- hierarchyLevel: "utility",
1924
- interactionModel: "none",
1925
- layoutImpact: "inline",
1926
- destructive: false,
1927
- sizes: ["sm", "md", "lg"]
1928
- },
1929
- checkbox: {
1930
- description: "Binary toggle for boolean values. Supports indeterminate state for partial selections.",
1931
- role: "data-entry",
1932
- hierarchyLevel: "primary",
1933
- interactionModel: "synchronous",
1934
- layoutImpact: "inline",
1935
- destructive: false,
1936
- sizes: ["sm", "md", "lg"]
1937
- },
1938
- radio: {
1939
- description: "Single selection within a mutually exclusive group. Always use inside RadioGroup.",
1940
- role: "data-entry",
1941
- hierarchyLevel: "primary",
1942
- interactionModel: "synchronous",
1943
- layoutImpact: "inline",
1944
- destructive: false,
1945
- sizes: ["sm", "md", "lg"]
1946
- },
1947
- select: {
1948
- description: "Dropdown picker for selecting from a list of options. Wraps native <select> for accessibility.",
1949
- role: "data-entry",
1950
- hierarchyLevel: "primary",
1951
- interactionModel: "synchronous",
1952
- layoutImpact: "block",
1953
- destructive: false,
1954
- sizes: ["sm", "md", "lg"]
1955
- },
1956
- toast: {
1957
- description: "Feedback messages for user actions. Alert is inline; Toast is an overlay with auto-dismiss.",
1958
- role: "feedback",
1959
- hierarchyLevel: "utility",
1960
- interactionModel: "asynchronous",
1961
- layoutImpact: "overlay",
1962
- destructive: false
1963
- },
1964
- spinner: {
1965
- description: "Loading indicator for async operations. Use with an accessible label for screen readers.",
1966
- role: "loading-indicator",
1967
- hierarchyLevel: "utility",
1968
- interactionModel: "asynchronous",
1969
- layoutImpact: "inline",
1970
- destructive: false,
1971
- sizes: ["xs", "sm", "md", "lg", "xl"]
1972
- }
1973
- };
1974
- function buildComponentMetadata(componentName, rule, config) {
1975
- const defaults = COMPONENT_DEFAULTS[componentName.toLowerCase()] ?? {};
1976
- const variants = rule.allowedVariants ?? ["default"];
1977
- const requiredProps = rule.requiredProps ?? [];
1978
- const tokens = {};
1979
- for (const [tokenName] of Object.entries(rule.tokens ?? {})) {
1980
- tokens[tokenName] = `--${tokenName}`;
1981
- }
1982
- const meta = {
1983
- component: pascalCase(componentName),
1984
- version: config.meta.version,
1985
- description: defaults.description ?? `A ${componentName} component.`,
1986
- role: defaults.role ?? "ui-element",
1987
- hierarchyLevel: defaults.hierarchyLevel ?? "utility",
1988
- interactionModel: defaults.interactionModel ?? "none",
1989
- layoutImpact: defaults.layoutImpact ?? "inline",
1990
- destructive: componentName.toLowerCase().includes("delete") || variants.includes("danger"),
1991
- allowedVariants: variants,
1992
- defaultVariant: variants[0] ?? "default",
1993
- requiredProps,
1994
- optionalProps: buildOptionalProps(componentName, defaults),
1995
- tokens,
1996
- accessibilityContract: {
1997
- keyboard: rule.a11y?.keyboard ?? true,
1998
- focusRing: rule.a11y?.focusRing ?? true,
1999
- ariaLabel: rule.a11y?.ariaLabel ?? "optional",
2000
- ...rule.a11y?.role ? { role: rule.a11y.role } : {}
2001
- },
2002
- governanceRules: {
2003
- ...rule.maxWidth ? { maxWidth: rule.maxWidth } : {},
2004
- ...rule.allowedRadius ? { allowedRadius: rule.allowedRadius } : {},
2005
- ...rule.allowedShadows ? { allowedShadows: rule.allowedShadows } : {},
2006
- ...rule.colorPalette ? { colorPalette: rule.colorPalette } : {}
2007
- }
2008
- };
2009
- if (defaults.sizes) {
2010
- meta.sizes = defaults.sizes;
2011
- }
2012
- return meta;
2013
- }
2014
- function buildOptionalProps(componentName, _defaults) {
2015
- const common = ["className", "style", "id", "data-testid"];
2016
- const byComponent = {
2017
- button: [
2018
- "size",
2019
- "loading",
2020
- "disabled",
2021
- "fullWidth",
2022
- "iconLeft",
2023
- "iconRight",
2024
- "onClick"
2025
- ],
2026
- input: [
2027
- "size",
2028
- "disabled",
2029
- "label",
2030
- "helperText",
2031
- "errorMessage",
2032
- "placeholder",
2033
- "startAdornment",
2034
- "endAdornment",
2035
- "onChange"
2036
- ],
2037
- card: ["maxWidth", "noPadding", "onClick"],
2038
- badge: ["size", "dot"],
2039
- checkbox: ["size", "disabled", "label", "helperText", "indeterminate", "checked", "onChange"],
2040
- radio: ["size", "disabled", "label", "value", "onChange"],
2041
- select: ["size", "disabled", "label", "helperText", "errorMessage", "placeholder", "options", "fullWidth", "onChange"],
2042
- toast: ["variant", "title", "dismissible", "duration", "onDismiss"],
2043
- spinner: ["size", "variant", "label"]
2044
- };
2045
- return [...byComponent[componentName.toLowerCase()] ?? [], ...common];
2046
- }
2047
- function pascalCase(str) {
2048
- return str.charAt(0).toUpperCase() + str.slice(1);
2049
- }
2050
- function buildIndexMetadata(config, componentNames, tokenCount) {
2051
- return {
2052
- name: config.meta.name,
2053
- version: config.meta.version,
2054
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2055
- components: componentNames.map(pascalCase),
2056
- tokenCount,
2057
- themes: Object.keys(config.themes ?? {})
2058
- };
2059
- }
2060
- function generateMetadata(config, rules, tokenCount) {
2061
- const files = [];
2062
- const componentNames = Object.keys(rules);
2063
- for (const [componentName, rule] of Object.entries(rules)) {
2064
- const metadata = buildComponentMetadata(componentName, rule, config);
2065
- files.push({
2066
- filename: `${componentName}.json`,
2067
- content: JSON.stringify(metadata, null, 2)
2068
- });
2069
- }
2070
- const index = buildIndexMetadata(config, componentNames, tokenCount);
2071
- files.push({
2072
- filename: "index.json",
2073
- content: JSON.stringify(index, null, 2)
2074
- });
2075
- return files;
2076
- }
2077
-
2078
1901
  // src/adapters/react/components/button.ts
2079
1902
  function generateButton(config, rule) {
2080
1903
  const variants = rule?.allowedVariants ?? [
@@ -2864,9 +2687,17 @@ export interface CheckboxProps
2864
2687
  // \u2500\u2500\u2500 Size map \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
2865
2688
 
2866
2689
  const SIZE_BOX: Record<"sm" | "md" | "lg", string> = {
2867
- sm: "14px",
2868
- md: "16px",
2869
- lg: "20px",
2690
+ sm: "var(--control-size-sm, 14px)",
2691
+ md: "var(--control-size-md, 16px)",
2692
+ lg: "var(--control-size-lg, 20px)",
2693
+ };
2694
+
2695
+ // SVG check/dash icon fits inside the box with a 6px inset, via calc() so it
2696
+ // tracks the CSS var when density changes at runtime.
2697
+ const SVG_SIZE: Record<"sm" | "md" | "lg", string> = {
2698
+ sm: "calc(var(--control-size-sm, 14px) - 6px)",
2699
+ md: "calc(var(--control-size-md, 16px) - 6px)",
2700
+ lg: "calc(var(--control-size-lg, 20px) - 6px)",
2870
2701
  };
2871
2702
 
2872
2703
  const SIZE_FONT: Record<"sm" | "md" | "lg", string> = {
@@ -3010,8 +2841,7 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
3010
2841
  <span style={boxStyle} aria-hidden="true">
3011
2842
  {isActive && (
3012
2843
  <svg
3013
- width={parseInt(boxSize) - 6}
3014
- height={parseInt(boxSize) - 6}
2844
+ style={{ width: SVG_SIZE[size], height: SVG_SIZE[size] }}
3015
2845
  viewBox="0 0 10 10"
3016
2846
  fill="none"
3017
2847
  stroke="#fff"
@@ -3113,9 +2943,9 @@ export interface RadioGroupProps {
3113
2943
  // \u2500\u2500\u2500 Size map \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
3114
2944
 
3115
2945
  const SIZE_BOX: Record<"sm" | "md" | "lg", string> = {
3116
- sm: "14px",
3117
- md: "16px",
3118
- lg: "20px",
2946
+ sm: "var(--control-size-sm, 14px)",
2947
+ md: "var(--control-size-md, 16px)",
2948
+ lg: "var(--control-size-lg, 20px)",
3119
2949
  };
3120
2950
 
3121
2951
  const SIZE_FONT: Record<"sm" | "md" | "lg", string> = {
@@ -4086,62 +3916,7 @@ function generateThemeProvider(config) {
4086
3916
  const themeNames = Object.keys(config.themes ?? { light: {}, dark: {} });
4087
3917
  const defaultTheme = themeNames.includes("light") ? "light" : themeNames[0] ?? "light";
4088
3918
  const themeType = themeNames.map((t) => `"${t}"`).join(" | ");
4089
- const isPro = isProUnlocked();
4090
3919
  const defaultDensity = config.meta.preset ?? "comfortable";
4091
- const densityImport = isPro ? `
4092
- import "../tokens/density.css";` : "";
4093
- const densityTypes = isPro ? `
4094
- export type DensityName = "compact" | "comfortable" | "spacious";
4095
- ` : "";
4096
- const densityContextTypes = isPro ? `
4097
- export interface DensityContextValue {
4098
- density: DensityName;
4099
- setDensity: (density: DensityName) => void;
4100
- }
4101
- ` : "";
4102
- const densityContext = isPro ? `
4103
- export const DensityContext = React.createContext<DensityContextValue>({
4104
- density: "${defaultDensity}",
4105
- setDensity: () => undefined,
4106
- });
4107
-
4108
- /**
4109
- * Hook to read and change the current density.
4110
- * Must be used inside a <ThemeProvider>.
4111
- */
4112
- export function useDensity(): DensityContextValue {
4113
- return React.useContext(DensityContext);
4114
- }
4115
- ` : "";
4116
- const densityProp = isPro ? `
4117
- /** Component density. Requires density.css to be imported. Defaults to "${defaultDensity}". */
4118
- density?: DensityName;` : "";
4119
- const densityOnChangeProp = isPro ? `
4120
- /** Called when setDensity is invoked. */
4121
- onDensityChange?: (density: DensityName) => void;` : "";
4122
- const densityState = isPro ? `
4123
- const [density, setDensityState] = React.useState<DensityName>(initialDensity);
4124
-
4125
- React.useEffect(() => {
4126
- setDensityState(initialDensity);
4127
- }, [initialDensity]);
4128
-
4129
- const setDensity = React.useCallback(
4130
- (next: DensityName) => {
4131
- setDensityState(next);
4132
- onDensityChange?.(next);
4133
- },
4134
- [onDensityChange],
4135
- );
4136
- ` : "";
4137
- const densityDestructure = isPro ? `,
4138
- density: initialDensity = "${defaultDensity}",
4139
- onDensityChange,` : "";
4140
- const densityProviderOpen = isPro ? `
4141
- <DensityContext.Provider value={{ density, setDensity }}>` : "";
4142
- const densityDataAttr = isPro ? ` data-density={density}` : "";
4143
- const densityProviderClose = isPro ? `
4144
- </DensityContext.Provider>` : "";
4145
3920
  return `/**
4146
3921
  * ThemeProvider \u2014 ${config.meta.name}
4147
3922
  *
@@ -4153,27 +3928,39 @@ export function useDensity(): DensityContextValue {
4153
3928
  * import "@${config.meta.name}/tokens/light.css"; // or dark.css
4154
3929
  * import { ThemeProvider } from "@${config.meta.name}";
4155
3930
  *
4156
- * <ThemeProvider theme="light"${isPro ? ` density="${defaultDensity}"` : ""}>
3931
+ * <ThemeProvider theme="light" density="${defaultDensity}">
4157
3932
  * <App />
4158
3933
  * </ThemeProvider>
4159
3934
  */
4160
3935
 
4161
- import React from "react";${densityImport}
3936
+ import React from "react";
3937
+ import "../tokens/density.css";
4162
3938
 
4163
3939
  // \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
4164
3940
 
4165
3941
  export type ThemeName = ${themeType};
4166
- ${densityTypes}
3942
+
3943
+ export type DensityName = "compact" | "comfortable" | "spacious";
3944
+
4167
3945
  export interface ThemeContextValue {
4168
3946
  theme: ThemeName;
4169
3947
  setTheme: (theme: ThemeName) => void;
4170
3948
  }
4171
- ${densityContextTypes}
3949
+
3950
+ export interface DensityContextValue {
3951
+ density: DensityName;
3952
+ setDensity: (density: DensityName) => void;
3953
+ }
3954
+
4172
3955
  export interface ThemeProviderProps {
4173
3956
  /** Initial theme. Defaults to "${defaultTheme}". */
4174
3957
  theme?: ThemeName;
4175
3958
  /** Called when setTheme is invoked \u2014 use to persist theme preference. */
4176
- onThemeChange?: (theme: ThemeName) => void;${densityProp}${densityOnChangeProp}
3959
+ onThemeChange?: (theme: ThemeName) => void;
3960
+ /** Component density. Requires density.css to be imported. Defaults to "${defaultDensity}". */
3961
+ density?: DensityName;
3962
+ /** Called when setDensity is invoked. */
3963
+ onDensityChange?: (density: DensityName) => void;
4177
3964
  children: React.ReactNode;
4178
3965
  }
4179
3966
 
@@ -4191,12 +3978,27 @@ export const ThemeContext = React.createContext<ThemeContextValue>({
4191
3978
  export function useTheme(): ThemeContextValue {
4192
3979
  return React.useContext(ThemeContext);
4193
3980
  }
4194
- ${densityContext}
3981
+
3982
+ export const DensityContext = React.createContext<DensityContextValue>({
3983
+ density: "${defaultDensity}",
3984
+ setDensity: () => undefined,
3985
+ });
3986
+
3987
+ /**
3988
+ * Hook to read and change the current density.
3989
+ * Must be used inside a <ThemeProvider>.
3990
+ */
3991
+ export function useDensity(): DensityContextValue {
3992
+ return React.useContext(DensityContext);
3993
+ }
3994
+
4195
3995
  // \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
4196
3996
 
4197
3997
  export function ThemeProvider({
4198
3998
  theme: initialTheme = "${defaultTheme}",
4199
- onThemeChange,${densityDestructure}
3999
+ onThemeChange,
4000
+ density: initialDensity = "${defaultDensity}",
4001
+ onDensityChange,
4200
4002
  children,
4201
4003
  }: ThemeProviderProps) {
4202
4004
  const [theme, setThemeState] = React.useState<ThemeName>(initialTheme);
@@ -4212,13 +4014,29 @@ export function ThemeProvider({
4212
4014
  },
4213
4015
  [onThemeChange],
4214
4016
  );
4215
- ${densityState}
4216
- return (${densityProviderOpen}
4017
+
4018
+ const [density, setDensityState] = React.useState<DensityName>(initialDensity);
4019
+
4020
+ React.useEffect(() => {
4021
+ setDensityState(initialDensity);
4022
+ }, [initialDensity]);
4023
+
4024
+ const setDensity = React.useCallback(
4025
+ (next: DensityName) => {
4026
+ setDensityState(next);
4027
+ onDensityChange?.(next);
4028
+ },
4029
+ [onDensityChange],
4030
+ );
4031
+
4032
+ return (
4033
+ <DensityContext.Provider value={{ density, setDensity }}>
4217
4034
  <ThemeContext.Provider value={{ theme, setTheme }}>
4218
- <div data-theme={theme}${densityDataAttr} style={{ display: "contents" }}>
4035
+ <div data-theme={theme} data-density={density} style={{ display: "contents" }}>
4219
4036
  {children}
4220
4037
  </div>
4221
- </ThemeContext.Provider>${densityProviderClose}
4038
+ </ThemeContext.Provider>
4039
+ </DensityContext.Provider>
4222
4040
  );
4223
4041
  }
4224
4042
  `;
@@ -4235,9 +4053,9 @@ function generateComponentIndex(config, componentNames) {
4235
4053
  ""
4236
4054
  ];
4237
4055
  for (const name of componentNames) {
4238
- lines.push(`export * from "./${name}";`);
4056
+ lines.push(`export * from "./components/${name}/${name}";`);
4239
4057
  }
4240
- lines.push(`export * from "./ThemeProvider";`);
4058
+ lines.push(`export * from "./components/ThemeProvider/ThemeProvider";`);
4241
4059
  lines.push("");
4242
4060
  return lines.join("\n");
4243
4061
  }
@@ -4875,13 +4693,11 @@ async function runGenerate(cwd, options) {
4875
4693
  await writeFile(path3.join(tokensDir, filename), content);
4876
4694
  logger.dim(` \u2192 tokens/${filename}`);
4877
4695
  }
4878
- if (isProUnlocked()) {
4879
- await writeFile(
4880
- path3.join(tokensDir, "density.css"),
4881
- emitDensityCss(config)
4882
- );
4883
- logger.dim(` \u2192 tokens/density.css`);
4884
- }
4696
+ await writeFile(
4697
+ path3.join(tokensDir, "density.css"),
4698
+ emitDensityCss(config)
4699
+ );
4700
+ logger.dim(` \u2192 tokens/density.css`);
4885
4701
  const tokenFiles = reactAdapter.generateTokenFiles(config, resolution);
4886
4702
  for (const { filename, content } of tokenFiles) {
4887
4703
  await writeFile(path3.join(tokensDir, filename), content);
@@ -4891,21 +4707,53 @@ async function runGenerate(cwd, options) {
4891
4707
  }
4892
4708
  if (!only || only === "components") {
4893
4709
  logger.step("Generating React components...");
4894
- const srcDir = path3.join(outRoot, "src");
4895
- await ensureDir(srcDir);
4710
+ const componentsDir = path3.join(outRoot, "components");
4711
+ await ensureDir(componentsDir);
4896
4712
  const generatedNames = [];
4713
+ const generatedComponentJsons = [];
4714
+ const flatTokens = {};
4715
+ for (const [k, v] of Object.entries(resolution.tokens)) {
4716
+ flatTokens[`--${k.replace(/^(global|semantic|component)\./, "")}`] = v;
4717
+ }
4718
+ const lightOverrides = config.themes?.["light"] ?? {};
4719
+ const darkOverrides = config.themes?.["dark"] ?? {};
4720
+ const lightCssVars = {
4721
+ ...flatTokens,
4722
+ ...Object.fromEntries(Object.entries(lightOverrides).map(([k, v]) => [`--${k}`, String(v)]))
4723
+ };
4724
+ const darkCssVars = {
4725
+ ...flatTokens,
4726
+ ...Object.fromEntries(Object.entries(darkOverrides).map(([k, v]) => [`--${k}`, String(v)]))
4727
+ };
4728
+ const resolvedCssVars = { light: lightCssVars, dark: darkCssVars };
4729
+ const { generateComponentJson } = await import("../generateComponentJson-XBEUWCW6.js");
4730
+ const { generateComponentMetadata } = await import("../generateComponentMetadata-2L5VNERD.js");
4897
4731
  for (const componentName of REACT_COMPONENTS) {
4732
+ const pascalName = componentName.charAt(0).toUpperCase() + componentName.slice(1);
4898
4733
  try {
4899
4734
  const { filename, content } = reactAdapter.generateComponent(
4900
4735
  componentName,
4901
4736
  config,
4902
4737
  rules[componentName]
4903
4738
  );
4904
- await writeFile(path3.join(srcDir, filename), content);
4905
- logger.dim(` \u2192 src/${filename}`);
4906
- generatedNames.push(
4907
- componentName.charAt(0).toUpperCase() + componentName.slice(1)
4739
+ const componentSubDir = path3.join(componentsDir, pascalName);
4740
+ await ensureDir(componentSubDir);
4741
+ await writeFile(path3.join(componentSubDir, filename), content);
4742
+ logger.dim(` \u2192 components/${pascalName}/${filename}`);
4743
+ generatedNames.push(pascalName);
4744
+ const componentJson = generateComponentJson(pascalName, resolvedCssVars);
4745
+ generatedComponentJsons.push(componentJson);
4746
+ await writeFile(
4747
+ path3.join(componentSubDir, `${pascalName}.json`),
4748
+ JSON.stringify(componentJson, null, 2)
4749
+ );
4750
+ logger.dim(` \u2192 components/${pascalName}/${pascalName}.json`);
4751
+ const metadata = generateComponentMetadata(pascalName);
4752
+ await writeFile(
4753
+ path3.join(componentSubDir, `${pascalName}.metadata.json`),
4754
+ JSON.stringify(metadata, null, 2)
4908
4755
  );
4756
+ logger.dim(` \u2192 components/${pascalName}/${pascalName}.metadata.json`);
4909
4757
  } catch (err) {
4910
4758
  logger.warn(
4911
4759
  `[dsforge] Could not generate ${componentName} \u2014 ${err.message}`
@@ -4913,23 +4761,31 @@ async function runGenerate(cwd, options) {
4913
4761
  }
4914
4762
  }
4915
4763
  const { filename: tpFile, content: tpContent } = reactAdapter.generateThemeProvider(config);
4916
- await writeFile(path3.join(srcDir, tpFile), tpContent);
4917
- logger.dim(` \u2192 src/${tpFile}`);
4764
+ const tpDir = path3.join(componentsDir, "ThemeProvider");
4765
+ await ensureDir(tpDir);
4766
+ await writeFile(path3.join(tpDir, tpFile), tpContent);
4767
+ logger.dim(` \u2192 components/ThemeProvider/${tpFile}`);
4768
+ try {
4769
+ const tpJson = generateComponentJson("ThemeProvider", resolvedCssVars);
4770
+ generatedComponentJsons.push(tpJson);
4771
+ await writeFile(
4772
+ path3.join(tpDir, "ThemeProvider.json"),
4773
+ JSON.stringify(tpJson, null, 2)
4774
+ );
4775
+ logger.dim(` \u2192 components/ThemeProvider/ThemeProvider.json`);
4776
+ const tpMeta = generateComponentMetadata("ThemeProvider");
4777
+ await writeFile(
4778
+ path3.join(tpDir, "ThemeProvider.metadata.json"),
4779
+ JSON.stringify(tpMeta, null, 2)
4780
+ );
4781
+ logger.dim(` \u2192 components/ThemeProvider/ThemeProvider.metadata.json`);
4782
+ } catch {
4783
+ }
4918
4784
  const { filename: idxFile, content: idxContent } = reactAdapter.generateComponentIndex(config, generatedNames);
4919
- await writeFile(path3.join(srcDir, idxFile), idxContent);
4920
- logger.dim(` \u2192 src/${idxFile}`);
4785
+ await writeFile(path3.join(outRoot, idxFile), idxContent);
4786
+ logger.dim(` \u2192 ${idxFile}`);
4921
4787
  logger.success(`${generatedNames.length} components generated`);
4922
- }
4923
- if (isProUnlocked() && (!only || only === "metadata")) {
4924
- logger.step("Writing AI metadata...");
4925
- const metaDir = path3.join(outRoot, "metadata");
4926
- await ensureDir(metaDir);
4927
- const metaFiles = generateMetadata(config, fullRules, tokenCount);
4928
- for (const { filename, content } of metaFiles) {
4929
- await writeFile(path3.join(metaDir, filename), content);
4930
- logger.dim(` \u2192 metadata/${filename}`);
4931
- }
4932
- logger.success(`Metadata written (${metaFiles.length} files)`);
4788
+ globalThis["__dsforgGeneratedJsons"] = generatedComponentJsons;
4933
4789
  }
4934
4790
  if (!only) {
4935
4791
  logger.step("Writing package files...");
@@ -4940,28 +4796,80 @@ async function runGenerate(cwd, options) {
4940
4796
  const tsConfig = generateTsConfig();
4941
4797
  await writeFile(path3.join(outRoot, "tsconfig.json"), tsConfig);
4942
4798
  logger.dim(` \u2192 tsconfig.json`);
4943
- const { generateReadme } = await import("../emitter-KNYIQTS5.js");
4799
+ const { generateReadme } = await import("../emitter-IC77G4QF.js");
4944
4800
  await writeFile(
4945
4801
  path3.join(outRoot, "README.md"),
4946
4802
  generateReadme(config, componentNames)
4947
4803
  );
4948
4804
  logger.dim(` \u2192 README.md`);
4949
4805
  const changelogPath = path3.join(outRoot, "CHANGELOG.md");
4950
- const fsExtra2 = await import("fs-extra");
4951
- const fsE2 = fsExtra2.default ?? fsExtra2;
4806
+ const fsExtra = await import("fs-extra");
4807
+ const fsE2 = fsExtra.default ?? fsExtra;
4952
4808
  if (!await fsE2.pathExists(changelogPath)) {
4953
4809
  await writeFile(changelogPath, generateChangelog(config));
4954
4810
  logger.dim(` \u2192 CHANGELOG.md (seeded)`);
4955
4811
  }
4956
4812
  logger.success(`Package files written`);
4957
4813
  }
4814
+ if (!only || only === "components") {
4815
+ logger.step("Writing registry + AI outputs...");
4816
+ const generatedJsons = globalThis["__dsforgGeneratedJsons"] ?? [];
4817
+ const { generateRegistry } = await import("../generateRegistry-3MEZDJAJ.js");
4818
+ const {
4819
+ generateSystemPrompt,
4820
+ generateComponentsJson,
4821
+ generateCursorContext,
4822
+ generateCopilotInstructions
4823
+ } = await import("../generateAiFolder-3OOFWBH7.js");
4824
+ const systemName = config.meta.name;
4825
+ const version = config.meta.version;
4826
+ const registry = generateRegistry(systemName, version, generatedJsons);
4827
+ await writeFile(path3.join(outRoot, "registry.json"), JSON.stringify(registry, null, 2));
4828
+ logger.dim(` \u2192 registry.json`);
4829
+ const { COMPONENT_METADATA_DEFINITIONS } = await import("../componentDefinitions-5LFCNFQY.js");
4830
+ const metadataList = generatedJsons.map((c) => COMPONENT_METADATA_DEFINITIONS[c.name]).filter((m) => Boolean(m));
4831
+ const flatTokensForAi = {};
4832
+ for (const [k, v] of Object.entries(resolution.tokens)) {
4833
+ const cssVar2 = `--${k.replace(/^(global|semantic|component)\./, "")}`;
4834
+ flatTokensForAi[cssVar2] = {
4835
+ light: config.themes?.["light"]?.[cssVar2.slice(2)] ?? v,
4836
+ dark: config.themes?.["dark"]?.[cssVar2.slice(2)] ?? v
4837
+ };
4838
+ }
4839
+ const aiDir = path3.join(outRoot, "ai");
4840
+ await ensureDir(aiDir);
4841
+ const cursorDir = path3.join(aiDir, ".cursor");
4842
+ await ensureDir(cursorDir);
4843
+ const componentNames = generatedJsons.map((c) => c.name);
4844
+ await writeFile(
4845
+ path3.join(aiDir, "system-prompt.md"),
4846
+ generateSystemPrompt(systemName, flatTokensForAi, componentNames)
4847
+ );
4848
+ logger.dim(` \u2192 ai/system-prompt.md`);
4849
+ await writeFile(
4850
+ path3.join(aiDir, "components.json"),
4851
+ generateComponentsJson(systemName, generatedJsons, metadataList)
4852
+ );
4853
+ logger.dim(` \u2192 ai/components.json`);
4854
+ await writeFile(
4855
+ path3.join(cursorDir, "context.md"),
4856
+ generateCursorContext(systemName)
4857
+ );
4858
+ logger.dim(` \u2192 ai/.cursor/context.md`);
4859
+ await writeFile(
4860
+ path3.join(outRoot, "copilot-instructions.md"),
4861
+ generateCopilotInstructions(systemName)
4862
+ );
4863
+ logger.dim(` \u2192 copilot-instructions.md`);
4864
+ logger.success(`Registry + AI outputs written`);
4865
+ }
4958
4866
  logger.step("Generating showcase...");
4959
- const { generateShowcase } = await import("../html-LQHDCSG4.js");
4867
+ const { generateShowcase } = await import("../html-4DD6GOHE.js");
4960
4868
  const showcaseHtml = generateShowcase(config, resolution);
4961
4869
  await writeFile(path3.join(outRoot, "showcase.html"), showcaseHtml);
4962
4870
  logger.dim(` \u2192 showcase.html`);
4963
- const fsExtra = await import("fs-extra");
4964
- const fsE = fsExtra.default ?? fsExtra;
4871
+ const fsExtraShowcase = await import("fs-extra");
4872
+ const fsE = fsExtraShowcase.default ?? fsExtraShowcase;
4965
4873
  const faviconSrc = path3.join(cwd, "assets", "favicon.svg");
4966
4874
  const faviconDest = path3.join(outRoot, "assets", "favicon.svg");
4967
4875
  if (await fsE.pathExists(faviconSrc)) {
@@ -5343,7 +5251,7 @@ async function runMenu() {
5343
5251
  // package.json
5344
5252
  var package_default = {
5345
5253
  name: "@nghitrum/dsforge",
5346
- version: "0.1.5-alpha.8",
5254
+ version: "0.2.0",
5347
5255
  description: "AI-native design system generator \u2014 tokens \u2192 components \u2192 docs \u2192 npm",
5348
5256
  keywords: [
5349
5257
  "design-system",