handoff-app 1.2.0-0 → 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 (50) hide show
  1. package/dist/app-builder/build.js +24 -10
  2. package/dist/app-builder/config-diff/index.d.ts +1 -1
  3. package/dist/app-builder/config-diff/registry.d.ts +2 -2
  4. package/dist/app-builder/config-diff/registry.js +2 -2
  5. package/dist/app-builder/config-diff/strategies/pattern.js +14 -1
  6. package/dist/app-builder/config-diff/types.d.ts +15 -1
  7. package/dist/app-builder/watchers.js +35 -3
  8. package/dist/app-builder/websocket.js +10 -4
  9. package/dist/cli.js +1 -1
  10. package/dist/config/normalizers/pattern.d.ts +0 -1
  11. package/dist/config/normalizers/pattern.js +0 -3
  12. package/dist/config/runtime.js +10 -4
  13. package/dist/pipeline/patterns.d.ts +3 -1
  14. package/dist/pipeline/patterns.js +2 -2
  15. package/dist/transformers/preview/component/builder.js +52 -5
  16. package/dist/transformers/preview/component/css.js +6 -4
  17. package/dist/transformers/preview/component/javascript.d.ts +0 -1
  18. package/dist/transformers/preview/component/javascript.js +25 -14
  19. package/dist/transformers/preview/pattern/api.d.ts +4 -0
  20. package/dist/transformers/preview/pattern/api.js +19 -1
  21. package/dist/transformers/preview/pattern/builder.d.ts +9 -1
  22. package/dist/transformers/preview/pattern/builder.js +123 -42
  23. package/dist/transformers/utils/vite-logger.js +4 -4
  24. package/dist/transformers/vite-config.js +2 -0
  25. package/dist/utils/duration.d.ts +2 -0
  26. package/dist/utils/duration.js +6 -0
  27. package/dist/utils/logger.d.ts +42 -5
  28. package/dist/utils/logger.js +135 -15
  29. package/package.json +1 -1
  30. package/src/app/pages/system/pattern/[pattern]/index.tsx +178 -55
  31. package/src/app-builder/build.ts +26 -10
  32. package/src/app-builder/config-diff/index.ts +1 -1
  33. package/src/app-builder/config-diff/registry.ts +3 -3
  34. package/src/app-builder/config-diff/strategies/pattern.ts +17 -2
  35. package/src/app-builder/config-diff/types.ts +16 -1
  36. package/src/app-builder/watchers.ts +15 -3
  37. package/src/app-builder/websocket.ts +16 -5
  38. package/src/cli.ts +1 -1
  39. package/src/config/normalizers/pattern.ts +0 -7
  40. package/src/config/runtime.ts +11 -4
  41. package/src/pipeline/patterns.ts +5 -3
  42. package/src/transformers/preview/component/builder.ts +61 -5
  43. package/src/transformers/preview/component/css.ts +6 -5
  44. package/src/transformers/preview/component/javascript.ts +29 -19
  45. package/src/transformers/preview/pattern/api.ts +17 -0
  46. package/src/transformers/preview/pattern/builder.ts +156 -58
  47. package/src/transformers/utils/vite-logger.ts +4 -4
  48. package/src/transformers/vite-config.ts +2 -0
  49. package/src/utils/duration.ts +3 -0
  50. package/src/utils/logger.ts +143 -16
@@ -107,9 +107,11 @@ const buildApp = (handoff, skipComponents) => __awaiter(void 0, void 0, void 0,
107
107
  // Build app
108
108
  const buildResult = cross_spawn_1.default.sync('npx', ['next', 'build'], {
109
109
  cwd: appPath,
110
- stdio: 'inherit',
110
+ stdio: ['inherit', 'pipe', 'pipe'],
111
111
  env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'production' }),
112
112
  });
113
+ logger_1.Logger.childProcessBuffer(buildResult.stdout);
114
+ logger_1.Logger.childProcessBuffer(buildResult.stderr);
113
115
  if (buildResult.status !== 0) {
114
116
  let errorMsg = `Next.js build failed with exit code ${buildResult.status}`;
115
117
  if (buildResult.error) {
@@ -155,19 +157,28 @@ const watchApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
155
157
  // create empty directory
156
158
  yield fs_extra_1.default.ensureDir(moduleOutput);
157
159
  }
160
+ logger_1.Logger.info(`Starting Next.js dev server (Turbopack) at http://${hostname}:${port}…`);
158
161
  const nextProcess = (0, cross_spawn_1.default)('npx', ['next', 'dev', '--turbopack', '--port', String(port)], {
159
162
  cwd: appPath,
160
- stdio: 'inherit',
163
+ stdio: ['inherit', 'pipe', 'pipe'],
161
164
  env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'development' }),
162
165
  });
163
- logger_1.Logger.success(`Ready on http://${hostname}:${port}`);
166
+ logger_1.Logger.pipeChildStreams(nextProcess.stdout, nextProcess.stderr);
164
167
  nextProcess.on('error', (error) => {
165
- logger_1.Logger.error(`Next.js dev process error: ${error}`);
168
+ logger_1.Logger.error(`Next.js dev process failed to start: ${error}`);
166
169
  process.exit(1);
167
170
  });
168
- nextProcess.on('close', (code) => {
169
- logger_1.Logger.success(`Next.js dev process closed with code ${code}`);
170
- process.exit(code);
171
+ nextProcess.on('close', (code, signal) => {
172
+ if (code === 0) {
173
+ logger_1.Logger.success(`Next.js dev process exited normally`);
174
+ }
175
+ else if (signal) {
176
+ logger_1.Logger.warn(`Next.js dev process stopped (${signal})`);
177
+ }
178
+ else {
179
+ logger_1.Logger.error(`Next.js dev process exited with code ${code}`);
180
+ }
181
+ process.exit(code !== null && code !== void 0 ? code : 1);
171
182
  });
172
183
  const wss = yield (0, websocket_1.createWebSocketServer)((_d = (_c = handoff.config.app.ports) === null || _c === void 0 ? void 0 : _c.websocket) !== null && _d !== void 0 ? _d : 3001);
173
184
  const chokidarConfig = {
@@ -205,12 +216,15 @@ const devApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
205
216
  }
206
217
  // Persist client configuration
207
218
  yield (0, client_config_1.persistClientConfig)(handoff);
208
- // Run
209
- const devResult = cross_spawn_1.default.sync('npx', ['next', 'dev', '--turbopack', '--port', String((_b = (_a = handoff.config.app.ports) === null || _a === void 0 ? void 0 : _a.app) !== null && _b !== void 0 ? _b : 3000)], {
219
+ const devPort = (_b = (_a = handoff.config.app.ports) === null || _a === void 0 ? void 0 : _a.app) !== null && _b !== void 0 ? _b : 3000;
220
+ logger_1.Logger.info(`Starting Next.js dev server (Turbopack) on port ${devPort}…`);
221
+ const devResult = cross_spawn_1.default.sync('npx', ['next', 'dev', '--turbopack', '--port', String(devPort)], {
210
222
  cwd: appPath,
211
- stdio: 'inherit',
223
+ stdio: ['inherit', 'pipe', 'pipe'],
212
224
  env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'development' }),
213
225
  });
226
+ logger_1.Logger.childProcessBuffer(devResult.stdout);
227
+ logger_1.Logger.childProcessBuffer(devResult.stderr);
214
228
  if (devResult.status !== 0) {
215
229
  let errorMsg = `Next.js dev failed with exit code ${devResult.status}`;
216
230
  if (devResult.error) {
@@ -1,4 +1,4 @@
1
- export type { ConfigDiffStrategy, RebuildHandle } from './types';
1
+ export type { ConfigDiffStrategy, FinalizeContext, RebuildHandle } from './types';
2
2
  export { type MapSnapshot, diffMapSnapshots, stableStringify } from './snapshot';
3
3
  export { patternDiffStrategy } from './strategies/pattern';
4
4
  export { getAllStrategies, getStrategy, runAllFinalizers } from './registry';
@@ -1,9 +1,9 @@
1
1
  import Handoff from '../..';
2
- import { ConfigDiffStrategy } from './types';
2
+ import { ConfigDiffStrategy, FinalizeContext } from './types';
3
3
  export declare const getStrategy: (kind: string) => ConfigDiffStrategy | undefined;
4
4
  export declare const getAllStrategies: () => ConfigDiffStrategy[];
5
5
  /**
6
6
  * Runs every registered strategy finalizer.
7
7
  * Called after component rebuilds so derived artifacts stay up-to-date.
8
8
  */
9
- export declare const runAllFinalizers: (handoff: Handoff) => Promise<void>;
9
+ export declare const runAllFinalizers: (handoff: Handoff, context?: FinalizeContext) => Promise<void>;
@@ -22,9 +22,9 @@ exports.getAllStrategies = getAllStrategies;
22
22
  * Runs every registered strategy finalizer.
23
23
  * Called after component rebuilds so derived artifacts stay up-to-date.
24
24
  */
25
- const runAllFinalizers = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
25
+ const runAllFinalizers = (handoff, context) => __awaiter(void 0, void 0, void 0, function* () {
26
26
  for (const strategy of strategies.values()) {
27
- yield strategy.finalize(handoff);
27
+ yield strategy.finalize(handoff, context);
28
28
  }
29
29
  });
30
30
  exports.runAllFinalizers = runAllFinalizers;
@@ -35,6 +35,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
35
35
  exports.patternDiffStrategy = void 0;
36
36
  const patterns_1 = require("../../../pipeline/patterns");
37
37
  const builder_1 = __importStar(require("../../../transformers/preview/component/builder"));
38
+ const builder_2 = require("../../../transformers/preview/pattern/builder");
38
39
  const logger_1 = require("../../../utils/logger");
39
40
  const snapshot_1 = require("../snapshot");
40
41
  /**
@@ -85,8 +86,20 @@ exports.patternDiffStrategy = {
85
86
  },
86
87
  };
87
88
  },
88
- finalize(handoff) {
89
+ finalize(handoff, context) {
89
90
  return __awaiter(this, void 0, void 0, function* () {
91
+ if (context === null || context === void 0 ? void 0 : context.skipPatternFinalizer) {
92
+ return;
93
+ }
94
+ const componentIds = context === null || context === void 0 ? void 0 : context.patternRebuildComponentIds;
95
+ if (componentIds === null || componentIds === void 0 ? void 0 : componentIds.length) {
96
+ const patternIds = (0, builder_2.getPatternIdsReferencingComponents)(handoff, componentIds);
97
+ if (patternIds.length === 0) {
98
+ return;
99
+ }
100
+ yield (0, patterns_1.buildPatterns)(handoff, { onlyPatternIds: new Set(patternIds) });
101
+ return;
102
+ }
90
103
  yield (0, patterns_1.buildPatterns)(handoff);
91
104
  });
92
105
  },
@@ -1,4 +1,18 @@
1
1
  import Handoff from '../..';
2
+ /**
3
+ * Optional hints for post-build finalizers (e.g. selective pattern rebuild).
4
+ */
5
+ export type FinalizeContext = {
6
+ /**
7
+ * Only patterns that reference at least one of these component ids are rebuilt.
8
+ * When omitted, all patterns are rebuilt.
9
+ */
10
+ patternRebuildComponentIds?: string[];
11
+ /**
12
+ * When true, skip pattern artifact generation (no component preview HTML changed).
13
+ */
14
+ skipPatternFinalizer?: boolean;
15
+ };
2
16
  /**
3
17
  * Handle returned by ConfigDiffStrategy.capture().
4
18
  *
@@ -15,5 +29,5 @@ export interface RebuildHandle {
15
29
  export interface ConfigDiffStrategy {
16
30
  readonly kind: string;
17
31
  capture(handoff: Handoff, entityId: string): RebuildHandle;
18
- finalize(handoff: Handoff): Promise<void>;
32
+ finalize(handoff: Handoff, context?: FinalizeContext): Promise<void>;
19
33
  }
@@ -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) {
@@ -16,7 +39,7 @@ exports.watchComponentDirectories = exports.watchRuntimeConfiguration = exports.
16
39
  const chokidar_1 = __importDefault(require("chokidar"));
17
40
  const fs_extra_1 = __importDefault(require("fs-extra"));
18
41
  const path_1 = __importDefault(require("path"));
19
- const builder_1 = __importDefault(require("../transformers/preview/component/builder"));
42
+ const builder_1 = __importStar(require("../transformers/preview/component/builder"));
20
43
  const css_1 = require("../transformers/preview/component/css");
21
44
  const javascript_1 = require("../transformers/preview/component/javascript");
22
45
  const logger_1 = require("../utils/logger");
@@ -181,8 +204,17 @@ const watchRuntimeComponents = (handoff, state, runtimeComponentPathsToWatch) =>
181
204
  const entryType = runtimeComponentEntryTypeByPath.get(normalizedFile);
182
205
  const segmentToUpdate = entryType ? (0, component_1.mapEntryTypeToSegment)(entryType) : undefined;
183
206
  const componentId = (0, component_1.resolveComponentIdForChangedFile)(handoff, file);
207
+ const skipPatterns = segmentToUpdate === builder_1.ComponentSegment.JavaScript ||
208
+ segmentToUpdate === builder_1.ComponentSegment.Style;
209
+ let finalizeContext;
210
+ if (skipPatterns) {
211
+ finalizeContext = { skipPatternFinalizer: true };
212
+ }
213
+ else if (componentId) {
214
+ finalizeContext = { patternRebuildComponentIds: [componentId] };
215
+ }
184
216
  yield (0, builder_1.default)(handoff, componentId, segmentToUpdate);
185
- yield (0, config_diff_1.runAllFinalizers)(handoff);
217
+ yield (0, config_diff_1.runAllFinalizers)(handoff, finalizeContext);
186
218
  }
187
219
  catch (e) {
188
220
  logger_1.Logger.error('Error processing component:', e);
@@ -298,7 +330,7 @@ const watchComponentDirectories = (handoff, state, chokidarConfig) => {
298
330
  (0, exports.watchRuntimeComponents)(handoff, state, (0, component_1.getRuntimeComponentsPathsToWatch)(handoff));
299
331
  (0, exports.watchRuntimeConfiguration)(handoff, state);
300
332
  yield (0, builder_1.default)(handoff, dirName);
301
- yield (0, config_diff_1.runAllFinalizers)(handoff);
333
+ yield (0, config_diff_1.runAllFinalizers)(handoff, { patternRebuildComponentIds: [dirName] });
302
334
  }
303
335
  catch (e) {
304
336
  logger_1.Logger.error('Error processing new component:', e);
@@ -20,7 +20,14 @@ const logger_1 = require("../utils/logger");
20
20
  * @returns A function that accepts a message string and broadcasts it to all connected clients.
21
21
  */
22
22
  const createWebSocketServer = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (port = 3001) {
23
+ logger_1.Logger.info(`Starting WebSocket server for live reload on port ${port}…`);
23
24
  const wss = new ws_1.WebSocket.Server({ port });
25
+ wss.on('listening', () => {
26
+ logger_1.Logger.info(`WebSocket server listening on ws://localhost:${port} (live reload)`);
27
+ });
28
+ wss.on('error', (err) => {
29
+ logger_1.Logger.error(`WebSocket server failed${err instanceof Error ? `: ${err.message}` : ''}`, err instanceof Error ? err : undefined);
30
+ });
24
31
  // Heartbeat function to mark a connection as alive.
25
32
  const heartbeat = function () {
26
33
  this.isAlive = true;
@@ -30,7 +37,7 @@ const createWebSocketServer = (...args_1) => __awaiter(void 0, [...args_1], void
30
37
  const extWs = ws;
31
38
  extWs.isAlive = true;
32
39
  extWs.send(JSON.stringify({ type: 'WELCOME' }));
33
- extWs.on('error', (error) => logger_1.Logger.error('WebSocket error:', error));
40
+ extWs.on('error', (error) => logger_1.Logger.error('WebSocket client error:', error));
34
41
  extWs.on('pong', heartbeat);
35
42
  });
36
43
  // Periodically ping clients to ensure they are still connected
@@ -38,7 +45,7 @@ const createWebSocketServer = (...args_1) => __awaiter(void 0, [...args_1], void
38
45
  wss.clients.forEach((client) => {
39
46
  const extWs = client;
40
47
  if (!extWs.isAlive) {
41
- logger_1.Logger.warn('Terminating inactive client');
48
+ logger_1.Logger.debug('Terminating inactive WebSocket client');
42
49
  return client.terminate();
43
50
  }
44
51
  extWs.isAlive = false;
@@ -49,10 +56,9 @@ const createWebSocketServer = (...args_1) => __awaiter(void 0, [...args_1], void
49
56
  wss.on('close', () => {
50
57
  clearInterval(pingInterval);
51
58
  });
52
- logger_1.Logger.success(`WebSocket server listening on ws://localhost:${port}`);
53
59
  // Return a function to broadcast a message to all connected clients
54
60
  return (message) => {
55
- logger_1.Logger.success(`Broadcasting message to ${wss.clients.size} client(s)`);
61
+ logger_1.Logger.debug(`Broadcasting reload to ${wss.clients.size} WebSocket client(s)`);
56
62
  wss.clients.forEach((client) => {
57
63
  if (client.readyState === ws_1.WebSocket.OPEN) {
58
64
  client.send(message);
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.2.0-0';
22
+ return 'Handoff App - 1.2.0-1';
23
23
  };
24
24
  /**
25
25
  * Define a CLI error
@@ -3,7 +3,6 @@ type RawPatternDeclaration = Record<string, any>;
3
3
  type NormalizePatternOptions = {
4
4
  declarationPath: string;
5
5
  fallbackId: string;
6
- warn: (message: string) => void;
7
6
  };
8
7
  export declare const normalizePatternDeclaration: (raw: RawPatternDeclaration, options: NormalizePatternOptions) => PatternListObject;
9
8
  export {};
@@ -13,9 +13,6 @@ const normalizePatternDeclaration = (raw, options) => {
13
13
  throw new Error(`Pattern "${options.fallbackId}" in "${options.declarationPath}" has an invalid component reference at index ${index}: missing "id".`);
14
14
  }
15
15
  const preview = typeof ref.preview === 'string' ? ref.preview.trim() : undefined;
16
- if (!preview && !ref.args) {
17
- options.warn(`Pattern "${options.fallbackId}" component ref "${ref.id}" at index ${index} has neither "preview" nor "args". The component's default preview will be used.`);
18
- }
19
16
  return {
20
17
  id: ref.id.trim(),
21
18
  preview,
@@ -217,7 +217,6 @@ const initRuntimeConfig = (handoff) => {
217
217
  pattern = (0, pattern_1.normalizePatternDeclaration)(rawPattern, {
218
218
  declarationPath: resolvedPatternConfigPath,
219
219
  fallbackId: patternBaseName,
220
- warn: (message) => logger_1.Logger.warn(message),
221
220
  });
222
221
  }
223
222
  catch (err) {
@@ -313,11 +312,18 @@ const injectPatternPreviews = (result) => {
313
312
  ref.resolvedPreview = ref.preview;
314
313
  continue;
315
314
  }
316
- // Case 2: no preview AND no args -> use the component's first existing preview
315
+ // Case 2: no preview AND no args -> use the component's first existing preview (never assume "default")
317
316
  if (!ref.preview && !ref.args) {
318
317
  const previewKeys = Object.keys(component.previews || {});
319
- ref.resolvedPreview = previewKeys[0] || 'default';
320
- ref.resolved = true;
318
+ const firstPreview = previewKeys[0];
319
+ if (firstPreview) {
320
+ ref.resolvedPreview = firstPreview;
321
+ ref.resolved = true;
322
+ }
323
+ else {
324
+ logger_1.Logger.warn(`Pattern "${patternId}" component ref "${ref.id}" has neither "preview" nor "args", and the component has no previews. This fragment will be skipped.`);
325
+ ref.resolved = false;
326
+ }
321
327
  continue;
322
328
  }
323
329
  // Case 3: args present (with or without preview base) -> create synthetic preview
@@ -1,6 +1,8 @@
1
1
  import Handoff from '..';
2
+ import { ProcessPatternsOptions } from '../transformers/preview/pattern/builder';
3
+ export type { ProcessPatternsOptions };
2
4
  /**
3
5
  * Builds pattern previews by composing pre-built component preview HTML.
4
6
  * Must be called AFTER buildComponents.
5
7
  */
6
- export declare const buildPatterns: (handoff: Handoff) => Promise<void>;
8
+ export declare const buildPatterns: (handoff: Handoff, options?: ProcessPatternsOptions) => Promise<void>;
@@ -15,7 +15,7 @@ const builder_1 = require("../transformers/preview/pattern/builder");
15
15
  * Builds pattern previews by composing pre-built component preview HTML.
16
16
  * Must be called AFTER buildComponents.
17
17
  */
18
- const buildPatterns = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
19
- yield (0, builder_1.processPatterns)(handoff);
18
+ const buildPatterns = (handoff, options) => __awaiter(void 0, void 0, void 0, function* () {
19
+ yield (0, builder_1.processPatterns)(handoff, options);
20
20
  });
21
21
  exports.buildPatterns = buildPatterns;
@@ -27,6 +27,7 @@ exports.ComponentSegment = void 0;
27
27
  exports.processComponents = processComponents;
28
28
  const cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
29
29
  const cache_1 = require("../../../cache");
30
+ const duration_1 = require("../../../utils/duration");
30
31
  const logger_1 = require("../../../utils/logger");
31
32
  const schema_1 = require("../../utils/schema");
32
33
  const types_1 = require("../types");
@@ -83,6 +84,37 @@ const ensureDefaultPreview = (data) => {
83
84
  const getBuildPreviews = (data) => {
84
85
  return Object.assign(Object.assign({}, ((data === null || data === void 0 ? void 0 : data.previews) || {})), ((data === null || data === void 0 ? void 0 : data.internalPatternPreviews) || {}));
85
86
  };
87
+ /** Vite-backed segments in execution order; extend here if new steps are added. */
88
+ const VITE_BUILD_SEGMENTS = [
89
+ { key: 'js', label: 'script' },
90
+ { key: 'css', label: 'styles' },
91
+ { key: 'previews', label: 'previews' },
92
+ ];
93
+ const isFullViteComponentBuild = (plan) => plan.js && plan.css && plan.previews;
94
+ const getActiveViteBuildLabels = (plan) => VITE_BUILD_SEGMENTS.filter(({ key }) => plan[key]).map(({ label }) => label);
95
+ /** e.g. "a", "a and b", "a, b, and c" — common CLI copy style. */
96
+ const formatEnglishList = (items) => {
97
+ if (items.length === 0)
98
+ return '';
99
+ if (items.length === 1)
100
+ return items[0];
101
+ if (items.length === 2)
102
+ return `${items[0]} and ${items[1]}`;
103
+ return `${items.slice(0, -1).join(', ')}, and ${items[items.length - 1]}`;
104
+ };
105
+ const viteBuildStartMessage = (componentId, plan) => {
106
+ if (isFullViteComponentBuild(plan)) {
107
+ return `Building component "${componentId}"…`;
108
+ }
109
+ return `Building ${formatEnglishList(getActiveViteBuildLabels(plan))} for component "${componentId}"…`;
110
+ };
111
+ const viteBuildFinishMessage = (componentId, plan, elapsedMs) => {
112
+ const duration = (0, duration_1.formatDurationMs)(elapsedMs);
113
+ if (isFullViteComponentBuild(plan)) {
114
+ return `Finished building component "${componentId}" in ${duration}`;
115
+ }
116
+ return `Finished building ${formatEnglishList(getActiveViteBuildLabels(plan))} for component "${componentId}" in ${duration}`;
117
+ };
86
118
  /**
87
119
  * Returns a normalized build plan describing which component segments need rebuilding.
88
120
  *
@@ -170,12 +202,17 @@ function processComponents(handoff, id, segmentToProcess, options) {
170
202
  if (cache) {
171
203
  (0, cache_1.pruneRemovedComponents)(cache, allComponentIds);
172
204
  }
173
- const skippedCount = allComponentIds.length - componentsToBuild.size;
174
- if (skippedCount > 0) {
175
- logger_1.Logger.info(`Building ${componentsToBuild.size} of ${allComponentIds.length} components (${skippedCount} unchanged)`);
205
+ const total = allComponentIds.length;
206
+ const toBuildCount = componentsToBuild.size;
207
+ const skippedCount = total - toBuildCount;
208
+ if (total > 0 && toBuildCount === 0) {
209
+ logger_1.Logger.info(`All ${total} components unchanged; skipping build`);
210
+ }
211
+ else if (skippedCount > 0 && toBuildCount > 0) {
212
+ logger_1.Logger.info(`Building ${toBuildCount} of ${total} components (${skippedCount} unchanged)`);
176
213
  }
177
- else if (componentsToBuild.size > 0) {
178
- logger_1.Logger.info(`Building all ${componentsToBuild.size} components`);
214
+ else if (toBuildCount > 0) {
215
+ logger_1.Logger.info(`Building all ${toBuildCount} components`);
179
216
  }
180
217
  else {
181
218
  logger_1.Logger.info('All components up to date, nothing to build');
@@ -266,6 +303,12 @@ function processComponents(handoff, id, segmentToProcess, options) {
266
303
  }
267
304
  // Components should always have at least one preview variation.
268
305
  ensureDefaultPreview(data);
306
+ const runsViteBuildSteps = buildPlan.js || buildPlan.css || buildPlan.previews;
307
+ let viteBuildStartedAtMs = 0;
308
+ if (runsViteBuildSteps) {
309
+ viteBuildStartedAtMs = Date.now();
310
+ logger_1.Logger.info(viteBuildStartMessage(runtimeComponentId, buildPlan));
311
+ }
269
312
  // Build JS if needed (new build, validation missing, or explicit segment request).
270
313
  if (buildPlan.js) {
271
314
  data = yield (0, javascript_1.default)(data, handoff);
@@ -278,6 +321,10 @@ function processComponents(handoff, id, segmentToProcess, options) {
278
321
  if (buildPlan.previews) {
279
322
  data = yield (0, html_1.default)(Object.assign(Object.assign({}, data), { previews: getBuildPreviews(data) }), handoff, components);
280
323
  }
324
+ if (runsViteBuildSteps) {
325
+ const elapsedMs = Date.now() - viteBuildStartedAtMs;
326
+ logger_1.Logger.info(viteBuildFinishMessage(runtimeComponentId, buildPlan, elapsedMs));
327
+ }
281
328
  /**
282
329
  * Run validation if explicitly requested and a hook is configured.
283
330
  * This allows custom logic to assess the validity of the generated component data.
@@ -17,6 +17,7 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
17
17
  const path_1 = __importDefault(require("path"));
18
18
  const vite_1 = require("vite");
19
19
  const config_1 = require("../../../config");
20
+ const duration_1 = require("../../../utils/duration");
20
21
  const logger_1 = require("../../../utils/logger");
21
22
  const vite_config_1 = __importDefault(require("../../vite-config"));
22
23
  const component_1 = require("../component");
@@ -155,9 +156,9 @@ const buildMainCss = (handoff) => __awaiter(void 0, void 0, void 0, function* ()
155
156
  const stat = yield fs_extra_1.default.stat(runtimeConfig.entries.scss);
156
157
  const entryPath = stat.isDirectory() ? path_1.default.resolve(runtimeConfig.entries.scss, 'main.scss') : runtimeConfig.entries.scss;
157
158
  if (entryPath === runtimeConfig.entries.scss || fs_extra_1.default.existsSync(entryPath)) {
158
- logger_1.Logger.success(`Building main CSS file...`);
159
+ logger_1.Logger.info(`Building styles for global entry (main.css)…`);
160
+ const startedAt = Date.now();
159
161
  try {
160
- // Setup SASS load paths
161
162
  const loadPaths = [
162
163
  path_1.default.resolve(handoff.workingPath),
163
164
  path_1.default.resolve(handoff.workingPath, handoff.exportsDirectory, handoff.getProjectId()),
@@ -173,9 +174,10 @@ const buildMainCss = (handoff) => __awaiter(void 0, void 0, void 0, function* ()
173
174
  loadPaths,
174
175
  handoff,
175
176
  });
177
+ logger_1.Logger.info(`Finished building styles for global entry (main.css) in ${(0, duration_1.formatDurationMs)(Date.now() - startedAt)}`);
176
178
  }
177
- catch (e) {
178
- logger_1.Logger.error(`Failed to build main CSS:`, e);
179
+ catch (_d) {
180
+ // buildCssBundle already logs failure for the entry path
179
181
  }
180
182
  }
181
183
  }
@@ -18,7 +18,6 @@ export declare const buildComponentJs: (data: TransformComponentTokensResult, ha
18
18
  *
19
19
  * @param handoff - The Handoff configuration object containing build settings
20
20
  * @returns A Promise that resolves when the build process is complete
21
- * @throws May throw an error if the build process fails
22
21
  */
23
22
  export declare const buildMainJS: (handoff: Handoff) => Promise<void>;
24
23
  export default buildComponentJs;
@@ -17,6 +17,7 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
17
17
  const path_1 = __importDefault(require("path"));
18
18
  const vite_1 = require("vite");
19
19
  const config_1 = require("../../../config");
20
+ const duration_1 = require("../../../utils/duration");
20
21
  const logger_1 = require("../../../utils/logger");
21
22
  const vite_config_1 = __importDefault(require("../../vite-config"));
22
23
  const component_1 = require("../component");
@@ -50,9 +51,6 @@ const buildJsBundle = (_a, handoff_1) => __awaiter(void 0, [_a, handoff_1], void
50
51
  }
51
52
  yield (0, vite_1.build)(viteConfig);
52
53
  }
53
- catch (e) {
54
- logger_1.Logger.error(`Failed to build JS for "${outputFilename}":`, e);
55
- }
56
54
  finally {
57
55
  // Restore the original NODE_ENV value after vite build completes
58
56
  // This prevents interference with Next.js app building/running processes
@@ -83,11 +81,17 @@ const buildComponentJs = (data, handoff) => __awaiter(void 0, void 0, void 0, fu
83
81
  const outputPath = (0, component_1.getComponentOutputPath)(handoff);
84
82
  try {
85
83
  const js = yield fs_extra_1.default.readFile(path_1.default.resolve(entry), 'utf8');
86
- yield buildJsBundle({
87
- entry,
88
- outputPath,
89
- outputFilename: `${id}.js`,
90
- }, handoff);
84
+ try {
85
+ yield buildJsBundle({
86
+ entry,
87
+ outputPath,
88
+ outputFilename: `${id}.js`,
89
+ }, handoff);
90
+ }
91
+ catch (e) {
92
+ logger_1.Logger.error(`Failed to bundle JS for component "${id}" (${id}.js):`, e);
93
+ return data;
94
+ }
91
95
  data.js = js;
92
96
  const compiled = yield fs_extra_1.default.readFile(path_1.default.resolve(outputPath, `${id}.js`), 'utf8');
93
97
  data['jsCompiled'] = compiled;
@@ -106,17 +110,24 @@ exports.buildComponentJs = buildComponentJs;
106
110
  *
107
111
  * @param handoff - The Handoff configuration object containing build settings
108
112
  * @returns A Promise that resolves when the build process is complete
109
- * @throws May throw an error if the build process fails
110
113
  */
111
114
  const buildMainJS = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
112
115
  const outputPath = (0, component_1.getComponentOutputPath)(handoff);
113
116
  const runtimeConfig = (0, config_1.initRuntimeConfig)(handoff)[0];
114
117
  if (runtimeConfig && runtimeConfig.entries.js && fs_extra_1.default.existsSync(path_1.default.resolve(runtimeConfig.entries.js))) {
115
- yield buildJsBundle({
116
- entry: runtimeConfig.entries.js,
117
- outputPath,
118
- outputFilename: 'main.js',
119
- }, handoff);
118
+ logger_1.Logger.info(`Building script for global entry (main.js)…`);
119
+ const startedAt = Date.now();
120
+ try {
121
+ yield buildJsBundle({
122
+ entry: runtimeConfig.entries.js,
123
+ outputPath,
124
+ outputFilename: 'main.js',
125
+ }, handoff);
126
+ logger_1.Logger.info(`Finished building script for global entry (main.js) in ${(0, duration_1.formatDurationMs)(Date.now() - startedAt)}`);
127
+ }
128
+ catch (e) {
129
+ logger_1.Logger.error(`Failed to build global script (main.js):`, e);
130
+ }
120
131
  }
121
132
  });
122
133
  exports.buildMainJS = buildMainJS;
@@ -4,4 +4,8 @@ export declare const getPatternAPIPath: (handoff: Handoff) => string;
4
4
  export declare const writePatternApi: (id: string, pattern: PatternObject, handoff: Handoff) => Promise<void>;
5
5
  export declare const writePatternHtml: (id: string, html: string, handoff: Handoff) => Promise<void>;
6
6
  export declare const writePatternSummaryApi: (handoff: Handoff, patterns: PatternListObject[]) => Promise<void>;
7
+ /**
8
+ * Reads the pattern list summary when present (used for partial pattern rebuilds).
9
+ */
10
+ export declare const readPatternSummaryApi: (handoff: Handoff) => Promise<PatternListObject[] | null>;
7
11
  export declare const syncPatternArtifacts: (handoff: Handoff, validIds: Iterable<string>) => Promise<void>;
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.syncPatternArtifacts = exports.writePatternSummaryApi = exports.writePatternHtml = exports.writePatternApi = exports.getPatternAPIPath = void 0;
15
+ exports.syncPatternArtifacts = exports.readPatternSummaryApi = exports.writePatternSummaryApi = exports.writePatternHtml = exports.writePatternApi = exports.getPatternAPIPath = void 0;
16
16
  const fs_extra_1 = __importDefault(require("fs-extra"));
17
17
  const path_1 = __importDefault(require("path"));
18
18
  const getPatternAPIPath = (handoff) => {
@@ -44,6 +44,24 @@ const writePatternSummaryApi = (handoff, patterns) => __awaiter(void 0, void 0,
44
44
  yield fs_extra_1.default.writeFile(path_1.default.resolve(apiPath, 'patterns.json'), JSON.stringify(patterns, null, 2));
45
45
  });
46
46
  exports.writePatternSummaryApi = writePatternSummaryApi;
47
+ /**
48
+ * Reads the pattern list summary when present (used for partial pattern rebuilds).
49
+ */
50
+ const readPatternSummaryApi = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
51
+ const summaryPath = path_1.default.resolve(handoff.workingPath, 'public/api/patterns.json');
52
+ if (!fs_extra_1.default.existsSync(summaryPath)) {
53
+ return null;
54
+ }
55
+ try {
56
+ const raw = yield fs_extra_1.default.readFile(summaryPath, 'utf8');
57
+ const parsed = JSON.parse(raw);
58
+ return Array.isArray(parsed) ? parsed : null;
59
+ }
60
+ catch (_a) {
61
+ return null;
62
+ }
63
+ });
64
+ exports.readPatternSummaryApi = readPatternSummaryApi;
47
65
  const syncPatternArtifacts = (handoff, validIds) => __awaiter(void 0, void 0, void 0, function* () {
48
66
  const validIdSet = new Set(validIds);
49
67
  const patternPath = (0, exports.getPatternAPIPath)(handoff);
@@ -1,9 +1,17 @@
1
1
  import Handoff from '../../../index';
2
2
  import { PatternListObject } from '../types';
3
+ export type ProcessPatternsOptions = {
4
+ /** When set, only these pattern ids are recomposed; others keep existing artifacts. */
5
+ onlyPatternIds?: Set<string>;
6
+ };
7
+ /**
8
+ * Returns pattern ids whose declarations reference any of the given component ids.
9
+ */
10
+ export declare const getPatternIdsReferencingComponents: (handoff: Handoff, componentIds: string[]) => string[];
3
11
  /**
4
12
  * Process all pattern declarations and compose their HTML from pre-built
5
13
  * component preview files. This function must run AFTER buildComponents
6
14
  * so that all component preview HTML files already exist on disk.
7
15
  */
8
- export declare function processPatterns(handoff: Handoff): Promise<PatternListObject[]>;
16
+ export declare function processPatterns(handoff: Handoff, options?: ProcessPatternsOptions): Promise<PatternListObject[]>;
9
17
  export default processPatterns;