handoff-app 1.1.3 → 1.2.0-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.
Files changed (160) hide show
  1. package/config/config.template.js +21 -14
  2. package/config/config.template.ts +54 -0
  3. package/config/docs/system.md +2 -0
  4. package/dist/app-builder/build.js +31 -12
  5. package/dist/app-builder/config-diff/index.d.ts +4 -0
  6. package/dist/app-builder/config-diff/index.js +12 -0
  7. package/dist/app-builder/config-diff/registry.d.ts +9 -0
  8. package/dist/app-builder/config-diff/registry.js +30 -0
  9. package/dist/app-builder/config-diff/snapshot.d.ts +13 -0
  10. package/dist/app-builder/config-diff/snapshot.js +43 -0
  11. package/dist/app-builder/config-diff/strategies/pattern.d.ts +2 -0
  12. package/dist/app-builder/config-diff/strategies/pattern.js +106 -0
  13. package/dist/app-builder/config-diff/types.d.ts +33 -0
  14. package/dist/app-builder/config-diff/types.js +2 -0
  15. package/dist/app-builder/watchers/component.d.ts +15 -0
  16. package/dist/app-builder/watchers/component.js +69 -0
  17. package/dist/app-builder/watchers/utils.d.ts +14 -0
  18. package/dist/app-builder/watchers/utils.js +44 -0
  19. package/dist/app-builder/watchers.d.ts +10 -19
  20. package/dist/app-builder/watchers.js +65 -79
  21. package/dist/app-builder/websocket.js +10 -4
  22. package/dist/cache/build-cache.js +14 -5
  23. package/dist/cli/create.js +67 -23
  24. package/dist/cli/eject.js +48 -7
  25. package/dist/cli/make.js +29 -4
  26. package/dist/cli/scaffold.js +60 -19
  27. package/dist/cli.js +1 -1
  28. package/dist/config/helpers.d.ts +7 -0
  29. package/dist/config/helpers.js +31 -0
  30. package/dist/config/index.d.ts +2 -1
  31. package/dist/config/index.js +5 -1
  32. package/dist/config/loader.d.ts +10 -1
  33. package/dist/config/loader.js +92 -28
  34. package/dist/config/normalizers/declaration.d.ts +11 -0
  35. package/dist/config/normalizers/declaration.js +119 -0
  36. package/dist/config/normalizers/pattern.d.ts +8 -0
  37. package/dist/config/normalizers/pattern.js +35 -0
  38. package/dist/config/runtime.d.ts +9 -3
  39. package/dist/config/runtime.js +260 -33
  40. package/dist/declarations/helpers.d.ts +6 -0
  41. package/dist/declarations/helpers.js +19 -0
  42. package/dist/declarations/index.d.ts +2 -0
  43. package/dist/declarations/index.js +9 -0
  44. package/dist/declarations/types.d.ts +60 -0
  45. package/dist/declarations/types.js +2 -0
  46. package/dist/index.d.ts +12 -1
  47. package/dist/index.js +34 -5
  48. package/dist/pipeline/index.d.ts +1 -0
  49. package/dist/pipeline/index.js +3 -1
  50. package/dist/pipeline/patterns.d.ts +8 -0
  51. package/dist/pipeline/patterns.js +21 -0
  52. package/dist/transformers/docgen/index.js +12 -2
  53. package/dist/transformers/plugins/csf-render.js +170 -34
  54. package/dist/transformers/plugins/ssr-render.js +18 -2
  55. package/dist/transformers/preview/component/api.js +16 -7
  56. package/dist/transformers/preview/component/builder.js +61 -8
  57. package/dist/transformers/preview/component/css.d.ts +8 -0
  58. package/dist/transformers/preview/component/css.js +6 -4
  59. package/dist/transformers/preview/component/html.js +14 -5
  60. package/dist/transformers/preview/component/javascript.d.ts +0 -1
  61. package/dist/transformers/preview/component/javascript.js +25 -14
  62. package/dist/transformers/preview/component/previews.d.ts +9 -0
  63. package/dist/transformers/preview/component/previews.js +10 -0
  64. package/dist/transformers/preview/pattern/api.d.ts +11 -0
  65. package/dist/transformers/preview/pattern/api.js +79 -0
  66. package/dist/transformers/preview/pattern/builder.d.ts +17 -0
  67. package/dist/transformers/preview/pattern/builder.js +167 -0
  68. package/dist/transformers/preview/pattern/html.d.ts +19 -0
  69. package/dist/transformers/preview/pattern/html.js +94 -0
  70. package/dist/transformers/preview/types.d.ts +54 -0
  71. package/dist/transformers/utils/csf.d.ts +16 -0
  72. package/dist/transformers/utils/csf.js +30 -0
  73. package/dist/transformers/utils/index.d.ts +1 -0
  74. package/dist/transformers/utils/index.js +2 -0
  75. package/dist/transformers/utils/module.d.ts +5 -0
  76. package/dist/transformers/utils/module.js +26 -13
  77. package/dist/transformers/utils/usage.d.ts +23 -0
  78. package/dist/transformers/utils/usage.js +294 -0
  79. package/dist/transformers/utils/vite-logger.js +4 -4
  80. package/dist/transformers/vite-config.js +2 -0
  81. package/dist/types/config.d.ts +32 -7
  82. package/dist/types/preview.d.ts +2 -0
  83. package/dist/utils/duration.d.ts +2 -0
  84. package/dist/utils/duration.js +6 -0
  85. package/dist/utils/logger.d.ts +42 -5
  86. package/dist/utils/logger.js +135 -15
  87. package/docs/cli.md +3 -3
  88. package/package.json +3 -1
  89. package/src/app/components/Component/PageSliceResolver.tsx +6 -6
  90. package/src/app/components/Component/Preview.tsx +17 -12
  91. package/src/app/components/Component/slices/ComponentDisplaySlice.tsx +16 -6
  92. package/src/app/components/Markdown/CodeHighlight.tsx +60 -11
  93. package/src/app/components/util/index.ts +71 -2
  94. package/src/app/pages/system/pattern/[pattern]/index.tsx +346 -0
  95. package/src/app/pages/system/pattern/index.tsx +14 -0
  96. package/src/app-builder/README.md +37 -9
  97. package/src/app-builder/build.ts +34 -12
  98. package/src/app-builder/config-diff/index.ts +4 -0
  99. package/src/app-builder/config-diff/registry.ts +23 -0
  100. package/src/app-builder/config-diff/snapshot.ts +51 -0
  101. package/src/app-builder/config-diff/strategies/pattern.ts +80 -0
  102. package/src/app-builder/config-diff/types.ts +36 -0
  103. package/src/app-builder/watchers/component.ts +72 -0
  104. package/src/app-builder/watchers/utils.ts +44 -0
  105. package/src/app-builder/watchers.ts +74 -80
  106. package/src/app-builder/websocket.ts +16 -5
  107. package/src/cache/build-cache.ts +14 -5
  108. package/src/cli/create.ts +86 -25
  109. package/src/cli/eject.ts +29 -7
  110. package/src/cli/make.ts +32 -5
  111. package/src/cli/scaffold.ts +68 -21
  112. package/src/cli.ts +1 -1
  113. package/src/config/README.md +10 -1
  114. package/src/config/helpers.ts +43 -0
  115. package/src/config/index.ts +4 -1
  116. package/src/config/loader.ts +110 -31
  117. package/src/config/normalizers/declaration.ts +176 -0
  118. package/src/config/normalizers/pattern.ts +56 -0
  119. package/src/config/runtime.ts +317 -35
  120. package/src/declarations/helpers.ts +36 -0
  121. package/src/declarations/index.ts +19 -0
  122. package/src/declarations/types.ts +70 -0
  123. package/src/index.ts +47 -6
  124. package/src/pipeline/index.ts +1 -0
  125. package/src/pipeline/patterns.ts +12 -0
  126. package/src/transformers/README.md +2 -2
  127. package/src/transformers/docgen/index.ts +13 -2
  128. package/src/transformers/plugins/csf-render.ts +216 -49
  129. package/src/transformers/plugins/ssr-render.ts +23 -7
  130. package/src/transformers/preview/component/api.ts +27 -7
  131. package/src/transformers/preview/component/builder.ts +82 -9
  132. package/src/transformers/preview/component/css.ts +6 -5
  133. package/src/transformers/preview/component/html.ts +18 -5
  134. package/src/transformers/preview/component/javascript.ts +29 -19
  135. package/src/transformers/preview/component/previews.ts +12 -0
  136. package/src/transformers/preview/pattern/api.ts +83 -0
  137. package/src/transformers/preview/pattern/builder.ts +192 -0
  138. package/src/transformers/preview/pattern/html.ts +107 -0
  139. package/src/transformers/preview/types.ts +58 -1
  140. package/src/transformers/utils/csf.ts +48 -0
  141. package/src/transformers/utils/index.ts +3 -0
  142. package/src/transformers/utils/module.ts +33 -18
  143. package/src/transformers/utils/usage.ts +319 -0
  144. package/src/transformers/utils/vite-logger.ts +4 -4
  145. package/src/transformers/vite-config.ts +2 -0
  146. package/src/types/README.md +1 -1
  147. package/src/types/config.ts +33 -7
  148. package/src/types/preview.ts +2 -0
  149. package/src/utils/duration.ts +3 -0
  150. package/src/utils/logger.ts +143 -16
  151. package/templates/default/components/button/Button.jsx.tpl +64 -0
  152. package/templates/default/components/button/button.react.handoff.js.tpl +31 -0
  153. package/templates/default/components/button/button.react.handoff.ts.tpl +31 -0
  154. package/templates/default/handoff.config.js.tpl +29 -16
  155. package/templates/default/handoff.config.ts.tpl +196 -0
  156. package/templates/default/handoff.config.with-examples.js.tpl +28 -17
  157. package/templates/default/handoff.config.with-examples.ts.tpl +195 -0
  158. package/templates/default/package.json.tpl +1 -3
  159. package/handoff.config.js +0 -90
  160. package/templates/default/components/button/button.js.tpl +0 -62
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCsfStoryEntries = getCsfStoryEntries;
4
+ exports.createCsfStoryPreviews = createCsfStoryPreviews;
5
+ const lodash_1 = require("lodash");
6
+ function getCsfStoryEntries(moduleExports) {
7
+ return Object.entries(moduleExports).filter(([key, value]) => {
8
+ if (key === 'default' || key === '__esModule')
9
+ return false;
10
+ if (typeof value === 'function')
11
+ return true;
12
+ if (value && typeof value === 'object')
13
+ return true;
14
+ return false;
15
+ });
16
+ }
17
+ function createCsfStoryPreviews(moduleExports) {
18
+ const meta = (moduleExports.default || {});
19
+ const stories = getCsfStoryEntries(moduleExports);
20
+ const previews = {};
21
+ for (const [storyKey, storyValue] of stories) {
22
+ const storyObj = (typeof storyValue === 'function' ? {} : storyValue);
23
+ previews[storyKey] = {
24
+ title: storyObj.name || (0, lodash_1.startCase)(storyKey),
25
+ values: Object.assign(Object.assign({}, (meta.args || {})), (storyObj.args || {})),
26
+ url: '',
27
+ };
28
+ }
29
+ return previews;
30
+ }
@@ -8,5 +8,6 @@ export * from './schema';
8
8
  export * from './html';
9
9
  export * from './build';
10
10
  export * from './module';
11
+ export * from './csf';
11
12
  export * from './handlebars';
12
13
  export * from './schema-loader';
@@ -28,6 +28,8 @@ __exportStar(require("./html"), exports);
28
28
  __exportStar(require("./build"), exports);
29
29
  // Module utilities
30
30
  __exportStar(require("./module"), exports);
31
+ // CSF utilities
32
+ __exportStar(require("./csf"), exports);
31
33
  // Handlebars utilities
32
34
  __exportStar(require("./handlebars"), exports);
33
35
  // Schema loader utilities
@@ -6,3 +6,8 @@ import { ModuleEvaluationResult } from '../types';
6
6
  * @returns Module evaluation result with exports
7
7
  */
8
8
  export declare function buildAndEvaluateModule(entryPath: string, handoff: any): Promise<ModuleEvaluationResult>;
9
+ /**
10
+ * Builds and evaluates a module synchronously using esbuild.
11
+ * Used by config/runtime code paths that must remain synchronous.
12
+ */
13
+ export declare function buildAndEvaluateModuleSync(entryPath: string, handoff: any): ModuleEvaluationResult;
@@ -13,8 +13,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.buildAndEvaluateModule = buildAndEvaluateModule;
16
+ exports.buildAndEvaluateModuleSync = buildAndEvaluateModuleSync;
16
17
  const esbuild_1 = __importDefault(require("esbuild"));
17
18
  const build_1 = require("./build");
19
+ function createModuleBuildConfig(entryPath, handoff) {
20
+ var _a, _b;
21
+ const defaultBuildConfig = Object.assign(Object.assign({}, build_1.DEFAULT_SSR_BUILD_CONFIG), { entryPoints: [entryPath] });
22
+ return ((_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.hooks) === null || _b === void 0 ? void 0 : _b.ssrBuildConfig)
23
+ ? handoff.config.hooks.ssrBuildConfig(defaultBuildConfig)
24
+ : defaultBuildConfig;
25
+ }
26
+ function evaluateBuiltModule(code) {
27
+ const mod = { exports: {} };
28
+ const func = new Function('require', 'module', 'exports', code);
29
+ func(require, mod, mod.exports);
30
+ return mod;
31
+ }
18
32
  /**
19
33
  * Builds and evaluates a module using esbuild
20
34
  * @param entryPath - Path to the module entry point
@@ -23,20 +37,19 @@ const build_1 = require("./build");
23
37
  */
24
38
  function buildAndEvaluateModule(entryPath, handoff) {
25
39
  return __awaiter(this, void 0, void 0, function* () {
26
- var _a, _b;
27
- // Default esbuild configuration
28
- const defaultBuildConfig = Object.assign(Object.assign({}, build_1.DEFAULT_SSR_BUILD_CONFIG), { entryPoints: [entryPath] });
29
- // Apply user's SSR build config hook if provided
30
- const buildConfig = ((_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.hooks) === null || _b === void 0 ? void 0 : _b.ssrBuildConfig)
31
- ? handoff.config.hooks.ssrBuildConfig(defaultBuildConfig)
32
- : defaultBuildConfig;
33
- // Compile the module
40
+ const buildConfig = createModuleBuildConfig(entryPath, handoff);
34
41
  const build = yield esbuild_1.default.build(buildConfig);
35
42
  const { text: code } = build.outputFiles[0];
36
- // Evaluate the compiled code
37
- const mod = { exports: {} };
38
- const func = new Function('require', 'module', 'exports', code);
39
- func(require, mod, mod.exports);
40
- return mod;
43
+ return evaluateBuiltModule(code);
41
44
  });
42
45
  }
46
+ /**
47
+ * Builds and evaluates a module synchronously using esbuild.
48
+ * Used by config/runtime code paths that must remain synchronous.
49
+ */
50
+ function buildAndEvaluateModuleSync(entryPath, handoff) {
51
+ const buildConfig = createModuleBuildConfig(entryPath, handoff);
52
+ const build = esbuild_1.default.buildSync(buildConfig);
53
+ const { text: code } = build.outputFiles[0];
54
+ return evaluateBuiltModule(code);
55
+ }
@@ -0,0 +1,23 @@
1
+ import { SlotMetadata } from '../preview/component';
2
+ /**
3
+ * Extracts the PascalCase component name from a file path.
4
+ * e.g. `/path/to/Button.tsx` -> `Button`
5
+ */
6
+ export declare function extractComponentName(filePath: string): string;
7
+ export interface GenerateUsageSnippetOptions {
8
+ componentName: string;
9
+ properties: Record<string, SlotMetadata>;
10
+ previewValues: Record<string, any>;
11
+ /** Template filename for the import path (e.g. "Button.tsx") */
12
+ templateFileName?: string;
13
+ importStatement?: string;
14
+ }
15
+ /**
16
+ * Generates a JSX usage snippet showing how to consume a React component
17
+ * with the given preview values.
18
+ *
19
+ * Iterates over the union of preview values and schema properties so that
20
+ * props not present in the preview are still represented when they carry
21
+ * a meaningful default or are required.
22
+ */
23
+ export declare function generateUsageSnippet(options: GenerateUsageSnippetOptions): string;
@@ -0,0 +1,294 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.extractComponentName = extractComponentName;
7
+ exports.generateUsageSnippet = generateUsageSnippet;
8
+ const path_1 = __importDefault(require("path"));
9
+ const component_1 = require("../preview/component");
10
+ const CHILDREN_PROP = 'children';
11
+ const INDENT = ' ';
12
+ function isValidIdentifier(key) {
13
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key);
14
+ }
15
+ /**
16
+ * Formats a prop value as a JSX attribute string.
17
+ * Returns the right-hand side only (e.g. `"hello"`, `{42}`, `{true}`).
18
+ */
19
+ function formatPropValue(value, type) {
20
+ if (value === undefined || value === null) {
21
+ return '{undefined}';
22
+ }
23
+ switch (type) {
24
+ case component_1.SlotType.BOOLEAN:
25
+ return typeof value === 'boolean' ? `{${value}}` : `{${Boolean(value)}}`;
26
+ case component_1.SlotType.NUMBER:
27
+ return `{${Number(value)}}`;
28
+ case component_1.SlotType.FUNCTION:
29
+ return '{() => { /* handler */ }}';
30
+ case component_1.SlotType.OBJECT:
31
+ return `{${formatObjectLiteral(value)}}`;
32
+ case component_1.SlotType.ARRAY:
33
+ return `{${formatArrayLiteral(value)}}`;
34
+ case component_1.SlotType.TEXT:
35
+ case component_1.SlotType.IMAGE:
36
+ case component_1.SlotType.BUTTON:
37
+ case component_1.SlotType.ENUM:
38
+ default:
39
+ if (typeof value === 'string') {
40
+ return `"${escapeJsxString(value)}"`;
41
+ }
42
+ if (typeof value === 'boolean') {
43
+ return `{${value}}`;
44
+ }
45
+ if (typeof value === 'number') {
46
+ return `{${value}}`;
47
+ }
48
+ if (typeof value === 'object') {
49
+ return `{${JSON.stringify(value, null, 2)}}`;
50
+ }
51
+ return `"${escapeJsxString(String(value))}"`;
52
+ }
53
+ }
54
+ function escapeJsxString(str) {
55
+ return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
56
+ }
57
+ function formatObjectLiteral(value) {
58
+ if (typeof value !== 'object' || value === null) {
59
+ return JSON.stringify(value);
60
+ }
61
+ const entries = Object.entries(value);
62
+ if (entries.length === 0)
63
+ return '{}';
64
+ if (entries.length <= 3) {
65
+ const inline = entries
66
+ .map(([k, v]) => `${isValidIdentifier(k) ? k : JSON.stringify(k)}: ${JSON.stringify(v)}`)
67
+ .join(', ');
68
+ if (inline.length <= 60)
69
+ return `{ ${inline} }`;
70
+ }
71
+ return JSON.stringify(value, null, 2);
72
+ }
73
+ function formatArrayLiteral(value) {
74
+ if (!Array.isArray(value))
75
+ return JSON.stringify(value);
76
+ if (value.length === 0)
77
+ return '[]';
78
+ const inline = JSON.stringify(value);
79
+ if (inline.length <= 60)
80
+ return inline;
81
+ return JSON.stringify(value, null, 2);
82
+ }
83
+ /**
84
+ * Determines whether a prop value matches its declared default and can be omitted.
85
+ */
86
+ function matchesDefault(value, slotDefault) {
87
+ if (slotDefault === undefined || slotDefault === null)
88
+ return false;
89
+ if (typeof value === typeof slotDefault && value === slotDefault)
90
+ return true;
91
+ // SlotMetadata.default is typed as `string | undefined`, but runtime values
92
+ // may be numbers or booleans. Do a loose string comparison as a fallback.
93
+ if (String(value) === String(slotDefault))
94
+ return true;
95
+ return false;
96
+ }
97
+ /**
98
+ * Extracts the PascalCase component name from a file path.
99
+ * e.g. `/path/to/Button.tsx` -> `Button`
100
+ */
101
+ function extractComponentName(filePath) {
102
+ const basename = path_1.default.basename(filePath);
103
+ const ext = path_1.default.extname(basename);
104
+ return basename.slice(0, basename.length - ext.length);
105
+ }
106
+ /**
107
+ * Generates a JSX usage snippet showing how to consume a React component
108
+ * with the given preview values.
109
+ *
110
+ * Iterates over the union of preview values and schema properties so that
111
+ * props not present in the preview are still represented when they carry
112
+ * a meaningful default or are required.
113
+ */
114
+ function generateUsageSnippet(options) {
115
+ var _a;
116
+ const { componentName, properties, previewValues, templateFileName, importStatement } = options;
117
+ const importPath = `./${templateFileName ? path_1.default.basename(templateFileName, path_1.default.extname(templateFileName)) : componentName}`;
118
+ const attrs = [];
119
+ let childrenValue = undefined;
120
+ let hasChildren = false;
121
+ // Merge all known prop keys: preview values first (preserve order), then
122
+ // any additional keys from the property schema that weren't in the preview.
123
+ const allKeys = new Set([...Object.keys(previewValues), ...Object.keys(properties)]);
124
+ for (const key of allKeys) {
125
+ const slot = properties[key];
126
+ const inPreview = key in previewValues;
127
+ const value = inPreview ? previewValues[key] : resolveSchemaValue(slot);
128
+ // Nothing to show for this prop
129
+ if (value === undefined || value === null) {
130
+ continue;
131
+ }
132
+ if (key === CHILDREN_PROP) {
133
+ hasChildren = true;
134
+ childrenValue = value;
135
+ continue;
136
+ }
137
+ // Skip function props that weren't explicitly provided in preview values
138
+ if (!inPreview && (slot === null || slot === void 0 ? void 0 : slot.type) === component_1.SlotType.FUNCTION) {
139
+ continue;
140
+ }
141
+ // Omit props that match their declared default
142
+ if (slot && matchesDefault(value, slot.default)) {
143
+ continue;
144
+ }
145
+ const type = (_a = slot === null || slot === void 0 ? void 0 : slot.type) !== null && _a !== void 0 ? _a : inferSlotType(value);
146
+ // Boolean true → JSX shorthand: <Comp disabled />
147
+ if (type === component_1.SlotType.BOOLEAN && value === true) {
148
+ attrs.push(key);
149
+ continue;
150
+ }
151
+ // Boolean false → omit (implicit JSX default)
152
+ if (type === component_1.SlotType.BOOLEAN && value === false) {
153
+ continue;
154
+ }
155
+ attrs.push(`${key}=${formatPropValue(value, type)}`);
156
+ }
157
+ const lines = [];
158
+ lines.push(importStatement || `import ${componentName} from '${importPath}';`);
159
+ lines.push('');
160
+ if (hasChildren && childrenValue !== undefined && childrenValue !== null && childrenValue !== '') {
161
+ const openTag = attrs.length > 0 ? `<${componentName} ${attrs.join(' ')}>` : `<${componentName}>`;
162
+ const childStr = typeof childrenValue === 'string'
163
+ ? childrenValue
164
+ : typeof childrenValue === 'number' || typeof childrenValue === 'boolean'
165
+ ? String(childrenValue)
166
+ : '{/* children */}';
167
+ if (childStr.length <= 40 && !childStr.includes('\n')) {
168
+ lines.push(`${openTag}${childStr}</${componentName}>`);
169
+ }
170
+ else {
171
+ lines.push(openTag);
172
+ for (const childLine of childStr.split('\n')) {
173
+ lines.push(`${INDENT}${childLine}`);
174
+ }
175
+ lines.push(`</${componentName}>`);
176
+ }
177
+ }
178
+ else {
179
+ if (attrs.length === 0) {
180
+ lines.push(`<${componentName} />`);
181
+ }
182
+ else if (attrs.length <= 3 && attrs.join(' ').length <= 60) {
183
+ lines.push(`<${componentName} ${attrs.join(' ')} />`);
184
+ }
185
+ else {
186
+ lines.push(`<${componentName}`);
187
+ for (const attr of attrs) {
188
+ lines.push(`${INDENT}${attr}`);
189
+ }
190
+ lines.push('/>');
191
+ }
192
+ }
193
+ return lines.join('\n');
194
+ }
195
+ /**
196
+ * Resolves a display value for a schema-only property (not present in preview values).
197
+ *
198
+ * - Uses the declared default when available and non-trivial.
199
+ * - For required props without a default, generates a type-appropriate placeholder.
200
+ * - Returns `undefined` for optional props with no useful default (they are skipped).
201
+ */
202
+ function resolveSchemaValue(slot) {
203
+ var _a;
204
+ if (!slot)
205
+ return undefined;
206
+ // Use explicit default if it's non-trivial
207
+ if (slot.default !== undefined && slot.default !== null && slot.default !== '') {
208
+ return coerceDefault(slot.default, slot.type);
209
+ }
210
+ // Required props must appear even without a default
211
+ if ((_a = slot.rules) === null || _a === void 0 ? void 0 : _a.required) {
212
+ return getPlaceholder(slot);
213
+ }
214
+ // Optional with no default → omit
215
+ return undefined;
216
+ }
217
+ /**
218
+ * Coerces a string default from SlotMetadata into the appropriate JS type
219
+ * for code-generation purposes.
220
+ */
221
+ function coerceDefault(value, type) {
222
+ if (typeof value !== 'string')
223
+ return value;
224
+ switch (type) {
225
+ case component_1.SlotType.BOOLEAN:
226
+ return value === 'true';
227
+ case component_1.SlotType.NUMBER: {
228
+ const n = Number(value);
229
+ return Number.isNaN(n) ? value : n;
230
+ }
231
+ default:
232
+ return value;
233
+ }
234
+ }
235
+ /**
236
+ * Generates a type-appropriate placeholder value for required props
237
+ * that have no default and no preview value.
238
+ */
239
+ function getPlaceholder(slot) {
240
+ switch (slot.type) {
241
+ case component_1.SlotType.ENUM: {
242
+ const member = extractFirstEnumMember(slot);
243
+ return member !== null && member !== void 0 ? member : 'value';
244
+ }
245
+ case component_1.SlotType.TEXT:
246
+ case component_1.SlotType.IMAGE:
247
+ return '...';
248
+ case component_1.SlotType.NUMBER:
249
+ return 0;
250
+ case component_1.SlotType.BOOLEAN:
251
+ return true;
252
+ case component_1.SlotType.ARRAY:
253
+ return [];
254
+ case component_1.SlotType.OBJECT:
255
+ return {};
256
+ case component_1.SlotType.FUNCTION:
257
+ return undefined; // handled separately
258
+ default:
259
+ return '...';
260
+ }
261
+ }
262
+ /**
263
+ * Attempts to extract the first enum member from a SlotMetadata's deepType
264
+ * (e.g. `"primary"` from `'primary' | 'secondary' | 'danger'`).
265
+ */
266
+ function extractFirstEnumMember(slot) {
267
+ var _a, _b;
268
+ // deepType.members contains the enum values when available
269
+ if (((_a = slot.deepType) === null || _a === void 0 ? void 0 : _a.kind) === 'enum' && ((_b = slot.deepType.members) === null || _b === void 0 ? void 0 : _b.length)) {
270
+ const raw = slot.deepType.members[0];
271
+ // Members are stored as quoted strings like `"primary"` — strip quotes
272
+ return typeof raw === 'string' ? raw.replace(/^["']|["']$/g, '') : undefined;
273
+ }
274
+ // Fall back to parsing the generic display string (e.g. `"primary" | "secondary"`)
275
+ if (slot.generic) {
276
+ const match = slot.generic.match(/["']([^"']+)["']/);
277
+ if (match)
278
+ return match[1];
279
+ }
280
+ return undefined;
281
+ }
282
+ function inferSlotType(value) {
283
+ if (typeof value === 'boolean')
284
+ return component_1.SlotType.BOOLEAN;
285
+ if (typeof value === 'number')
286
+ return component_1.SlotType.NUMBER;
287
+ if (typeof value === 'function')
288
+ return component_1.SlotType.FUNCTION;
289
+ if (Array.isArray(value))
290
+ return component_1.SlotType.ARRAY;
291
+ if (typeof value === 'object' && value !== null)
292
+ return component_1.SlotType.OBJECT;
293
+ return component_1.SlotType.TEXT;
294
+ }
@@ -7,21 +7,21 @@ const createViteLogger = () => {
7
7
  return {
8
8
  hasWarned: false,
9
9
  info(msg, options) {
10
- logger_1.Logger.info(msg);
10
+ logger_1.Logger.info(msg, 'vite');
11
11
  },
12
12
  warn(msg, options) {
13
13
  this.hasWarned = true;
14
- logger_1.Logger.warn(msg);
14
+ logger_1.Logger.warn(msg, 'vite');
15
15
  },
16
16
  warnOnce(msg, options) {
17
17
  if (warnedMessages.has(msg))
18
18
  return;
19
19
  warnedMessages.add(msg);
20
20
  this.hasWarned = true;
21
- logger_1.Logger.warn(msg);
21
+ logger_1.Logger.warn(msg, 'vite');
22
22
  },
23
23
  error(msg, options) {
24
- logger_1.Logger.error(msg);
24
+ logger_1.Logger.error(msg, undefined, 'vite');
25
25
  },
26
26
  clearScreen(type) {
27
27
  // No-op to preserve terminal history
@@ -2,7 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  // vite.config.ts
4
4
  const vite_1 = require("vite");
5
+ const vite_logger_1 = require("./utils/vite-logger");
5
6
  exports.default = (0, vite_1.defineConfig)({
7
+ customLogger: (0, vite_logger_1.createViteLogger)(),
6
8
  publicDir: false,
7
9
  build: {
8
10
  lib: {
@@ -3,7 +3,7 @@ import type Handlebars from 'handlebars';
3
3
  import { Types as HandoffTypes } from 'handoff-core';
4
4
  import { InlineConfig } from 'vite';
5
5
  import { SlotMetadata } from '../transformers/preview/component';
6
- import { ComponentListObject, TransformComponentTokensResult } from '../transformers/preview/types';
6
+ import { ComponentListObject, PatternListObject, TransformComponentTokensResult } from '../transformers/preview/types';
7
7
  import { ValidationResult } from './preview';
8
8
  export interface ImageStyle {
9
9
  name: string;
@@ -65,13 +65,19 @@ export interface NextAppConfig {
65
65
  theme?: string;
66
66
  title: string;
67
67
  client: string;
68
- google_tag_manager: string | null | undefined;
69
- type_copy: string;
70
- type_sort: string[];
71
- color_sort: string[];
68
+ google_tag_manager?: string | null | undefined;
69
+ googleTagManager?: string | null | undefined;
70
+ type_copy?: string;
71
+ typeCopy?: string;
72
+ type_sort?: string[];
73
+ typeSort?: string[];
74
+ color_sort?: string[];
75
+ colorSort?: string[];
72
76
  breakpoints: Breakpoints;
73
- component_sort: string[];
74
- base_path: string;
77
+ component_sort?: string[];
78
+ componentSort?: string[];
79
+ base_path?: string;
80
+ basePath?: string;
75
81
  attribution: boolean;
76
82
  ports?: {
77
83
  app: number;
@@ -80,7 +86,9 @@ export interface NextAppConfig {
80
86
  }
81
87
  export interface Config {
82
88
  dev_access_token?: string | null | undefined;
89
+ devAccessToken?: string | null | undefined;
83
90
  figma_project_id?: string | null | undefined;
91
+ figmaProjectId?: string | null | undefined;
84
92
  exportsOutputDirectory?: string;
85
93
  sitesOutputDirectory?: string;
86
94
  useVariables?: boolean;
@@ -123,6 +131,12 @@ export interface Config {
123
131
  * @example ["components/button", "components/input"]
124
132
  */
125
133
  components?: string[];
134
+ /**
135
+ * Array of pattern paths to be included in the build.
136
+ * Patterns compose multiple component previews into single-page views.
137
+ * @example ["patterns/hero-section", "patterns"]
138
+ */
139
+ patterns?: string[];
126
140
  };
127
141
  /**
128
142
  * Configuration for asset zip file download links
@@ -140,6 +154,10 @@ export interface Config {
140
154
  */
141
155
  logos?: string;
142
156
  };
157
+ assetsZipLinks?: {
158
+ icons?: string;
159
+ logos?: string;
160
+ };
143
161
  /**
144
162
  * Configuration hooks for extending functionality
145
163
  */
@@ -278,6 +296,10 @@ export interface RuntimeConfigComponentOptions {
278
296
  };
279
297
  };
280
298
  }
299
+ export interface ConfigFileEntry {
300
+ kind: string;
301
+ entityId: string;
302
+ }
281
303
  export interface RuntimeConfig {
282
304
  entries?: {
283
305
  scss?: string;
@@ -286,6 +308,9 @@ export interface RuntimeConfig {
286
308
  components: {
287
309
  [id: string]: ComponentListObject;
288
310
  };
311
+ patterns: {
312
+ [id: string]: PatternListObject;
313
+ };
289
314
  };
290
315
  options: {
291
316
  [key: string]: RuntimeConfigComponentOptions;
@@ -54,6 +54,7 @@ export interface PreviewObject {
54
54
  [key: string]: string;
55
55
  };
56
56
  url: string;
57
+ usage?: string;
57
58
  };
58
59
  };
59
60
  properties?: {
@@ -61,6 +62,7 @@ export interface PreviewObject {
61
62
  };
62
63
  code: string;
63
64
  html?: string;
65
+ usage?: string;
64
66
  format: string;
65
67
  variant?: Record<string, string>;
66
68
  options?: {
@@ -0,0 +1,2 @@
1
+ /** Human-readable duration for CLI build logs (ms under 1s, seconds with 2 decimals otherwise). */
2
+ export declare const formatDurationMs: (elapsedMs: number) => string;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatDurationMs = void 0;
4
+ /** Human-readable duration for CLI build logs (ms under 1s, seconds with 2 decimals otherwise). */
5
+ const formatDurationMs = (elapsedMs) => elapsedMs < 1000 ? `${Math.round(elapsedMs)}ms` : `${(elapsedMs / 1000).toFixed(2)}s`;
6
+ exports.formatDurationMs = formatDurationMs;
@@ -1,13 +1,50 @@
1
+ /** Logical source of a log line — used for a fixed-width–style colored tag after the timestamp. */
2
+ export type LogScope = 'handoff' | 'vite' | 'next';
3
+ /**
4
+ * Env (e.g. project `.env` via `dotenv/config`):
5
+ * - `HANDOFF_LOG_LEVEL` — `debug` | `info` | `warn` | `error` | `silent` (default: `info`).
6
+ * Controls minimum severity: at `warn`, info/success/debug lines are hidden; at `error`, only errors.
7
+ * - `HANDOFF_LOG_SCOPES` — comma-separated allowlist: `handoff`, `vite`, `next`. Defaults to all three if unset or empty.
8
+ *
9
+ * Programmatic `Logger.init({ debug: true })` (Handoff `debug` flag) forces debug-level verbosity unless overridden.
10
+ */
1
11
  export declare class Logger {
12
+ /** From Handoff constructor; enables debug lines together with env. */
2
13
  private static debugMode;
14
+ /** Minimum message severity to print: 0=debug, 1=info, 2=warn, 3=error. `silent` uses 99. */
15
+ private static severityThreshold;
16
+ /** Enabled scopes; defaults to all of `handoff`, `vite`, `next`. */
17
+ private static scopesFilter;
3
18
  static init(options?: {
4
19
  debug?: boolean;
5
20
  }): void;
21
+ /** Re-read `process.env` (useful if env is loaded after first `init`). */
22
+ static applyEnvConfig(): void;
23
+ private static scopeEnabled;
24
+ /** Message severities: debug=0, info-tier=1, warn=2, error=3 */
25
+ private static shouldEmit;
6
26
  private static getTimestamp;
7
- static log(message: string): void;
8
- static info(message: string): void;
9
- static success(message: string): void;
10
- static warn(message: string): void;
11
- static error(message: string, error?: any): void;
27
+ /** Short label shown after the timestamp so you can scan for handoff vs vite vs next. */
28
+ private static scopeTag;
29
+ private static basePrefix;
30
+ static log(message: string, scope?: LogScope): void;
31
+ /**
32
+ * Vite often passes strings that already contain ANSI; avoid wrapping those in chalk.cyan so colors stay correct.
33
+ */
34
+ static info(message: string, scope?: LogScope): void;
35
+ static success(message: string, scope?: LogScope): void;
36
+ static warn(message: string, scope?: LogScope): void;
37
+ static error(message: string, error?: any, scope?: LogScope): void;
12
38
  static debug(message: string, data?: any): void;
39
+ /**
40
+ * Log one line from a child process with timestamp + scope tag. Child ANSI is preserved (no extra chalk on the line).
41
+ */
42
+ static childProcessLine(line: string, scope?: LogScope): void;
43
+ /**
44
+ * Pipe stdout/stderr from a spawned child through {@link childProcessLine}. Use with
45
+ * `stdio: ['inherit' | 'ignore', 'pipe', 'pipe']`.
46
+ */
47
+ static pipeChildStreams(stdout: NodeJS.ReadableStream | null, stderr: NodeJS.ReadableStream | null, scope?: LogScope): void;
48
+ /** Forward a completed child-process buffer (e.g. from `spawn.sync` with stdio pipe) line-by-line. */
49
+ static childProcessBuffer(buffer: Buffer | null | undefined, scope?: LogScope): void;
13
50
  }