handoff-app 1.1.3 → 1.2.0-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) 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 +7 -2
  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 +93 -0
  13. package/dist/app-builder/config-diff/types.d.ts +19 -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 +56 -102
  21. package/dist/cache/build-cache.js +14 -5
  22. package/dist/cli/create.js +67 -23
  23. package/dist/cli/eject.js +48 -7
  24. package/dist/cli/make.js +29 -4
  25. package/dist/cli/scaffold.js +60 -19
  26. package/dist/cli.js +1 -1
  27. package/dist/config/helpers.d.ts +7 -0
  28. package/dist/config/helpers.js +31 -0
  29. package/dist/config/index.d.ts +2 -1
  30. package/dist/config/index.js +5 -1
  31. package/dist/config/loader.d.ts +10 -1
  32. package/dist/config/loader.js +92 -28
  33. package/dist/config/normalizers/declaration.d.ts +11 -0
  34. package/dist/config/normalizers/declaration.js +119 -0
  35. package/dist/config/normalizers/pattern.d.ts +9 -0
  36. package/dist/config/normalizers/pattern.js +38 -0
  37. package/dist/config/runtime.d.ts +9 -3
  38. package/dist/config/runtime.js +254 -33
  39. package/dist/declarations/helpers.d.ts +6 -0
  40. package/dist/declarations/helpers.js +19 -0
  41. package/dist/declarations/index.d.ts +2 -0
  42. package/dist/declarations/index.js +9 -0
  43. package/dist/declarations/types.d.ts +60 -0
  44. package/dist/declarations/types.js +2 -0
  45. package/dist/index.d.ts +12 -1
  46. package/dist/index.js +34 -5
  47. package/dist/pipeline/index.d.ts +1 -0
  48. package/dist/pipeline/index.js +3 -1
  49. package/dist/pipeline/patterns.d.ts +6 -0
  50. package/dist/pipeline/patterns.js +21 -0
  51. package/dist/transformers/docgen/index.js +12 -2
  52. package/dist/transformers/plugins/csf-render.js +170 -34
  53. package/dist/transformers/plugins/ssr-render.js +18 -2
  54. package/dist/transformers/preview/component/api.js +16 -7
  55. package/dist/transformers/preview/component/builder.js +9 -3
  56. package/dist/transformers/preview/component/css.d.ts +8 -0
  57. package/dist/transformers/preview/component/html.js +14 -5
  58. package/dist/transformers/preview/component/previews.d.ts +9 -0
  59. package/dist/transformers/preview/component/previews.js +10 -0
  60. package/dist/transformers/preview/pattern/api.d.ts +7 -0
  61. package/dist/transformers/preview/pattern/api.js +61 -0
  62. package/dist/transformers/preview/pattern/builder.d.ts +9 -0
  63. package/dist/transformers/preview/pattern/builder.js +86 -0
  64. package/dist/transformers/preview/pattern/html.d.ts +19 -0
  65. package/dist/transformers/preview/pattern/html.js +94 -0
  66. package/dist/transformers/preview/types.d.ts +54 -0
  67. package/dist/transformers/utils/csf.d.ts +16 -0
  68. package/dist/transformers/utils/csf.js +30 -0
  69. package/dist/transformers/utils/index.d.ts +1 -0
  70. package/dist/transformers/utils/index.js +2 -0
  71. package/dist/transformers/utils/module.d.ts +5 -0
  72. package/dist/transformers/utils/module.js +26 -13
  73. package/dist/transformers/utils/usage.d.ts +23 -0
  74. package/dist/transformers/utils/usage.js +294 -0
  75. package/dist/types/config.d.ts +32 -7
  76. package/dist/types/preview.d.ts +2 -0
  77. package/docs/cli.md +3 -3
  78. package/package.json +3 -1
  79. package/src/app/components/Component/PageSliceResolver.tsx +6 -6
  80. package/src/app/components/Component/Preview.tsx +17 -12
  81. package/src/app/components/Component/slices/ComponentDisplaySlice.tsx +16 -6
  82. package/src/app/components/Markdown/CodeHighlight.tsx +60 -11
  83. package/src/app/components/util/index.ts +71 -2
  84. package/src/app/pages/system/pattern/[pattern]/index.tsx +223 -0
  85. package/src/app/pages/system/pattern/index.tsx +14 -0
  86. package/src/app-builder/README.md +37 -9
  87. package/src/app-builder/build.ts +8 -2
  88. package/src/app-builder/config-diff/index.ts +4 -0
  89. package/src/app-builder/config-diff/registry.ts +23 -0
  90. package/src/app-builder/config-diff/snapshot.ts +51 -0
  91. package/src/app-builder/config-diff/strategies/pattern.ts +65 -0
  92. package/src/app-builder/config-diff/types.ts +21 -0
  93. package/src/app-builder/watchers/component.ts +72 -0
  94. package/src/app-builder/watchers/utils.ts +44 -0
  95. package/src/app-builder/watchers.ts +62 -80
  96. package/src/cache/build-cache.ts +14 -5
  97. package/src/cli/create.ts +86 -25
  98. package/src/cli/eject.ts +29 -7
  99. package/src/cli/make.ts +32 -5
  100. package/src/cli/scaffold.ts +68 -21
  101. package/src/cli.ts +1 -1
  102. package/src/config/README.md +10 -1
  103. package/src/config/helpers.ts +43 -0
  104. package/src/config/index.ts +4 -1
  105. package/src/config/loader.ts +110 -31
  106. package/src/config/normalizers/declaration.ts +176 -0
  107. package/src/config/normalizers/pattern.ts +63 -0
  108. package/src/config/runtime.ts +310 -35
  109. package/src/declarations/helpers.ts +36 -0
  110. package/src/declarations/index.ts +19 -0
  111. package/src/declarations/types.ts +70 -0
  112. package/src/index.ts +47 -6
  113. package/src/pipeline/index.ts +1 -0
  114. package/src/pipeline/patterns.ts +10 -0
  115. package/src/transformers/README.md +2 -2
  116. package/src/transformers/docgen/index.ts +13 -2
  117. package/src/transformers/plugins/csf-render.ts +216 -49
  118. package/src/transformers/plugins/ssr-render.ts +23 -7
  119. package/src/transformers/preview/component/api.ts +27 -7
  120. package/src/transformers/preview/component/builder.ts +21 -4
  121. package/src/transformers/preview/component/html.ts +18 -5
  122. package/src/transformers/preview/component/previews.ts +12 -0
  123. package/src/transformers/preview/pattern/api.ts +66 -0
  124. package/src/transformers/preview/pattern/builder.ts +94 -0
  125. package/src/transformers/preview/pattern/html.ts +107 -0
  126. package/src/transformers/preview/types.ts +58 -1
  127. package/src/transformers/utils/csf.ts +48 -0
  128. package/src/transformers/utils/index.ts +3 -0
  129. package/src/transformers/utils/module.ts +33 -18
  130. package/src/transformers/utils/usage.ts +319 -0
  131. package/src/types/README.md +1 -1
  132. package/src/types/config.ts +33 -7
  133. package/src/types/preview.ts +2 -0
  134. package/templates/default/components/button/Button.jsx.tpl +64 -0
  135. package/templates/default/components/button/button.react.handoff.js.tpl +31 -0
  136. package/templates/default/components/button/button.react.handoff.ts.tpl +31 -0
  137. package/templates/default/handoff.config.js.tpl +29 -16
  138. package/templates/default/handoff.config.ts.tpl +196 -0
  139. package/templates/default/handoff.config.with-examples.js.tpl +28 -17
  140. package/templates/default/handoff.config.with-examples.ts.tpl +195 -0
  141. package/templates/default/package.json.tpl +1 -3
  142. package/handoff.config.js +0 -90
  143. package/templates/default/components/button/button.js.tpl +0 -62
@@ -1,27 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
2
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
3
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
4
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -35,17 +12,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
13
  };
37
14
  Object.defineProperty(exports, "__esModule", { value: true });
38
- exports.watchComponentDirectories = exports.watchRuntimeConfiguration = exports.watchRuntimeComponents = exports.getRuntimeComponentsPathsToWatch = exports.mapEntryTypeToSegment = exports.watchGlobalEntries = exports.watchPages = exports.watchAppSource = exports.watchPublicDirectory = void 0;
15
+ exports.watchComponentDirectories = exports.watchRuntimeConfiguration = exports.watchRuntimeComponents = exports.watchGlobalEntries = exports.watchPages = exports.watchAppSource = exports.watchPublicDirectory = exports.mapEntryTypeToSegment = exports.getRuntimeComponentsPathsToWatch = void 0;
39
16
  const chokidar_1 = __importDefault(require("chokidar"));
40
17
  const fs_extra_1 = __importDefault(require("fs-extra"));
41
18
  const path_1 = __importDefault(require("path"));
19
+ const builder_1 = __importDefault(require("../transformers/preview/component/builder"));
42
20
  const css_1 = require("../transformers/preview/component/css");
43
21
  const javascript_1 = require("../transformers/preview/component/javascript");
44
- const builder_1 = __importStar(require("../transformers/preview/component/builder"));
45
22
  const logger_1 = require("../utils/logger");
46
23
  const path_2 = require("../utils/path");
47
24
  const client_config_1 = require("./client-config");
25
+ const config_diff_1 = require("./config-diff");
48
26
  const paths_1 = require("./paths");
27
+ const component_1 = require("./watchers/component");
28
+ const utils_1 = require("./watchers/utils");
29
+ var component_2 = require("./watchers/component");
30
+ Object.defineProperty(exports, "getRuntimeComponentsPathsToWatch", { enumerable: true, get: function () { return component_2.getRuntimeComponentsPathsToWatch; } });
31
+ Object.defineProperty(exports, "mapEntryTypeToSegment", { enumerable: true, get: function () { return component_2.mapEntryTypeToSegment; } });
49
32
  /**
50
33
  * Watches the working public directory for changes and updates the app.
51
34
  */
@@ -56,8 +39,7 @@ const watchPublicDirectory = (handoff, wss, state, chokidarConfig) => {
56
39
  case 'add':
57
40
  case 'change':
58
41
  case 'unlink':
59
- if (!state.debounce) {
60
- state.debounce = true;
42
+ yield (0, utils_1.scheduleHandler)(state, 'publicDirectory', () => __awaiter(void 0, void 0, void 0, function* () {
61
43
  try {
62
44
  logger_1.Logger.warn('Public directory changed. Handoff will ingest the new data...');
63
45
  yield (0, paths_1.syncPublicFiles)(handoff);
@@ -66,10 +48,7 @@ const watchPublicDirectory = (handoff, wss, state, chokidarConfig) => {
66
48
  catch (e) {
67
49
  logger_1.Logger.error('Error syncing public directory:', e);
68
50
  }
69
- finally {
70
- state.debounce = false;
71
- }
72
- }
51
+ }));
73
52
  break;
74
53
  }
75
54
  }));
@@ -151,8 +130,7 @@ const watchGlobalEntries = (handoff, state, chokidarConfig) => {
151
130
  case 'add':
152
131
  case 'change':
153
132
  case 'unlink':
154
- if (!state.debounce) {
155
- state.debounce = true;
133
+ yield (0, utils_1.scheduleHandler)(state, 'globalEntries', () => __awaiter(void 0, void 0, void 0, function* () {
156
134
  try {
157
135
  logger_1.Logger.warn('Global entry changed. Rebuilding bundles and components...');
158
136
  const shouldRebuildMainCss = !!scssEntry && scssPathsToWatch.some((watchPath) => (0, path_2.isPathInside)(file, watchPath));
@@ -164,57 +142,17 @@ const watchGlobalEntries = (handoff, state, chokidarConfig) => {
164
142
  yield (0, javascript_1.buildMainJS)(handoff);
165
143
  }
166
144
  yield (0, builder_1.default)(handoff);
145
+ yield (0, config_diff_1.runAllFinalizers)(handoff);
167
146
  }
168
147
  catch (e) {
169
148
  logger_1.Logger.error('Error rebuilding after global entry change:', e);
170
149
  }
171
- finally {
172
- state.debounce = false;
173
- }
174
- }
150
+ }));
175
151
  break;
176
152
  }
177
153
  }));
178
154
  };
179
155
  exports.watchGlobalEntries = watchGlobalEntries;
180
- /**
181
- * Maps configuration entry types to component segments.
182
- */
183
- const mapEntryTypeToSegment = (type) => {
184
- return {
185
- js: builder_1.ComponentSegment.JavaScript,
186
- scss: builder_1.ComponentSegment.Style,
187
- template: builder_1.ComponentSegment.Previews,
188
- templates: builder_1.ComponentSegment.Previews,
189
- }[type];
190
- };
191
- exports.mapEntryTypeToSegment = mapEntryTypeToSegment;
192
- /**
193
- * Gets the paths of runtime components to watch.
194
- *
195
- * @returns A Map of paths to watch and their entry types
196
- */
197
- const getRuntimeComponentsPathsToWatch = (handoff) => {
198
- var _a, _b, _c;
199
- const result = new Map();
200
- for (const runtimeComponentId of Object.keys((_b = (_a = handoff.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entries.components) !== null && _b !== void 0 ? _b : {})) {
201
- const runtimeComponent = handoff.runtimeConfig.entries.components[runtimeComponentId];
202
- for (const [runtimeComponentEntryType, runtimeComponentEntryPath] of Object.entries((_c = runtimeComponent.entries) !== null && _c !== void 0 ? _c : {})) {
203
- const normalizedComponentEntryPath = runtimeComponentEntryPath;
204
- if (fs_extra_1.default.existsSync(normalizedComponentEntryPath)) {
205
- const entryType = runtimeComponentEntryType;
206
- if (fs_extra_1.default.statSync(normalizedComponentEntryPath).isFile()) {
207
- result.set(path_1.default.resolve(normalizedComponentEntryPath), entryType);
208
- }
209
- else {
210
- result.set(normalizedComponentEntryPath, entryType);
211
- }
212
- }
213
- }
214
- }
215
- return result;
216
- };
217
- exports.getRuntimeComponentsPathsToWatch = getRuntimeComponentsPathsToWatch;
218
156
  /**
219
157
  * Watches runtime components for changes.
220
158
  */
@@ -238,21 +176,18 @@ const watchRuntimeComponents = (handoff, state, runtimeComponentPathsToWatch) =>
238
176
  case 'add':
239
177
  case 'change':
240
178
  case 'unlink':
241
- if (!state.debounce) {
242
- state.debounce = true;
179
+ yield (0, utils_1.scheduleHandler)(state, `runtimeComponent:${normalizedFile}`, () => __awaiter(void 0, void 0, void 0, function* () {
243
180
  try {
244
181
  const entryType = runtimeComponentEntryTypeByPath.get(normalizedFile);
245
- const segmentToUpdate = entryType ? (0, exports.mapEntryTypeToSegment)(entryType) : undefined;
246
- const componentDir = path_1.default.basename(path_1.default.dirname(file));
247
- yield (0, builder_1.default)(handoff, componentDir, segmentToUpdate);
182
+ const segmentToUpdate = entryType ? (0, component_1.mapEntryTypeToSegment)(entryType) : undefined;
183
+ const componentId = (0, component_1.resolveComponentIdForChangedFile)(handoff, file);
184
+ yield (0, builder_1.default)(handoff, componentId, segmentToUpdate);
185
+ yield (0, config_diff_1.runAllFinalizers)(handoff);
248
186
  }
249
187
  catch (e) {
250
188
  logger_1.Logger.error('Error processing component:', e);
251
189
  }
252
- finally {
253
- state.debounce = false;
254
- }
255
- }
190
+ }));
256
191
  break;
257
192
  }
258
193
  }));
@@ -261,6 +196,11 @@ const watchRuntimeComponents = (handoff, state, runtimeComponentPathsToWatch) =>
261
196
  exports.watchRuntimeComponents = watchRuntimeComponents;
262
197
  /**
263
198
  * Watches the runtime configuration for changes.
199
+ *
200
+ * Uses the generic {@link ConfigDiffStrategy} registry so that each entity
201
+ * kind (patterns today, potentially themes / slots later) supplies its own
202
+ * snapshot → diff → selective-rebuild logic without the watcher needing to
203
+ * know the details.
264
204
  */
265
205
  const watchRuntimeConfiguration = (handoff, state) => {
266
206
  if (state.runtimeConfigurationWatcher) {
@@ -269,26 +209,44 @@ const watchRuntimeConfiguration = (handoff, state) => {
269
209
  if (handoff.getConfigFilePaths().length > 0) {
270
210
  state.runtimeConfigurationWatcher = chokidar_1.default.watch(handoff.getConfigFilePaths(), { ignoreInitial: true });
271
211
  state.runtimeConfigurationWatcher.on('all', (event, file) => __awaiter(void 0, void 0, void 0, function* () {
212
+ const normalizedFile = (0, path_2.normalizePathForCompare)(file);
272
213
  switch (event) {
273
214
  case 'add':
274
215
  case 'change':
275
216
  case 'unlink':
276
- if (!state.debounce) {
277
- state.debounce = true;
217
+ yield (0, utils_1.scheduleHandler)(state, `runtimeConfig:${normalizedFile}`, () => __awaiter(void 0, void 0, void 0, function* () {
218
+ var _a;
278
219
  try {
279
- file = path_1.default.dirname(file);
220
+ const changedFile = file;
221
+ // Look up the entity this config file belongs to BEFORE reload.
222
+ const entryBefore = handoff.getConfigFileEntry(changedFile);
223
+ const strategy = entryBefore ? (0, config_diff_1.getStrategy)(entryBefore.kind) : undefined;
224
+ const handle = entryBefore && strategy ? strategy.capture(handoff, entryBefore.entityId) : undefined;
280
225
  handoff.reload();
281
226
  yield (0, client_config_1.persistClientConfig)(handoff);
282
- (0, exports.watchRuntimeComponents)(handoff, state, (0, exports.getRuntimeComponentsPathsToWatch)(handoff));
283
- yield (0, builder_1.default)(handoff, path_1.default.basename(file));
227
+ (0, exports.watchRuntimeComponents)(handoff, state, (0, component_1.getRuntimeComponentsPathsToWatch)(handoff));
228
+ (0, exports.watchRuntimeConfiguration)(handoff, state);
229
+ const entryAfter = handoff.getConfigFileEntry(changedFile);
230
+ const normalizedChanged = (0, path_2.normalizePathForCompare)(changedFile);
231
+ const normalizedMainConfig = handoff.getMainConfigFilePath()
232
+ ? (0, path_2.normalizePathForCompare)(handoff.getMainConfigFilePath())
233
+ : undefined;
234
+ if (normalizedMainConfig && normalizedChanged === normalizedMainConfig) {
235
+ yield (0, builder_1.default)(handoff);
236
+ }
237
+ else if (handle) {
238
+ yield handle.apply(handoff, entryAfter === null || entryAfter === void 0 ? void 0 : entryAfter.entityId);
239
+ }
240
+ else {
241
+ const effectiveId = (_a = entryAfter === null || entryAfter === void 0 ? void 0 : entryAfter.entityId) !== null && _a !== void 0 ? _a : path_1.default.basename(path_1.default.dirname(changedFile));
242
+ yield (0, builder_1.default)(handoff, effectiveId);
243
+ }
244
+ yield (0, config_diff_1.runAllFinalizers)(handoff);
284
245
  }
285
246
  catch (e) {
286
247
  logger_1.Logger.error('Error reloading runtime configuration:', e);
287
248
  }
288
- finally {
289
- state.debounce = false;
290
- }
291
- }
249
+ }));
292
250
  break;
293
251
  }
294
252
  }));
@@ -321,35 +279,31 @@ const watchComponentDirectories = (handoff, state, chokidarConfig) => {
321
279
  const knownComponents = new Set(Object.keys((_e = (_d = (_c = handoff.runtimeConfig) === null || _c === void 0 ? void 0 : _c.entries) === null || _d === void 0 ? void 0 : _d.components) !== null && _e !== void 0 ? _e : {}));
322
280
  state.componentDirectoriesWatcher = chokidar_1.default.watch(dirsToWatch, Object.assign(Object.assign({}, chokidarConfig), { depth: 1 }));
323
281
  state.componentDirectoriesWatcher.on('add', (file) => __awaiter(void 0, void 0, void 0, function* () {
324
- var _a, _b, _c;
325
282
  const basename = path_1.default.basename(file);
326
283
  const dirName = path_1.default.basename(path_1.default.dirname(file));
327
284
  const isConfigFile = basename.endsWith('.json') || basename.endsWith('.js') || basename.endsWith('.cjs');
328
285
  const isNewComponent = isConfigFile && basename.startsWith(dirName) && !knownComponents.has(dirName);
329
286
  if (!isNewComponent)
330
287
  return;
331
- if (!state.debounce) {
332
- state.debounce = true;
288
+ yield (0, utils_1.scheduleHandler)(state, `newComponent:${dirName}`, () => __awaiter(void 0, void 0, void 0, function* () {
289
+ var _a, _b, _c;
333
290
  try {
334
291
  logger_1.Logger.warn(`New component detected: ${dirName}. Reloading configuration...`);
335
292
  handoff.reload();
336
293
  knownComponents.add(dirName);
337
- // Update known set with all current components
338
294
  for (const id of Object.keys((_c = (_b = (_a = handoff.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entries) === null || _b === void 0 ? void 0 : _b.components) !== null && _c !== void 0 ? _c : {})) {
339
295
  knownComponents.add(id);
340
296
  }
341
297
  yield (0, client_config_1.persistClientConfig)(handoff);
342
- (0, exports.watchRuntimeComponents)(handoff, state, (0, exports.getRuntimeComponentsPathsToWatch)(handoff));
298
+ (0, exports.watchRuntimeComponents)(handoff, state, (0, component_1.getRuntimeComponentsPathsToWatch)(handoff));
343
299
  (0, exports.watchRuntimeConfiguration)(handoff, state);
344
300
  yield (0, builder_1.default)(handoff, dirName);
301
+ yield (0, config_diff_1.runAllFinalizers)(handoff);
345
302
  }
346
303
  catch (e) {
347
304
  logger_1.Logger.error('Error processing new component:', e);
348
305
  }
349
- finally {
350
- state.debounce = false;
351
- }
352
- }
306
+ }));
353
307
  }));
354
308
  };
355
309
  exports.watchComponentDirectories = watchComponentDirectories;
@@ -155,8 +155,8 @@ function getComponentFilePaths(handoff, componentId) {
155
155
  files.push(entries.scss);
156
156
  componentDirs.add((0, path_2.normalizePathForCompare)(path_1.default.dirname(entries.scss)));
157
157
  }
158
- // Handle both 'template' (singular) and 'templates' (plural) entry types
159
- const templatePath = entries.template || entries.templates;
158
+ // Handle template-style entries across renderer families
159
+ const templatePath = entries.template || entries.templates || entries.component || entries.story;
160
160
  if (templatePath) {
161
161
  try {
162
162
  const stat = fs_extra_1.default.statSync(templatePath);
@@ -178,10 +178,19 @@ function getComponentFilePaths(handoff, componentId) {
178
178
  }
179
179
  // Find the config file path for this component using exact config filename + directory matching.
180
180
  const configPaths = handoff.getConfigFilePaths();
181
- const expectedConfigFileNames = new Set([`${componentId}.json`, `${componentId}.js`, `${componentId}.cjs`]);
181
+ const expectedConfigFileNames = new Set([
182
+ `${componentId}.handoff.ts`,
183
+ `${componentId}.handoff.js`,
184
+ `${componentId}.handoff.cjs`,
185
+ `${componentId}.handoff.json`,
186
+ `${componentId}.json`,
187
+ `${componentId}.js`,
188
+ `${componentId}.cjs`,
189
+ ]);
182
190
  const matchingConfigPath = configPaths.find((configPath) => {
183
191
  const configFileName = path_1.default.basename(configPath);
184
- if (!expectedConfigFileNames.has(configFileName)) {
192
+ const isModernDeclaration = /\.handoff\.(ts|js|cjs|json)$/.test(configFileName);
193
+ if (!expectedConfigFileNames.has(configFileName) && !isModernDeclaration) {
185
194
  return false;
186
195
  }
187
196
  if (componentDirs.size === 0) {
@@ -210,7 +219,7 @@ function computeComponentFileStates(handoff, componentId) {
210
219
  }
211
220
  let templateDirFiles;
212
221
  if (templateDir) {
213
- templateDirFiles = yield (0, file_state_1.computeDirectoryState)(templateDir, ['.hbs', '.html', '.tsx', '.ts']);
222
+ templateDirFiles = yield (0, file_state_1.computeDirectoryState)(templateDir, ['.hbs', '.html', '.tsx', '.ts', '.jsx', '.js']);
214
223
  }
215
224
  return { files, templateDirFiles };
216
225
  });
@@ -85,22 +85,35 @@ const createDirectory = (dirPath) => {
85
85
  * Get mandatory templates based on whether examples are included
86
86
  * Uses different handoff config templates for blank vs. with-examples projects
87
87
  */
88
- const getMandatoryTemplates = (includeExamples) => [
89
- { source: 'package.json.tpl', destination: 'package.json' },
90
- {
91
- source: includeExamples ? 'handoff.config.with-examples.js.tpl' : 'handoff.config.js.tpl',
92
- destination: 'handoff.config.js'
93
- },
94
- { source: 'tsconfig.json.tpl', destination: 'tsconfig.json' },
95
- { source: 'gitignore.tpl', destination: '.gitignore' },
96
- { source: 'env.tpl', destination: '.env' },
97
- { source: 'README.md.tpl', destination: 'README.md' },
98
- ];
99
- // Example component files (only included when user opts for examples)
100
- const exampleTemplates = [
101
- { source: 'components/button/Button.tsx.tpl', destination: 'components/button/Button.tsx', isExample: true },
102
- { source: 'components/button/button.js.tpl', destination: 'components/button/button.js', isExample: true },
103
- ];
88
+ const getMandatoryTemplates = (includeExamples, languagePreference) => {
89
+ const configFormat = languagePreference;
90
+ const configTemplateBaseName = includeExamples ? 'handoff.config.with-examples' : 'handoff.config';
91
+ const configTemplateSource = `${configTemplateBaseName}.${configFormat === 'typescript' ? 'ts' : 'js'}.tpl`;
92
+ const configDestination = `handoff.config.${configFormat === 'typescript' ? 'ts' : 'js'}`;
93
+ const templates = [
94
+ { source: 'package.json.tpl', destination: 'package.json' },
95
+ {
96
+ source: configTemplateSource,
97
+ destination: configDestination,
98
+ },
99
+ { source: 'gitignore.tpl', destination: '.gitignore' },
100
+ { source: 'env.tpl', destination: '.env' },
101
+ { source: 'README.md.tpl', destination: 'README.md' },
102
+ ];
103
+ if (languagePreference === 'typescript') {
104
+ templates.push({ source: 'tsconfig.json.tpl', destination: 'tsconfig.json' });
105
+ }
106
+ return templates;
107
+ };
108
+ const getExampleTemplates = (languagePreference) => {
109
+ const declarationSuffix = languagePreference === 'typescript' ? 'ts' : 'js';
110
+ const declarationFile = `components/button/button.react.handoff.${declarationSuffix}.tpl`;
111
+ const componentFile = languagePreference === 'typescript' ? 'components/button/Button.tsx.tpl' : 'components/button/Button.jsx.tpl';
112
+ return [
113
+ { source: declarationFile, destination: `components/button/button.handoff.${languagePreference === 'typescript' ? 'ts' : 'js'}`, isExample: true },
114
+ { source: componentFile, destination: `components/button/Button.${languagePreference === 'typescript' ? 'tsx' : 'jsx'}`, isExample: true },
115
+ ];
116
+ };
104
117
  /**
105
118
  * Render a template string by replacing placeholders with values
106
119
  */
@@ -109,18 +122,34 @@ const renderTemplate = (content, data) => {
109
122
  if (!(key in data)) {
110
123
  console.warn(chalk_1.default.yellow(`Warning: Unknown template placeholder '{{${key}}}'`));
111
124
  }
112
- return data[key] || '';
125
+ const value = data[key];
126
+ return typeof value === 'string' ? value : '';
113
127
  });
114
128
  };
129
+ const getDependenciesToInstall = (options) => {
130
+ const dependencies = ['handoff-app@latest'];
131
+ const devDependencies = [];
132
+ if (options.includeExamples) {
133
+ dependencies.push('react', 'react-dom');
134
+ if (options.languagePreference === 'typescript') {
135
+ devDependencies.push('@types/react', '@types/react-dom');
136
+ }
137
+ }
138
+ return { dependencies, devDependencies };
139
+ };
115
140
  /**
116
- * Execute npm install
141
+ * Execute npm install for required dependencies.
117
142
  */
118
- const runNpmInstall = (projectPath) => __awaiter(void 0, void 0, void 0, function* () {
143
+ const installDependencies = (projectPath, options) => __awaiter(void 0, void 0, void 0, function* () {
144
+ const { dependencies, devDependencies } = getDependenciesToInstall(options);
119
145
  const spinner = p.spinner();
120
146
  spinner.start('Installing dependencies...');
121
147
  return new Promise((resolve, reject) => {
122
148
  var _a;
123
- const npmProcess = (0, child_process_1.spawn)('npm', ['install'], {
149
+ const installCommand = devDependencies.length
150
+ ? `npm install --save-prefix="^" ${dependencies.join(' ')} && npm install --save-dev --save-prefix="^" ${devDependencies.join(' ')}`
151
+ : `npm install --save-prefix="^" ${dependencies.join(' ')}`;
152
+ const npmProcess = (0, child_process_1.spawn)(installCommand, {
124
153
  cwd: projectPath,
125
154
  stdio: 'pipe',
126
155
  shell: true,
@@ -208,6 +237,19 @@ const create = () => __awaiter(void 0, void 0, void 0, function* () {
208
237
  process.exit(0);
209
238
  }
210
239
  const includeExamples = projectType === 'with-examples';
240
+ const languagePreferenceSelection = yield p.select({
241
+ message: 'Preferred project language (declarations, examples, and handoff.config)?',
242
+ options: [
243
+ { value: 'typescript', label: 'TypeScript', hint: 'Uses .handoff.ts and handoff.config.ts' },
244
+ { value: 'javascript', label: 'JavaScript', hint: 'Uses .handoff.js and handoff.config.js' },
245
+ ],
246
+ initialValue: 'typescript',
247
+ });
248
+ if (p.isCancel(languagePreferenceSelection)) {
249
+ p.cancel('Project creation cancelled.');
250
+ process.exit(0);
251
+ }
252
+ const languagePreference = languagePreferenceSelection;
211
253
  // Get Figma project ID
212
254
  p.log.step(chalk_1.default.blue('Figma Configuration'));
213
255
  const figmaProjectId = yield p.text({
@@ -233,6 +275,8 @@ const create = () => __awaiter(void 0, void 0, void 0, function* () {
233
275
  projectName: projectName,
234
276
  figmaProjectId: (figmaProjectId || '').trim(),
235
277
  figmaAccessToken: (figmaAccessToken || '').trim(),
278
+ languagePreference,
279
+ includeExamples,
236
280
  };
237
281
  // Create files
238
282
  const spinner = p.spinner();
@@ -243,8 +287,8 @@ const create = () => __awaiter(void 0, void 0, void 0, function* () {
243
287
  }
244
288
  // Combine mandatory templates with example templates based on user choice
245
289
  const templates = [
246
- ...getMandatoryTemplates(includeExamples),
247
- ...(includeExamples ? exampleTemplates : []),
290
+ ...getMandatoryTemplates(includeExamples, languagePreference),
291
+ ...(includeExamples ? getExampleTemplates(languagePreference) : []),
248
292
  ];
249
293
  try {
250
294
  for (const template of templates) {
@@ -267,7 +311,7 @@ const create = () => __awaiter(void 0, void 0, void 0, function* () {
267
311
  throw err;
268
312
  }
269
313
  // Install dependencies
270
- yield runNpmInstall(projectPath);
314
+ yield installDependencies(projectPath, options);
271
315
  // Success message
272
316
  const nextSteps = [`cd ${projectName}`];
273
317
  if (figmaProjectId && figmaAccessToken) {
package/dist/cli/eject.js CHANGED
@@ -1,4 +1,27 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
26
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
27
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -13,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
36
  };
14
37
  Object.defineProperty(exports, "__esModule", { value: true });
15
38
  exports.ejectTheme = exports.ejectPages = exports.ejectConfig = void 0;
39
+ const p = __importStar(require("@clack/prompts"));
16
40
  const fs_extra_1 = __importDefault(require("fs-extra"));
17
41
  const path_1 = __importDefault(require("path"));
18
42
  const logger_1 = require("../utils/logger");
@@ -21,14 +45,31 @@ const logger_1 = require("../utils/logger");
21
45
  * @param handoff
22
46
  */
23
47
  const ejectConfig = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
24
- const configPath = path_1.default.resolve(path_1.default.join(handoff.workingPath, 'handoff.config.js'));
25
- if (fs_extra_1.default.existsSync(configPath)) {
26
- if (!handoff.force) {
27
- logger_1.Logger.error(`Config file already exists. Use "--force" to overwrite.`);
28
- }
48
+ const existingConfigNames = ['handoff.config.ts', 'handoff.config.js', 'handoff.config.cjs', 'handoff.config.json'];
49
+ const existingConfigPaths = existingConfigNames
50
+ .map((name) => path_1.default.resolve(path_1.default.join(handoff.workingPath, name)))
51
+ .filter((filePath) => fs_extra_1.default.existsSync(filePath));
52
+ if (existingConfigPaths.length > 0 && !handoff.force) {
53
+ logger_1.Logger.error(`Config file already exists. Use "--force" to overwrite.`);
54
+ return handoff;
55
+ }
56
+ const defaultFormat = fs_extra_1.default.existsSync(path_1.default.resolve(handoff.workingPath, 'tsconfig.json')) ? 'typescript' : 'javascript';
57
+ const formatSelection = yield p.select({
58
+ message: 'Which config format would you like to eject?',
59
+ options: [
60
+ { value: 'typescript', label: 'TypeScript', hint: 'Generates handoff.config.ts' },
61
+ { value: 'javascript', label: 'JavaScript', hint: 'Generates handoff.config.js' },
62
+ ],
63
+ initialValue: defaultFormat,
64
+ });
65
+ if (p.isCancel(formatSelection)) {
66
+ logger_1.Logger.warn('Config eject cancelled.');
67
+ return handoff;
29
68
  }
30
- // load the template as a string
31
- const template = fs_extra_1.default.readFileSync(path_1.default.resolve(handoff.modulePath, 'config/config.template.js'), 'utf8');
69
+ const useTypeScript = formatSelection === 'typescript';
70
+ const configPath = path_1.default.resolve(path_1.default.join(handoff.workingPath, useTypeScript ? 'handoff.config.ts' : 'handoff.config.js'));
71
+ const templatePath = path_1.default.resolve(handoff.modulePath, useTypeScript ? 'config/config.template.ts' : 'config/config.template.js');
72
+ const template = fs_extra_1.default.readFileSync(templatePath, 'utf8');
32
73
  fs_extra_1.default.writeFileSync(configPath, template);
33
74
  logger_1.Logger.success(`Config ejected to ${configPath}`);
34
75
  return handoff;
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;