handoff-app 1.1.3-1 → 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 (144) hide show
  1. package/Changelog.md +19 -15
  2. package/config/config.template.js +21 -14
  3. package/config/config.template.ts +54 -0
  4. package/config/docs/system.md +2 -0
  5. package/dist/app-builder/build.js +7 -2
  6. package/dist/app-builder/config-diff/index.d.ts +4 -0
  7. package/dist/app-builder/config-diff/index.js +12 -0
  8. package/dist/app-builder/config-diff/registry.d.ts +9 -0
  9. package/dist/app-builder/config-diff/registry.js +30 -0
  10. package/dist/app-builder/config-diff/snapshot.d.ts +13 -0
  11. package/dist/app-builder/config-diff/snapshot.js +43 -0
  12. package/dist/app-builder/config-diff/strategies/pattern.d.ts +2 -0
  13. package/dist/app-builder/config-diff/strategies/pattern.js +93 -0
  14. package/dist/app-builder/config-diff/types.d.ts +19 -0
  15. package/dist/app-builder/config-diff/types.js +2 -0
  16. package/dist/app-builder/watchers/component.d.ts +15 -0
  17. package/dist/app-builder/watchers/component.js +69 -0
  18. package/dist/app-builder/watchers/utils.d.ts +14 -0
  19. package/dist/app-builder/watchers/utils.js +44 -0
  20. package/dist/app-builder/watchers.d.ts +10 -19
  21. package/dist/app-builder/watchers.js +56 -102
  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 +9 -0
  37. package/dist/config/normalizers/pattern.js +38 -0
  38. package/dist/config/runtime.d.ts +9 -3
  39. package/dist/config/runtime.js +254 -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 +6 -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 +9 -3
  57. package/dist/transformers/preview/component/css.d.ts +8 -0
  58. package/dist/transformers/preview/component/html.js +14 -5
  59. package/dist/transformers/preview/component/previews.d.ts +9 -0
  60. package/dist/transformers/preview/component/previews.js +10 -0
  61. package/dist/transformers/preview/pattern/api.d.ts +7 -0
  62. package/dist/transformers/preview/pattern/api.js +61 -0
  63. package/dist/transformers/preview/pattern/builder.d.ts +9 -0
  64. package/dist/transformers/preview/pattern/builder.js +86 -0
  65. package/dist/transformers/preview/pattern/html.d.ts +19 -0
  66. package/dist/transformers/preview/pattern/html.js +94 -0
  67. package/dist/transformers/preview/types.d.ts +54 -0
  68. package/dist/transformers/utils/csf.d.ts +16 -0
  69. package/dist/transformers/utils/csf.js +30 -0
  70. package/dist/transformers/utils/index.d.ts +1 -0
  71. package/dist/transformers/utils/index.js +2 -0
  72. package/dist/transformers/utils/module.d.ts +5 -0
  73. package/dist/transformers/utils/module.js +26 -13
  74. package/dist/transformers/utils/usage.d.ts +23 -0
  75. package/dist/transformers/utils/usage.js +294 -0
  76. package/dist/types/config.d.ts +32 -7
  77. package/dist/types/preview.d.ts +2 -0
  78. package/docs/cli.md +3 -3
  79. package/package.json +3 -1
  80. package/src/app/components/Component/PageSliceResolver.tsx +6 -6
  81. package/src/app/components/Component/Preview.tsx +17 -12
  82. package/src/app/components/Component/slices/ComponentDisplaySlice.tsx +16 -6
  83. package/src/app/components/Markdown/CodeHighlight.tsx +60 -11
  84. package/src/app/components/util/index.ts +71 -2
  85. package/src/app/pages/system/pattern/[pattern]/index.tsx +223 -0
  86. package/src/app/pages/system/pattern/index.tsx +14 -0
  87. package/src/app-builder/README.md +37 -9
  88. package/src/app-builder/build.ts +8 -2
  89. package/src/app-builder/config-diff/index.ts +4 -0
  90. package/src/app-builder/config-diff/registry.ts +23 -0
  91. package/src/app-builder/config-diff/snapshot.ts +51 -0
  92. package/src/app-builder/config-diff/strategies/pattern.ts +65 -0
  93. package/src/app-builder/config-diff/types.ts +21 -0
  94. package/src/app-builder/watchers/component.ts +72 -0
  95. package/src/app-builder/watchers/utils.ts +44 -0
  96. package/src/app-builder/watchers.ts +62 -80
  97. package/src/cache/build-cache.ts +14 -5
  98. package/src/cli/create.ts +86 -25
  99. package/src/cli/eject.ts +29 -7
  100. package/src/cli/make.ts +32 -5
  101. package/src/cli/scaffold.ts +68 -21
  102. package/src/cli.ts +1 -1
  103. package/src/config/README.md +10 -1
  104. package/src/config/helpers.ts +43 -0
  105. package/src/config/index.ts +4 -1
  106. package/src/config/loader.ts +110 -31
  107. package/src/config/normalizers/declaration.ts +176 -0
  108. package/src/config/normalizers/pattern.ts +63 -0
  109. package/src/config/runtime.ts +310 -35
  110. package/src/declarations/helpers.ts +36 -0
  111. package/src/declarations/index.ts +19 -0
  112. package/src/declarations/types.ts +70 -0
  113. package/src/index.ts +47 -6
  114. package/src/pipeline/index.ts +1 -0
  115. package/src/pipeline/patterns.ts +10 -0
  116. package/src/transformers/README.md +2 -2
  117. package/src/transformers/docgen/index.ts +13 -2
  118. package/src/transformers/plugins/csf-render.ts +216 -49
  119. package/src/transformers/plugins/ssr-render.ts +23 -7
  120. package/src/transformers/preview/component/api.ts +27 -7
  121. package/src/transformers/preview/component/builder.ts +21 -4
  122. package/src/transformers/preview/component/html.ts +18 -5
  123. package/src/transformers/preview/component/previews.ts +12 -0
  124. package/src/transformers/preview/pattern/api.ts +66 -0
  125. package/src/transformers/preview/pattern/builder.ts +94 -0
  126. package/src/transformers/preview/pattern/html.ts +107 -0
  127. package/src/transformers/preview/types.ts +58 -1
  128. package/src/transformers/utils/csf.ts +48 -0
  129. package/src/transformers/utils/index.ts +3 -0
  130. package/src/transformers/utils/module.ts +33 -18
  131. package/src/transformers/utils/usage.ts +319 -0
  132. package/src/types/README.md +1 -1
  133. package/src/types/config.ts +33 -7
  134. package/src/types/preview.ts +2 -0
  135. package/templates/default/components/button/Button.jsx.tpl +64 -0
  136. package/templates/default/components/button/button.react.handoff.js.tpl +31 -0
  137. package/templates/default/components/button/button.react.handoff.ts.tpl +31 -0
  138. package/templates/default/handoff.config.js.tpl +29 -16
  139. package/templates/default/handoff.config.ts.tpl +196 -0
  140. package/templates/default/handoff.config.with-examples.js.tpl +28 -17
  141. package/templates/default/handoff.config.with-examples.ts.tpl +195 -0
  142. package/templates/default/package.json.tpl +1 -3
  143. package/handoff.config.js +0 -90
  144. package/templates/default/components/button/button.js.tpl +0 -62
package/Changelog.md CHANGED
@@ -14,36 +14,36 @@ block styling, and GitHub Flavored Markdown table support.
14
14
 
15
15
  ### Features
16
16
 
17
- - **N-level deep markdown routing** — Replaced the fixed two-level routing
17
+ * **N-level deep markdown routing** — Replaced the fixed two-level routing
18
18
  (`[level1]` / `[level1]/[level2]`) with a single `[...slug]` catch-all route
19
19
  that supports markdown pages at any directory depth. A new recursive
20
20
  `buildCatchAllStaticPaths` function walks `config/docs` and `pages`
21
21
  directories to collect `.md` files at any nesting level. The sidebar menu now
22
22
  also recursively scans directories to surface deeply nested pages.
23
23
 
24
- - **Automatic table of contents** — The existing but unused `PageTOC` component
24
+ * **Automatic table of contents** — The existing but unused `PageTOC` component
25
25
  is now wired into the catch-all markdown page. It scans rendered headings
26
26
  (h1–h6) via a `MutationObserver` and populates the right-hand "On This Page"
27
27
  anchor nav automatically. Fixed a timing bug where headings were scanned
28
28
  before the markdown content was in the DOM, and fixed anchor IDs that were
29
29
  incorrectly prefixed with `#` (producing `##id` in URLs).
30
30
 
31
- - **Standardized code block styling** — Markdown fenced code blocks now render
31
+ * **Standardized code block styling** — Markdown fenced code blocks now render
32
32
  with a toolbar header matching the site's `CodeHighlight` component: a
33
33
  language icon, filename or language label, and a copy-to-clipboard button. A
34
34
  new `remarkCodeMeta` remark plugin preserves the fence info string so metadata
35
- like `` ```bash filename="deploy.sh" `` is parsed and displayed. The Prism
35
+ like ` ```bash filename="deploy.sh" ` is parsed and displayed. The Prism
36
36
  theme respects dark mode via `next-themes`, and registered languages now
37
37
  include bash, css, scss, jsx, tsx, typescript, yaml, handlebars, and more.
38
38
 
39
- - **Markdown table support** — Added `remark-gfm` to all `ReactMarkdown`
39
+ * **Markdown table support** — Added `remark-gfm` to all `ReactMarkdown`
40
40
  instances across the codebase (17 files). Pipe tables, strikethrough,
41
41
  autolinks, and task lists now render correctly. Tables are styled via Tailwind
42
42
  Typography's `.prose table` defaults.
43
43
 
44
44
  ### Bug Fixes
45
45
 
46
- - **New components not detected in dev mode** — `npm run start` now watches
46
+ * **New components not detected in dev mode** — `npm run start` now watches
47
47
  the parent component directories (from `config.entries.components`) for
48
48
  newly created components. Previously, only components known at startup had
49
49
  their files watched; adding a new component directory required restarting
@@ -52,41 +52,45 @@ block styling, and GitHub Flavored Markdown table support.
52
52
  reloads the runtime config, restarts the component and configuration
53
53
  watchers, and processes the new component automatically.
54
54
 
55
- - **Heading anchor IDs truncated** — The markdown heading anchor generator
55
+ * **Heading anchor IDs truncated** — The markdown heading anchor generator
56
56
  was only reading the first child node (`children[0]`) instead of the full
57
57
  heading text, producing single-character or partial IDs (e.g. `id="u"`
58
58
  instead of `id="using-icons-in-templates"`). A new `extractText` helper
59
59
  recursively walks all children to build the complete anchor slug.
60
60
 
61
- - **Fenced code blocks without a language unstyled** — Bare ```` ``` ````
61
+ * **Fenced code blocks without a language unstyled** — Bare ` ``` `
62
62
  blocks (no language identifier) now receive the same styled treatment as
63
63
  language-tagged blocks: toolbar, copy button, and themed background.
64
64
  Inline code is unaffected.
65
65
 
66
- - **Code blocks pushing page width** — Added `min-w-0` and `max-w-full` on
66
+ * **Code blocks pushing page width** — Added `min-w-0` and `max-w-full` on
67
67
  the code block wrapper and the grid content column to prevent wide code
68
68
  from stretching the layout and pushing the TOC off-screen.
69
69
 
70
- - **Frontmatter menu duplicated by auto-scan** — When a markdown page
70
+ * **Frontmatter menu duplicated by auto-scan** — When a markdown page
71
71
  defines its sidebar menu via frontmatter `menu`, the automatic directory
72
72
  scanner no longer appends duplicate entries. Auto-scanning now only runs
73
73
  as a fallback when no frontmatter menu is present.
74
74
 
75
- - **`.prose` max-width too narrow** — Overrode Tailwind Typography's default
75
+ * **`.prose` max-width too narrow** — Overrode Tailwind Typography's default
76
76
  `max-width: 65ch` on `.prose` to `100%` so markdown content fills the
77
77
  available container width.
78
78
 
79
79
  ### Changed
80
80
 
81
- - Deleted `src/app/pages/[level1]/index.tsx` and
81
+ * Deleted `src/app/pages/[level1]/index.tsx` and
82
82
  `src/app/pages/[level1]/[level2]/index.tsx` (replaced by `[...slug]`).
83
- - Removed `buildL1StaticPaths`, `buildL2StaticPaths`, and the `SubPageType`
83
+ * Removed `buildL1StaticPaths`, `buildL2StaticPaths`, and the `SubPageType`
84
84
  interface from `src/app/components/util/index.ts`.
85
- - `ComponentGuidelines` now uses `MarkdownComponents` and the remark plugins
85
+ * `ComponentGuidelines` now uses `MarkdownComponents` and the remark plugins
86
86
  for consistent rendering.
87
- - Added `system/tokens/**` paths to `knownPaths` to prevent catch-all route
87
+ * Added `system/tokens/**` paths to `knownPaths` to prevent catch-all route
88
88
  conflicts with dedicated token pages.
89
89
 
90
+ ### Security Patch
91
+
92
+ * Brace Expansion was updated to prevent a memory exhaustion issue.
93
+
90
94
  ## \[1.1.2] - 2026-03-26
91
95
 
92
96
  This release improves integration with downstream platforms (for example HubSpot) by letting
@@ -1,8 +1,11 @@
1
- /** @type {import('handoff-app').Config} */
2
1
  const path = require('path');
3
- module.exports = {
2
+ const { defineConfig } = require('handoff-app');
3
+ /** @typedef {import('handoff-app').Config} HandoffConfig */
4
+
5
+ /** @type {HandoffConfig} */
6
+ const config = {
4
7
  // Set your Figma project ID here or in the environment variable HANDOFF_FIGMA_PROJECT_ID
5
- figma_project_id: null,
8
+ figmaProjectId: null,
6
9
  // Set the output directory for the exported components
7
10
  exportsOutputDirectory: 'exported',
8
11
  // Set the output directory for the sites
@@ -16,13 +19,13 @@ module.exports = {
16
19
  // Set the client for the application, used in the footer
17
20
  client: 'Handoff Technologies',
18
21
  // Set the Google Tag Manager ID to add GTM scripts
19
- google_tag_manager: null,
22
+ googleTagManager: null,
20
23
  // Set the attribution for the application
21
24
  attribution: true, // Should we remove this?
22
25
  // Set the type copy for the typeography page
23
- type_copy: 'Almost before we knew it, we had left the ground.',
26
+ typeCopy: 'Almost before we knew it, we had left the ground.',
24
27
  // Set the type sort for the typeography page
25
- type_sort: [
28
+ typeSort: [
26
29
  'Heading 1',
27
30
  'Heading 2',
28
31
  'Heading 3',
@@ -36,17 +39,16 @@ module.exports = {
36
39
  'Link',
37
40
  ],
38
41
  // Set the color sort for the color page
39
- color_sort: ['primary', 'secondary', 'extra', 'system'],
42
+ colorSort: ['primary', 'secondary', 'extra', 'system'],
40
43
  // Set the component sort for the component page
41
- component_sort: ['primary', 'secondary', 'transparent'],
44
+ componentSort: ['primary', 'secondary', 'transparent'],
42
45
  // Set the base path for the application
43
- base_path: '',
46
+ basePath: '',
44
47
  // Set the breakpoints for the application
45
48
  breakpoints: {
46
- sm: { size: 576, name: 'Small' },
47
- md: { size: 768, name: 'Medium' },
48
- lg: { size: 992, name: 'Large' },
49
- xl: { size: 1200, name: 'Extra Large' },
49
+ mobile: { size: 400, name: 'Mobile' },
50
+ tablet: { size: 800, name: 'Medium' },
51
+ desktop: { size: 1100, name: 'Large' },
50
52
  },
51
53
  // Set the ports for the application
52
54
  ports: { app: 3000, websocket: 3001 },
@@ -56,9 +58,12 @@ module.exports = {
56
58
  scss: './sass/main.scss',
57
59
  js: './js/main.js',
58
60
  components: ['./components'],
61
+ // Uncomment to enable pattern documentation pages.
62
+ // Patterns compose component previews into single-page views.
63
+ // patterns: ['./patterns'],
59
64
  },
60
65
  // These are the asset zip file download links
61
- assets_zip_links: {
66
+ assetsZipLinks: {
62
67
  icons: null,
63
68
  logos: null,
64
69
  },
@@ -86,3 +91,5 @@ module.exports = {
86
91
  },
87
92
  },
88
93
  };
94
+
95
+ module.exports = defineConfig(config);
@@ -0,0 +1,54 @@
1
+ import { defineConfig } from 'handoff-app';
2
+
3
+ export default defineConfig({
4
+ figmaProjectId: null,
5
+ exportsOutputDirectory: 'exported',
6
+ sitesOutputDirectory: 'out',
7
+ app: {
8
+ theme: 'default',
9
+ title: 'Handoff Design System',
10
+ client: 'Handoff Technologies',
11
+ googleTagManager: null,
12
+ attribution: true,
13
+ typeCopy: 'Almost before we knew it, we had left the ground.',
14
+ typeSort: [
15
+ 'Heading 1',
16
+ 'Heading 2',
17
+ 'Heading 3',
18
+ 'Heading 4',
19
+ 'Heading 5',
20
+ 'Heading 6',
21
+ 'Paragraph',
22
+ 'Subheading',
23
+ 'Blockquote',
24
+ 'Input Labels',
25
+ 'Link',
26
+ ],
27
+ colorSort: ['primary', 'secondary', 'extra', 'system'],
28
+ componentSort: ['primary', 'secondary', 'transparent'],
29
+ basePath: '',
30
+ breakpoints: {
31
+ mobile: { size: 400, name: 'Mobile' },
32
+ tablet: { size: 800, name: 'Medium' },
33
+ desktop: { size: 1100, name: 'Large' },
34
+ },
35
+ ports: { app: 3000, websocket: 3001 },
36
+ },
37
+ entries: {
38
+ scss: './sass/main.scss',
39
+ js: './js/main.js',
40
+ components: ['./components'],
41
+ // patterns: ['./patterns'],
42
+ },
43
+ assetsZipLinks: {
44
+ icons: null,
45
+ logos: null,
46
+ },
47
+ hooks: {
48
+ cssBuildConfig: (config) => config,
49
+ validateComponent: () => ({}),
50
+ clientBuildConfig: (config) => config,
51
+ htmlBuildConfig: (config) => config,
52
+ jsBuildConfig: (config) => config,
53
+ },
54
+ });
@@ -16,4 +16,6 @@ menu:
16
16
  tokens: true
17
17
  - title: Components
18
18
  components: true
19
+ - title: Patterns
20
+ patterns: true
19
21
  ---
@@ -17,6 +17,7 @@ const cross_spawn_1 = __importDefault(require("cross-spawn"));
17
17
  const fs_extra_1 = __importDefault(require("fs-extra"));
18
18
  const path_1 = __importDefault(require("path"));
19
19
  const components_1 = require("../pipeline/components");
20
+ const patterns_1 = require("../pipeline/patterns");
20
21
  const css_1 = require("../transformers/preview/component/css");
21
22
  const javascript_1 = require("../transformers/preview/component/javascript");
22
23
  const builder_1 = __importDefault(require("../transformers/preview/component/builder"));
@@ -95,9 +96,10 @@ const buildApp = (handoff, skipComponents) => __awaiter(void 0, void 0, void 0,
95
96
  skipComponents = skipComponents !== null && skipComponents !== void 0 ? skipComponents : false;
96
97
  // Perform cleanup
97
98
  yield cleanupAppDirectory(handoff);
98
- // Build components
99
+ // Build components, then patterns (patterns depend on component output)
99
100
  if (!skipComponents) {
100
101
  yield (0, components_1.buildComponents)(handoff);
102
+ yield (0, patterns_1.buildPatterns)(handoff);
101
103
  }
102
104
  // Prepare app
103
105
  const appPath = yield initializeProjectApp(handoff);
@@ -137,6 +139,8 @@ const watchApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
137
139
  yield (0, builder_1.default)(handoff, undefined, undefined, { useCache: true });
138
140
  yield (0, javascript_1.buildMainJS)(handoff);
139
141
  yield (0, css_1.buildMainCss)(handoff);
142
+ // Build patterns after components are ready
143
+ yield (0, patterns_1.buildPatterns)(handoff);
140
144
  const appPath = yield initializeProjectApp(handoff);
141
145
  // Persist client configuration
142
146
  yield (0, client_config_1.persistClientConfig)(handoff);
@@ -172,7 +176,8 @@ const watchApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
172
176
  ignoreInitial: true,
173
177
  };
174
178
  const state = {
175
- debounce: false,
179
+ busy: false,
180
+ pendingHandlers: new Map(),
176
181
  runtimeComponentsWatcher: null,
177
182
  runtimeConfigurationWatcher: null,
178
183
  componentDirectoriesWatcher: null,
@@ -0,0 +1,4 @@
1
+ export type { ConfigDiffStrategy, RebuildHandle } from './types';
2
+ export { type MapSnapshot, diffMapSnapshots, stableStringify } from './snapshot';
3
+ export { patternDiffStrategy } from './strategies/pattern';
4
+ export { getAllStrategies, getStrategy, runAllFinalizers } from './registry';
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAllFinalizers = exports.getStrategy = exports.getAllStrategies = exports.patternDiffStrategy = exports.stableStringify = exports.diffMapSnapshots = void 0;
4
+ var snapshot_1 = require("./snapshot");
5
+ Object.defineProperty(exports, "diffMapSnapshots", { enumerable: true, get: function () { return snapshot_1.diffMapSnapshots; } });
6
+ Object.defineProperty(exports, "stableStringify", { enumerable: true, get: function () { return snapshot_1.stableStringify; } });
7
+ var pattern_1 = require("./strategies/pattern");
8
+ Object.defineProperty(exports, "patternDiffStrategy", { enumerable: true, get: function () { return pattern_1.patternDiffStrategy; } });
9
+ var registry_1 = require("./registry");
10
+ Object.defineProperty(exports, "getAllStrategies", { enumerable: true, get: function () { return registry_1.getAllStrategies; } });
11
+ Object.defineProperty(exports, "getStrategy", { enumerable: true, get: function () { return registry_1.getStrategy; } });
12
+ Object.defineProperty(exports, "runAllFinalizers", { enumerable: true, get: function () { return registry_1.runAllFinalizers; } });
@@ -0,0 +1,9 @@
1
+ import Handoff from '../..';
2
+ import { ConfigDiffStrategy } from './types';
3
+ export declare const getStrategy: (kind: string) => ConfigDiffStrategy | undefined;
4
+ export declare const getAllStrategies: () => ConfigDiffStrategy[];
5
+ /**
6
+ * Runs every registered strategy finalizer.
7
+ * Called after component rebuilds so derived artifacts stay up-to-date.
8
+ */
9
+ export declare const runAllFinalizers: (handoff: Handoff) => Promise<void>;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.runAllFinalizers = exports.getAllStrategies = exports.getStrategy = void 0;
13
+ const pattern_1 = require("./strategies/pattern");
14
+ const strategies = new Map([
15
+ [pattern_1.patternDiffStrategy.kind, pattern_1.patternDiffStrategy],
16
+ ]);
17
+ const getStrategy = (kind) => strategies.get(kind);
18
+ exports.getStrategy = getStrategy;
19
+ const getAllStrategies = () => Array.from(strategies.values());
20
+ exports.getAllStrategies = getAllStrategies;
21
+ /**
22
+ * Runs every registered strategy finalizer.
23
+ * Called after component rebuilds so derived artifacts stay up-to-date.
24
+ */
25
+ const runAllFinalizers = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
26
+ for (const strategy of strategies.values()) {
27
+ yield strategy.finalize(handoff);
28
+ }
29
+ });
30
+ exports.runAllFinalizers = runAllFinalizers;
@@ -0,0 +1,13 @@
1
+ /** outerKey -> Map<innerKey, serializedValue> */
2
+ export type MapSnapshot = Map<string, Map<string, string>>;
3
+ /**
4
+ * Deterministic JSON serializer that sorts object keys at every nesting level.
5
+ * Prevents false-positive diffs when identical values are constructed with
6
+ * different key insertion order across reloads.
7
+ */
8
+ export declare const stableStringify: (value: unknown) => string;
9
+ /**
10
+ * Compares two MapSnapshots and returns the outer keys where the inner maps
11
+ * differ (added, removed, or values changed).
12
+ */
13
+ export declare const diffMapSnapshots: (oldSnapshot: MapSnapshot, newSnapshot: MapSnapshot) => string[];
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.diffMapSnapshots = exports.stableStringify = void 0;
4
+ /**
5
+ * Deterministic JSON serializer that sorts object keys at every nesting level.
6
+ * Prevents false-positive diffs when identical values are constructed with
7
+ * different key insertion order across reloads.
8
+ */
9
+ const stableStringify = (value) => JSON.stringify(value, (_, v) => v && typeof v === 'object' && !Array.isArray(v)
10
+ ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a.localeCompare(b)))
11
+ : v);
12
+ exports.stableStringify = stableStringify;
13
+ /**
14
+ * Compares two MapSnapshots and returns the outer keys where the inner maps
15
+ * differ (added, removed, or values changed).
16
+ */
17
+ const diffMapSnapshots = (oldSnapshot, newSnapshot) => {
18
+ const affected = new Set();
19
+ for (const [key, oldEntries] of oldSnapshot) {
20
+ const newEntries = newSnapshot.get(key);
21
+ if (!newEntries) {
22
+ affected.add(key);
23
+ continue;
24
+ }
25
+ if (oldEntries.size !== newEntries.size) {
26
+ affected.add(key);
27
+ continue;
28
+ }
29
+ for (const [subKey, oldValue] of oldEntries) {
30
+ if (newEntries.get(subKey) !== oldValue) {
31
+ affected.add(key);
32
+ break;
33
+ }
34
+ }
35
+ }
36
+ for (const key of newSnapshot.keys()) {
37
+ if (!oldSnapshot.has(key)) {
38
+ affected.add(key);
39
+ }
40
+ }
41
+ return Array.from(affected);
42
+ };
43
+ exports.diffMapSnapshots = diffMapSnapshots;
@@ -0,0 +1,2 @@
1
+ import { ConfigDiffStrategy } from '../types';
2
+ export declare const patternDiffStrategy: ConfigDiffStrategy;
@@ -0,0 +1,93 @@
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
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.patternDiffStrategy = void 0;
36
+ const patterns_1 = require("../../../pipeline/patterns");
37
+ const builder_1 = __importStar(require("../../../transformers/preview/component/builder"));
38
+ const logger_1 = require("../../../utils/logger");
39
+ const snapshot_1 = require("../snapshot");
40
+ /**
41
+ * Snapshots every __pattern_{patternId}_* synthetic preview entry across all
42
+ * components. Outer key is component id, inner key is synthetic preview key.
43
+ */
44
+ const snapshotPatternSynthetics = (handoff, patternId) => {
45
+ var _a, _b, _c, _d;
46
+ const components = (_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 : {};
47
+ const prefix = `__pattern_${patternId}_`;
48
+ const result = new Map();
49
+ for (const [componentId, component] of Object.entries(components)) {
50
+ const previews = (_d = component.internalPatternPreviews) !== null && _d !== void 0 ? _d : {};
51
+ const entries = new Map();
52
+ for (const [key, preview] of Object.entries(previews)) {
53
+ if (key.startsWith(prefix)) {
54
+ entries.set(key, (0, snapshot_1.stableStringify)(preview.values));
55
+ }
56
+ }
57
+ if (entries.size > 0) {
58
+ result.set(componentId, entries);
59
+ }
60
+ }
61
+ return result;
62
+ };
63
+ exports.patternDiffStrategy = {
64
+ kind: 'pattern',
65
+ capture(handoff, entityId) {
66
+ const oldSnapshot = snapshotPatternSynthetics(handoff, entityId);
67
+ return {
68
+ apply(handoff, entityIdAfterReload) {
69
+ return __awaiter(this, void 0, void 0, function* () {
70
+ const newSnapshot = entityIdAfterReload
71
+ ? snapshotPatternSynthetics(handoff, entityIdAfterReload)
72
+ : new Map();
73
+ const affectedIds = (0, snapshot_1.diffMapSnapshots)(oldSnapshot, newSnapshot);
74
+ const displayId = entityIdAfterReload || entityId;
75
+ if (affectedIds.length > 0) {
76
+ logger_1.Logger.debug(`Pattern "${displayId}" changed: rebuilding ${affectedIds.length} affected component(s): ${affectedIds.join(', ')}`);
77
+ for (const componentId of affectedIds) {
78
+ yield (0, builder_1.default)(handoff, componentId, builder_1.ComponentSegment.Previews);
79
+ }
80
+ }
81
+ else {
82
+ logger_1.Logger.debug(`Pattern "${displayId}" changed: no synthetic previews affected, skipping component rebuild.`);
83
+ }
84
+ });
85
+ },
86
+ };
87
+ },
88
+ finalize(handoff) {
89
+ return __awaiter(this, void 0, void 0, function* () {
90
+ yield (0, patterns_1.buildPatterns)(handoff);
91
+ });
92
+ },
93
+ };
@@ -0,0 +1,19 @@
1
+ import Handoff from '../..';
2
+ /**
3
+ * Handle returned by ConfigDiffStrategy.capture().
4
+ *
5
+ * Captures the pre-reload state in a closure so the snapshot type stays
6
+ * fully encapsulated and the watcher only sees this opaque handle.
7
+ */
8
+ export interface RebuildHandle {
9
+ apply(handoff: Handoff, entityIdAfterReload: string | undefined): Promise<void>;
10
+ }
11
+ /**
12
+ * Encapsulates the snapshot -> reload -> diff -> selective-rebuild protocol
13
+ * for a single config-file entity kind.
14
+ */
15
+ export interface ConfigDiffStrategy {
16
+ readonly kind: string;
17
+ capture(handoff: Handoff, entityId: string): RebuildHandle;
18
+ finalize(handoff: Handoff): Promise<void>;
19
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,15 @@
1
+ import Handoff from '../..';
2
+ import { ComponentSegment } from '../../transformers/preview/component/builder';
3
+ import { ComponentListObject } from '../../transformers/preview/types';
4
+ export type RuntimeComponentEntryType = keyof ComponentListObject['entries'];
5
+ /**
6
+ * Maps configuration entry types to component segments.
7
+ */
8
+ export declare const mapEntryTypeToSegment: (type: RuntimeComponentEntryType) => ComponentSegment | undefined;
9
+ export declare const resolveComponentIdForChangedFile: (handoff: Handoff, changedFilePath: string) => string | undefined;
10
+ /**
11
+ * Gets the paths of runtime components to watch.
12
+ *
13
+ * @returns A map of paths to watch and their entry types.
14
+ */
15
+ export declare const getRuntimeComponentsPathsToWatch: (handoff: Handoff) => Map<string, RuntimeComponentEntryType>;
@@ -0,0 +1,69 @@
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.getRuntimeComponentsPathsToWatch = exports.resolveComponentIdForChangedFile = exports.mapEntryTypeToSegment = void 0;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const builder_1 = require("../../transformers/preview/component/builder");
10
+ const path_2 = require("../../utils/path");
11
+ /**
12
+ * Maps configuration entry types to component segments.
13
+ */
14
+ const mapEntryTypeToSegment = (type) => {
15
+ return {
16
+ js: builder_1.ComponentSegment.JavaScript,
17
+ scss: builder_1.ComponentSegment.Style,
18
+ template: builder_1.ComponentSegment.Previews,
19
+ templates: builder_1.ComponentSegment.Previews,
20
+ component: builder_1.ComponentSegment.Previews,
21
+ story: builder_1.ComponentSegment.Previews,
22
+ }[type];
23
+ };
24
+ exports.mapEntryTypeToSegment = mapEntryTypeToSegment;
25
+ const resolveComponentIdForChangedFile = (handoff, changedFilePath) => {
26
+ var _a, _b, _c, _d;
27
+ const normalizedChangedPath = (0, path_2.normalizePathForCompare)(changedFilePath);
28
+ const runtimeComponents = (_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 : {};
29
+ for (const [componentId, componentDef] of Object.entries(runtimeComponents)) {
30
+ const entries = (_d = componentDef.entries) !== null && _d !== void 0 ? _d : {};
31
+ for (const entryPath of Object.values(entries)) {
32
+ if (!entryPath)
33
+ continue;
34
+ const normalizedEntryPath = (0, path_2.normalizePathForCompare)(entryPath);
35
+ if (normalizedEntryPath === normalizedChangedPath) {
36
+ return componentId;
37
+ }
38
+ }
39
+ }
40
+ // Fallback for legacy path conventions when exact match is unavailable.
41
+ return path_1.default.basename(path_1.default.dirname(changedFilePath));
42
+ };
43
+ exports.resolveComponentIdForChangedFile = resolveComponentIdForChangedFile;
44
+ /**
45
+ * Gets the paths of runtime components to watch.
46
+ *
47
+ * @returns A map of paths to watch and their entry types.
48
+ */
49
+ const getRuntimeComponentsPathsToWatch = (handoff) => {
50
+ var _a, _b, _c;
51
+ const result = new Map();
52
+ for (const runtimeComponentId of Object.keys((_b = (_a = handoff.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entries.components) !== null && _b !== void 0 ? _b : {})) {
53
+ const runtimeComponent = handoff.runtimeConfig.entries.components[runtimeComponentId];
54
+ for (const [runtimeComponentEntryType, runtimeComponentEntryPath] of Object.entries((_c = runtimeComponent.entries) !== null && _c !== void 0 ? _c : {})) {
55
+ const normalizedComponentEntryPath = runtimeComponentEntryPath;
56
+ if (fs_extra_1.default.existsSync(normalizedComponentEntryPath)) {
57
+ const entryType = runtimeComponentEntryType;
58
+ if (fs_extra_1.default.statSync(normalizedComponentEntryPath).isFile()) {
59
+ result.set(path_1.default.resolve(normalizedComponentEntryPath), entryType);
60
+ }
61
+ else {
62
+ result.set(normalizedComponentEntryPath, entryType);
63
+ }
64
+ }
65
+ }
66
+ }
67
+ return result;
68
+ };
69
+ exports.getRuntimeComponentsPathsToWatch = getRuntimeComponentsPathsToWatch;
@@ -0,0 +1,14 @@
1
+ import chokidar from 'chokidar';
2
+ export interface WatcherState {
3
+ busy: boolean;
4
+ pendingHandlers: Map<string, () => Promise<void>>;
5
+ runtimeComponentsWatcher: chokidar.FSWatcher | null;
6
+ runtimeConfigurationWatcher: chokidar.FSWatcher | null;
7
+ componentDirectoriesWatcher: chokidar.FSWatcher | null;
8
+ }
9
+ /**
10
+ * Schedules a handler for execution with mutual exclusion and per-source
11
+ * coalescing. When the lock is free the handler runs immediately. When busy,
12
+ * only the latest handler per source key is kept.
13
+ */
14
+ export declare const scheduleHandler: (state: WatcherState, source: string, handler: () => Promise<void>) => Promise<void>;