@nghitrum/dsforge 0.1.5-alpha.12 → 0.1.5-alpha.13

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 CHANGED
@@ -194,7 +194,6 @@ Run `dsforge` with no arguments for an interactive menu.
194
194
  ## Coming next
195
195
 
196
196
  - Additional components: Modal, Table, Tooltip, DatePicker
197
- - **Pro**: AI-assisted token generation from brand guidelines or a Figma file
198
197
  - Figma Variables API sync
199
198
  - CI integration — fail builds on governance violations
200
199
 
@@ -1,28 +1,3 @@
1
- // src/lib/license.ts
2
- import { readFileSync } from "fs";
3
- import { join } from "path";
4
- function readKeyFromDotEnv() {
5
- try {
6
- const content = readFileSync(join(process.cwd(), ".env"), "utf8");
7
- for (const raw of content.split("\n")) {
8
- const line = raw.trim();
9
- if (!line || line.startsWith("#")) continue;
10
- const eq = line.indexOf("=");
11
- if (eq === -1) continue;
12
- const key = line.slice(0, eq).trim();
13
- if (key !== "DSFORGE_KEY") continue;
14
- const val = line.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
15
- return val || void 0;
16
- }
17
- } catch {
18
- }
19
- return void 0;
20
- }
21
- function isProUnlocked() {
22
- const key = process.env["DSFORGE_KEY"] ?? readKeyFromDotEnv();
23
- return typeof key === "string" && key.length > 0;
24
- }
25
-
26
1
  // src/presets/index.ts
27
2
  var PRESETS = [
28
3
  "compact",
@@ -107,7 +82,6 @@ function applyPreset(config, preset) {
107
82
  }
108
83
 
109
84
  export {
110
- isProUnlocked,
111
85
  PRESETS,
112
86
  SPACING_PRESETS,
113
87
  RADIUS_PRESETS,
package/dist/cli/index.js CHANGED
@@ -11,9 +11,8 @@ import {
11
11
  RADIUS_PRESETS,
12
12
  SPACING_PRESETS,
13
13
  applyPreset,
14
- buildSemanticSpacing,
15
- isProUnlocked
16
- } from "../chunk-YUPXTQZ5.js";
14
+ buildSemanticSpacing
15
+ } from "../chunk-5YT3VNE6.js";
17
16
 
18
17
  // src/cli/index.ts
19
18
  import { program } from "commander";
@@ -738,15 +737,7 @@ async function runInit(cwd, options) {
738
737
  }
739
738
  const name = rawName.replace(/\s+/g, "-").toLowerCase();
740
739
  let preset;
741
- if (!isProUnlocked()) {
742
- if (options.preset && options.preset !== "comfortable") {
743
- logger.hint(
744
- `Preset "${options.preset}" requires dsforge Pro`,
745
- `Set DSFORGE_KEY to unlock compact and spacious. Using comfortable.`
746
- );
747
- }
748
- preset = "comfortable";
749
- } else if (options.preset && VALID_PRESETS.includes(options.preset)) {
740
+ if (options.preset && VALID_PRESETS.includes(options.preset)) {
750
741
  preset = options.preset;
751
742
  } else {
752
743
  const answer = await ask(
@@ -3925,62 +3916,7 @@ function generateThemeProvider(config) {
3925
3916
  const themeNames = Object.keys(config.themes ?? { light: {}, dark: {} });
3926
3917
  const defaultTheme = themeNames.includes("light") ? "light" : themeNames[0] ?? "light";
3927
3918
  const themeType = themeNames.map((t) => `"${t}"`).join(" | ");
3928
- const isPro = isProUnlocked();
3929
3919
  const defaultDensity = config.meta.preset ?? "comfortable";
3930
- const densityImport = isPro ? `
3931
- import "../tokens/density.css";` : "";
3932
- const densityTypes = isPro ? `
3933
- export type DensityName = "compact" | "comfortable" | "spacious";
3934
- ` : "";
3935
- const densityContextTypes = isPro ? `
3936
- export interface DensityContextValue {
3937
- density: DensityName;
3938
- setDensity: (density: DensityName) => void;
3939
- }
3940
- ` : "";
3941
- const densityContext = isPro ? `
3942
- export const DensityContext = React.createContext<DensityContextValue>({
3943
- density: "${defaultDensity}",
3944
- setDensity: () => undefined,
3945
- });
3946
-
3947
- /**
3948
- * Hook to read and change the current density.
3949
- * Must be used inside a <ThemeProvider>.
3950
- */
3951
- export function useDensity(): DensityContextValue {
3952
- return React.useContext(DensityContext);
3953
- }
3954
- ` : "";
3955
- const densityProp = isPro ? `
3956
- /** Component density. Requires density.css to be imported. Defaults to "${defaultDensity}". */
3957
- density?: DensityName;` : "";
3958
- const densityOnChangeProp = isPro ? `
3959
- /** Called when setDensity is invoked. */
3960
- onDensityChange?: (density: DensityName) => void;` : "";
3961
- const densityState = isPro ? `
3962
- const [density, setDensityState] = React.useState<DensityName>(initialDensity);
3963
-
3964
- React.useEffect(() => {
3965
- setDensityState(initialDensity);
3966
- }, [initialDensity]);
3967
-
3968
- const setDensity = React.useCallback(
3969
- (next: DensityName) => {
3970
- setDensityState(next);
3971
- onDensityChange?.(next);
3972
- },
3973
- [onDensityChange],
3974
- );
3975
- ` : "";
3976
- const densityDestructure = isPro ? `,
3977
- density: initialDensity = "${defaultDensity}",
3978
- onDensityChange,` : "";
3979
- const densityProviderOpen = isPro ? `
3980
- <DensityContext.Provider value={{ density, setDensity }}>` : "";
3981
- const densityDataAttr = isPro ? ` data-density={density}` : "";
3982
- const densityProviderClose = isPro ? `
3983
- </DensityContext.Provider>` : "";
3984
3920
  return `/**
3985
3921
  * ThemeProvider \u2014 ${config.meta.name}
3986
3922
  *
@@ -3992,27 +3928,39 @@ export function useDensity(): DensityContextValue {
3992
3928
  * import "@${config.meta.name}/tokens/light.css"; // or dark.css
3993
3929
  * import { ThemeProvider } from "@${config.meta.name}";
3994
3930
  *
3995
- * <ThemeProvider theme="light"${isPro ? ` density="${defaultDensity}"` : ""}>
3931
+ * <ThemeProvider theme="light" density="${defaultDensity}">
3996
3932
  * <App />
3997
3933
  * </ThemeProvider>
3998
3934
  */
3999
3935
 
4000
- import React from "react";${densityImport}
3936
+ import React from "react";
3937
+ import "../tokens/density.css";
4001
3938
 
4002
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
4003
3940
 
4004
3941
  export type ThemeName = ${themeType};
4005
- ${densityTypes}
3942
+
3943
+ export type DensityName = "compact" | "comfortable" | "spacious";
3944
+
4006
3945
  export interface ThemeContextValue {
4007
3946
  theme: ThemeName;
4008
3947
  setTheme: (theme: ThemeName) => void;
4009
3948
  }
4010
- ${densityContextTypes}
3949
+
3950
+ export interface DensityContextValue {
3951
+ density: DensityName;
3952
+ setDensity: (density: DensityName) => void;
3953
+ }
3954
+
4011
3955
  export interface ThemeProviderProps {
4012
3956
  /** Initial theme. Defaults to "${defaultTheme}". */
4013
3957
  theme?: ThemeName;
4014
3958
  /** Called when setTheme is invoked \u2014 use to persist theme preference. */
4015
- 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;
4016
3964
  children: React.ReactNode;
4017
3965
  }
4018
3966
 
@@ -4030,12 +3978,27 @@ export const ThemeContext = React.createContext<ThemeContextValue>({
4030
3978
  export function useTheme(): ThemeContextValue {
4031
3979
  return React.useContext(ThemeContext);
4032
3980
  }
4033
- ${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
+
4034
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
4035
3996
 
4036
3997
  export function ThemeProvider({
4037
3998
  theme: initialTheme = "${defaultTheme}",
4038
- onThemeChange,${densityDestructure}
3999
+ onThemeChange,
4000
+ density: initialDensity = "${defaultDensity}",
4001
+ onDensityChange,
4039
4002
  children,
4040
4003
  }: ThemeProviderProps) {
4041
4004
  const [theme, setThemeState] = React.useState<ThemeName>(initialTheme);
@@ -4051,13 +4014,29 @@ export function ThemeProvider({
4051
4014
  },
4052
4015
  [onThemeChange],
4053
4016
  );
4054
- ${densityState}
4055
- 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 }}>
4056
4034
  <ThemeContext.Provider value={{ theme, setTheme }}>
4057
- <div data-theme={theme}${densityDataAttr} style={{ display: "contents" }}>
4035
+ <div data-theme={theme} data-density={density} style={{ display: "contents" }}>
4058
4036
  {children}
4059
4037
  </div>
4060
- </ThemeContext.Provider>${densityProviderClose}
4038
+ </ThemeContext.Provider>
4039
+ </DensityContext.Provider>
4061
4040
  );
4062
4041
  }
4063
4042
  `;
@@ -4714,13 +4693,11 @@ async function runGenerate(cwd, options) {
4714
4693
  await writeFile(path3.join(tokensDir, filename), content);
4715
4694
  logger.dim(` \u2192 tokens/${filename}`);
4716
4695
  }
4717
- if (isProUnlocked()) {
4718
- await writeFile(
4719
- path3.join(tokensDir, "density.css"),
4720
- emitDensityCss(config)
4721
- );
4722
- logger.dim(` \u2192 tokens/density.css`);
4723
- }
4696
+ await writeFile(
4697
+ path3.join(tokensDir, "density.css"),
4698
+ emitDensityCss(config)
4699
+ );
4700
+ logger.dim(` \u2192 tokens/density.css`);
4724
4701
  const tokenFiles = reactAdapter.generateTokenFiles(config, resolution);
4725
4702
  for (const { filename, content } of tokenFiles) {
4726
4703
  await writeFile(path3.join(tokensDir, filename), content);
@@ -4771,14 +4748,12 @@ async function runGenerate(cwd, options) {
4771
4748
  JSON.stringify(componentJson, null, 2)
4772
4749
  );
4773
4750
  logger.dim(` \u2192 components/${pascalName}/${pascalName}.json`);
4774
- if (isProUnlocked()) {
4775
- const metadata = generateComponentMetadata(pascalName);
4776
- await writeFile(
4777
- path3.join(componentSubDir, `${pascalName}.metadata.json`),
4778
- JSON.stringify(metadata, null, 2)
4779
- );
4780
- logger.dim(` \u2192 components/${pascalName}/${pascalName}.metadata.json`);
4781
- }
4751
+ const metadata = generateComponentMetadata(pascalName);
4752
+ await writeFile(
4753
+ path3.join(componentSubDir, `${pascalName}.metadata.json`),
4754
+ JSON.stringify(metadata, null, 2)
4755
+ );
4756
+ logger.dim(` \u2192 components/${pascalName}/${pascalName}.metadata.json`);
4782
4757
  } catch (err) {
4783
4758
  logger.warn(
4784
4759
  `[dsforge] Could not generate ${componentName} \u2014 ${err.message}`
@@ -4798,14 +4773,12 @@ async function runGenerate(cwd, options) {
4798
4773
  JSON.stringify(tpJson, null, 2)
4799
4774
  );
4800
4775
  logger.dim(` \u2192 components/ThemeProvider/ThemeProvider.json`);
4801
- if (isProUnlocked()) {
4802
- const tpMeta = generateComponentMetadata("ThemeProvider");
4803
- await writeFile(
4804
- path3.join(tpDir, "ThemeProvider.metadata.json"),
4805
- JSON.stringify(tpMeta, null, 2)
4806
- );
4807
- logger.dim(` \u2192 components/ThemeProvider/ThemeProvider.metadata.json`);
4808
- }
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`);
4809
4782
  } catch {
4810
4783
  }
4811
4784
  const { filename: idxFile, content: idxContent } = reactAdapter.generateComponentIndex(config, generatedNames);
@@ -4838,8 +4811,8 @@ async function runGenerate(cwd, options) {
4838
4811
  }
4839
4812
  logger.success(`Package files written`);
4840
4813
  }
4841
- if (isProUnlocked() && (!only || only === "components")) {
4842
- logger.step("Writing Pro outputs...");
4814
+ if (!only || only === "components") {
4815
+ logger.step("Writing registry + AI outputs...");
4843
4816
  const generatedJsons = globalThis["__dsforgGeneratedJsons"] ?? [];
4844
4817
  const { generateRegistry } = await import("../generateRegistry-3MEZDJAJ.js");
4845
4818
  const {
@@ -4888,10 +4861,10 @@ async function runGenerate(cwd, options) {
4888
4861
  generateCopilotInstructions(systemName)
4889
4862
  );
4890
4863
  logger.dim(` \u2192 copilot-instructions.md`);
4891
- logger.success(`Pro outputs written`);
4864
+ logger.success(`Registry + AI outputs written`);
4892
4865
  }
4893
4866
  logger.step("Generating showcase...");
4894
- const { generateShowcase } = await import("../html-XGJ22SXB.js");
4867
+ const { generateShowcase } = await import("../html-4DD6GOHE.js");
4895
4868
  const showcaseHtml = generateShowcase(config, resolution);
4896
4869
  await writeFile(path3.join(outRoot, "showcase.html"), showcaseHtml);
4897
4870
  logger.dim(` \u2192 showcase.html`);
@@ -5278,7 +5251,7 @@ async function runMenu() {
5278
5251
  // package.json
5279
5252
  var package_default = {
5280
5253
  name: "@nghitrum/dsforge",
5281
- version: "0.1.5-alpha.12",
5254
+ version: "0.1.5-alpha.13",
5282
5255
  description: "AI-native design system generator \u2014 tokens \u2192 components \u2192 docs \u2192 npm",
5283
5256
  keywords: [
5284
5257
  "design-system",
@@ -2,9 +2,8 @@ import {
2
2
  PRESETS,
3
3
  RADIUS_PRESETS,
4
4
  SPACING_PRESETS,
5
- buildSemanticSpacing,
6
- isProUnlocked
7
- } from "./chunk-YUPXTQZ5.js";
5
+ buildSemanticSpacing
6
+ } from "./chunk-5YT3VNE6.js";
8
7
  import {
9
8
  COMPONENT_JSON_DEFINITIONS,
10
9
  COMPONENT_METADATA_DEFINITIONS
@@ -215,13 +214,6 @@ function buildMotionSection(config) {
215
214
  }
216
215
 
217
216
  // src/generators/showcase/page.ts
218
- var lockedPanel = (label) => `
219
- <div class="locked-panel">
220
- <div class="locked-icon">\u2298</div>
221
- <div class="locked-title">${label} \u2014 dsforge Pro</div>
222
- <p class="locked-desc">This tab is available with a dsforge Pro license.</p>
223
- <p class="locked-hint">Set the <code>DSFORGE_KEY</code> environment variable to unlock.</p>
224
- </div>`;
225
217
  function buildPropsTable(props) {
226
218
  const sorted = [...props].sort((a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0));
227
219
  return `
@@ -364,7 +356,7 @@ function buildAiMetaHtml(id, meta) {
364
356
  </div>`;
365
357
  }
366
358
  function buildComponentPage(input) {
367
- const { id, description, overviewHtml, json, showcaseExamples, a11yContract, a11yItems, metadata, isPro } = input;
359
+ const { id, description, overviewHtml, json, showcaseExamples, a11yContract, a11yItems, metadata } = input;
368
360
  const tabId = (tab) => `${id}-tab-${tab}`;
369
361
  const panelId = (tab) => `${id}-panel-${tab}`;
370
362
  const overviewContent = `
@@ -380,13 +372,13 @@ function buildComponentPage(input) {
380
372
  </div>
381
373
  <p class="a11y-desc">${esc(item.description)}</p>
382
374
  </div>`).join("")}</div>`;
383
- const aiContent = isPro && metadata ? buildAiMetaHtml(id, metadata) : lockedPanel("AI Metadata");
375
+ const aiContent = metadata ? buildAiMetaHtml(id, metadata) : "";
384
376
  const tabs = [
385
- { id: "overview", label: "Overview", content: overviewContent, locked: false },
386
- { id: "props", label: "Props", content: propsContent, locked: false },
387
- { id: "examples", label: "Examples", content: examplesContent, locked: false },
388
- { id: "accessibility", label: "Accessibility", content: a11yContent, locked: false },
389
- { id: "ai-metadata", label: "AI Metadata", content: aiContent, locked: !isPro }
377
+ { id: "overview", label: "Overview", content: overviewContent },
378
+ { id: "props", label: "Props", content: propsContent },
379
+ { id: "examples", label: "Examples", content: examplesContent },
380
+ { id: "accessibility", label: "Accessibility", content: a11yContent },
381
+ { id: "ai-metadata", label: "AI Metadata", content: aiContent }
390
382
  ];
391
383
  return `
392
384
  <div class="comp-tabs" id="${id}-tabs">
@@ -394,14 +386,13 @@ function buildComponentPage(input) {
394
386
  ${tabs.map(
395
387
  (t, i) => `
396
388
  <button
397
- class="comp-tab${i === 0 ? " active" : ""}${t.locked ? " locked" : ""}"
389
+ class="comp-tab${i === 0 ? " active" : ""}"
398
390
  id="${tabId(t.id)}"
399
391
  role="tab"
400
392
  aria-selected="${i === 0}"
401
393
  aria-controls="${panelId(t.id)}"
402
- onclick="${t.locked ? "return false" : `switchTab('${id}', '${t.id}', this)`}"
403
- ${t.locked ? 'title="Unlock with dsforge Pro"' : ""}
404
- >${esc(t.label)}${t.locked ? " &#x1F512;" : ""}</button>`
394
+ onclick="switchTab('${id}', '${t.id}', this)"
395
+ >${esc(t.label)}</button>`
405
396
  ).join("")}
406
397
  </div>
407
398
  ${tabs.map(
@@ -2099,7 +2090,6 @@ function generateShowcase(config, resolution) {
2099
2090
  ];
2100
2091
  const componentItems = SHOWCASE_COMPONENTS.map(({ id, label }) => ({ id, label }));
2101
2092
  const allItems = [...foundationItems, ...componentItems];
2102
- const isPro = isProUnlocked();
2103
2093
  const flatTokens = Object.fromEntries(
2104
2094
  Object.entries(tokens).map(([k, v]) => [
2105
2095
  k.replace(/^(global|semantic|component)\./, ""),
@@ -2147,8 +2137,7 @@ function generateShowcase(config, resolution) {
2147
2137
  showcaseExamples: defData.examples,
2148
2138
  a11yContract: metaDef?.accessibilityContract ?? null,
2149
2139
  a11yItems: defData.a11y,
2150
- metadata: isPro ? metaDef : null,
2151
- isPro
2140
+ metadata: metaDef
2152
2141
  })
2153
2142
  ];
2154
2143
  })
@@ -2160,13 +2149,12 @@ function generateShowcase(config, resolution) {
2160
2149
  const showcaseData = {
2161
2150
  systemName: name,
2162
2151
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2163
- isPro,
2164
2152
  components: SHOWCASE_COMPONENTS.map((entry) => {
2165
2153
  const jsonDef = COMPONENT_JSON_DEFINITIONS[entry.label];
2166
2154
  const metaDef = COMPONENT_METADATA_DEFINITIONS[entry.label] ?? null;
2167
2155
  return {
2168
2156
  json: jsonDef ? { ...jsonDef, cssVars: resolvedCssVars } : null,
2169
- metadata: isPro ? metaDef : null
2157
+ metadata: metaDef
2170
2158
  };
2171
2159
  })
2172
2160
  };
@@ -2263,27 +2251,17 @@ ${densityCss}
2263
2251
  border: 1px solid var(--color-border-default, #e2e8f0);
2264
2252
  border-radius: 7px; padding: 3px;
2265
2253
  }
2266
- .density-toggle.locked { opacity: 0.5; cursor: not-allowed; }
2267
2254
  .density-btn {
2268
2255
  padding: 3px 10px; border-radius: 4px; border: none;
2269
2256
  background: transparent; font-size: 12px; cursor: pointer;
2270
2257
  color: var(--color-text-secondary, #64748b);
2271
2258
  transition: background 120ms, color 120ms;
2272
2259
  }
2273
- .density-btn:disabled { cursor: not-allowed; }
2274
2260
  .density-btn.active {
2275
2261
  background: var(--color-bg-default, #fff);
2276
2262
  color: var(--color-text-primary, #0f172a); font-weight: 500;
2277
2263
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.06);
2278
2264
  }
2279
- .density-lock {
2280
- font-size: 10px; font-weight: 600; letter-spacing: 0.04em;
2281
- color: var(--color-text-secondary, #64748b);
2282
- padding: 2px 6px; border-radius: 4px;
2283
- background: var(--color-bg-overlay, #f1f5f9);
2284
- border: 1px solid var(--color-border-default, #e2e8f0);
2285
- white-space: nowrap;
2286
- }
2287
2265
  .content { padding: 36px 40px 80px; max-width: 860px; }
2288
2266
  .page { display: none; }
2289
2267
  .page.active { display: block; }
@@ -2485,21 +2463,6 @@ ${densityCss}
2485
2463
  .ai-guidance-list { padding-left: 20px; display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
2486
2464
  .ai-guidance-list li { font-size: 13px; color: var(--color-text-secondary, #64748b); line-height: 1.6; }
2487
2465
 
2488
- /* \u2500\u2500 Locked tabs \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 */
2489
- .comp-tab.locked { opacity: 0.45; cursor: default; }
2490
- .comp-tab.locked:hover { color: var(--color-text-secondary, #64748b); }
2491
- .locked-panel {
2492
- display: flex; flex-direction: column; align-items: center; justify-content: center;
2493
- padding: 64px 32px; text-align: center;
2494
- border: 1px dashed var(--color-border-default, #e2e8f0); border-radius: 10px;
2495
- background: var(--color-bg-subtle, #f8fafc);
2496
- }
2497
- .locked-icon { font-size: 28px; color: var(--color-text-secondary, #64748b); margin-bottom: 12px; }
2498
- .locked-title { font-size: 14px; font-weight: 600; color: var(--color-text-primary, #0f172a); margin-bottom: 8px; }
2499
- .locked-desc { font-size: 13px; color: var(--color-text-secondary, #64748b); max-width: 360px; line-height: 1.6; margin-bottom: 6px; }
2500
- .locked-hint { font-size: 12px; color: var(--color-text-secondary, #64748b); font-family: monospace; }
2501
- .locked-hint code { background: var(--color-bg-overlay, #f1f5f9); padding: 1px 6px; border-radius: 4px; border: 1px solid var(--color-border-default, #e2e8f0); }
2502
-
2503
2466
  /* \u2500\u2500 Component primitives \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 */
2504
2467
  .ds-btn { border: none; cursor: pointer; font-size: 14px; font-weight: 500; padding: var(--component-padding-sm, 8px) var(--component-padding-md, 16px); border-radius: var(--radius-md, 4px); transition: filter 120ms; }
2505
2468
  .ds-btn:hover:not(:disabled) { filter: brightness(0.92); }
@@ -2570,16 +2533,11 @@ ${densityCss}
2570
2533
  ${esc(name)} / <span id="topbar-current">Colors</span>
2571
2534
  </div>
2572
2535
  <div class="topbar-actions">
2573
- ${isPro ? `<div class="density-toggle" id="density-toggle">
2574
- ${PRESETS.map((p) => `
2575
- <button class="density-btn${p === defaultDensity ? " active" : ""}" onclick="setDensity('${p}', this)">${p.charAt(0).toUpperCase() + p.slice(1)}</button>
2576
- `).join("")}
2577
- </div>` : `<div class="density-toggle locked" title="Density switching requires dsforge Pro. Set DSFORGE_KEY to unlock.">
2578
- ${PRESETS.map((p) => `
2579
- <button class="density-btn${p === defaultDensity ? " active" : ""}" disabled>${p.charAt(0).toUpperCase() + p.slice(1)}</button>
2580
- `).join("")}
2581
- <span class="density-lock">\u2298 Pro</span>
2582
- </div>`}
2536
+ <div class="density-toggle" id="density-toggle">
2537
+ ${PRESETS.map((p) => `
2538
+ <button class="density-btn${p === defaultDensity ? " active" : ""}" onclick="setDensity('${p}', this)">${p.charAt(0).toUpperCase() + p.slice(1)}</button>
2539
+ `).join("")}
2540
+ </div>
2583
2541
  ${themes.length >= 2 ? `
2584
2542
  <div class="theme-toggle">
2585
2543
  ${themes.map(
package/dist/index.js CHANGED
@@ -1143,31 +1143,6 @@ var rl = readline.createInterface({
1143
1143
  output: process.stdout
1144
1144
  });
1145
1145
 
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
1146
  // src/presets/index.ts
1172
1147
  var SPACING_PRESETS = {
1173
1148
  compact: {
@@ -2612,62 +2587,7 @@ function generateThemeProvider(config) {
2612
2587
  const themeNames = Object.keys(config.themes ?? { light: {}, dark: {} });
2613
2588
  const defaultTheme = themeNames.includes("light") ? "light" : themeNames[0] ?? "light";
2614
2589
  const themeType = themeNames.map((t) => `"${t}"`).join(" | ");
2615
- const isPro = isProUnlocked();
2616
2590
  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>` : "";
2671
2591
  return `/**
2672
2592
  * ThemeProvider \u2014 ${config.meta.name}
2673
2593
  *
@@ -2679,27 +2599,39 @@ export function useDensity(): DensityContextValue {
2679
2599
  * import "@${config.meta.name}/tokens/light.css"; // or dark.css
2680
2600
  * import { ThemeProvider } from "@${config.meta.name}";
2681
2601
  *
2682
- * <ThemeProvider theme="light"${isPro ? ` density="${defaultDensity}"` : ""}>
2602
+ * <ThemeProvider theme="light" density="${defaultDensity}">
2683
2603
  * <App />
2684
2604
  * </ThemeProvider>
2685
2605
  */
2686
2606
 
2687
- import React from "react";${densityImport}
2607
+ import React from "react";
2608
+ import "../tokens/density.css";
2688
2609
 
2689
2610
  // \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
2690
2611
 
2691
2612
  export type ThemeName = ${themeType};
2692
- ${densityTypes}
2613
+
2614
+ export type DensityName = "compact" | "comfortable" | "spacious";
2615
+
2693
2616
  export interface ThemeContextValue {
2694
2617
  theme: ThemeName;
2695
2618
  setTheme: (theme: ThemeName) => void;
2696
2619
  }
2697
- ${densityContextTypes}
2620
+
2621
+ export interface DensityContextValue {
2622
+ density: DensityName;
2623
+ setDensity: (density: DensityName) => void;
2624
+ }
2625
+
2698
2626
  export interface ThemeProviderProps {
2699
2627
  /** Initial theme. Defaults to "${defaultTheme}". */
2700
2628
  theme?: ThemeName;
2701
2629
  /** Called when setTheme is invoked \u2014 use to persist theme preference. */
2702
- onThemeChange?: (theme: ThemeName) => void;${densityProp}${densityOnChangeProp}
2630
+ onThemeChange?: (theme: ThemeName) => void;
2631
+ /** Component density. Requires density.css to be imported. Defaults to "${defaultDensity}". */
2632
+ density?: DensityName;
2633
+ /** Called when setDensity is invoked. */
2634
+ onDensityChange?: (density: DensityName) => void;
2703
2635
  children: React.ReactNode;
2704
2636
  }
2705
2637
 
@@ -2717,12 +2649,27 @@ export const ThemeContext = React.createContext<ThemeContextValue>({
2717
2649
  export function useTheme(): ThemeContextValue {
2718
2650
  return React.useContext(ThemeContext);
2719
2651
  }
2720
- ${densityContext}
2652
+
2653
+ export const DensityContext = React.createContext<DensityContextValue>({
2654
+ density: "${defaultDensity}",
2655
+ setDensity: () => undefined,
2656
+ });
2657
+
2658
+ /**
2659
+ * Hook to read and change the current density.
2660
+ * Must be used inside a <ThemeProvider>.
2661
+ */
2662
+ export function useDensity(): DensityContextValue {
2663
+ return React.useContext(DensityContext);
2664
+ }
2665
+
2721
2666
  // \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
2722
2667
 
2723
2668
  export function ThemeProvider({
2724
2669
  theme: initialTheme = "${defaultTheme}",
2725
- onThemeChange,${densityDestructure}
2670
+ onThemeChange,
2671
+ density: initialDensity = "${defaultDensity}",
2672
+ onDensityChange,
2726
2673
  children,
2727
2674
  }: ThemeProviderProps) {
2728
2675
  const [theme, setThemeState] = React.useState<ThemeName>(initialTheme);
@@ -2738,13 +2685,29 @@ export function ThemeProvider({
2738
2685
  },
2739
2686
  [onThemeChange],
2740
2687
  );
2741
- ${densityState}
2742
- return (${densityProviderOpen}
2688
+
2689
+ const [density, setDensityState] = React.useState<DensityName>(initialDensity);
2690
+
2691
+ React.useEffect(() => {
2692
+ setDensityState(initialDensity);
2693
+ }, [initialDensity]);
2694
+
2695
+ const setDensity = React.useCallback(
2696
+ (next: DensityName) => {
2697
+ setDensityState(next);
2698
+ onDensityChange?.(next);
2699
+ },
2700
+ [onDensityChange],
2701
+ );
2702
+
2703
+ return (
2704
+ <DensityContext.Provider value={{ density, setDensity }}>
2743
2705
  <ThemeContext.Provider value={{ theme, setTheme }}>
2744
- <div data-theme={theme}${densityDataAttr} style={{ display: "contents" }}>
2706
+ <div data-theme={theme} data-density={density} style={{ display: "contents" }}>
2745
2707
  {children}
2746
2708
  </div>
2747
- </ThemeContext.Provider>${densityProviderClose}
2709
+ </ThemeContext.Provider>
2710
+ </DensityContext.Provider>
2748
2711
  );
2749
2712
  }
2750
2713
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nghitrum/dsforge",
3
- "version": "0.1.5-alpha.12",
3
+ "version": "0.1.5-alpha.13",
4
4
  "description": "AI-native design system generator — tokens → components → docs → npm",
5
5
  "keywords": [
6
6
  "design-system",