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
package/dist/cli/make.js CHANGED
@@ -148,7 +148,7 @@ const makeComponent = (handoff, name) => __awaiter(void 0, void 0, void 0, funct
148
148
  }
149
149
  else {
150
150
  componentsRoot = path_1.default.resolve(handoff.workingPath, DEFAULT_COMPONENTS_DIR);
151
- logger_1.Logger.warn(`No entries.components configured in handoff.config.js. ` +
151
+ logger_1.Logger.warn(`No entries.components configured in handoff.config.*. ` +
152
152
  `Scaffolding into "${DEFAULT_COMPONENTS_DIR}/". ` +
153
153
  `Add this path to entries.components in your config so the build picks it up.`);
154
154
  }
@@ -168,9 +168,6 @@ const makeComponent = (handoff, name) => __awaiter(void 0, void 0, void 0, funct
168
168
  const htmlTemplate = fs_extra_1.default.readFileSync(htmlPath, 'utf8');
169
169
  fs_extra_1.default.writeFileSync(targetHtml, htmlTemplate);
170
170
  logger_1.Logger.success(`New component ${name}.hbs was created in ${workingPath}`);
171
- const jsonpath = path_1.default.resolve(templatePath, 'template.json');
172
- const jsonTemplate = fs_extra_1.default.readFileSync(jsonpath, 'utf8');
173
- fs_extra_1.default.writeFileSync(path_1.default.resolve(workingPath, `${name}.json`), jsonTemplate);
174
171
  const writeJSFile = yield p.confirm({
175
172
  message: `Generate a supporting javascript file ${name}.js?`,
176
173
  initialValue: false,
@@ -191,6 +188,34 @@ const makeComponent = (handoff, name) => __awaiter(void 0, void 0, void 0, funct
191
188
  const scssTemplate = fs_extra_1.default.readFileSync(scssPath, 'utf8');
192
189
  fs_extra_1.default.writeFileSync(path_1.default.resolve(workingPath, `${name}.scss`), scssTemplate);
193
190
  }
191
+ const declarationEntries = [`template: './${name}.hbs'`];
192
+ if (writeJSFile === true) {
193
+ declarationEntries.push(`js: './${name}.js'`);
194
+ }
195
+ if (writeSassFile === true) {
196
+ declarationEntries.push(`scss: './${name}.scss'`);
197
+ }
198
+ const declarationContent = `const { defineHandlebarsComponent } = require('handoff-app');
199
+
200
+ module.exports = defineHandlebarsComponent({
201
+ id: '${name}',
202
+ name: '',
203
+ description: '',
204
+ group: '',
205
+ type: 'element',
206
+ entries: {
207
+ ${declarationEntries.join(',\n ')}
208
+ },
209
+ previews: {
210
+ default: {
211
+ title: 'Default',
212
+ args: {}
213
+ }
214
+ }
215
+ });
216
+ `;
217
+ fs_extra_1.default.writeFileSync(path_1.default.resolve(workingPath, `${name}.handoff.js`), declarationContent);
218
+ logger_1.Logger.success(`New component declaration ${name}.handoff.js was created in ${workingPath}`);
194
219
  return handoff;
195
220
  });
196
221
  exports.makeComponent = makeComponent;
@@ -41,9 +41,8 @@ const chalk_1 = __importDefault(require("chalk"));
41
41
  const fs_extra_1 = __importDefault(require("fs-extra"));
42
42
  const path_1 = __importDefault(require("path"));
43
43
  // Constants
44
- const CONFIG_FILES = ['handoff.config.json', 'handoff.config.js', 'handoff.config.cjs'];
44
+ const CONFIG_FILES = ['handoff.config.ts', 'handoff.config.js', 'handoff.config.cjs', 'handoff.config.json'];
45
45
  const COMPONENTS_DIR = 'components';
46
- const DEFAULT_VERSION = '1.0.0';
47
46
  const DEFAULT_GROUPS = [
48
47
  { value: 'Atomic Elements', label: 'Atomic Elements' },
49
48
  { value: 'Forms', label: 'Forms' },
@@ -64,25 +63,48 @@ const toTitleCase = (str) => {
64
63
  * Generate the component JS stub content
65
64
  */
66
65
  const generateComponentStub = (config) => {
67
- // Build entries object with relative paths to generated files
68
66
  const entriesLines = [];
69
67
  if (config.generateTsx) {
70
- entriesLines.push(` template: './${config.name}.tsx',`);
68
+ entriesLines.push(` component: './${config.name}.tsx',`);
69
+ }
70
+ else {
71
+ entriesLines.push(` template: './${config.name}.hbs',`);
71
72
  }
72
73
  if (config.generateScss) {
73
74
  entriesLines.push(` scss: './${config.name}.scss',`);
74
75
  }
75
- const entriesBlock = entriesLines.length > 0
76
- ? ` entries: {\n${entriesLines.join('\n')}\n },\n`
77
- : '';
78
- return `/** @type {import('handoff-app').Component} */
79
- module.exports = {
80
- title: "${config.title}",
76
+ const commonMetadata = ` id: "${config.name}",
77
+ name: "${config.title}",
81
78
  description: "${config.description}",
82
79
  group: "${config.group}",
83
80
  type: "element",
84
- figmaComponentId: "${config.name}",
85
- ${entriesBlock}};
81
+ figmaComponentId: "${config.name}",`;
82
+ if (config.generateTsx) {
83
+ return `const { defineReactComponent } = require('handoff-app');
84
+ const ${toTitleCase(config.name).replace(/\s/g, '')} = require('./${config.name}').default;
85
+
86
+ module.exports = defineReactComponent(${toTitleCase(config.name).replace(/\s/g, '')}, {
87
+ ${commonMetadata}
88
+ entries: {
89
+ ${entriesLines.join('\n')}
90
+ },
91
+ previews: {
92
+ default: { title: "Default", args: {} }
93
+ }
94
+ });
95
+ `;
96
+ }
97
+ return `const { defineHandlebarsComponent } = require('handoff-app');
98
+
99
+ module.exports = defineHandlebarsComponent({
100
+ ${commonMetadata}
101
+ entries: {
102
+ ${entriesLines.join('\n')}
103
+ },
104
+ previews: {
105
+ default: { title: "Default", args: {} }
106
+ }
107
+ });
86
108
  `;
87
109
  };
88
110
  /**
@@ -236,8 +258,8 @@ const updateConfigFile = (handoff, componentNames) => __awaiter(void 0, void 0,
236
258
  yield fs_extra_1.default.writeJSON(configPath, config, { spaces: 2 });
237
259
  return { success: true, isJsConfig: false, configPath, skipped: false };
238
260
  }
239
- // For JS/CJS config files, try to update them programmatically
240
- if (configFile.endsWith('.js') || configFile.endsWith('.cjs')) {
261
+ // For code config files, try to update them programmatically
262
+ if (configFile.endsWith('.ts') || configFile.endsWith('.js') || configFile.endsWith('.cjs')) {
241
263
  try {
242
264
  let content = yield fs_extra_1.default.readFile(configPath, 'utf8');
243
265
  // Check if entries.components already exists
@@ -281,7 +303,7 @@ const updateConfigFile = (handoff, componentNames) => __awaiter(void 0, void 0,
281
303
  yield fs_extra_1.default.writeFile(configPath, content, 'utf8');
282
304
  return { success: true, isJsConfig: true, configPath, skipped: false };
283
305
  }
284
- // No entries object, try to add it before the closing of module.exports
306
+ // No entries object, try to add it before the closing of exported object
285
307
  const formattedArray = formatComponentsArray(newComponentPaths);
286
308
  const entriesBlock = ` entries: {\n components: ${formattedArray},\n },\n`;
287
309
  // Try to insert after module.exports = {
@@ -290,6 +312,18 @@ const updateConfigFile = (handoff, componentNames) => __awaiter(void 0, void 0,
290
312
  yield fs_extra_1.default.writeFile(configPath, content, 'utf8');
291
313
  return { success: true, isJsConfig: true, configPath, skipped: false };
292
314
  }
315
+ // Try to insert after export default {
316
+ if (/export\s+default\s+\{/.test(content)) {
317
+ content = content.replace(/(export\s+default\s+\{)/, `$1\n${entriesBlock}`);
318
+ yield fs_extra_1.default.writeFile(configPath, content, 'utf8');
319
+ return { success: true, isJsConfig: true, configPath, skipped: false };
320
+ }
321
+ // Try to insert into defineConfig({ ... })
322
+ if (/defineConfig\s*\(\s*\{/.test(content)) {
323
+ content = content.replace(/(defineConfig\s*\(\s*\{)/, `$1\n${entriesBlock}`);
324
+ yield fs_extra_1.default.writeFile(configPath, content, 'utf8');
325
+ return { success: true, isJsConfig: true, configPath, skipped: false };
326
+ }
293
327
  // Fallback: couldn't parse the JS config
294
328
  return { success: false, isJsConfig: true, configPath, skipped: false };
295
329
  }
@@ -304,11 +338,11 @@ const updateConfigFile = (handoff, componentNames) => __awaiter(void 0, void 0,
304
338
  */
305
339
  const createComponentFiles = (handoff, config) => __awaiter(void 0, void 0, void 0, function* () {
306
340
  const createdFiles = [];
307
- const componentDir = path_1.default.resolve(handoff.workingPath, COMPONENTS_DIR, config.name, DEFAULT_VERSION);
341
+ const componentDir = path_1.default.resolve(handoff.workingPath, COMPONENTS_DIR, config.name);
308
342
  // Ensure directory exists
309
343
  yield fs_extra_1.default.ensureDir(componentDir);
310
- // Create the main component JS file
311
- const jsPath = path_1.default.join(componentDir, `${config.name}.js`);
344
+ // Create the main component declaration file
345
+ const jsPath = path_1.default.join(componentDir, `${config.name}.handoff.js`);
312
346
  yield fs_extra_1.default.writeFile(jsPath, generateComponentStub(config));
313
347
  createdFiles.push(jsPath);
314
348
  // Optionally create TSX file
@@ -317,6 +351,13 @@ const createComponentFiles = (handoff, config) => __awaiter(void 0, void 0, void
317
351
  yield fs_extra_1.default.writeFile(tsxPath, generateReactComponentStub(config.name, config.variantProps));
318
352
  createdFiles.push(tsxPath);
319
353
  }
354
+ else {
355
+ const hbsTemplatePath = path_1.default.resolve(handoff.modulePath, 'config/templates/component/template.hbs');
356
+ const hbsTemplate = yield fs_extra_1.default.readFile(hbsTemplatePath, 'utf8');
357
+ const hbsPath = path_1.default.join(componentDir, `${config.name}.hbs`);
358
+ yield fs_extra_1.default.writeFile(hbsPath, hbsTemplate);
359
+ createdFiles.push(hbsPath);
360
+ }
320
361
  // Optionally create SCSS file
321
362
  if (config.generateScss) {
322
363
  const scssPath = path_1.default.join(componentDir, `${config.name}.scss`);
@@ -332,7 +373,7 @@ const displayComponentPreview = (config) => {
332
373
  const stub = generateComponentStub(config);
333
374
  const lines = stub.split('\n');
334
375
  const preview = lines.slice(0, 12).join('\n') + '\n ...\n};';
335
- p.note(preview, `Preview: ${config.name}.js`);
376
+ p.note(preview, `Preview: ${config.name}.handoff.js`);
336
377
  };
337
378
  /**
338
379
  * Main scaffold entry point
package/dist/cli.js CHANGED
@@ -19,7 +19,7 @@ class HandoffCliError extends Error {
19
19
  * Show the help message
20
20
  */
21
21
  const showVersion = () => {
22
- return 'Handoff App - 1.1.3';
22
+ return 'Handoff App - 1.2.0-1';
23
23
  };
24
24
  /**
25
25
  * Define a CLI error
@@ -0,0 +1,7 @@
1
+ import { Config } from '../types/config';
2
+ export declare const normalizeConfig: (config: Config) => Config;
3
+ /**
4
+ * Optional helper for strongly-typed config authoring.
5
+ * It normalizes modern camelCase keys to legacy runtime keys.
6
+ */
7
+ export declare const defineConfig: (config: Config) => Config;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.defineConfig = exports.normalizeConfig = void 0;
15
+ const normalizeAppConfig = (app) => {
16
+ if (!app)
17
+ return app;
18
+ const { googleTagManager, typeCopy, typeSort, colorSort, componentSort, basePath } = app, rest = __rest(app, ["googleTagManager", "typeCopy", "typeSort", "colorSort", "componentSort", "basePath"]);
19
+ return Object.assign(Object.assign({}, rest), { google_tag_manager: googleTagManager !== null && googleTagManager !== void 0 ? googleTagManager : app.google_tag_manager, type_copy: typeCopy !== null && typeCopy !== void 0 ? typeCopy : app.type_copy, type_sort: typeSort !== null && typeSort !== void 0 ? typeSort : app.type_sort, color_sort: colorSort !== null && colorSort !== void 0 ? colorSort : app.color_sort, component_sort: componentSort !== null && componentSort !== void 0 ? componentSort : app.component_sort, base_path: basePath !== null && basePath !== void 0 ? basePath : app.base_path });
20
+ };
21
+ const normalizeConfig = (config) => {
22
+ const { devAccessToken, figmaProjectId, assetsZipLinks, app } = config, rest = __rest(config, ["devAccessToken", "figmaProjectId", "assetsZipLinks", "app"]);
23
+ return Object.assign(Object.assign({}, rest), { dev_access_token: devAccessToken !== null && devAccessToken !== void 0 ? devAccessToken : config.dev_access_token, figma_project_id: figmaProjectId !== null && figmaProjectId !== void 0 ? figmaProjectId : config.figma_project_id, assets_zip_links: assetsZipLinks !== null && assetsZipLinks !== void 0 ? assetsZipLinks : config.assets_zip_links, app: normalizeAppConfig(app) });
24
+ };
25
+ exports.normalizeConfig = normalizeConfig;
26
+ /**
27
+ * Optional helper for strongly-typed config authoring.
28
+ * It normalizes modern camelCase keys to legacy runtime keys.
29
+ */
30
+ const defineConfig = (config) => (0, exports.normalizeConfig)(config);
31
+ exports.defineConfig = defineConfig;
@@ -1,5 +1,6 @@
1
1
  export { defaultConfig, getClientConfig } from './defaults';
2
2
  export type { ImageStyle } from './defaults';
3
- export { initConfig } from './loader';
3
+ export { initConfig, initConfigWithMetadata } from './loader';
4
+ export { defineConfig } from './helpers';
4
5
  export { initRuntimeConfig } from './runtime';
5
6
  export { validateConfig } from './validator';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateConfig = exports.initRuntimeConfig = exports.initConfig = exports.getClientConfig = exports.defaultConfig = void 0;
3
+ exports.validateConfig = exports.initRuntimeConfig = exports.defineConfig = exports.initConfigWithMetadata = exports.initConfig = exports.getClientConfig = exports.defaultConfig = void 0;
4
4
  // Config defaults and client config
5
5
  var defaults_1 = require("./defaults");
6
6
  Object.defineProperty(exports, "defaultConfig", { enumerable: true, get: function () { return defaults_1.defaultConfig; } });
@@ -8,6 +8,10 @@ Object.defineProperty(exports, "getClientConfig", { enumerable: true, get: funct
8
8
  // Config file loading
9
9
  var loader_1 = require("./loader");
10
10
  Object.defineProperty(exports, "initConfig", { enumerable: true, get: function () { return loader_1.initConfig; } });
11
+ Object.defineProperty(exports, "initConfigWithMetadata", { enumerable: true, get: function () { return loader_1.initConfigWithMetadata; } });
12
+ // Config helpers
13
+ var helpers_1 = require("./helpers");
14
+ Object.defineProperty(exports, "defineConfig", { enumerable: true, get: function () { return helpers_1.defineConfig; } });
11
15
  // Runtime config resolution
12
16
  var runtime_1 = require("./runtime");
13
17
  Object.defineProperty(exports, "initRuntimeConfig", { enumerable: true, get: function () { return runtime_1.initRuntimeConfig; } });
@@ -1,11 +1,20 @@
1
1
  import { Config } from '../types/config';
2
+ type ConfigLoadResult = {
3
+ config: Config;
4
+ configPath?: string;
5
+ };
6
+ /**
7
+ * Loads the handoff configuration from the project root and returns metadata.
8
+ */
9
+ export declare const initConfigWithMetadata: (configOverride?: Partial<Config>) => ConfigLoadResult;
2
10
  /**
3
11
  * Loads the handoff configuration from the project root.
4
12
  *
5
- * Searches for config files in order: handoff.config.json, handoff.config.js, handoff.config.cjs.
13
+ * Searches for config files in order: handoff.config.ts, handoff.config.js, handoff.config.cjs, handoff.config.json.
6
14
  * Merges file config with any provided overrides, then applies defaults for missing values.
7
15
  *
8
16
  * @param configOverride - Optional partial config to override file-loaded values.
9
17
  * @returns The fully resolved Config object.
10
18
  */
11
19
  export declare const initConfig: (configOverride?: Partial<Config>) => Config;
20
+ export {};
@@ -3,47 +3,111 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.initConfig = void 0;
6
+ exports.initConfig = exports.initConfigWithMetadata = void 0;
7
+ const esbuild_1 = __importDefault(require("esbuild"));
7
8
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const module_1 = require("module");
8
10
  const path_1 = __importDefault(require("path"));
11
+ const logger_1 = require("../utils/logger");
9
12
  const defaults_1 = require("./defaults");
13
+ const CONFIG_FILE_PREFERENCE = [
14
+ 'handoff.config.ts',
15
+ 'handoff.config.js',
16
+ 'handoff.config.cjs',
17
+ 'handoff.config.json',
18
+ ];
19
+ const evaluateTypeScriptConfig = (filePath, handoffModulePath) => {
20
+ var _a, _b;
21
+ const buildResult = esbuild_1.default.buildSync({
22
+ entryPoints: [filePath],
23
+ bundle: true,
24
+ write: false,
25
+ platform: 'node',
26
+ format: 'cjs',
27
+ target: 'node16',
28
+ logLevel: 'silent',
29
+ external: ['handoff-app'],
30
+ });
31
+ const code = (_b = (_a = buildResult.outputFiles) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.text;
32
+ if (!code) {
33
+ throw new Error(`Unable to compile config file "${filePath}"`);
34
+ }
35
+ const mod = { exports: {} };
36
+ const localRequire = (0, module_1.createRequire)(filePath);
37
+ const handoffRequire = (0, module_1.createRequire)(path_1.default.resolve(handoffModulePath, 'package.json'));
38
+ const runtimeRequire = (id) => {
39
+ try {
40
+ return localRequire(id);
41
+ }
42
+ catch (_a) {
43
+ return handoffRequire(id);
44
+ }
45
+ };
46
+ const evaluator = new Function('require', 'module', 'exports', '__filename', '__dirname', code);
47
+ evaluator(runtimeRequire, mod, mod.exports, filePath, path_1.default.dirname(filePath));
48
+ return mod.exports;
49
+ };
50
+ const loadConfigFile = (configPath) => {
51
+ if (configPath.endsWith('.json')) {
52
+ const buffer = fs_extra_1.default.readFileSync(configPath);
53
+ return JSON.parse(buffer.toString());
54
+ }
55
+ if (configPath.endsWith('.ts')) {
56
+ const handoffModulePath = path_1.default.resolve(__dirname, '../..');
57
+ const importedConfig = evaluateTypeScriptConfig(configPath, handoffModulePath);
58
+ return (importedConfig.default || importedConfig);
59
+ }
60
+ // Invalidate require cache to ensure fresh read
61
+ delete require.cache[require.resolve(configPath)];
62
+ const importedConfig = require(configPath);
63
+ return (importedConfig.default || importedConfig);
64
+ };
65
+ const resolveConfigFilePath = () => {
66
+ const existing = CONFIG_FILE_PREFERENCE
67
+ .map((fileName) => path_1.default.resolve(process.cwd(), fileName))
68
+ .filter((filePath) => fs_extra_1.default.existsSync(filePath));
69
+ if (!existing.length) {
70
+ return { selected: undefined, ignored: [] };
71
+ }
72
+ const [selected, ...ignored] = existing;
73
+ return { selected, ignored };
74
+ };
10
75
  /**
11
- * Loads the handoff configuration from the project root.
12
- *
13
- * Searches for config files in order: handoff.config.json, handoff.config.js, handoff.config.cjs.
14
- * Merges file config with any provided overrides, then applies defaults for missing values.
15
- *
16
- * @param configOverride - Optional partial config to override file-loaded values.
17
- * @returns The fully resolved Config object.
76
+ * Loads the handoff configuration from the project root and returns metadata.
18
77
  */
19
- const initConfig = (configOverride) => {
78
+ const initConfigWithMetadata = (configOverride) => {
20
79
  let config = {};
21
- const possibleConfigFiles = ['handoff.config.json', 'handoff.config.js', 'handoff.config.cjs'];
22
- // Find the first existing config file
23
- const configFile = possibleConfigFiles.find((file) => fs_extra_1.default.existsSync(path_1.default.resolve(process.cwd(), file)));
24
- if (configFile) {
25
- const configPath = path_1.default.resolve(process.cwd(), configFile);
26
- if (configFile.endsWith('.json')) {
27
- const defBuffer = fs_extra_1.default.readFileSync(configPath);
28
- config = JSON.parse(defBuffer.toString());
29
- }
30
- else if (configFile.endsWith('.js') || configFile.endsWith('.cjs')) {
31
- // Invalidate require cache to ensure fresh read
32
- delete require.cache[require.resolve(configPath)];
33
- const importedConfig = require(configPath);
34
- config = importedConfig.default || importedConfig;
35
- }
80
+ const { selected: configPath, ignored } = resolveConfigFilePath();
81
+ if (ignored.length > 0 && configPath) {
82
+ logger_1.Logger.warn(`Multiple config files found. Using "${path_1.default.basename(configPath)}" and ignoring: ${ignored
83
+ .map((filePath) => `"${path_1.default.basename(filePath)}"`)
84
+ .join(', ')}.`);
85
+ }
86
+ if (configPath) {
87
+ config = loadConfigFile(configPath);
36
88
  }
37
89
  // Apply overrides if provided
38
90
  if (configOverride) {
39
- Object.keys(configOverride).forEach((key) => {
40
- const value = configOverride[key];
91
+ Object.entries(configOverride).forEach(([key, value]) => {
41
92
  if (value !== undefined) {
42
93
  config[key] = value;
43
94
  }
44
95
  });
45
96
  }
46
- const returnConfig = Object.assign(Object.assign({}, (0, defaults_1.defaultConfig)()), config);
47
- return returnConfig;
97
+ const resolvedConfig = Object.assign(Object.assign({}, (0, defaults_1.defaultConfig)()), config);
98
+ return { config: resolvedConfig, configPath };
99
+ };
100
+ exports.initConfigWithMetadata = initConfigWithMetadata;
101
+ /**
102
+ * Loads the handoff configuration from the project root.
103
+ *
104
+ * Searches for config files in order: handoff.config.ts, handoff.config.js, handoff.config.cjs, handoff.config.json.
105
+ * Merges file config with any provided overrides, then applies defaults for missing values.
106
+ *
107
+ * @param configOverride - Optional partial config to override file-loaded values.
108
+ * @returns The fully resolved Config object.
109
+ */
110
+ const initConfig = (configOverride) => {
111
+ return (0, exports.initConfigWithMetadata)(configOverride).config;
48
112
  };
49
113
  exports.initConfig = initConfig;
@@ -0,0 +1,11 @@
1
+ import { RendererKind } from '../../declarations/types';
2
+ import { ComponentListObject } from '../../transformers/preview/types';
3
+ type RawDeclaration = Record<string, any>;
4
+ type NormalizeOptions = {
5
+ declarationPath: string;
6
+ fallbackId: string;
7
+ warn: (message: string) => void;
8
+ };
9
+ export declare const inferRendererFromEntries: (entries?: Record<string, string | undefined>) => RendererKind | undefined;
10
+ export declare const normalizeComponentDeclaration: (raw: RawDeclaration, options: NormalizeOptions) => ComponentListObject;
11
+ export {};
@@ -0,0 +1,119 @@
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.normalizeComponentDeclaration = exports.inferRendererFromEntries = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const isCsfStoryFile = (filePath) => {
9
+ return !!filePath && /\.stories\.(jsx|tsx|js|ts)$/.test(filePath);
10
+ };
11
+ const isReactComponentFile = (filePath) => {
12
+ return !!filePath && /\.(jsx|tsx)$/.test(filePath) && !isCsfStoryFile(filePath);
13
+ };
14
+ const isHandlebarsFile = (filePath) => {
15
+ return !!filePath && /\.hbs$/.test(filePath);
16
+ };
17
+ const inferRendererFromEntries = (entries) => {
18
+ if (!entries)
19
+ return undefined;
20
+ if (entries.story || isCsfStoryFile(entries.template)) {
21
+ return 'csf';
22
+ }
23
+ if (entries.component || isReactComponentFile(entries.template)) {
24
+ return 'react';
25
+ }
26
+ if (isHandlebarsFile(entries.template)) {
27
+ return 'handlebars';
28
+ }
29
+ return undefined;
30
+ };
31
+ exports.inferRendererFromEntries = inferRendererFromEntries;
32
+ const resolveEntryPaths = (entries, declarationPath) => {
33
+ const declarationDir = path_1.default.dirname(declarationPath);
34
+ const resolved = {};
35
+ for (const [key, value] of Object.entries(entries)) {
36
+ if (!value)
37
+ continue;
38
+ resolved[key] = path_1.default.resolve(declarationDir, value);
39
+ }
40
+ return resolved;
41
+ };
42
+ const normalizePreviews = (previews, declarationPath, warn) => {
43
+ if (!previews)
44
+ return previews;
45
+ const normalized = Object.assign({}, previews);
46
+ for (const [previewName, previewValue] of Object.entries(previews)) {
47
+ if (!previewValue || typeof previewValue !== 'object')
48
+ continue;
49
+ const preview = Object.assign({}, previewValue);
50
+ if (preview.args !== undefined) {
51
+ if (preview.values !== undefined) {
52
+ warn(`Preview "${previewName}" in "${declarationPath}" defines both "args" and "values". Using "args".`);
53
+ }
54
+ preview.values = preview.args;
55
+ delete preview.args;
56
+ }
57
+ normalized[previewName] = preview;
58
+ }
59
+ return normalized;
60
+ };
61
+ const normalizeBestPracticeFields = (raw, declarationPath, warn) => {
62
+ const normalized = Object.assign({}, raw);
63
+ const hasCamelShouldDo = normalized.shouldDo !== undefined;
64
+ const hasSnakeShouldDo = normalized.should_do !== undefined;
65
+ const hasCamelShouldNotDo = normalized.shouldNotDo !== undefined;
66
+ const hasSnakeShouldNotDo = normalized.should_not_do !== undefined;
67
+ if (hasCamelShouldDo && hasSnakeShouldDo) {
68
+ warn(`"${declarationPath}" defines both "shouldDo" and "should_do". Using "shouldDo".`);
69
+ }
70
+ if (hasCamelShouldNotDo && hasSnakeShouldNotDo) {
71
+ warn(`"${declarationPath}" defines both "shouldNotDo" and "should_not_do". Using "shouldNotDo".`);
72
+ }
73
+ if (hasCamelShouldDo) {
74
+ normalized.should_do = normalized.shouldDo;
75
+ }
76
+ if (hasCamelShouldNotDo) {
77
+ normalized.should_not_do = normalized.shouldNotDo;
78
+ }
79
+ if (!hasCamelShouldDo && hasSnakeShouldDo) {
80
+ normalized.shouldDo = normalized.should_do;
81
+ }
82
+ if (!hasCamelShouldNotDo && hasSnakeShouldNotDo) {
83
+ normalized.shouldNotDo = normalized.should_not_do;
84
+ }
85
+ return normalized;
86
+ };
87
+ const normalizeComponentDeclaration = (raw, options) => {
88
+ const normalizedRaw = normalizeBestPracticeFields(Object.assign({}, raw), options.declarationPath, options.warn);
89
+ const entries = resolveEntryPaths(Object.assign({}, (normalizedRaw.entries || {})), options.declarationPath);
90
+ const resolvedRenderer = normalizedRaw.renderer || (0, exports.inferRendererFromEntries)(entries);
91
+ if (resolvedRenderer === 'react') {
92
+ const componentPath = entries.component || entries.template;
93
+ if (!componentPath) {
94
+ throw new Error(`Component "${options.fallbackId}" in "${options.declarationPath}" uses renderer "react" but is missing entries.component`);
95
+ }
96
+ entries.component = componentPath;
97
+ entries.template = componentPath;
98
+ }
99
+ if (resolvedRenderer === 'csf') {
100
+ const storyPath = entries.story || entries.template;
101
+ if (!storyPath) {
102
+ throw new Error(`Component "${options.fallbackId}" in "${options.declarationPath}" uses renderer "csf" but is missing entries.story`);
103
+ }
104
+ entries.story = storyPath;
105
+ entries.template = storyPath;
106
+ }
107
+ if (resolvedRenderer === 'handlebars') {
108
+ if (!entries.template) {
109
+ throw new Error(`Component "${options.fallbackId}" in "${options.declarationPath}" uses renderer "handlebars" but is missing entries.template`);
110
+ }
111
+ }
112
+ if (normalizedRaw.page && normalizedRaw.page.slices && !Array.isArray(normalizedRaw.page.slices)) {
113
+ throw new Error(`Component "${options.fallbackId}" in "${options.declarationPath}" has invalid page.slices; expected an array.`);
114
+ }
115
+ const normalizedPreviews = normalizePreviews(normalizedRaw.previews, options.declarationPath, options.warn);
116
+ const explicitId = typeof normalizedRaw.id === 'string' && normalizedRaw.id.trim().length > 0 ? normalizedRaw.id.trim() : undefined;
117
+ return Object.assign(Object.assign({}, normalizedRaw), { id: explicitId || options.fallbackId, title: normalizedRaw.name || normalizedRaw.title || '', renderer: resolvedRenderer, entries, previews: normalizedPreviews });
118
+ };
119
+ exports.normalizeComponentDeclaration = normalizeComponentDeclaration;
@@ -0,0 +1,8 @@
1
+ import { PatternListObject } from '../../transformers/preview/types';
2
+ type RawPatternDeclaration = Record<string, any>;
3
+ type NormalizePatternOptions = {
4
+ declarationPath: string;
5
+ fallbackId: string;
6
+ };
7
+ export declare const normalizePatternDeclaration: (raw: RawPatternDeclaration, options: NormalizePatternOptions) => PatternListObject;
8
+ export {};
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizePatternDeclaration = void 0;
4
+ const normalizePatternDeclaration = (raw, options) => {
5
+ if (!raw.name || typeof raw.name !== 'string') {
6
+ throw new Error(`Pattern "${options.fallbackId}" in "${options.declarationPath}" is missing a required "name" field.`);
7
+ }
8
+ if (!Array.isArray(raw.components) || raw.components.length === 0) {
9
+ throw new Error(`Pattern "${options.fallbackId}" in "${options.declarationPath}" must define a non-empty "components" array.`);
10
+ }
11
+ const components = raw.components.map((ref, index) => {
12
+ if (!ref || typeof ref.id !== 'string' || ref.id.trim().length === 0) {
13
+ throw new Error(`Pattern "${options.fallbackId}" in "${options.declarationPath}" has an invalid component reference at index ${index}: missing "id".`);
14
+ }
15
+ const preview = typeof ref.preview === 'string' ? ref.preview.trim() : undefined;
16
+ return {
17
+ id: ref.id.trim(),
18
+ preview,
19
+ args: ref.args && typeof ref.args === 'object' ? Object.assign({}, ref.args) : undefined,
20
+ };
21
+ });
22
+ const explicitId = typeof raw.id === 'string' && raw.id.trim().length > 0
23
+ ? raw.id.trim()
24
+ : undefined;
25
+ return {
26
+ id: explicitId || options.fallbackId,
27
+ title: raw.name,
28
+ description: typeof raw.description === 'string' ? raw.description : undefined,
29
+ group: typeof raw.group === 'string' ? raw.group : undefined,
30
+ tags: Array.isArray(raw.tags) ? raw.tags.filter((t) => typeof t === 'string') : undefined,
31
+ components,
32
+ path: '',
33
+ };
34
+ };
35
+ exports.normalizePatternDeclaration = normalizePatternDeclaration;
@@ -1,4 +1,4 @@
1
- import { Config, RuntimeConfig } from '../types/config';
1
+ import { Config, ConfigFileEntry, RuntimeConfig } from '../types/config';
2
2
  /**
3
3
  * Handoff instance shape needed by initRuntimeConfig.
4
4
  * Avoids importing the full Handoff class to prevent circular deps.
@@ -6,15 +6,16 @@ import { Config, RuntimeConfig } from '../types/config';
6
6
  interface HandoffContext {
7
7
  config: Config;
8
8
  workingPath: string;
9
+ modulePath: string;
9
10
  }
10
11
  /**
11
12
  * Initializes the runtime configuration by resolving component entries,
12
13
  * SCSS/JS paths, and transformer options from the handoff config.
13
14
  *
14
15
  * @param handoff - Object with config and workingPath.
15
- * @returns A tuple of [RuntimeConfig, configFilePaths].
16
+ * @returns A tuple of [RuntimeConfig, configFilePaths, configFileIndex].
16
17
  */
17
- export declare const initRuntimeConfig: (handoff: HandoffContext) => [runtimeConfig: RuntimeConfig, configs: string[]];
18
+ export declare const initRuntimeConfig: (handoff: HandoffContext) => [runtimeConfig: RuntimeConfig, configs: string[], configFileIndex: Map<string, ConfigFileEntry>];
18
19
  /**
19
20
  * Returns a list of component directories for a given path.
20
21
  *
@@ -29,6 +30,11 @@ export declare const initRuntimeConfig: (handoff: HandoffContext) => [runtimeCon
29
30
  * @returns An array of string paths to component directories.
30
31
  */
31
32
  export declare const getComponentsForPath: (searchPath: string) => string[];
33
+ /**
34
+ * Generic directory discovery for both components and patterns.
35
+ * Reuses the same logic as getComponentsForPath.
36
+ */
37
+ export declare const getItemsForPath: (searchPath: string) => string[];
32
38
  /**
33
39
  * Recursively converts all keys and string values in an object to lowercase.
34
40
  */