handoff-app 1.1.3-0 → 1.1.3
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.
- package/Changelog.md +49 -8
- package/dist/app-builder/build.js +2 -0
- package/dist/app-builder/watchers.d.ts +8 -0
- package/dist/app-builder/watchers.js +59 -5
- package/dist/cli.js +0 -0
- package/package.json +1 -1
- package/src/app/components/util/index.ts +6 -0
- package/src/app-builder/build.ts +3 -1
- package/src/app-builder/watchers.ts +66 -4
package/Changelog.md
CHANGED
|
@@ -14,41 +14,82 @@ block styling, and GitHub Flavored Markdown table support.
|
|
|
14
14
|
|
|
15
15
|
### Features
|
|
16
16
|
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
### Bug Fixes
|
|
45
|
+
|
|
46
|
+
* **New components not detected in dev mode** — `npm run start` now watches
|
|
47
|
+
the parent component directories (from `config.entries.components`) for
|
|
48
|
+
newly created components. Previously, only components known at startup had
|
|
49
|
+
their files watched; adding a new component directory required restarting
|
|
50
|
+
the dev server. A new `watchComponentDirectories` watcher detects when a
|
|
51
|
+
config file (`.json` / `.js` / `.cjs`) appears in a new subdirectory,
|
|
52
|
+
reloads the runtime config, restarts the component and configuration
|
|
53
|
+
watchers, and processes the new component automatically.
|
|
54
|
+
|
|
55
|
+
* **Heading anchor IDs truncated** — The markdown heading anchor generator
|
|
56
|
+
was only reading the first child node (`children[0]`) instead of the full
|
|
57
|
+
heading text, producing single-character or partial IDs (e.g. `id="u"`
|
|
58
|
+
instead of `id="using-icons-in-templates"`). A new `extractText` helper
|
|
59
|
+
recursively walks all children to build the complete anchor slug.
|
|
60
|
+
|
|
61
|
+
* **Fenced code blocks without a language unstyled** — Bare ` ``` `
|
|
62
|
+
blocks (no language identifier) now receive the same styled treatment as
|
|
63
|
+
language-tagged blocks: toolbar, copy button, and themed background.
|
|
64
|
+
Inline code is unaffected.
|
|
65
|
+
|
|
66
|
+
* **Code blocks pushing page width** — Added `min-w-0` and `max-w-full` on
|
|
67
|
+
the code block wrapper and the grid content column to prevent wide code
|
|
68
|
+
from stretching the layout and pushing the TOC off-screen.
|
|
69
|
+
|
|
70
|
+
* **Frontmatter menu duplicated by auto-scan** — When a markdown page
|
|
71
|
+
defines its sidebar menu via frontmatter `menu`, the automatic directory
|
|
72
|
+
scanner no longer appends duplicate entries. Auto-scanning now only runs
|
|
73
|
+
as a fallback when no frontmatter menu is present.
|
|
74
|
+
|
|
75
|
+
* **`.prose` max-width too narrow** — Overrode Tailwind Typography's default
|
|
76
|
+
`max-width: 65ch` on `.prose` to `100%` so markdown content fills the
|
|
77
|
+
available container width.
|
|
78
|
+
|
|
44
79
|
### Changed
|
|
45
80
|
|
|
46
|
-
|
|
81
|
+
* Deleted `src/app/pages/[level1]/index.tsx` and
|
|
47
82
|
`src/app/pages/[level1]/[level2]/index.tsx` (replaced by `[...slug]`).
|
|
48
|
-
|
|
83
|
+
* Removed `buildL1StaticPaths`, `buildL2StaticPaths`, and the `SubPageType`
|
|
49
84
|
interface from `src/app/components/util/index.ts`.
|
|
50
|
-
|
|
85
|
+
* `ComponentGuidelines` now uses `MarkdownComponents` and the remark plugins
|
|
51
86
|
for consistent rendering.
|
|
87
|
+
* Added `system/tokens/**` paths to `knownPaths` to prevent catch-all route
|
|
88
|
+
conflicts with dedicated token pages.
|
|
89
|
+
|
|
90
|
+
### Security Patch
|
|
91
|
+
|
|
92
|
+
* Brace Expansion was updated to prevent a memory exhaustion issue.
|
|
52
93
|
|
|
53
94
|
## \[1.1.2] - 2026-03-26
|
|
54
95
|
|
|
@@ -175,10 +175,12 @@ const watchApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
175
175
|
debounce: false,
|
|
176
176
|
runtimeComponentsWatcher: null,
|
|
177
177
|
runtimeConfigurationWatcher: null,
|
|
178
|
+
componentDirectoriesWatcher: null,
|
|
178
179
|
};
|
|
179
180
|
(0, watchers_1.watchPublicDirectory)(handoff, wss, state, chokidarConfig);
|
|
180
181
|
(0, watchers_1.watchRuntimeComponents)(handoff, state, (0, watchers_1.getRuntimeComponentsPathsToWatch)(handoff));
|
|
181
182
|
(0, watchers_1.watchRuntimeConfiguration)(handoff, state);
|
|
183
|
+
(0, watchers_1.watchComponentDirectories)(handoff, state, chokidarConfig);
|
|
182
184
|
(0, watchers_1.watchGlobalEntries)(handoff, state, chokidarConfig);
|
|
183
185
|
(0, watchers_1.watchPages)(handoff, chokidarConfig);
|
|
184
186
|
});
|
|
@@ -6,6 +6,7 @@ export interface WatcherState {
|
|
|
6
6
|
debounce: boolean;
|
|
7
7
|
runtimeComponentsWatcher: chokidar.FSWatcher | null;
|
|
8
8
|
runtimeConfigurationWatcher: chokidar.FSWatcher | null;
|
|
9
|
+
componentDirectoriesWatcher: chokidar.FSWatcher | null;
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
12
|
* Watches the working public directory for changes and updates the app.
|
|
@@ -43,3 +44,10 @@ export declare const watchRuntimeComponents: (handoff: Handoff, state: WatcherSt
|
|
|
43
44
|
* Watches the runtime configuration for changes.
|
|
44
45
|
*/
|
|
45
46
|
export declare const watchRuntimeConfiguration: (handoff: Handoff, state: WatcherState) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Watches the parent component directories from config.entries.components
|
|
49
|
+
* for new components being added. When a new config file (e.g. button.json)
|
|
50
|
+
* appears in a new subdirectory, reloads the runtime config and restarts
|
|
51
|
+
* the component/configuration watchers so the new component is picked up.
|
|
52
|
+
*/
|
|
53
|
+
export declare const watchComponentDirectories: (handoff: Handoff, state: WatcherState, chokidarConfig: chokidar.WatchOptions) => void;
|
|
@@ -35,7 +35,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
35
35
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
36
|
};
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
-
exports.watchRuntimeConfiguration = exports.watchRuntimeComponents = exports.getRuntimeComponentsPathsToWatch = exports.mapEntryTypeToSegment = exports.watchGlobalEntries = exports.watchPages = exports.watchAppSource = exports.watchPublicDirectory = void 0;
|
|
38
|
+
exports.watchComponentDirectories = exports.watchRuntimeConfiguration = exports.watchRuntimeComponents = exports.getRuntimeComponentsPathsToWatch = exports.mapEntryTypeToSegment = exports.watchGlobalEntries = exports.watchPages = exports.watchAppSource = exports.watchPublicDirectory = void 0;
|
|
39
39
|
const chokidar_1 = __importDefault(require("chokidar"));
|
|
40
40
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
41
41
|
const path_1 = __importDefault(require("path"));
|
|
@@ -277,13 +277,9 @@ const watchRuntimeConfiguration = (handoff, state) => {
|
|
|
277
277
|
state.debounce = true;
|
|
278
278
|
try {
|
|
279
279
|
file = path_1.default.dirname(file);
|
|
280
|
-
// Reload the Handoff instance to pick up configuration changes
|
|
281
280
|
handoff.reload();
|
|
282
|
-
// After reloading, persist the updated client configuration
|
|
283
281
|
yield (0, client_config_1.persistClientConfig)(handoff);
|
|
284
|
-
// Restart the runtime components watcher to track potentially updated/added/removed components
|
|
285
282
|
(0, exports.watchRuntimeComponents)(handoff, state, (0, exports.getRuntimeComponentsPathsToWatch)(handoff));
|
|
286
|
-
// Process components based on the updated configuration and file path
|
|
287
283
|
yield (0, builder_1.default)(handoff, path_1.default.basename(file));
|
|
288
284
|
}
|
|
289
285
|
catch (e) {
|
|
@@ -299,3 +295,61 @@ const watchRuntimeConfiguration = (handoff, state) => {
|
|
|
299
295
|
}
|
|
300
296
|
};
|
|
301
297
|
exports.watchRuntimeConfiguration = watchRuntimeConfiguration;
|
|
298
|
+
/**
|
|
299
|
+
* Watches the parent component directories from config.entries.components
|
|
300
|
+
* for new components being added. When a new config file (e.g. button.json)
|
|
301
|
+
* appears in a new subdirectory, reloads the runtime config and restarts
|
|
302
|
+
* the component/configuration watchers so the new component is picked up.
|
|
303
|
+
*/
|
|
304
|
+
const watchComponentDirectories = (handoff, state, chokidarConfig) => {
|
|
305
|
+
var _a, _b, _c, _d, _e;
|
|
306
|
+
if (state.componentDirectoriesWatcher) {
|
|
307
|
+
state.componentDirectoriesWatcher.close();
|
|
308
|
+
}
|
|
309
|
+
const componentPaths = (_b = (_a = handoff.config.entries) === null || _a === void 0 ? void 0 : _a.components) !== null && _b !== void 0 ? _b : [];
|
|
310
|
+
if (componentPaths.length === 0)
|
|
311
|
+
return;
|
|
312
|
+
const dirsToWatch = [];
|
|
313
|
+
for (const componentPath of componentPaths) {
|
|
314
|
+
const resolved = path_1.default.resolve(handoff.workingPath, componentPath);
|
|
315
|
+
if (fs_extra_1.default.existsSync(resolved) && fs_extra_1.default.statSync(resolved).isDirectory()) {
|
|
316
|
+
dirsToWatch.push(resolved);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (dirsToWatch.length === 0)
|
|
320
|
+
return;
|
|
321
|
+
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
|
+
state.componentDirectoriesWatcher = chokidar_1.default.watch(dirsToWatch, Object.assign(Object.assign({}, chokidarConfig), { depth: 1 }));
|
|
323
|
+
state.componentDirectoriesWatcher.on('add', (file) => __awaiter(void 0, void 0, void 0, function* () {
|
|
324
|
+
var _a, _b, _c;
|
|
325
|
+
const basename = path_1.default.basename(file);
|
|
326
|
+
const dirName = path_1.default.basename(path_1.default.dirname(file));
|
|
327
|
+
const isConfigFile = basename.endsWith('.json') || basename.endsWith('.js') || basename.endsWith('.cjs');
|
|
328
|
+
const isNewComponent = isConfigFile && basename.startsWith(dirName) && !knownComponents.has(dirName);
|
|
329
|
+
if (!isNewComponent)
|
|
330
|
+
return;
|
|
331
|
+
if (!state.debounce) {
|
|
332
|
+
state.debounce = true;
|
|
333
|
+
try {
|
|
334
|
+
logger_1.Logger.warn(`New component detected: ${dirName}. Reloading configuration...`);
|
|
335
|
+
handoff.reload();
|
|
336
|
+
knownComponents.add(dirName);
|
|
337
|
+
// Update known set with all current components
|
|
338
|
+
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
|
+
knownComponents.add(id);
|
|
340
|
+
}
|
|
341
|
+
yield (0, client_config_1.persistClientConfig)(handoff);
|
|
342
|
+
(0, exports.watchRuntimeComponents)(handoff, state, (0, exports.getRuntimeComponentsPathsToWatch)(handoff));
|
|
343
|
+
(0, exports.watchRuntimeConfiguration)(handoff, state);
|
|
344
|
+
yield (0, builder_1.default)(handoff, dirName);
|
|
345
|
+
}
|
|
346
|
+
catch (e) {
|
|
347
|
+
logger_1.Logger.error('Error processing new component:', e);
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
state.debounce = false;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}));
|
|
354
|
+
};
|
|
355
|
+
exports.watchComponentDirectories = watchComponentDirectories;
|
package/dist/cli.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -92,6 +92,12 @@ export const knownPaths = [
|
|
|
92
92
|
'foundations/typography',
|
|
93
93
|
'system',
|
|
94
94
|
'system/component',
|
|
95
|
+
'system/tokens',
|
|
96
|
+
'system/tokens/foundations',
|
|
97
|
+
'system/tokens/foundations/colors',
|
|
98
|
+
'system/tokens/foundations/effects',
|
|
99
|
+
'system/tokens/foundations/typography',
|
|
100
|
+
'system/tokens/components',
|
|
95
101
|
];
|
|
96
102
|
|
|
97
103
|
/**
|
package/src/app-builder/build.ts
CHANGED
|
@@ -9,7 +9,7 @@ import processComponents from '../transformers/preview/component/builder';
|
|
|
9
9
|
import { Logger } from '../utils/logger';
|
|
10
10
|
import { generateTokensApi, persistClientConfig } from './client-config';
|
|
11
11
|
import { getAppPath, syncPublicFiles } from './paths';
|
|
12
|
-
import { WatcherState, getRuntimeComponentsPathsToWatch, watchAppSource, watchGlobalEntries, watchPages, watchPublicDirectory, watchRuntimeComponents, watchRuntimeConfiguration } from './watchers';
|
|
12
|
+
import { WatcherState, getRuntimeComponentsPathsToWatch, watchAppSource, watchComponentDirectories, watchGlobalEntries, watchPages, watchPublicDirectory, watchRuntimeComponents, watchRuntimeConfiguration } from './watchers';
|
|
13
13
|
import { createWebSocketServer } from './websocket';
|
|
14
14
|
|
|
15
15
|
const escapeForSingleQuotedJsString = (value: string): string => value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
@@ -193,11 +193,13 @@ export const watchApp = async (handoff: Handoff): Promise<void> => {
|
|
|
193
193
|
debounce: false,
|
|
194
194
|
runtimeComponentsWatcher: null,
|
|
195
195
|
runtimeConfigurationWatcher: null,
|
|
196
|
+
componentDirectoriesWatcher: null,
|
|
196
197
|
};
|
|
197
198
|
|
|
198
199
|
watchPublicDirectory(handoff, wss, state, chokidarConfig);
|
|
199
200
|
watchRuntimeComponents(handoff, state, getRuntimeComponentsPathsToWatch(handoff));
|
|
200
201
|
watchRuntimeConfiguration(handoff, state);
|
|
202
|
+
watchComponentDirectories(handoff, state, chokidarConfig);
|
|
201
203
|
watchGlobalEntries(handoff, state, chokidarConfig);
|
|
202
204
|
watchPages(handoff, chokidarConfig);
|
|
203
205
|
};
|
|
@@ -15,6 +15,7 @@ export interface WatcherState {
|
|
|
15
15
|
debounce: boolean;
|
|
16
16
|
runtimeComponentsWatcher: chokidar.FSWatcher | null;
|
|
17
17
|
runtimeConfigurationWatcher: chokidar.FSWatcher | null;
|
|
18
|
+
componentDirectoriesWatcher: chokidar.FSWatcher | null;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -258,13 +259,9 @@ export const watchRuntimeConfiguration = (handoff: Handoff, state: WatcherState)
|
|
|
258
259
|
state.debounce = true;
|
|
259
260
|
try {
|
|
260
261
|
file = path.dirname(file);
|
|
261
|
-
// Reload the Handoff instance to pick up configuration changes
|
|
262
262
|
handoff.reload();
|
|
263
|
-
// After reloading, persist the updated client configuration
|
|
264
263
|
await persistClientConfig(handoff);
|
|
265
|
-
// Restart the runtime components watcher to track potentially updated/added/removed components
|
|
266
264
|
watchRuntimeComponents(handoff, state, getRuntimeComponentsPathsToWatch(handoff));
|
|
267
|
-
// Process components based on the updated configuration and file path
|
|
268
265
|
await processComponents(handoff, path.basename(file));
|
|
269
266
|
} catch (e) {
|
|
270
267
|
Logger.error('Error reloading runtime configuration:', e);
|
|
@@ -277,3 +274,68 @@ export const watchRuntimeConfiguration = (handoff: Handoff, state: WatcherState)
|
|
|
277
274
|
});
|
|
278
275
|
}
|
|
279
276
|
};
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Watches the parent component directories from config.entries.components
|
|
280
|
+
* for new components being added. When a new config file (e.g. button.json)
|
|
281
|
+
* appears in a new subdirectory, reloads the runtime config and restarts
|
|
282
|
+
* the component/configuration watchers so the new component is picked up.
|
|
283
|
+
*/
|
|
284
|
+
export const watchComponentDirectories = (handoff: Handoff, state: WatcherState, chokidarConfig: chokidar.WatchOptions) => {
|
|
285
|
+
if (state.componentDirectoriesWatcher) {
|
|
286
|
+
state.componentDirectoriesWatcher.close();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const componentPaths = handoff.config.entries?.components ?? [];
|
|
290
|
+
if (componentPaths.length === 0) return;
|
|
291
|
+
|
|
292
|
+
const dirsToWatch: string[] = [];
|
|
293
|
+
for (const componentPath of componentPaths) {
|
|
294
|
+
const resolved = path.resolve(handoff.workingPath, componentPath);
|
|
295
|
+
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
296
|
+
dirsToWatch.push(resolved);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (dirsToWatch.length === 0) return;
|
|
301
|
+
|
|
302
|
+
const knownComponents = new Set(Object.keys(handoff.runtimeConfig?.entries?.components ?? {}));
|
|
303
|
+
|
|
304
|
+
state.componentDirectoriesWatcher = chokidar.watch(dirsToWatch, {
|
|
305
|
+
...chokidarConfig,
|
|
306
|
+
depth: 1,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
state.componentDirectoriesWatcher.on('add', async (file) => {
|
|
310
|
+
const basename = path.basename(file);
|
|
311
|
+
const dirName = path.basename(path.dirname(file));
|
|
312
|
+
|
|
313
|
+
const isConfigFile = basename.endsWith('.json') || basename.endsWith('.js') || basename.endsWith('.cjs');
|
|
314
|
+
const isNewComponent = isConfigFile && basename.startsWith(dirName) && !knownComponents.has(dirName);
|
|
315
|
+
|
|
316
|
+
if (!isNewComponent) return;
|
|
317
|
+
|
|
318
|
+
if (!state.debounce) {
|
|
319
|
+
state.debounce = true;
|
|
320
|
+
try {
|
|
321
|
+
Logger.warn(`New component detected: ${dirName}. Reloading configuration...`);
|
|
322
|
+
handoff.reload();
|
|
323
|
+
knownComponents.add(dirName);
|
|
324
|
+
|
|
325
|
+
// Update known set with all current components
|
|
326
|
+
for (const id of Object.keys(handoff.runtimeConfig?.entries?.components ?? {})) {
|
|
327
|
+
knownComponents.add(id);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
await persistClientConfig(handoff);
|
|
331
|
+
watchRuntimeComponents(handoff, state, getRuntimeComponentsPathsToWatch(handoff));
|
|
332
|
+
watchRuntimeConfiguration(handoff, state);
|
|
333
|
+
await processComponents(handoff, dirName);
|
|
334
|
+
} catch (e) {
|
|
335
|
+
Logger.error('Error processing new component:', e);
|
|
336
|
+
} finally {
|
|
337
|
+
state.debounce = false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
};
|