@getcoherent/core 0.1.0 → 0.2.1

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
@@ -953,9 +953,9 @@ declare const PageAnalysisSchema: z.ZodOptional<z.ZodObject<{
953
953
  }>>;
954
954
  type PageAnalysis = z.infer<typeof PageAnalysisSchema>;
955
955
  declare const PageDefinitionSchema: z.ZodObject<{
956
- id: z.ZodString;
956
+ id: z.ZodPipeline<z.ZodEffects<z.ZodString, string, string>, z.ZodString>;
957
957
  name: z.ZodString;
958
- route: z.ZodString;
958
+ route: z.ZodPipeline<z.ZodEffects<z.ZodString, string, string>, z.ZodString>;
959
959
  layout: z.ZodEnum<["centered", "sidebar-left", "sidebar-right", "full-width", "grid"]>;
960
960
  sections: z.ZodDefault<z.ZodArray<z.ZodObject<{
961
961
  id: z.ZodString;
@@ -1916,9 +1916,9 @@ declare const DesignSystemConfigSchema: z.ZodObject<{
1916
1916
  generatedCode?: string | undefined;
1917
1917
  }>, "many">>>;
1918
1918
  pages: z.ZodArray<z.ZodObject<{
1919
- id: z.ZodString;
1919
+ id: z.ZodPipeline<z.ZodEffects<z.ZodString, string, string>, z.ZodString>;
1920
1920
  name: z.ZodString;
1921
- route: z.ZodString;
1921
+ route: z.ZodPipeline<z.ZodEffects<z.ZodString, string, string>, z.ZodString>;
1922
1922
  layout: z.ZodEnum<["centered", "sidebar-left", "sidebar-right", "full-width", "grid"]>;
1923
1923
  sections: z.ZodDefault<z.ZodArray<z.ZodObject<{
1924
1924
  id: z.ZodString;
@@ -2938,6 +2938,11 @@ declare class DesignSystemManager {
2938
2938
  */
2939
2939
  reload(): Promise<void>;
2940
2940
  }
2941
+ /**
2942
+ * Extract the config object from a TypeScript source file using balanced-brace matching.
2943
+ * More robust than a greedy regex — correctly handles nested objects and trailing content.
2944
+ */
2945
+ declare function extractConfigObject(source: string): string;
2941
2946
 
2942
2947
  /**
2943
2948
  * Component Manager
@@ -4264,6 +4269,12 @@ declare const CLI_VERSION = "0.1.0";
4264
4269
 
4265
4270
  declare function buildCssVariables(config: DesignSystemConfig): string;
4266
4271
 
4272
+ /**
4273
+ * Atomic file write: writes to a temp file first, then renames.
4274
+ * Prevents data corruption if the process crashes mid-write.
4275
+ */
4276
+ declare function atomicWriteFile(filePath: string, content: string): Promise<void>;
4277
+
4267
4278
  /**
4268
4279
  * Story 2.11 Part C: Consistency audit for shared components.
4269
4280
  * - Verify usedIn vs actual imports
@@ -4294,4 +4305,4 @@ interface AuditResult {
4294
4305
  */
4295
4306
  declare function runAudit(projectRoot: string): Promise<AuditResult>;
4296
4307
 
4297
- export { type AuditEntryResult, type AuditResult, type BlogContent, CLI_VERSION, type ChangelogContent, type ColorToken, ColorTokenSchema, type ComponentCriteria, type ComponentDefinition, ComponentDefinitionSchema, type ComponentDependency, ComponentGenerator, ComponentManager, type ComponentSize, ComponentSizeSchema, type ComponentSpec, type ComponentVariant, ComponentVariantSchema, type ContactContent, type CreateSharedComponentInput, type DashboardContent, type DesignSystemConfig, DesignSystemConfigSchema, DesignSystemGenerator, DesignSystemManager, type DesignTokens, DesignTokensSchema, type DiscoveryResult, EXAMPLE_MULTIPAGE_CONFIG, EXAMPLE_SPA_CONFIG, FIGMA_BASE_IDS, FRAMEWORK_VERSIONS, type FaqContent, type Features, FeaturesSchema, type FigmaBaseId, FigmaClient, type FigmaClientOptions, type FigmaColorStyle, type FigmaComponentData, type FigmaComponentMap, type FigmaComponentMeta, type FigmaDocumentNode, type FigmaEffectStyle, type FigmaFileResponse, type FigmaIntermediateData, type FigmaLayout, type FigmaNode, type FigmaNormalizationResult, type FigmaNormalizedEntry, type FigmaPageData, type FigmaProperty, type FigmaRgba, type FigmaStyleMeta, type FigmaTextStyle, type FigmaTokenExtractionResult, type FigmaVariant, type GalleryContent, type GenerateSharedComponentInput, type GenerateSharedComponentResult, type GeneratedPage, type LandingContent, type LayoutBlockDefinition, LayoutBlockDefinitionSchema, type ListingContent, MANIFEST_FILENAME, type ModificationRequest, type ModificationResult, type Navigation, type NavigationItem, NavigationItemSchema, NavigationSchema, type OnboardingContent, type PageAnalysis, PageAnalysisSchema, type PageContent, type PageDefinition, PageDefinitionSchema, PageGenerator, type PageLayout, PageLayoutSchema, PageManager, type PageSection, PageSectionSchema, type PricingContent, type ProfileContent, ProjectScaffolder, type RadiusToken, RadiusTokenSchema, type SettingsContent, type SharedComponentEntry, SharedComponentEntrySchema, type SharedComponentType, SharedComponentTypeSchema, type SharedComponentsManifest, SharedComponentsManifestSchema, type SpacingToken, SpacingTokenSchema, TailwindConfigGenerator, type TemplateOptions, type TypographyToken, TypographyTokenSchema, allocateNextCid, buildCssVariables, componentExists, createEntry, extractTokensFromFigma, figmaComponentNameToBaseId, figmaRgbaToHex, findSharedComponent, findSharedComponentByIdOrName, formatCid, generatePageFromFrame, generatePagesFromFigma, generateSharedComponent, generateSharedComponentTsx, getComponent, getManifestPath, getPage, getPageFilePath, getSupportedPageTypes, getTemplateForPageType, integrateSharedLayoutIntoRootLayout, loadManifest, mergeExtractedColorsWithDefaults, normalizeFigmaComponents, pageRouteExists, parseCid, parseFigmaFileResponse, removeEntry, resolveUniqueName, runAudit, saveManifest, setSharedMapping, toSharedFileName, updateEntry, updateUsedIn, validateConfig, validatePartialConfig };
4308
+ export { type AuditEntryResult, type AuditResult, type BlogContent, CLI_VERSION, type ChangelogContent, type ColorToken, ColorTokenSchema, type ComponentCriteria, type ComponentDefinition, ComponentDefinitionSchema, type ComponentDependency, ComponentGenerator, ComponentManager, type ComponentSize, ComponentSizeSchema, type ComponentSpec, type ComponentVariant, ComponentVariantSchema, type ContactContent, type CreateSharedComponentInput, type DashboardContent, type DesignSystemConfig, DesignSystemConfigSchema, DesignSystemGenerator, DesignSystemManager, type DesignTokens, DesignTokensSchema, type DiscoveryResult, EXAMPLE_MULTIPAGE_CONFIG, EXAMPLE_SPA_CONFIG, FIGMA_BASE_IDS, FRAMEWORK_VERSIONS, type FaqContent, type Features, FeaturesSchema, type FigmaBaseId, FigmaClient, type FigmaClientOptions, type FigmaColorStyle, type FigmaComponentData, type FigmaComponentMap, type FigmaComponentMeta, type FigmaDocumentNode, type FigmaEffectStyle, type FigmaFileResponse, type FigmaIntermediateData, type FigmaLayout, type FigmaNode, type FigmaNormalizationResult, type FigmaNormalizedEntry, type FigmaPageData, type FigmaProperty, type FigmaRgba, type FigmaStyleMeta, type FigmaTextStyle, type FigmaTokenExtractionResult, type FigmaVariant, type GalleryContent, type GenerateSharedComponentInput, type GenerateSharedComponentResult, type GeneratedPage, type LandingContent, type LayoutBlockDefinition, LayoutBlockDefinitionSchema, type ListingContent, MANIFEST_FILENAME, type ModificationRequest, type ModificationResult, type Navigation, type NavigationItem, NavigationItemSchema, NavigationSchema, type OnboardingContent, type PageAnalysis, PageAnalysisSchema, type PageContent, type PageDefinition, PageDefinitionSchema, PageGenerator, type PageLayout, PageLayoutSchema, PageManager, type PageSection, PageSectionSchema, type PricingContent, type ProfileContent, ProjectScaffolder, type RadiusToken, RadiusTokenSchema, type SettingsContent, type SharedComponentEntry, SharedComponentEntrySchema, type SharedComponentType, SharedComponentTypeSchema, type SharedComponentsManifest, SharedComponentsManifestSchema, type SpacingToken, SpacingTokenSchema, TailwindConfigGenerator, type TemplateOptions, type TypographyToken, TypographyTokenSchema, allocateNextCid, atomicWriteFile, buildCssVariables, componentExists, createEntry, extractConfigObject, extractTokensFromFigma, figmaComponentNameToBaseId, figmaRgbaToHex, findSharedComponent, findSharedComponentByIdOrName, formatCid, generatePageFromFrame, generatePagesFromFigma, generateSharedComponent, generateSharedComponentTsx, getComponent, getManifestPath, getPage, getPageFilePath, getSupportedPageTypes, getTemplateForPageType, integrateSharedLayoutIntoRootLayout, loadManifest, mergeExtractedColorsWithDefaults, normalizeFigmaComponents, pageRouteExists, parseCid, parseFigmaFileResponse, removeEntry, resolveUniqueName, runAudit, saveManifest, setSharedMapping, toSharedFileName, updateEntry, updateUsedIn, validateConfig, validatePartialConfig };
package/dist/index.js CHANGED
@@ -97,15 +97,7 @@ var ComponentDefinitionSchema = z.object({
97
97
  id: z.string().regex(/^[a-z][a-z0-9-]*$/, "Must be kebab-case"),
98
98
  name: z.string(),
99
99
  // PascalCase (e.g., "Button", "InputField")
100
- category: z.enum([
101
- "form",
102
- "layout",
103
- "navigation",
104
- "feedback",
105
- "data-display",
106
- "overlay",
107
- "typography"
108
- ]),
100
+ category: z.enum(["form", "layout", "navigation", "feedback", "data-display", "overlay", "typography"]),
109
101
  // Source
110
102
  source: z.enum(["shadcn", "custom"]),
111
103
  shadcnComponent: z.string().optional(),
@@ -167,10 +159,12 @@ var PageLayoutSchema = z.enum([
167
159
  // CSS Grid layout
168
160
  ]);
169
161
  var PageAnalysisSchema = z.object({
170
- sections: z.array(z.object({
171
- name: z.string(),
172
- order: z.number()
173
- })).optional(),
162
+ sections: z.array(
163
+ z.object({
164
+ name: z.string(),
165
+ order: z.number()
166
+ })
167
+ ).optional(),
174
168
  componentUsage: z.record(z.string(), z.number()).optional(),
175
169
  iconCount: z.number().optional(),
176
170
  layoutPattern: z.string().optional(),
@@ -179,9 +173,11 @@ var PageAnalysisSchema = z.object({
179
173
  }).optional();
180
174
  var PageDefinitionSchema = z.object({
181
175
  // Identity
182
- id: z.string().regex(/^[a-z][a-z0-9-]*$/, "Must be kebab-case"),
176
+ id: z.string().transform(
177
+ (s) => s.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "")
178
+ ).pipe(z.string().regex(/^[a-z][a-z0-9-]*$/, "Must be kebab-case")),
183
179
  name: z.string(),
184
- route: z.string().regex(/^\/[a-z0-9-/]*$/, "Must start with /"),
180
+ route: z.string().transform((r) => r.startsWith("/") ? r : `/${r}`).pipe(z.string().regex(/^\/[a-z0-9\-/[\]]*$/, "Must be a valid route (e.g. /page, /products/[id])")),
185
181
  // Layout
186
182
  layout: PageLayoutSchema,
187
183
  sections: z.array(PageSectionSchema).default([]),
@@ -473,10 +469,24 @@ function parseCid(id) {
473
469
  }
474
470
 
475
471
  // src/managers/DesignSystemManager.ts
476
- import { readFile, writeFile } from "fs/promises";
472
+ import { readFile } from "fs/promises";
473
+
474
+ // src/utils/atomicWrite.ts
475
+ import { writeFile, rename, mkdir } from "fs/promises";
477
476
  import { dirname } from "path";
478
477
  import { existsSync } from "fs";
479
- import { mkdir } from "fs/promises";
478
+ import { randomBytes } from "crypto";
479
+ async function atomicWriteFile(filePath, content) {
480
+ const dir = dirname(filePath);
481
+ if (!existsSync(dir)) {
482
+ await mkdir(dir, { recursive: true });
483
+ }
484
+ const tmpPath = `${filePath}.${randomBytes(4).toString("hex")}.tmp`;
485
+ await writeFile(tmpPath, content, "utf-8");
486
+ await rename(tmpPath, filePath);
487
+ }
488
+
489
+ // src/managers/DesignSystemManager.ts
480
490
  var DesignSystemManager = class {
481
491
  config = null;
482
492
  configPath;
@@ -490,11 +500,7 @@ var DesignSystemManager = class {
490
500
  async load() {
491
501
  try {
492
502
  const fileContent = await readFile(this.configPath, "utf-8");
493
- const configMatch = fileContent.match(/export const config[^=]*=\s*({[\s\S]*})/m);
494
- if (!configMatch) {
495
- throw new Error("Invalid config file format. Expected: export const config = { ... }");
496
- }
497
- const configJson = configMatch[1];
503
+ const configJson = extractConfigObject(fileContent);
498
504
  const config = JSON.parse(configJson);
499
505
  this.config = validateConfig(config);
500
506
  this.loadComponentRegistry();
@@ -522,11 +528,7 @@ var DesignSystemManager = class {
522
528
 
523
529
  export const config = ${JSON.stringify(this.config, null, 2)} as const
524
530
  `;
525
- const dir = dirname(this.configPath);
526
- if (!existsSync(dir)) {
527
- await mkdir(dir, { recursive: true });
528
- }
529
- await writeFile(this.configPath, fileContent, "utf-8");
531
+ await atomicWriteFile(this.configPath, fileContent);
530
532
  } catch (error) {
531
533
  if (error instanceof Error) {
532
534
  throw new Error(`Failed to save config to ${this.configPath}: ${error.message}`);
@@ -790,6 +792,44 @@ export const config = ${JSON.stringify(this.config, null, 2)} as const
790
792
  await this.load();
791
793
  }
792
794
  };
795
+ function extractConfigObject(source) {
796
+ const marker = source.match(/export\s+const\s+config[^=]*=\s*/);
797
+ if (!marker || marker.index === void 0) {
798
+ throw new Error("Invalid config file format. Expected: export const config = { ... }");
799
+ }
800
+ const startSearch = marker.index + marker[0].length;
801
+ const openBrace = source.indexOf("{", startSearch);
802
+ if (openBrace === -1) {
803
+ throw new Error("Invalid config file format: no opening brace found after config =");
804
+ }
805
+ let depth = 0;
806
+ let inString = false;
807
+ let escape = false;
808
+ for (let i = openBrace; i < source.length; i++) {
809
+ const ch = source[i];
810
+ if (escape) {
811
+ escape = false;
812
+ continue;
813
+ }
814
+ if (ch === "\\" && inString) {
815
+ escape = true;
816
+ continue;
817
+ }
818
+ if (ch === '"' && !escape) {
819
+ inString = !inString;
820
+ continue;
821
+ }
822
+ if (inString) continue;
823
+ if (ch === "{") depth++;
824
+ else if (ch === "}") {
825
+ depth--;
826
+ if (depth === 0) {
827
+ return source.slice(openBrace, i + 1);
828
+ }
829
+ }
830
+ }
831
+ throw new Error("Invalid config file format: unbalanced braces");
832
+ }
793
833
 
794
834
  // src/managers/ComponentManager.ts
795
835
  var ComponentManager = class {
@@ -904,10 +944,7 @@ var ComponentManager = class {
904
944
  ];
905
945
  return {
906
946
  success: true,
907
- modified: [
908
- `component:${id}`,
909
- ...affectedPages.map((page) => `page:${page}`)
910
- ],
947
+ modified: [`component:${id}`, ...affectedPages.map((page) => `page:${page}`)],
911
948
  config: this.config,
912
949
  message: `Updated component ${updated.name} (${id})`,
913
950
  warnings: allWarnings.length > 0 ? allWarnings : void 0
@@ -932,10 +969,7 @@ var ComponentManager = class {
932
969
  modified: [],
933
970
  config: this.config,
934
971
  message: `Cannot delete component used in ${component.usedInPages.length} page(s)`,
935
- warnings: [
936
- "Remove component from all pages before deleting",
937
- `Used in: ${component.usedInPages.join(", ")}`
938
- ]
972
+ warnings: ["Remove component from all pages before deleting", `Used in: ${component.usedInPages.join(", ")}`]
939
973
  };
940
974
  }
941
975
  this.config.components = this.config.components.filter((c) => c.id !== id);
@@ -959,9 +993,7 @@ var ComponentManager = class {
959
993
  }
960
994
  if (criteria.name) {
961
995
  const nameLower = criteria.name.toLowerCase();
962
- results = results.filter(
963
- (c) => c.name.toLowerCase().includes(nameLower)
964
- );
996
+ results = results.filter((c) => c.name.toLowerCase().includes(nameLower));
965
997
  }
966
998
  if (criteria.category) {
967
999
  results = results.filter((c) => c.category === criteria.category);
@@ -970,24 +1002,16 @@ var ComponentManager = class {
970
1002
  results = results.filter((c) => c.source === criteria.source);
971
1003
  }
972
1004
  if (criteria.shadcnComponent) {
973
- results = results.filter(
974
- (c) => c.shadcnComponent === criteria.shadcnComponent
975
- );
1005
+ results = results.filter((c) => c.shadcnComponent === criteria.shadcnComponent);
976
1006
  }
977
1007
  if (criteria.usedInPage) {
978
- results = results.filter(
979
- (c) => c.usedInPages.includes(criteria.usedInPage)
980
- );
1008
+ results = results.filter((c) => c.usedInPages.includes(criteria.usedInPage));
981
1009
  }
982
1010
  if (criteria.hasVariant) {
983
- results = results.filter(
984
- (c) => c.variants.some((v) => v.name === criteria.hasVariant)
985
- );
1011
+ results = results.filter((c) => c.variants.some((v) => v.name === criteria.hasVariant));
986
1012
  }
987
1013
  if (criteria.hasSize) {
988
- results = results.filter(
989
- (c) => c.sizes.some((s) => s.name === criteria.hasSize)
990
- );
1014
+ results = results.filter((c) => c.sizes.some((s) => s.name === criteria.hasSize));
991
1015
  }
992
1016
  return results;
993
1017
  }
@@ -1040,9 +1064,7 @@ var ComponentManager = class {
1040
1064
  if (index !== -1) {
1041
1065
  component.usedInPages.splice(index, 1);
1042
1066
  component.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1043
- const configIndex = this.config.components.findIndex(
1044
- (c) => c.id === componentId
1045
- );
1067
+ const configIndex = this.config.components.findIndex((c) => c.id === componentId);
1046
1068
  if (configIndex !== -1) {
1047
1069
  this.config.components[configIndex] = component;
1048
1070
  this.componentRegistry.set(componentId, component);
@@ -1126,9 +1148,7 @@ var ComponentManager = class {
1126
1148
  }
1127
1149
  if (requested.requiredVariants) {
1128
1150
  const existingVariantNames = existing.variants.map((v) => v.name);
1129
- const hasAllVariants = requested.requiredVariants.every(
1130
- (v) => existingVariantNames.includes(v)
1131
- );
1151
+ const hasAllVariants = requested.requiredVariants.every((v) => existingVariantNames.includes(v));
1132
1152
  if (!hasAllVariants) {
1133
1153
  return false;
1134
1154
  }
@@ -1138,18 +1158,13 @@ var ComponentManager = class {
1138
1158
  const isValidSize = (s) => {
1139
1159
  return ["xs", "sm", "md", "lg", "xl"].includes(s);
1140
1160
  };
1141
- const hasAllSizes = requested.requiredSizes.every(
1142
- (s) => isValidSize(s) && existingSizeNames.includes(s)
1143
- );
1161
+ const hasAllSizes = requested.requiredSizes.every((s) => isValidSize(s) && existingSizeNames.includes(s));
1144
1162
  if (!hasAllSizes) {
1145
1163
  return false;
1146
1164
  }
1147
1165
  }
1148
1166
  if (requested.baseClassName) {
1149
- if (!this.isSimilarClassName(
1150
- existing.baseClassName,
1151
- requested.baseClassName
1152
- )) {
1167
+ if (!this.isSimilarClassName(existing.baseClassName, requested.baseClassName)) {
1153
1168
  return false;
1154
1169
  }
1155
1170
  }
@@ -1167,17 +1182,13 @@ var ComponentManager = class {
1167
1182
  }
1168
1183
  if (requested.name) {
1169
1184
  const requestedName = requested.name.toLowerCase();
1170
- const nameMatch = candidates.find(
1171
- (c) => c.name.toLowerCase() === requestedName
1172
- );
1185
+ const nameMatch = candidates.find((c) => c.name.toLowerCase() === requestedName);
1173
1186
  if (nameMatch) {
1174
1187
  return nameMatch;
1175
1188
  }
1176
1189
  }
1177
1190
  if (requested.category) {
1178
- const categoryMatch = candidates.find(
1179
- (c) => c.category === requested.category
1180
- );
1191
+ const categoryMatch = candidates.find((c) => c.category === requested.category);
1181
1192
  if (categoryMatch) {
1182
1193
  return categoryMatch;
1183
1194
  }
@@ -1435,9 +1446,7 @@ var PageManager = class {
1435
1446
  this.config.pages = this.config.pages.filter((p) => p.id !== id);
1436
1447
  this.config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1437
1448
  if (this.config.navigation?.enabled) {
1438
- this.config.navigation.items = this.config.navigation.items.filter(
1439
- (item) => item.route !== page.route
1440
- );
1449
+ this.config.navigation.items = this.config.navigation.items.filter((item) => item.route !== page.route);
1441
1450
  }
1442
1451
  this.config = validateConfig(this.config);
1443
1452
  return {
@@ -1602,9 +1611,7 @@ var PageManager = class {
1602
1611
  });
1603
1612
  }
1604
1613
  });
1605
- navigation.items = navigation.items.filter(
1606
- (item) => this.config.pages.some((page) => page.route === item.route)
1607
- );
1614
+ navigation.items = navigation.items.filter((item) => this.config.pages.some((page) => page.route === item.route));
1608
1615
  navigation.items.sort((a, b) => a.order - b.order);
1609
1616
  navigation.items.forEach((item, index) => {
1610
1617
  item.order = index + 1;
@@ -1627,7 +1634,7 @@ var PageManager = class {
1627
1634
  */
1628
1635
  generateNextJsPage(def) {
1629
1636
  const imports = this.generateImports(def);
1630
- const metadata = this.generateMetadata(def);
1637
+ const _metadata = this.generateMetadata(def);
1631
1638
  const sections = this.generateSections(def);
1632
1639
  const containerClass = this.getContainerClass(def.layout);
1633
1640
  return `import { Metadata } from 'next'
@@ -1745,17 +1752,13 @@ ${navigation ? ` ${navigation}
1745
1752
  * Generate imports for page components
1746
1753
  */
1747
1754
  generateImports(def) {
1748
- const componentIds = new Set(
1749
- def.sections.map((section) => section.componentId)
1750
- );
1755
+ const componentIds = new Set(def.sections.map((section) => section.componentId));
1751
1756
  const imports = [];
1752
1757
  componentIds.forEach((componentId) => {
1753
1758
  const component = getComponent(this.config, componentId);
1754
1759
  if (component) {
1755
1760
  const componentName = component.name;
1756
- imports.push(
1757
- `import { ${componentName} } from '@/components/${componentName}'`
1758
- );
1761
+ imports.push(`import { ${componentName} } from '@/components/${componentName}'`);
1759
1762
  }
1760
1763
  });
1761
1764
  return imports.join("\n");
@@ -1797,9 +1800,7 @@ ${navigation ? ` ${navigation}
1797
1800
  if (!this.config.navigation?.enabled) {
1798
1801
  return "";
1799
1802
  }
1800
- const items = this.config.navigation.items.map(
1801
- (item) => ` <Link href="${item.route}">${item.label}</Link>`
1802
- ).join("\n");
1803
+ const items = this.config.navigation.items.map((item) => ` <Link href="${item.route}">${item.label}</Link>`).join("\n");
1803
1804
  return `<nav className="navigation">
1804
1805
  ${items}
1805
1806
  </nav>`;
@@ -1809,18 +1810,18 @@ ${items}
1809
1810
  */
1810
1811
  getContainerClass(layout) {
1811
1812
  const layoutClasses = {
1812
- "centered": "max-w-4xl mx-auto px-4",
1813
+ centered: "max-w-4xl mx-auto px-4",
1813
1814
  "sidebar-left": "flex",
1814
1815
  "sidebar-right": "flex flex-row-reverse",
1815
1816
  "full-width": "w-full",
1816
- "grid": "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
1817
+ grid: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
1817
1818
  };
1818
1819
  return layoutClasses[layout] || "";
1819
1820
  }
1820
1821
  /**
1821
1822
  * Get body class based on layout type
1822
1823
  */
1823
- getBodyClass(layout) {
1824
+ getBodyClass(_layout) {
1824
1825
  return "min-h-screen bg-background text-foreground";
1825
1826
  }
1826
1827
  /**
@@ -3167,7 +3168,7 @@ ${componentName}.displayName = '${componentName}'`;
3167
3168
  /**
3168
3169
  * Apply design tokens to className string
3169
3170
  */
3170
- applyTokens(className, tokens) {
3171
+ applyTokens(className, _tokens) {
3171
3172
  return className;
3172
3173
  }
3173
3174
  /**
@@ -4808,7 +4809,9 @@ export default function TokensPage() {
4808
4809
  lines.push("import { useEffect, useState } from 'react'");
4809
4810
  lines.push("");
4810
4811
  lines.push("export default function ColorsPage() {");
4811
- lines.push(" const [tokens, setTokens] = useState<{ colors?: { light?: Record<string, string>; dark?: Record<string, string> } } | null>(null)");
4812
+ lines.push(
4813
+ " const [tokens, setTokens] = useState<{ colors?: { light?: Record<string, string>; dark?: Record<string, string> } } | null>(null)"
4814
+ );
4812
4815
  lines.push(" const [loading, setLoading] = useState(true)");
4813
4816
  lines.push("");
4814
4817
  lines.push(" useEffect(() => {");
@@ -4854,7 +4857,9 @@ export default function TokensPage() {
4854
4857
  lines.push(" />");
4855
4858
  lines.push(' <div className="min-w-0 flex-1">');
4856
4859
  lines.push(' <div className="font-medium capitalize">{key}</div>');
4857
- lines.push(' <div className="text-xs text-muted-foreground font-mono">{value} \xB7 var({toCssVar(key)})</div>');
4860
+ lines.push(
4861
+ ' <div className="text-xs text-muted-foreground font-mono">{value} \xB7 var({toCssVar(key)})</div>'
4862
+ );
4858
4863
  lines.push(" </div>");
4859
4864
  lines.push(" </div>");
4860
4865
  lines.push(" )");
@@ -4941,7 +4946,9 @@ export default function TokensPage() {
4941
4946
  lines.push(' <div key={name} className="space-y-2">');
4942
4947
  lines.push(' <div className="text-sm font-medium capitalize">{name}</div>');
4943
4948
  lines.push(' <div className="text-xs text-muted-foreground font-mono">{value as string}</div>');
4944
- lines.push(' <div className="text-lg" style={{ fontFamily: value as string }}>The quick brown fox jumps over the lazy dog</div>');
4949
+ lines.push(
4950
+ ' <div className="text-lg" style={{ fontFamily: value as string }}>The quick brown fox jumps over the lazy dog</div>'
4951
+ );
4945
4952
  lines.push(" </div>");
4946
4953
  lines.push(" ))}");
4947
4954
  lines.push(" </div>");
@@ -4953,9 +4960,13 @@ export default function TokensPage() {
4953
4960
  lines.push(' <div key={name} className="space-y-1">');
4954
4961
  lines.push(' <div className="flex items-baseline gap-2">');
4955
4962
  lines.push(' <span className="text-sm font-medium">{name}</span>');
4956
- lines.push(' <span className="text-xs text-muted-foreground font-mono">{value as string} ({remToPx(value as string)})</span>');
4963
+ lines.push(
4964
+ ' <span className="text-xs text-muted-foreground font-mono">{value as string} ({remToPx(value as string)})</span>'
4965
+ );
4957
4966
  lines.push(" </div>");
4958
- lines.push(" <div style={{ fontSize: value as string }}>The quick brown fox jumps over the lazy dog</div>");
4967
+ lines.push(
4968
+ " <div style={{ fontSize: value as string }}>The quick brown fox jumps over the lazy dog</div>"
4969
+ );
4959
4970
  lines.push(" </div>");
4960
4971
  lines.push(" ))}");
4961
4972
  lines.push(" </div>");
@@ -4980,7 +4991,9 @@ export default function TokensPage() {
4980
4991
  lines.push(" {Object.entries(lineHeight).map(([name, value]) => (");
4981
4992
  lines.push(' <div key={name} className="space-y-1">');
4982
4993
  lines.push(' <div className="text-sm font-medium">{name} ({String(value)})</div>');
4983
- lines.push(' <div className="text-sm bg-muted/50 p-3 rounded max-w-md" style={{ lineHeight: value as number }}>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.</div>');
4994
+ lines.push(
4995
+ ' <div className="text-sm bg-muted/50 p-3 rounded max-w-md" style={{ lineHeight: value as number }}>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.</div>'
4996
+ );
4984
4997
  lines.push(" </div>");
4985
4998
  lines.push(" ))}");
4986
4999
  lines.push(" </div>");
@@ -5068,79 +5081,83 @@ export default function TokensPage() {
5068
5081
  return lines.join("\n");
5069
5082
  }
5070
5083
  generateSitemapPage() {
5071
- const pages = this.config.pages || [];
5072
- const pageItems = pages.map((p) => {
5073
- const route = p.route || "/" + p.id;
5074
- const name = p.name || p.id;
5075
- const analysis = p.pageAnalysis;
5076
- const sections = analysis?.sections ? JSON.stringify(analysis.sections.map((s) => s.name)) : "[]";
5077
- const compUsage = analysis?.componentUsage ? JSON.stringify(analysis.componentUsage) : "{}";
5078
- const iconCount = analysis?.iconCount ?? 0;
5079
- const layout = analysis?.layoutPattern ? JSON.stringify(analysis.layoutPattern) : "null";
5080
- const hasForm = analysis?.hasForm ?? false;
5081
- return `{ name: ${JSON.stringify(name)}, route: ${JSON.stringify(route)}, sections: ${sections}, componentUsage: ${compUsage}, iconCount: ${iconCount}, layoutPattern: ${layout}, hasForm: ${hasForm} }`;
5082
- });
5083
- const lines = [];
5084
- lines.push("'use client'");
5085
- lines.push("");
5086
- lines.push("import Link from 'next/link'");
5087
- lines.push("");
5088
- lines.push("interface PageInfo {");
5089
- lines.push(" name: string");
5090
- lines.push(" route: string");
5091
- lines.push(" sections: string[]");
5092
- lines.push(" componentUsage: Record<string, number>");
5093
- lines.push(" iconCount: number");
5094
- lines.push(" layoutPattern: string | null");
5095
- lines.push(" hasForm: boolean");
5096
- lines.push("}");
5097
- lines.push("");
5098
- lines.push("const PAGES: PageInfo[] = [");
5099
- lines.push(" " + pageItems.join(",\n "));
5100
- lines.push("]");
5101
- lines.push("");
5102
- lines.push("export default function SitemapPage() {");
5103
- lines.push(" return (");
5104
- lines.push(' <div className="space-y-8">');
5105
- lines.push(" <div>");
5106
- lines.push(' <h1 className="text-3xl font-bold tracking-tight">Sitemap</h1>');
5107
- lines.push(' <p className="text-muted-foreground mt-1">All pages, sections, and component usage</p>');
5108
- lines.push(" </div>");
5109
- lines.push(' <div className="space-y-4">');
5110
- lines.push(" {PAGES.map((page) => (");
5111
- lines.push(' <div key={page.route} className="rounded-xl border p-4 space-y-3">');
5112
- lines.push(' <div className="flex items-center gap-3">');
5113
- lines.push(' <Link href={page.route} className="font-semibold text-sm hover:text-primary transition-colors">');
5114
- lines.push(" {page.name}");
5115
- lines.push(" </Link>");
5116
- lines.push(' <span className="text-xs text-muted-foreground font-mono">{page.route}</span>');
5117
- lines.push(' {page.layoutPattern && <span className="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground">{page.layoutPattern}</span>}');
5118
- lines.push(' {page.hasForm && <span className="text-[10px] px-1.5 py-0.5 rounded bg-primary/10 text-primary">form</span>}');
5119
- lines.push(" </div>");
5120
- lines.push(" {page.sections.length > 0 && (");
5121
- lines.push(' <div className="flex flex-wrap gap-1.5">');
5122
- lines.push(" {page.sections.map((s) => (");
5123
- lines.push(' <span key={s} className="text-[11px] px-2 py-0.5 rounded-full border text-muted-foreground">{s}</span>');
5124
- lines.push(" ))}");
5125
- lines.push(" </div>");
5126
- lines.push(" )}");
5127
- lines.push(" {Object.keys(page.componentUsage).length > 0 && (");
5128
- lines.push(' <div className="flex flex-wrap gap-1.5">');
5129
- lines.push(" {Object.entries(page.componentUsage).filter(([,c]) => c > 0).map(([name, count]) => (");
5130
- lines.push(' <span key={name} className="text-[11px] px-2 py-0.5 rounded-full bg-muted text-muted-foreground">');
5131
- lines.push(' {name}<span className="text-[10px] text-muted-foreground/60 ml-0.5">\xD7{count as number}</span>');
5132
- lines.push(" </span>");
5133
- lines.push(" ))}");
5134
- lines.push(' {page.iconCount > 0 && <span className="text-[11px] px-2 py-0.5 rounded-full bg-muted text-muted-foreground">Icons \xD7{page.iconCount}</span>}');
5135
- lines.push(" </div>");
5136
- lines.push(" )}");
5137
- lines.push(" </div>");
5138
- lines.push(" ))}");
5139
- lines.push(" </div>");
5140
- lines.push(" </div>");
5141
- lines.push(" )");
5142
- lines.push("}");
5143
- return lines.join("\n");
5084
+ return `import { readFileSync } from 'fs'
5085
+ import { join } from 'path'
5086
+ import Link from 'next/link'
5087
+
5088
+ function loadPages() {
5089
+ try {
5090
+ const raw = readFileSync(join(process.cwd(), 'design-system.config.ts'), 'utf-8')
5091
+ const jsonMatch = raw.match(/export\\s+const\\s+config\\s*=\\s*/)
5092
+ const jsonStart = jsonMatch ? raw.indexOf('{', raw.indexOf(jsonMatch[0])) : -1
5093
+ if (jsonStart === -1) return []
5094
+ let jsonStr = raw.slice(jsonStart)
5095
+ jsonStr = jsonStr.replace(/\\}\\s*(as\\s+const|satisfies\\s+\\w+)\\s*;?\\s*$/, '}')
5096
+ jsonStr = jsonStr.replace(/,\\s*([\\]\\}])/g, '$1')
5097
+ const json = JSON.parse(jsonStr)
5098
+ return (json.pages || [])
5099
+ .filter((p: any) => {
5100
+ const route = p.route || '/' + p.id
5101
+ return !route.includes('[') && !route.includes(']')
5102
+ })
5103
+ .map((p: any) => ({
5104
+ name: p.name || p.id,
5105
+ route: p.route || '/' + p.id,
5106
+ sections: p.pageAnalysis?.sections?.map((s: any) => s.name) || [],
5107
+ componentUsage: p.pageAnalysis?.componentUsage || {},
5108
+ iconCount: p.pageAnalysis?.iconCount ?? 0,
5109
+ layoutPattern: p.pageAnalysis?.layoutPattern || null,
5110
+ hasForm: p.pageAnalysis?.hasForm ?? false,
5111
+ }))
5112
+ } catch {
5113
+ return []
5114
+ }
5115
+ }
5116
+
5117
+ export default function SitemapPage() {
5118
+ const pages = loadPages()
5119
+
5120
+ return (
5121
+ <div className="space-y-8">
5122
+ <div>
5123
+ <h1 className="text-3xl font-bold tracking-tight">Sitemap</h1>
5124
+ <p className="text-muted-foreground mt-1">All pages, sections, and component usage</p>
5125
+ </div>
5126
+ <div className="space-y-4">
5127
+ {pages.map((page: any) => (
5128
+ <div key={page.route} className="rounded-xl border p-4 space-y-3">
5129
+ <div className="flex items-center gap-3">
5130
+ <Link href={page.route} className="font-semibold text-sm hover:text-primary transition-colors">
5131
+ {page.name}
5132
+ </Link>
5133
+ <span className="text-xs text-muted-foreground font-mono">{page.route}</span>
5134
+ {page.layoutPattern && <span className="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground">{page.layoutPattern}</span>}
5135
+ {page.hasForm && <span className="text-[10px] px-1.5 py-0.5 rounded bg-primary/10 text-primary">form</span>}
5136
+ </div>
5137
+ {page.sections.length > 0 && (
5138
+ <div className="flex flex-wrap gap-1.5">
5139
+ {page.sections.map((s: string) => (
5140
+ <span key={s} className="text-[11px] px-2 py-0.5 rounded-full border text-muted-foreground">{s}</span>
5141
+ ))}
5142
+ </div>
5143
+ )}
5144
+ {Object.keys(page.componentUsage).length > 0 && (
5145
+ <div className="flex flex-wrap gap-1.5">
5146
+ {Object.entries(page.componentUsage).filter(([,c]) => (c as number) > 0).map(([name, count]) => (
5147
+ <span key={name} className="text-[11px] px-2 py-0.5 rounded-full bg-muted text-muted-foreground">
5148
+ {name}<span className="text-[10px] text-muted-foreground/60 ml-0.5">{String.fromCharCode(215)}{count as number}</span>
5149
+ </span>
5150
+ ))}
5151
+ {page.iconCount > 0 && <span className="text-[11px] px-2 py-0.5 rounded-full bg-muted text-muted-foreground">Icons {String.fromCharCode(215)}{page.iconCount}</span>}
5152
+ </div>
5153
+ )}
5154
+ </div>
5155
+ ))}
5156
+ </div>
5157
+ </div>
5158
+ )
5159
+ }
5160
+ `;
5144
5161
  }
5145
5162
  };
5146
5163
 
@@ -5195,7 +5212,6 @@ ${sections}
5195
5212
  }
5196
5213
  `;
5197
5214
  }
5198
- const metadata = this.generateMetadata(def);
5199
5215
  return `import { Metadata } from 'next'
5200
5216
  ${imports}
5201
5217
 
@@ -5310,9 +5326,7 @@ ${sections}
5310
5326
  if (component) {
5311
5327
  const componentName = component.name;
5312
5328
  const fileName = this.toKebabCase(componentName);
5313
- imports.push(
5314
- `import { ${componentName} } from '@/components/ui/${fileName}'`
5315
- );
5329
+ imports.push(`import { ${componentName} } from '@/components/ui/${fileName}'`);
5316
5330
  }
5317
5331
  });
5318
5332
  return imports.length > 0 ? imports.join("\n") : "";
@@ -5527,11 +5541,11 @@ ${sections}
5527
5541
  */
5528
5542
  getContainerClass(layout) {
5529
5543
  const layoutClasses = {
5530
- "centered": "max-w-4xl mx-auto px-4 py-8",
5544
+ centered: "max-w-4xl mx-auto px-4 py-8",
5531
5545
  "sidebar-left": "flex min-h-screen",
5532
5546
  "sidebar-right": "flex flex-row-reverse min-h-screen",
5533
5547
  "full-width": "w-full",
5534
- "grid": "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 p-6"
5548
+ grid: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 p-6"
5535
5549
  };
5536
5550
  return layoutClasses[layout] || "container mx-auto px-4 py-8";
5537
5551
  }
@@ -5548,7 +5562,7 @@ ${sections}
5548
5562
  /**
5549
5563
  * Generate Next.js App Router root layout
5550
5564
  */
5551
- generateNextJSLayout(layout) {
5565
+ generateNextJSLayout(_layout) {
5552
5566
  const cssVars = buildCssVariables(this.config);
5553
5567
  const navEnabled = this.config.navigation?.enabled;
5554
5568
  const navRendered = navEnabled ? "<AppNav />" : "";
@@ -5611,9 +5625,20 @@ ${navEnabled ? ` ${navRendered}
5611
5625
  if (!this.config.navigation?.enabled) {
5612
5626
  return "";
5613
5627
  }
5614
- const authRoutes = /* @__PURE__ */ new Set(["/login", "/signin", "/sign-in", "/signup", "/sign-up", "/register", "/forgot-password", "/reset-password"]);
5628
+ const authRoutes = /* @__PURE__ */ new Set([
5629
+ "/login",
5630
+ "/signin",
5631
+ "/sign-in",
5632
+ "/signup",
5633
+ "/sign-up",
5634
+ "/register",
5635
+ "/forgot-password",
5636
+ "/reset-password"
5637
+ ]);
5615
5638
  const authCheck = [...authRoutes].map((r) => `pathname === '${r}'`).join(" || ");
5616
- const visibleItems = this.config.navigation.items.filter((item) => !authRoutes.has(item.route));
5639
+ const visibleItems = this.config.navigation.items.filter(
5640
+ (item) => !authRoutes.has(item.route) && !item.route.includes("[")
5641
+ );
5617
5642
  const hasMultipleItems = visibleItems.length > 1;
5618
5643
  const items = visibleItems.map(
5619
5644
  (item) => `<Link href="${item.route}" className={\`text-sm font-medium px-3 py-2 rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring \${pathname === "${item.route}" ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted/50'}\`}>${item.label}</Link>`
@@ -5686,23 +5711,25 @@ export function AppNav() {
5686
5711
  return (
5687
5712
  <Fragment>
5688
5713
  {!hasSharedHeader && (
5689
- <nav className="sticky top-0 z-50 flex h-14 shrink-0 items-center justify-between border-b px-4 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
5690
- <div className="flex items-center gap-6">
5691
- <Link href="/" className="flex items-center gap-2.5 text-sm font-semibold text-foreground hover:text-foreground/90 transition-colors shrink-0">
5692
- <CoherentLogo size={20} className="text-primary" />
5693
- <span>Coherent Design Method</span>
5694
- </Link>
5695
- ${navItemsBlock}
5696
- </div>
5697
- <div className="flex items-center gap-1">
5698
- <ThemeToggle />
5699
- <Link
5700
- href="/design-system"
5701
- className="flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors"
5702
- >
5703
- <CoherentLogo size={16} />
5704
- Design System
5705
- </Link>
5714
+ <nav className="sticky top-0 z-50 shrink-0 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
5715
+ <div className="mx-auto flex h-14 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
5716
+ <div className="flex items-center gap-6">
5717
+ <Link href="/" className="flex items-center gap-2.5 text-sm font-semibold text-foreground hover:text-foreground/90 transition-colors shrink-0">
5718
+ <CoherentLogo size={20} className="text-primary" />
5719
+ <span>Coherent Design Method</span>
5720
+ </Link>
5721
+ ${navItemsBlock}
5722
+ </div>
5723
+ <div className="flex items-center gap-1">
5724
+ <ThemeToggle />
5725
+ <Link
5726
+ href="/design-system"
5727
+ className="flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors"
5728
+ >
5729
+ <CoherentLogo size={16} />
5730
+ Design System
5731
+ </Link>
5732
+ </div>
5706
5733
  </div>
5707
5734
  </nav>
5708
5735
  )}
@@ -5722,7 +5749,7 @@ export function AppNav() {
5722
5749
  /**
5723
5750
  * Generate React SPA root layout
5724
5751
  */
5725
- generateReactSPALayout(layout) {
5752
+ generateReactSPALayout(_layout) {
5726
5753
  const navigation = this.config.navigation?.enabled ? this.generateNavigation("react-router") : "";
5727
5754
  return `import { Outlet } from 'react-router-dom'
5728
5755
  import './globals.css'
@@ -6098,8 +6125,7 @@ function getDefaultTemplate(componentName, type, name) {
6098
6125
  const safeName = componentName.replace(/[^a-zA-Z0-9]/g, "") || "Block";
6099
6126
  const lower = name.toLowerCase();
6100
6127
  if (lower.includes("footer")) return FOOTER_PLACEHOLDER(safeName);
6101
- if (type === "layout" || lower.includes("header") || lower.includes("nav"))
6102
- return LAYOUT_PLACEHOLDER(safeName);
6128
+ if (type === "layout" || lower.includes("header") || lower.includes("nav")) return LAYOUT_PLACEHOLDER(safeName);
6103
6129
  return SECTION_PLACEHOLDER(safeName);
6104
6130
  }
6105
6131
  async function generateSharedComponent(projectRoot, input) {
@@ -6161,9 +6187,7 @@ async function integrateSharedLayoutIntoRootLayout(projectRoot) {
6161
6187
  else headers.push(pascal);
6162
6188
  }
6163
6189
  if (headers.length === 0 && footers.length === 0) return false;
6164
- const importLines = layoutComponents.map(
6165
- (e) => `import { ${toPascalCase(e.name)} } from '${getImportPath(e.name)}'`
6166
- );
6190
+ const importLines = layoutComponents.map((e) => `import { ${toPascalCase(e.name)} } from '${getImportPath(e.name)}'`);
6167
6191
  let result = content;
6168
6192
  const globalsImport = "import './globals.css'";
6169
6193
  const globalsIdx = result.indexOf(globalsImport);
@@ -6174,6 +6198,10 @@ async function integrateSharedLayoutIntoRootLayout(projectRoot) {
6174
6198
  result = result.slice(0, lineEnd) + line + "\n" + result.slice(lineEnd);
6175
6199
  }
6176
6200
  }
6201
+ if (headers.length > 0) {
6202
+ result = result.replace(/\s*<AppNav\s*\/>\s*\n?/g, "\n");
6203
+ result = result.replace(/^import\s.*['"]\.\/AppNav['"]\s*;?\n?/gm, "");
6204
+ }
6177
6205
  if (headers.length > 0) {
6178
6206
  const bodyOpen = result.indexOf("<body");
6179
6207
  if (bodyOpen === -1) return false;
@@ -6268,12 +6296,7 @@ var ProjectScaffolder = class _ProjectScaffolder {
6268
6296
  * Create directory structure
6269
6297
  */
6270
6298
  async createDirectories() {
6271
- const dirs = [
6272
- "app",
6273
- "components",
6274
- "lib",
6275
- "public"
6276
- ];
6299
+ const dirs = ["app", "components", "lib", "public"];
6277
6300
  for (const dir of dirs) {
6278
6301
  const fullPath = join4(this.projectRoot, dir);
6279
6302
  if (!existsSync3(fullPath)) {
@@ -6550,7 +6573,9 @@ export function cn(...inputs: ClassValue[]) {
6550
6573
  }
6551
6574
  }
6552
6575
  async generateDefaultPages() {
6553
- await this.writeFile("app/not-found.tsx", `import Link from 'next/link'
6576
+ await this.writeFile(
6577
+ "app/not-found.tsx",
6578
+ `import Link from 'next/link'
6554
6579
 
6555
6580
  export default function NotFound() {
6556
6581
  return (
@@ -6566,8 +6591,11 @@ export default function NotFound() {
6566
6591
  </main>
6567
6592
  )
6568
6593
  }
6569
- `);
6570
- await this.writeFile("app/loading.tsx", `export default function Loading() {
6594
+ `
6595
+ );
6596
+ await this.writeFile(
6597
+ "app/loading.tsx",
6598
+ `export default function Loading() {
6571
6599
  return (
6572
6600
  <div className="flex min-h-[60vh] items-center justify-center">
6573
6601
  <div className="space-y-4 w-full max-w-md px-4">
@@ -6582,8 +6610,11 @@ export default function NotFound() {
6582
6610
  </div>
6583
6611
  )
6584
6612
  }
6585
- `);
6586
- await this.writeFile("app/error.tsx", `'use client'
6613
+ `
6614
+ );
6615
+ await this.writeFile(
6616
+ "app/error.tsx",
6617
+ `'use client'
6587
6618
 
6588
6619
  export default function ErrorPage({
6589
6620
  error,
@@ -6607,35 +6638,22 @@ export default function ErrorPage({
6607
6638
  </main>
6608
6639
  )
6609
6640
  }
6610
- `);
6641
+ `
6642
+ );
6611
6643
  }
6612
6644
  async generateFavicon() {
6613
- const primaryColor = this.config.tokens?.colors?.light?.primary || "#3B82F6";
6645
+ const logoPath = "M10 4C10.5523 4 11 4.44772 11 5V13H19C19.5523 13 20 13.4477 20 14V20.4287C19.9999 22.401 18.401 23.9999 16.4287 24H3.57129C1.59895 23.9999 7.5245e-05 22.401 0 20.4287V7.57129C7.53742e-05 5.59895 1.59895 4.00008 3.57129 4H10ZM2 20.4287C2.00008 21.2965 2.70352 21.9999 3.57129 22H9V15H2V20.4287ZM11 22H16.4287C17.2965 21.9999 17.9999 21.2965 18 20.4287V15H11V22ZM3.57129 6C2.70352 6.00008 2.00008 6.70352 2 7.57129V13H9V6H3.57129ZM20.5 0C22.433 0 24 1.567 24 3.5V9.90039C23.9998 10.5076 23.5076 10.9998 22.9004 11H14.0996C13.4924 10.9998 13.0002 10.5076 13 9.90039V1.09961C13.0002 0.492409 13.4924 0.000211011 14.0996 0H20.5ZM15 9H22V3.5C22 2.67157 21.3284 2 20.5 2H15V9Z";
6646
+ const logoSvg = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
6647
+ <path d="${logoPath}" fill="#FFA500"/>
6648
+ </svg>`;
6649
+ await this.writeFile("public/coherent-logo.svg", logoSvg);
6614
6650
  const faviconSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
6615
- <rect width="32" height="32" rx="8" fill="${primaryColor}"/>
6651
+ <rect width="32" height="32" rx="8" fill="#3B82F6"/>
6616
6652
  <g transform="translate(4, 4)">
6617
- <g clip-path="url(#clip0)">
6618
- <path d="M10 4C10.5523 4 11 4.44772 11 5V13H19C19.5523 13 20 13.4477 20 14V20.4287C19.9999 22.401 18.401 23.9999 16.4287 24H3.57129C1.59895 23.9999 7.5245e-05 22.401 0 20.4287V7.57129C7.53742e-05 5.59895 1.59895 4.00008 3.57129 4H10ZM2 20.4287C2.00008 21.2965 2.70352 21.9999 3.57129 22H9V15H2V20.4287ZM11 22H16.4287C17.2965 21.9999 17.9999 21.2965 18 20.4287V15H11V22ZM3.57129 6C2.70352 6.00008 2.00008 6.70352 2 7.57129V13H9V6H3.57129ZM20.5 0C22.433 0 24 1.567 24 3.5V9.90039C23.9998 10.5076 23.5076 10.9998 22.9004 11H14.0996C13.4924 10.9998 13.0002 10.5076 13 9.90039V1.09961C13.0002 0.492409 13.4924 0.000211011 14.0996 0H20.5ZM15 9H22V3.5C22 2.67157 21.3284 2 20.5 2H15V9Z" fill="white"/>
6619
- </g>
6620
- <defs>
6621
- <clipPath id="clip0">
6622
- <rect width="24" height="24" fill="white"/>
6623
- </clipPath>
6624
- </defs>
6653
+ <path d="${logoPath}" fill="white"/>
6625
6654
  </g>
6626
6655
  </svg>`;
6627
6656
  await this.writeFile("public/favicon.svg", faviconSvg);
6628
- const logoSvg = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
6629
- <g clip-path="url(#clip0_23738_31)">
6630
- <path d="M10 4C10.5523 4 11 4.44772 11 5V13H19C19.5523 13 20 13.4477 20 14V20.4287C19.9999 22.401 18.401 23.9999 16.4287 24H3.57129C1.59895 23.9999 7.5245e-05 22.401 0 20.4287V7.57129C7.53742e-05 5.59895 1.59895 4.00008 3.57129 4H10ZM2 20.4287C2.00008 21.2965 2.70352 21.9999 3.57129 22H9V15H2V20.4287ZM11 22H16.4287C17.2965 21.9999 17.9999 21.2965 18 20.4287V15H11V22ZM3.57129 6C2.70352 6.00008 2.00008 6.70352 2 7.57129V13H9V6H3.57129ZM20.5 0C22.433 0 24 1.567 24 3.5V9.90039C23.9998 10.5076 23.5076 10.9998 22.9004 11H14.0996C13.4924 10.9998 13.0002 10.5076 13 9.90039V1.09961C13.0002 0.492409 13.4924 0.000211011 14.0996 0H20.5ZM15 9H22V3.5C22 2.67157 21.3284 2 20.5 2H15V9Z" fill="#FFA500"/>
6631
- </g>
6632
- <defs>
6633
- <clipPath id="clip0_23738_31">
6634
- <rect width="24" height="24" fill="white"/>
6635
- </clipPath>
6636
- </defs>
6637
- </svg>`;
6638
- await this.writeFile("public/coherent-logo.svg", logoSvg);
6639
6657
  }
6640
6658
  /**
6641
6659
  * Generate .gitignore
@@ -7730,7 +7748,9 @@ ${activitySection}
7730
7748
  function onboardingTemplate(content, options) {
7731
7749
  const { title, description, steps, totalSteps } = content;
7732
7750
  const { pageName } = options;
7733
- const stepBars = steps.map((_, i) => " <div key={i} className={`flex-1 h-2 rounded-full ${i <= step ? 'bg-primary' : 'bg-muted'}`} />").join("\n");
7751
+ const stepBars = steps.map(
7752
+ (_, _i) => " <div key={_i} className={`flex-1 h-2 rounded-full ${_i <= step ? 'bg-primary' : 'bg-muted'}`} />"
7753
+ ).join("\n");
7734
7754
  const stepContent = steps.map(
7735
7755
  (s, i) => ` {step === ${i} && (
7736
7756
  <div className="${D.card} p-6">
@@ -7931,7 +7951,7 @@ var FigmaClient = class {
7931
7951
  async fetchWithRetry(url, retries = 2) {
7932
7952
  const res = await fetch(url, {
7933
7953
  method: "GET",
7934
- headers: { "X-Figma-Token": this.token, "Accept": "application/json" }
7954
+ headers: { "X-Figma-Token": this.token, Accept: "application/json" }
7935
7955
  });
7936
7956
  if (res.status === 429 && retries > 0) {
7937
7957
  const retryAfter = parseInt(res.headers.get("Retry-After") ?? "", 10);
@@ -8142,8 +8162,7 @@ function extractTokensFromFigma(data) {
8142
8162
  for (const ts of textStyles) {
8143
8163
  const n = normalizeName(ts.name);
8144
8164
  if (ts.fontSize != null) {
8145
- if (/^(h1|heading\s*1|display|title\s*large)$/.test(n))
8146
- fontSize["2xl"] = `${ts.fontSize}px`;
8165
+ if (/^(h1|heading\s*1|display|title\s*large)$/.test(n)) fontSize["2xl"] = `${ts.fontSize}px`;
8147
8166
  else if (/^(h2|heading\s*2)$/.test(n)) fontSize["xl"] = `${ts.fontSize}px`;
8148
8167
  else if (/^(body|paragraph|text)$/.test(n)) fontSize.base = `${ts.fontSize}px`;
8149
8168
  else if (/^(small|caption)$/.test(n)) fontSize.sm = `${ts.fontSize}px`;
@@ -8599,9 +8618,11 @@ export {
8599
8618
  TailwindConfigGenerator,
8600
8619
  TypographyTokenSchema,
8601
8620
  allocateNextCid,
8621
+ atomicWriteFile,
8602
8622
  buildCssVariables,
8603
8623
  componentExists,
8604
8624
  createEntry,
8625
+ extractConfigObject,
8605
8626
  extractTokensFromFigma,
8606
8627
  figmaComponentNameToBaseId,
8607
8628
  figmaRgbaToHex,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.0",
6
+ "version": "0.2.1",
7
7
  "description": "Core design system engine for Coherent",
8
8
  "type": "module",
9
9
  "main": "./dist/index.js",
@@ -49,4 +49,3 @@
49
49
  "vitest": "^1.2.1"
50
50
  }
51
51
  }
52
-