handoff-app 1.2.0-1 → 1.2.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.
- package/Changelog.md +65 -0
- package/dist/app-builder/paths.js +18 -1
- package/dist/cli.js +1 -1
- package/dist/transformers/plugins/csf-render.js +2 -1
- package/dist/transformers/plugins/ssr-render.js +2 -1
- package/dist/transformers/preview/component/api.d.ts +2 -13
- package/dist/transformers/preview/component/api.js +7 -46
- package/dist/transformers/preview/component/artifacts.d.ts +4 -0
- package/dist/transformers/preview/component/artifacts.js +126 -0
- package/dist/transformers/preview/component/builder.js +10 -1
- package/dist/transformers/preview/component/css.d.ts +2 -0
- package/dist/transformers/preview/component/css.js +21 -7
- package/dist/transformers/preview/component/javascript.d.ts +1 -0
- package/dist/transformers/preview/component/javascript.js +24 -9
- package/dist/transformers/preview/component/summary.d.ts +6 -0
- package/dist/transformers/preview/component/summary.js +55 -0
- package/dist/transformers/preview/component.js +3 -25
- package/package.json +1 -1
- package/src/app-builder/paths.ts +22 -1
- package/src/cli.ts +1 -1
- package/src/transformers/plugins/csf-render.ts +2 -1
- package/src/transformers/plugins/ssr-render.ts +2 -1
- package/src/transformers/preview/component/api.ts +2 -50
- package/src/transformers/preview/component/artifacts.ts +131 -0
- package/src/transformers/preview/component/builder.ts +11 -1
- package/src/transformers/preview/component/css.ts +22 -7
- package/src/transformers/preview/component/javascript.ts +25 -8
- package/src/transformers/preview/component/summary.ts +54 -0
- package/src/transformers/preview/component.ts +2 -1
package/Changelog.md
CHANGED
|
@@ -6,6 +6,71 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
6
6
|
and this project adheres to
|
|
7
7
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [1.2.0] - 2026-04-03
|
|
10
|
+
|
|
11
|
+
This release introduces support for patterns and significantly
|
|
12
|
+
modernizes the configuration and declaration workflow. It expands Handoff from
|
|
13
|
+
documenting individual components to documenting larger design-system
|
|
14
|
+
compositions, while also improving TypeScript support, preview usability, and
|
|
15
|
+
overall build and development reliability.
|
|
16
|
+
|
|
17
|
+
### Patterns
|
|
18
|
+
|
|
19
|
+
* Added support for design system patterns as a new documentation
|
|
20
|
+
layer above individual components.
|
|
21
|
+
* Patterns support composing multiple component previews into a single page.
|
|
22
|
+
* Pattern definitions can reference existing named component previews or
|
|
23
|
+
provide inline args for custom pattern-specific compositions.
|
|
24
|
+
* Added support for `entries.patterns`, allowing projects to register composed
|
|
25
|
+
design-system patterns alongside components.
|
|
26
|
+
* Added pattern summary and detail artifacts so patterns behave as first-class
|
|
27
|
+
generated output alongside components.
|
|
28
|
+
|
|
29
|
+
### Typed Config and Declaration Workflow
|
|
30
|
+
|
|
31
|
+
* Added support for typed TypeScript and JavaScript config and declaration
|
|
32
|
+
files across the project workflow.
|
|
33
|
+
* Established TypeScript-based config and declaration files as the preferred
|
|
34
|
+
convention, while continuing to support JavaScript, CJS, and JSON where
|
|
35
|
+
applicable.
|
|
36
|
+
* Added clear config resolution precedence when multiple root config files are
|
|
37
|
+
present.
|
|
38
|
+
* Introduced the `.handoff.ts` and `.handoff.js` declaration convention for
|
|
39
|
+
components and patterns.
|
|
40
|
+
* Introduced structured helper APIs including `defineConfig`,
|
|
41
|
+
`defineReactComponent`, `defineHandlebarsComponent`,
|
|
42
|
+
`defineCsfComponent`, and `definePattern`.
|
|
43
|
+
* Updated runtime loading and normalization so typed declarations and modern
|
|
44
|
+
config shapes work consistently across the pipeline.
|
|
45
|
+
* Updated scaffolding and CLI flows to support both TypeScript and JavaScript
|
|
46
|
+
project setup.
|
|
47
|
+
* Updated generated examples and component scaffolds to follow the new
|
|
48
|
+
`.handoff.*` declaration convention.
|
|
49
|
+
|
|
50
|
+
### Usage Snippets and Preview Improvements
|
|
51
|
+
|
|
52
|
+
* Added usage snippets as a new documentation feature, with output that
|
|
53
|
+
reflects the currently selected preview variant.
|
|
54
|
+
* Expanded CSF preview handling to produce more useful implementation examples
|
|
55
|
+
for React and Storybook-style components.
|
|
56
|
+
* Improved story and preview resolution so generated documentation is more
|
|
57
|
+
actionable for engineers consuming the design system.
|
|
58
|
+
* Enhanced code display behavior to better support usage-oriented
|
|
59
|
+
documentation rather than only static rendered output.
|
|
60
|
+
|
|
61
|
+
### General Improvements
|
|
62
|
+
|
|
63
|
+
* Improved incremental build and watch behavior so changes trigger more
|
|
64
|
+
selective and reliable rebuilds.
|
|
65
|
+
* Added better cleanup of stale generated artifacts to keep component and
|
|
66
|
+
pattern output in sync as files change or are removed.
|
|
67
|
+
* Improved summary artifact maintenance during rebuilds so generated API output
|
|
68
|
+
stays aligned with the current source of truth.
|
|
69
|
+
* Enhanced logging with clearer scoped output across Handoff, Vite, and Next.js
|
|
70
|
+
processes.
|
|
71
|
+
* Improved child-process logging and development server reporting to make local
|
|
72
|
+
debugging and build visibility better.
|
|
73
|
+
|
|
9
74
|
## \[1.1.3] - 2026-03-27
|
|
10
75
|
|
|
11
76
|
This release reworks the markdown page rendering system with four improvements:
|
|
@@ -41,6 +41,15 @@ const getAppPath = (handoff) => {
|
|
|
41
41
|
return path_1.default.resolve(handoff.modulePath, '.handoff', `${handoff.getProjectId()}`);
|
|
42
42
|
};
|
|
43
43
|
exports.getAppPath = getAppPath;
|
|
44
|
+
const mirrorDirectory = (sourcePath, destinationPath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
45
|
+
if (!(yield fs_extra_1.default.pathExists(sourcePath))) {
|
|
46
|
+
yield fs_extra_1.default.remove(destinationPath);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
yield fs_extra_1.default.remove(destinationPath);
|
|
50
|
+
yield fs_extra_1.default.ensureDir(path_1.default.dirname(destinationPath));
|
|
51
|
+
yield fs_extra_1.default.copy(sourcePath, destinationPath, { overwrite: true });
|
|
52
|
+
});
|
|
44
53
|
/**
|
|
45
54
|
* Copy the public dir from the working dir to the module dir.
|
|
46
55
|
*/
|
|
@@ -48,9 +57,17 @@ const syncPublicFiles = (handoff) => __awaiter(void 0, void 0, void 0, function*
|
|
|
48
57
|
const appPath = (0, exports.getAppPath)(handoff);
|
|
49
58
|
const workingPublicPath = (0, exports.getWorkingPublicPath)(handoff);
|
|
50
59
|
if (workingPublicPath) {
|
|
51
|
-
|
|
60
|
+
const destinationPublicPath = path_1.default.resolve(appPath, 'public');
|
|
61
|
+
const sourceApiPath = path_1.default.resolve(workingPublicPath, 'api');
|
|
62
|
+
const destinationApiPath = path_1.default.resolve(destinationPublicPath, 'api');
|
|
63
|
+
yield fs_extra_1.default.copy(workingPublicPath, destinationPublicPath, {
|
|
52
64
|
overwrite: true,
|
|
65
|
+
filter: (file) => {
|
|
66
|
+
const relativePath = path_1.default.relative(workingPublicPath, file);
|
|
67
|
+
return relativePath !== 'api' && !relativePath.startsWith(`api${path_1.default.sep}`);
|
|
68
|
+
},
|
|
53
69
|
});
|
|
70
|
+
yield mirrorDirectory(sourceApiPath, destinationApiPath);
|
|
54
71
|
}
|
|
55
72
|
});
|
|
56
73
|
exports.syncPublicFiles = syncPublicFiles;
|
package/dist/cli.js
CHANGED
|
@@ -22,6 +22,7 @@ const react_element_to_jsx_string_1 = __importDefault(require("react-element-to-
|
|
|
22
22
|
const logger_1 = require("../../utils/logger");
|
|
23
23
|
const docgen_1 = require("../docgen");
|
|
24
24
|
const component_1 = require("../preview/component");
|
|
25
|
+
const css_1 = require("../preview/component/css");
|
|
25
26
|
const html_1 = require("../utils/html");
|
|
26
27
|
const module_1 = require("../utils/module");
|
|
27
28
|
const schema_1 = require("../utils/schema");
|
|
@@ -240,7 +241,7 @@ function createHtmlDocument(componentId, previewTitle, renderedHtml) {
|
|
|
240
241
|
<html lang="en">
|
|
241
242
|
<head>
|
|
242
243
|
<meta charset="UTF-8" />
|
|
243
|
-
<link rel="stylesheet" href="${(_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : ''}/api/component
|
|
244
|
+
<link rel="stylesheet" href="${(_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : ''}/api/component/${css_1.MAIN_COMPONENT_CSS_FILE}" />
|
|
244
245
|
<link rel="stylesheet" href="${(_b = process.env.HANDOFF_APP_BASE_PATH) !== null && _b !== void 0 ? _b : ''}/api/component/${componentId}.css" />
|
|
245
246
|
<link rel="stylesheet" href="${(_c = process.env.HANDOFF_APP_BASE_PATH) !== null && _c !== void 0 ? _c : ''}/assets/css/preview.css" />
|
|
246
247
|
<title>${previewTitle}</title>
|
|
@@ -21,6 +21,7 @@ const server_1 = __importDefault(require("react-dom/server"));
|
|
|
21
21
|
const vite_1 = require("vite");
|
|
22
22
|
const logger_1 = require("../../utils/logger");
|
|
23
23
|
const docgen_1 = require("../docgen");
|
|
24
|
+
const css_1 = require("../preview/component/css");
|
|
24
25
|
const build_1 = require("../utils/build");
|
|
25
26
|
const html_1 = require("../utils/html");
|
|
26
27
|
const module_1 = require("../utils/module");
|
|
@@ -117,7 +118,7 @@ function generateHtmlDocument(componentId, previewTitle, renderedHtml, clientJs,
|
|
|
117
118
|
<html lang="en">
|
|
118
119
|
<head>
|
|
119
120
|
<meta charset="UTF-8" />
|
|
120
|
-
<link rel="stylesheet" href="${(_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : ''}/api/component
|
|
121
|
+
<link rel="stylesheet" href="${(_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : ''}/api/component/${css_1.MAIN_COMPONENT_CSS_FILE}" />
|
|
121
122
|
<link rel="stylesheet" href="${(_b = process.env.HANDOFF_APP_BASE_PATH) !== null && _b !== void 0 ? _b : ''}/api/component/${componentId}.css" />
|
|
122
123
|
<link rel="stylesheet" href="${(_c = process.env.HANDOFF_APP_BASE_PATH) !== null && _c !== void 0 ? _c : ''}/assets/css/preview.css" />
|
|
123
124
|
<script id="${PLUGIN_CONSTANTS.PROPS_SCRIPT_ID}" type="application/json">${JSON.stringify(props)}</script>
|
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
import Handoff from '../../../index';
|
|
2
2
|
import { ComponentListObject, TransformComponentTokensResult } from '../types';
|
|
3
|
+
export declare const sanitizeComponentApiData: (component: TransformComponentTokensResult) => TransformComponentTokensResult;
|
|
3
4
|
export declare const getAPIPath: (handoff: Handoff) => string;
|
|
4
|
-
/**
|
|
5
|
-
* Build the preview API from the component data
|
|
6
|
-
* @param handoff
|
|
7
|
-
* @param componentData
|
|
8
|
-
*/
|
|
9
|
-
declare const writeComponentSummaryAPI: (handoff: Handoff, componentData: ComponentListObject[]) => Promise<void>;
|
|
10
5
|
export declare const writeComponentApi: (id: string, component: TransformComponentTokensResult, handoff: Handoff, preserveKeys?: string[]) => Promise<void>;
|
|
11
|
-
/**
|
|
12
|
-
* Update the main component summary API with the new component data
|
|
13
|
-
* @param handoff
|
|
14
|
-
* @param componentData
|
|
15
|
-
*/
|
|
16
|
-
export declare const updateComponentSummaryApi: (handoff: Handoff, componentData: ComponentListObject[], isFullRebuild?: boolean) => Promise<void>;
|
|
17
6
|
/**
|
|
18
7
|
* Read the component API data
|
|
19
8
|
* @param handoff
|
|
@@ -28,4 +17,4 @@ export declare const readComponentApi: (handoff: Handoff, id: string) => Promise
|
|
|
28
17
|
* @returns The component summary or null if not found
|
|
29
18
|
*/
|
|
30
19
|
export declare const readComponentMetadataApi: (handoff: Handoff, id: string) => Promise<ComponentListObject | null>;
|
|
31
|
-
export default
|
|
20
|
+
export default writeComponentApi;
|
|
@@ -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.readComponentMetadataApi = exports.readComponentApi = exports.
|
|
15
|
+
exports.readComponentMetadataApi = exports.readComponentApi = exports.writeComponentApi = exports.getAPIPath = exports.sanitizeComponentApiData = void 0;
|
|
16
16
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
17
17
|
const path_1 = __importDefault(require("path"));
|
|
18
18
|
const previews_1 = require("./previews");
|
|
@@ -23,6 +23,7 @@ const sanitizeComponentApiData = (component) => {
|
|
|
23
23
|
delete sanitized.internalPatternPreviews;
|
|
24
24
|
return sanitized;
|
|
25
25
|
};
|
|
26
|
+
exports.sanitizeComponentApiData = sanitizeComponentApiData;
|
|
26
27
|
/**
|
|
27
28
|
* Merges values from a source object into a target object, returning a new object.
|
|
28
29
|
* For each key present in either object:
|
|
@@ -62,28 +63,19 @@ const getAPIPath = (handoff) => {
|
|
|
62
63
|
return apiPath;
|
|
63
64
|
};
|
|
64
65
|
exports.getAPIPath = getAPIPath;
|
|
65
|
-
/**
|
|
66
|
-
* Build the preview API from the component data
|
|
67
|
-
* @param handoff
|
|
68
|
-
* @param componentData
|
|
69
|
-
*/
|
|
70
|
-
const writeComponentSummaryAPI = (handoff, componentData) => __awaiter(void 0, void 0, void 0, function* () {
|
|
71
|
-
componentData.sort((a, b) => a.title.localeCompare(b.title));
|
|
72
|
-
yield fs_extra_1.default.writeFile(path_1.default.resolve((0, exports.getAPIPath)(handoff), 'components.json'), JSON.stringify(componentData, null, 2));
|
|
73
|
-
});
|
|
74
66
|
const writeComponentApi = (id_1, component_1, handoff_1, ...args_1) => __awaiter(void 0, [id_1, component_1, handoff_1, ...args_1], void 0, function* (id, component, handoff, preserveKeys = []) {
|
|
75
67
|
const outputDirPath = path_1.default.resolve((0, exports.getAPIPath)(handoff), 'component');
|
|
76
68
|
const outputFilePath = path_1.default.resolve(outputDirPath, `${id}.json`);
|
|
77
|
-
const sanitizedComponent = sanitizeComponentApiData(component);
|
|
69
|
+
const sanitizedComponent = (0, exports.sanitizeComponentApiData)(component);
|
|
78
70
|
if (fs_extra_1.default.existsSync(outputFilePath)) {
|
|
79
71
|
const existingJson = yield fs_extra_1.default.readFile(outputFilePath, 'utf8');
|
|
80
72
|
if (existingJson) {
|
|
81
73
|
try {
|
|
82
|
-
const existingData = sanitizeComponentApiData(JSON.parse(existingJson));
|
|
74
|
+
const existingData = (0, exports.sanitizeComponentApiData)(JSON.parse(existingJson));
|
|
83
75
|
// Special case: always allow page to be cleared when undefined
|
|
84
76
|
// This handles the case where page slices are removed
|
|
85
77
|
const finalPreserveKeys = sanitizedComponent.page === undefined ? preserveKeys.filter((key) => key !== 'page') : preserveKeys;
|
|
86
|
-
const mergedData = sanitizeComponentApiData(updateObject(existingData, sanitizedComponent, finalPreserveKeys));
|
|
78
|
+
const mergedData = (0, exports.sanitizeComponentApiData)(updateObject(existingData, sanitizedComponent, finalPreserveKeys));
|
|
87
79
|
yield fs_extra_1.default.writeFile(outputFilePath, JSON.stringify(mergedData, null, 2));
|
|
88
80
|
return;
|
|
89
81
|
}
|
|
@@ -98,37 +90,6 @@ const writeComponentApi = (id_1, component_1, handoff_1, ...args_1) => __awaiter
|
|
|
98
90
|
yield fs_extra_1.default.writeFile(outputFilePath, JSON.stringify(sanitizedComponent, null, 2));
|
|
99
91
|
});
|
|
100
92
|
exports.writeComponentApi = writeComponentApi;
|
|
101
|
-
/**
|
|
102
|
-
* Update the main component summary API with the new component data
|
|
103
|
-
* @param handoff
|
|
104
|
-
* @param componentData
|
|
105
|
-
*/
|
|
106
|
-
const updateComponentSummaryApi = (handoff_1, componentData_1, ...args_1) => __awaiter(void 0, [handoff_1, componentData_1, ...args_1], void 0, function* (handoff, componentData, isFullRebuild = false) {
|
|
107
|
-
if (isFullRebuild) {
|
|
108
|
-
// Full rebuild: replace the entire file
|
|
109
|
-
yield writeComponentSummaryAPI(handoff, componentData);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
// Partial update: merge with existing data
|
|
113
|
-
const apiPath = path_1.default.resolve((0, exports.getAPIPath)(handoff), 'components.json');
|
|
114
|
-
let existingData = [];
|
|
115
|
-
if (fs_extra_1.default.existsSync(apiPath)) {
|
|
116
|
-
try {
|
|
117
|
-
const existing = yield fs_extra_1.default.readFile(apiPath, 'utf8');
|
|
118
|
-
existingData = JSON.parse(existing).map((component) => (Object.assign(Object.assign({}, component), { previews: (0, previews_1.getDocumentedPreviews)(component.previews) })));
|
|
119
|
-
}
|
|
120
|
-
catch (_a) {
|
|
121
|
-
// Corrupt or missing JSON — treat as empty
|
|
122
|
-
existingData = [];
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// Replace existing entries with same ID
|
|
126
|
-
const incomingIds = new Set(componentData.map((c) => c.id));
|
|
127
|
-
const merged = [...componentData, ...existingData.filter((c) => !incomingIds.has(c.id))];
|
|
128
|
-
// Always write the file (even if merged is empty)
|
|
129
|
-
yield writeComponentSummaryAPI(handoff, merged);
|
|
130
|
-
});
|
|
131
|
-
exports.updateComponentSummaryApi = updateComponentSummaryApi;
|
|
132
93
|
/**
|
|
133
94
|
* Read the component API data
|
|
134
95
|
* @param handoff
|
|
@@ -141,7 +102,7 @@ const readComponentApi = (handoff, id) => __awaiter(void 0, void 0, void 0, func
|
|
|
141
102
|
try {
|
|
142
103
|
const existingJson = yield fs_extra_1.default.readFile(outputFilePath, 'utf8');
|
|
143
104
|
if (existingJson) {
|
|
144
|
-
return sanitizeComponentApiData(JSON.parse(existingJson));
|
|
105
|
+
return (0, exports.sanitizeComponentApiData)(JSON.parse(existingJson));
|
|
145
106
|
}
|
|
146
107
|
}
|
|
147
108
|
catch (_) {
|
|
@@ -180,4 +141,4 @@ const readComponentMetadataApi = (handoff, id) => __awaiter(void 0, void 0, void
|
|
|
180
141
|
};
|
|
181
142
|
});
|
|
182
143
|
exports.readComponentMetadataApi = readComponentMetadataApi;
|
|
183
|
-
exports.default =
|
|
144
|
+
exports.default = exports.writeComponentApi;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import Handoff from '../../../index';
|
|
2
|
+
export declare const getComponentApiPath: (handoff: Handoff) => string;
|
|
3
|
+
export declare const removeComponentApi: (handoff: Handoff, id: string) => Promise<void>;
|
|
4
|
+
export declare const syncComponentArtifacts: (handoff: Handoff) => Promise<void>;
|
|
@@ -0,0 +1,126 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.syncComponentArtifacts = exports.removeComponentApi = exports.getComponentApiPath = void 0;
|
|
16
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const previews_1 = require("./previews");
|
|
19
|
+
const api_1 = require("./api");
|
|
20
|
+
const css_1 = require("./css");
|
|
21
|
+
const javascript_1 = require("./javascript");
|
|
22
|
+
const COMPONENT_JS_RESERVED_FILES = new Set([javascript_1.MAIN_COMPONENT_JS_FILE]);
|
|
23
|
+
const COMPONENT_CSS_RESERVED_FILES = new Set([css_1.MAIN_COMPONENT_CSS_FILE, css_1.SHARED_COMPONENT_CSS_FILE]);
|
|
24
|
+
const getComponentApiPath = (handoff) => path_1.default.resolve((0, api_1.getAPIPath)(handoff), 'component');
|
|
25
|
+
exports.getComponentApiPath = getComponentApiPath;
|
|
26
|
+
const getComponentPreviewKeys = (handoff, componentId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
27
|
+
var _a, _b, _c;
|
|
28
|
+
const runtimeComponent = (_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 ? void 0 : _c[componentId];
|
|
29
|
+
const previewKeys = new Set();
|
|
30
|
+
const outputFilePath = path_1.default.resolve((0, exports.getComponentApiPath)(handoff), `${componentId}.json`);
|
|
31
|
+
if (fs_extra_1.default.existsSync(outputFilePath)) {
|
|
32
|
+
try {
|
|
33
|
+
const existingJson = yield fs_extra_1.default.readFile(outputFilePath, 'utf8');
|
|
34
|
+
if (existingJson) {
|
|
35
|
+
const existingData = (0, api_1.sanitizeComponentApiData)(JSON.parse(existingJson));
|
|
36
|
+
for (const previewKey of Object.keys((existingData === null || existingData === void 0 ? void 0 : existingData.previews) || {})) {
|
|
37
|
+
previewKeys.add(previewKey);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (_d) {
|
|
42
|
+
// Fall back to runtime config below.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (const previewKey of Object.keys((0, previews_1.getDocumentedPreviews)(runtimeComponent === null || runtimeComponent === void 0 ? void 0 : runtimeComponent.previews) || {})) {
|
|
46
|
+
previewKeys.add(previewKey);
|
|
47
|
+
}
|
|
48
|
+
for (const previewKey of Object.keys((runtimeComponent === null || runtimeComponent === void 0 ? void 0 : runtimeComponent.internalPatternPreviews) || {})) {
|
|
49
|
+
previewKeys.add(previewKey);
|
|
50
|
+
}
|
|
51
|
+
return previewKeys;
|
|
52
|
+
});
|
|
53
|
+
const getArtifactComponentId = (fileName, componentIds) => {
|
|
54
|
+
let match = null;
|
|
55
|
+
for (const componentId of componentIds) {
|
|
56
|
+
if (!fileName.startsWith(`${componentId}-`))
|
|
57
|
+
continue;
|
|
58
|
+
if (!match || componentId.length > match.length) {
|
|
59
|
+
match = componentId;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return match;
|
|
63
|
+
};
|
|
64
|
+
const removeComponentApi = (handoff, id) => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
|
+
const outputDirPath = (0, exports.getComponentApiPath)(handoff);
|
|
66
|
+
const outputFilePath = path_1.default.resolve(outputDirPath, `${id}.json`);
|
|
67
|
+
if (yield fs_extra_1.default.pathExists(outputFilePath)) {
|
|
68
|
+
yield fs_extra_1.default.remove(outputFilePath);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
exports.removeComponentApi = removeComponentApi;
|
|
72
|
+
const syncComponentArtifacts = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
|
|
73
|
+
var _a, _b, _c;
|
|
74
|
+
const componentPath = (0, exports.getComponentApiPath)(handoff);
|
|
75
|
+
yield fs_extra_1.default.ensureDir(componentPath);
|
|
76
|
+
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 : {};
|
|
77
|
+
const runtimeIds = Object.keys(runtimeComponents);
|
|
78
|
+
const entries = yield fs_extra_1.default.readdir(componentPath);
|
|
79
|
+
const discoveredArtifactIds = new Set(runtimeIds);
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const parsed = path_1.default.parse(entry);
|
|
82
|
+
if (parsed.ext === '.json' || parsed.ext === '.js' || parsed.ext === '.css') {
|
|
83
|
+
if (!COMPONENT_JS_RESERVED_FILES.has(entry) && !COMPONENT_CSS_RESERVED_FILES.has(entry)) {
|
|
84
|
+
discoveredArtifactIds.add(parsed.name);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const validFiles = new Set();
|
|
89
|
+
for (const componentId of runtimeIds) {
|
|
90
|
+
validFiles.add(`${componentId}.json`);
|
|
91
|
+
if (yield fs_extra_1.default.pathExists(path_1.default.resolve(componentPath, `${componentId}.js`))) {
|
|
92
|
+
validFiles.add(`${componentId}.js`);
|
|
93
|
+
}
|
|
94
|
+
if (yield fs_extra_1.default.pathExists(path_1.default.resolve(componentPath, `${componentId}.css`))) {
|
|
95
|
+
validFiles.add(`${componentId}.css`);
|
|
96
|
+
}
|
|
97
|
+
const previewKeys = yield getComponentPreviewKeys(handoff, componentId);
|
|
98
|
+
for (const previewKey of previewKeys) {
|
|
99
|
+
validFiles.add(`${componentId}-${previewKey}.html`);
|
|
100
|
+
validFiles.add(`${componentId}-${previewKey}-inspect.html`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
if (COMPONENT_JS_RESERVED_FILES.has(entry) || COMPONENT_CSS_RESERVED_FILES.has(entry)) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const parsed = path_1.default.parse(entry);
|
|
108
|
+
if (parsed.ext === '.json' || parsed.ext === '.js' || parsed.ext === '.css') {
|
|
109
|
+
if (!validFiles.has(entry)) {
|
|
110
|
+
yield fs_extra_1.default.remove(path_1.default.resolve(componentPath, entry));
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (parsed.ext !== '.html') {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const ownerId = getArtifactComponentId(entry, discoveredArtifactIds);
|
|
118
|
+
if (!ownerId) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (!validFiles.has(entry)) {
|
|
122
|
+
yield fs_extra_1.default.remove(path_1.default.resolve(componentPath, entry));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
exports.syncComponentArtifacts = syncComponentArtifacts;
|
|
@@ -32,7 +32,9 @@ const logger_1 = require("../../../utils/logger");
|
|
|
32
32
|
const schema_1 = require("../../utils/schema");
|
|
33
33
|
const types_1 = require("../types");
|
|
34
34
|
const api_1 = require("./api");
|
|
35
|
+
const artifacts_1 = require("./artifacts");
|
|
35
36
|
const previews_1 = require("./previews");
|
|
37
|
+
const summary_1 = require("./summary");
|
|
36
38
|
const css_1 = __importDefault(require("./css"));
|
|
37
39
|
const html_1 = __importDefault(require("./html"));
|
|
38
40
|
const javascript_1 = __importDefault(require("./javascript"));
|
|
@@ -153,6 +155,12 @@ function processComponents(handoff, id, segmentToProcess, options) {
|
|
|
153
155
|
const components = (_a = documentationObject === null || documentationObject === void 0 ? void 0 : documentationObject.components) !== null && _a !== void 0 ? _a : {};
|
|
154
156
|
const runtimeComponents = (_d = (_c = (_b = handoff.runtimeConfig) === null || _b === void 0 ? void 0 : _b.entries) === null || _c === void 0 ? void 0 : _c.components) !== null && _d !== void 0 ? _d : {};
|
|
155
157
|
const allComponentIds = Object.keys(runtimeComponents);
|
|
158
|
+
if (id && !runtimeComponents[id]) {
|
|
159
|
+
yield (0, artifacts_1.removeComponentApi)(handoff, id);
|
|
160
|
+
yield (0, summary_1.removeComponentFromSummaryApi)(handoff, id);
|
|
161
|
+
yield (0, artifacts_1.syncComponentArtifacts)(handoff);
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
156
164
|
// Determine which components need building based on cache (when enabled)
|
|
157
165
|
let componentsToBuild;
|
|
158
166
|
let cache = null;
|
|
@@ -367,7 +375,8 @@ function processComponents(handoff, id, segmentToProcess, options) {
|
|
|
367
375
|
}
|
|
368
376
|
// Always merge and write summary file, even if no components processed
|
|
369
377
|
const isFullRebuild = !id;
|
|
370
|
-
yield (0,
|
|
378
|
+
yield (0, summary_1.updateComponentSummaryApi)(handoff, result, isFullRebuild);
|
|
379
|
+
yield (0, artifacts_1.syncComponentArtifacts)(handoff);
|
|
371
380
|
return result;
|
|
372
381
|
});
|
|
373
382
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import Handoff from '../../../index';
|
|
2
2
|
import { TransformComponentTokensResult } from '../types';
|
|
3
|
+
export declare const MAIN_COMPONENT_CSS_FILE = "main.css";
|
|
4
|
+
export declare const SHARED_COMPONENT_CSS_FILE = "shared.css";
|
|
3
5
|
declare const buildComponentCss: (data: TransformComponentTokensResult, handoff: Handoff) => Promise<{
|
|
4
6
|
id: string;
|
|
5
7
|
type?: import("../types").ComponentType;
|
|
@@ -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.buildMainCss = void 0;
|
|
15
|
+
exports.buildMainCss = exports.SHARED_COMPONENT_CSS_FILE = exports.MAIN_COMPONENT_CSS_FILE = void 0;
|
|
16
16
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
17
17
|
const path_1 = __importDefault(require("path"));
|
|
18
18
|
const vite_1 = require("vite");
|
|
@@ -22,6 +22,8 @@ const logger_1 = require("../../../utils/logger");
|
|
|
22
22
|
const vite_config_1 = __importDefault(require("../../vite-config"));
|
|
23
23
|
const component_1 = require("../component");
|
|
24
24
|
const { pathToFileURL } = require('url');
|
|
25
|
+
exports.MAIN_COMPONENT_CSS_FILE = 'main.css';
|
|
26
|
+
exports.SHARED_COMPONENT_CSS_FILE = 'shared.css';
|
|
25
27
|
/**
|
|
26
28
|
* Builds a CSS bundle using Vite
|
|
27
29
|
*
|
|
@@ -91,17 +93,26 @@ const buildComponentCss = (data, handoff) => __awaiter(void 0, void 0, void 0, f
|
|
|
91
93
|
var _a, _b, _c;
|
|
92
94
|
const id = data.id;
|
|
93
95
|
logger_1.Logger.debug(`buildComponentCss`, id);
|
|
96
|
+
const outputPath = (0, component_1.getComponentOutputPath)(handoff);
|
|
97
|
+
const builtCssPath = path_1.default.resolve(outputPath, `${id}.css`);
|
|
94
98
|
const entry = (_a = data.entries) === null || _a === void 0 ? void 0 : _a.scss;
|
|
95
99
|
if (!entry) {
|
|
100
|
+
// Keep generated output aligned with the current component declaration.
|
|
101
|
+
yield fs_extra_1.default.remove(builtCssPath);
|
|
102
|
+
delete data['css'];
|
|
103
|
+
delete data['sass'];
|
|
104
|
+
delete data['sharedStyles'];
|
|
96
105
|
return data;
|
|
97
106
|
}
|
|
98
107
|
const extension = path_1.default.extname(entry);
|
|
99
108
|
if (!extension) {
|
|
100
109
|
return data;
|
|
101
110
|
}
|
|
102
|
-
const outputPath = (0, component_1.getComponentOutputPath)(handoff);
|
|
103
111
|
try {
|
|
104
112
|
if (extension === '.scss' || extension === '.css') {
|
|
113
|
+
// Remove the previous artifact before rebuilding so "no emitted CSS"
|
|
114
|
+
// doesn't silently preserve stale output from an earlier build.
|
|
115
|
+
yield fs_extra_1.default.remove(builtCssPath);
|
|
105
116
|
// Read the original source
|
|
106
117
|
const sourceContent = yield fs_extra_1.default.readFile(entry, 'utf8');
|
|
107
118
|
if (extension === '.scss') {
|
|
@@ -124,7 +135,6 @@ const buildComponentCss = (data, handoff) => __awaiter(void 0, void 0, void 0, f
|
|
|
124
135
|
handoff,
|
|
125
136
|
});
|
|
126
137
|
// Read the built CSS
|
|
127
|
-
const builtCssPath = path_1.default.resolve(outputPath, `${id}.css`);
|
|
128
138
|
if (fs_extra_1.default.existsSync(builtCssPath)) {
|
|
129
139
|
const builtCss = yield fs_extra_1.default.readFile(builtCssPath, 'utf8');
|
|
130
140
|
data['css'] = builtCss;
|
|
@@ -134,9 +144,13 @@ const buildComponentCss = (data, handoff) => __awaiter(void 0, void 0, void 0, f
|
|
|
134
144
|
if (splitCSS && splitCSS.length > 1) {
|
|
135
145
|
data['css'] = splitCSS[1];
|
|
136
146
|
data['sharedStyles'] = splitCSS[0];
|
|
137
|
-
yield fs_extra_1.default.writeFile(path_1.default.resolve(outputPath,
|
|
147
|
+
yield fs_extra_1.default.writeFile(path_1.default.resolve(outputPath, exports.SHARED_COMPONENT_CSS_FILE), data['sharedStyles']);
|
|
138
148
|
}
|
|
139
149
|
}
|
|
150
|
+
else {
|
|
151
|
+
delete data['css'];
|
|
152
|
+
delete data['sharedStyles'];
|
|
153
|
+
}
|
|
140
154
|
}
|
|
141
155
|
}
|
|
142
156
|
catch (e) {
|
|
@@ -156,7 +170,7 @@ const buildMainCss = (handoff) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
156
170
|
const stat = yield fs_extra_1.default.stat(runtimeConfig.entries.scss);
|
|
157
171
|
const entryPath = stat.isDirectory() ? path_1.default.resolve(runtimeConfig.entries.scss, 'main.scss') : runtimeConfig.entries.scss;
|
|
158
172
|
if (entryPath === runtimeConfig.entries.scss || fs_extra_1.default.existsSync(entryPath)) {
|
|
159
|
-
logger_1.Logger.info(`Building styles for global entry (
|
|
173
|
+
logger_1.Logger.info(`Building styles for global entry (${exports.MAIN_COMPONENT_CSS_FILE})…`);
|
|
160
174
|
const startedAt = Date.now();
|
|
161
175
|
try {
|
|
162
176
|
const loadPaths = [
|
|
@@ -170,11 +184,11 @@ const buildMainCss = (handoff) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
170
184
|
yield buildCssBundle({
|
|
171
185
|
entry: entryPath,
|
|
172
186
|
outputPath,
|
|
173
|
-
outputFilename:
|
|
187
|
+
outputFilename: exports.MAIN_COMPONENT_CSS_FILE,
|
|
174
188
|
loadPaths,
|
|
175
189
|
handoff,
|
|
176
190
|
});
|
|
177
|
-
logger_1.Logger.info(`Finished building styles for global entry (
|
|
191
|
+
logger_1.Logger.info(`Finished building styles for global entry (${exports.MAIN_COMPONENT_CSS_FILE}) in ${(0, duration_1.formatDurationMs)(Date.now() - startedAt)}`);
|
|
178
192
|
}
|
|
179
193
|
catch (_d) {
|
|
180
194
|
// buildCssBundle already logs failure for the entry path
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Handoff from '../../../index';
|
|
2
2
|
import { TransformComponentTokensResult } from '../types';
|
|
3
|
+
export declare const MAIN_COMPONENT_JS_FILE = "main.js";
|
|
3
4
|
/**
|
|
4
5
|
* Builds the JavaScript file for a single component if it exists.
|
|
5
6
|
* Reads the component JavaScript file, bundles it using the buildJsBundle utility,
|
|
@@ -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.buildMainJS = exports.buildComponentJs = void 0;
|
|
15
|
+
exports.buildMainJS = exports.buildComponentJs = exports.MAIN_COMPONENT_JS_FILE = void 0;
|
|
16
16
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
17
17
|
const path_1 = __importDefault(require("path"));
|
|
18
18
|
const vite_1 = require("vite");
|
|
@@ -21,6 +21,7 @@ const duration_1 = require("../../../utils/duration");
|
|
|
21
21
|
const logger_1 = require("../../../utils/logger");
|
|
22
22
|
const vite_config_1 = __importDefault(require("../../vite-config"));
|
|
23
23
|
const component_1 = require("../component");
|
|
24
|
+
exports.MAIN_COMPONENT_JS_FILE = 'main.js';
|
|
24
25
|
/**
|
|
25
26
|
* Builds a JavaScript bundle using Vite
|
|
26
27
|
*
|
|
@@ -75,11 +76,20 @@ const buildJsBundle = (_a, handoff_1) => __awaiter(void 0, [_a, handoff_1], void
|
|
|
75
76
|
const buildComponentJs = (data, handoff) => __awaiter(void 0, void 0, void 0, function* () {
|
|
76
77
|
var _a;
|
|
77
78
|
const id = data.id;
|
|
79
|
+
const outputPath = (0, component_1.getComponentOutputPath)(handoff);
|
|
80
|
+
const builtJsPath = path_1.default.resolve(outputPath, `${id}.js`);
|
|
78
81
|
const entry = (_a = data.entries) === null || _a === void 0 ? void 0 : _a.js;
|
|
79
|
-
if (!entry)
|
|
82
|
+
if (!entry) {
|
|
83
|
+
// Keep generated output aligned with the current component declaration.
|
|
84
|
+
yield fs_extra_1.default.remove(builtJsPath);
|
|
85
|
+
delete data.js;
|
|
86
|
+
delete data['jsCompiled'];
|
|
80
87
|
return data;
|
|
81
|
-
|
|
88
|
+
}
|
|
82
89
|
try {
|
|
90
|
+
// Remove the previous artifact before rebuilding so a no-output build
|
|
91
|
+
// cannot accidentally preserve stale compiled JS.
|
|
92
|
+
yield fs_extra_1.default.remove(builtJsPath);
|
|
83
93
|
const js = yield fs_extra_1.default.readFile(path_1.default.resolve(entry), 'utf8');
|
|
84
94
|
try {
|
|
85
95
|
yield buildJsBundle({
|
|
@@ -93,8 +103,13 @@ const buildComponentJs = (data, handoff) => __awaiter(void 0, void 0, void 0, fu
|
|
|
93
103
|
return data;
|
|
94
104
|
}
|
|
95
105
|
data.js = js;
|
|
96
|
-
|
|
97
|
-
|
|
106
|
+
if (yield fs_extra_1.default.pathExists(builtJsPath)) {
|
|
107
|
+
const compiled = yield fs_extra_1.default.readFile(builtJsPath, 'utf8');
|
|
108
|
+
data['jsCompiled'] = compiled;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
delete data['jsCompiled'];
|
|
112
|
+
}
|
|
98
113
|
}
|
|
99
114
|
catch (e) {
|
|
100
115
|
logger_1.Logger.error(`JS build failed for component "${id}":`, e);
|
|
@@ -115,18 +130,18 @@ const buildMainJS = (handoff) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
115
130
|
const outputPath = (0, component_1.getComponentOutputPath)(handoff);
|
|
116
131
|
const runtimeConfig = (0, config_1.initRuntimeConfig)(handoff)[0];
|
|
117
132
|
if (runtimeConfig && runtimeConfig.entries.js && fs_extra_1.default.existsSync(path_1.default.resolve(runtimeConfig.entries.js))) {
|
|
118
|
-
logger_1.Logger.info(`Building script for global entry (
|
|
133
|
+
logger_1.Logger.info(`Building script for global entry (${exports.MAIN_COMPONENT_JS_FILE})…`);
|
|
119
134
|
const startedAt = Date.now();
|
|
120
135
|
try {
|
|
121
136
|
yield buildJsBundle({
|
|
122
137
|
entry: runtimeConfig.entries.js,
|
|
123
138
|
outputPath,
|
|
124
|
-
outputFilename:
|
|
139
|
+
outputFilename: exports.MAIN_COMPONENT_JS_FILE,
|
|
125
140
|
}, handoff);
|
|
126
|
-
logger_1.Logger.info(`Finished building script for global entry (
|
|
141
|
+
logger_1.Logger.info(`Finished building script for global entry (${exports.MAIN_COMPONENT_JS_FILE}) in ${(0, duration_1.formatDurationMs)(Date.now() - startedAt)}`);
|
|
127
142
|
}
|
|
128
143
|
catch (e) {
|
|
129
|
-
logger_1.Logger.error(`Failed to build global script (
|
|
144
|
+
logger_1.Logger.error(`Failed to build global script (${exports.MAIN_COMPONENT_JS_FILE}):`, e);
|
|
130
145
|
}
|
|
131
146
|
}
|
|
132
147
|
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import Handoff from '../../../index';
|
|
2
|
+
import { ComponentListObject } from '../types';
|
|
3
|
+
declare const writeComponentSummaryAPI: (handoff: Handoff, componentData: ComponentListObject[]) => Promise<void>;
|
|
4
|
+
export declare const updateComponentSummaryApi: (handoff: Handoff, componentData: ComponentListObject[], isFullRebuild?: boolean) => Promise<void>;
|
|
5
|
+
export declare const removeComponentFromSummaryApi: (handoff: Handoff, id: string) => Promise<void>;
|
|
6
|
+
export default writeComponentSummaryAPI;
|