handoff-app 1.0.3 → 1.0.4

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 (35) hide show
  1. package/Changelog.md +10 -5
  2. package/dist/cli.js +0 -0
  3. package/dist/index.js +4 -1
  4. package/dist/transformers/plugins/ssr-render.js +4 -3
  5. package/dist/transformers/preview/component/api.js +3 -2
  6. package/dist/transformers/preview/component/builder.js +2 -1
  7. package/dist/transformers/utils/handlebars.js +3 -2
  8. package/package.json +1 -1
  9. package/src/app/components/Component/Preview.tsx +121 -121
  10. package/src/app/components/ComponentSearch.tsx +1 -1
  11. package/src/app/components/util/index.ts +17 -9
  12. package/src/app/pages/system/component/[component]/index.tsx +1 -1
  13. package/src/app/pages/system/index.tsx +2 -2
  14. package/src/app/pages/system/tokens/index.tsx +10 -10
  15. package/src/index.ts +3 -0
  16. package/src/transformers/plugins/ssr-render.ts +3 -3
  17. package/src/transformers/preview/component/api.ts +1 -1
  18. package/src/transformers/preview/component/builder.ts +1 -1
  19. package/src/transformers/utils/handlebars.ts +2 -2
  20. package/dist/commands/platform/init.d.ts +0 -6
  21. package/dist/commands/platform/init.js +0 -231
  22. package/dist/commands/platform/login.d.ts +0 -6
  23. package/dist/commands/platform/login.js +0 -108
  24. package/dist/commands/platform/logout.d.ts +0 -6
  25. package/dist/commands/platform/logout.js +0 -88
  26. package/dist/commands/platform/pull.d.ts +0 -8
  27. package/dist/commands/platform/pull.js +0 -112
  28. package/dist/commands/platform/push.d.ts +0 -8
  29. package/dist/commands/platform/push.js +0 -428
  30. package/dist/platform/client.d.ts +0 -122
  31. package/dist/platform/client.js +0 -228
  32. package/dist/platform/credentials.d.ts +0 -40
  33. package/dist/platform/credentials.js +0 -91
  34. package/dist/platform/sync.d.ts +0 -68
  35. package/dist/platform/sync.js +0 -349
package/Changelog.md CHANGED
@@ -6,7 +6,7 @@ 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.0.3] - 2026-02-17
9
+ ## \[1.0.4] - 2026-02-19
10
10
 
11
11
  Bugfixes, UX improvements to the component page, improvements to react docgen,
12
12
  a new quickstart command, and security fixes.
@@ -31,6 +31,11 @@ a new quickstart command, and security fixes.
31
31
  * The API preview would through a 500 error under some conditions
32
32
  * Using TSX components would cause an error in the code preview if the tsx component
33
33
  used some kinds of valid react formatting.
34
+ * If you create pages without a declared weight, handoff would throw an error
35
+ * There was an old structure in the way we used handlebars that surfaces a warning
36
+ on build. We've updated that method.
37
+ * We removed an old page template that was no longer needed.
38
+ * The API calls to the component url were not working well in subpaths.
34
39
 
35
40
  ## Type Gen
36
41
 
@@ -57,6 +62,10 @@ a new quickstart command, and security fixes.
57
62
  * Fixes a bug in webpack that made the include path lookups fail. @handoff and @/util now
58
63
  works properly.
59
64
 
65
+ ## \[1.0.3]
66
+
67
+ This this version was skipped.
68
+
60
69
  ## \[1.0.2] - 2026-01-12
61
70
 
62
71
  Bugfixes focused on developer quality of life.
@@ -67,10 +76,6 @@ Bugfixes focused on developer quality of life.
67
76
  resulting in a 404 error. This patch resolves a race condition in the HMR, by
68
77
  preventing double write of the next config.
69
78
  * Patch an issue where the example component doesn't load in a clean project
70
- * If you create pages without a declared weight, handoff would throw an error
71
- * There was an old structure in the way we used handlebars that surfaces a warning
72
- on build. We've updated that method.
73
- * We removed an old page template that was no longer needed.
74
79
 
75
80
  ## \[1.0.1] - 2025-12-28
76
81
 
package/dist/cli.js CHANGED
File without changes
package/dist/index.js CHANGED
@@ -71,12 +71,15 @@ class Handoff {
71
71
  global.handoff = this;
72
72
  }
73
73
  init(configOverride) {
74
- var _a, _b;
74
+ var _a, _b, _c;
75
75
  const config = initConfig(configOverride !== null && configOverride !== void 0 ? configOverride : {});
76
76
  this.config = config;
77
77
  this.exportsDirectory = (_a = config.exportsOutputDirectory) !== null && _a !== void 0 ? _a : this.exportsDirectory;
78
78
  this.sitesDirectory = (_b = config.sitesOutputDirectory) !== null && _b !== void 0 ? _b : this.exportsDirectory;
79
79
  [this.runtimeConfig, this._configFilePaths] = (0, exports.initRuntimeConfig)(this);
80
+ if (this.config.app.base_path && !process.env.HANDOFF_APP_BASE_PATH) {
81
+ process.env.HANDOFF_APP_BASE_PATH = (_c = this.config.app.base_path) !== null && _c !== void 0 ? _c : '';
82
+ }
80
83
  return this;
81
84
  }
82
85
  reload() {
@@ -111,13 +111,14 @@ function generateClientHydrationSource(componentPath) {
111
111
  * @returns Complete HTML document
112
112
  */
113
113
  function generateHtmlDocument(componentId, previewTitle, renderedHtml, clientJs, props) {
114
+ var _a, _b, _c;
114
115
  return `<!DOCTYPE html>
115
116
  <html lang="en">
116
117
  <head>
117
118
  <meta charset="UTF-8" />
118
- <link rel="stylesheet" href="/api/component/main.css" />
119
- <link rel="stylesheet" href="/api/component/${componentId}.css" />
120
- <link rel="stylesheet" href="/assets/css/preview.css" />
119
+ <link rel="stylesheet" href="${(_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : ''}/api/component/main.css" />
120
+ <link rel="stylesheet" href="${(_b = process.env.HANDOFF_APP_BASE_PATH) !== null && _b !== void 0 ? _b : ''}/api/component/${componentId}.css" />
121
+ <link rel="stylesheet" href="${(_c = process.env.HANDOFF_APP_BASE_PATH) !== null && _c !== void 0 ? _c : ''}/assets/css/preview.css" />
121
122
  <script id="${PLUGIN_CONSTANTS.PROPS_SCRIPT_ID}" type="application/json">${JSON.stringify(props)}</script>
122
123
  <script type="module">
123
124
  ${clientJs}
@@ -95,20 +95,21 @@ exports.writeComponentApi = writeComponentApi;
95
95
  * @param componentData
96
96
  */
97
97
  const updateComponentSummaryApi = (handoff_1, componentData_1, ...args_1) => __awaiter(void 0, [handoff_1, componentData_1, ...args_1], void 0, function* (handoff, componentData, isFullRebuild = false) {
98
+ var _a;
98
99
  if (isFullRebuild) {
99
100
  // Full rebuild: replace the entire file
100
101
  yield writeComponentSummaryAPI(handoff, componentData);
101
102
  return;
102
103
  }
103
104
  // Partial update: merge with existing data
104
- const apiPath = path_1.default.resolve(handoff.workingPath, 'public/api/components.json');
105
+ const apiPath = path_1.default.resolve(handoff.workingPath, `${(_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : ''}/api/components.json`);
105
106
  let existingData = [];
106
107
  if (fs_extra_1.default.existsSync(apiPath)) {
107
108
  try {
108
109
  const existing = yield fs_extra_1.default.readFile(apiPath, 'utf8');
109
110
  existingData = JSON.parse(existing);
110
111
  }
111
- catch (_a) {
112
+ catch (_b) {
112
113
  // Corrupt or missing JSON — treat as empty
113
114
  existingData = [];
114
115
  }
@@ -313,6 +313,7 @@ function processComponents(handoff, id, segmentToProcess, options) {
313
313
  * @returns
314
314
  */
315
315
  const buildComponentSummary = (id, data) => {
316
+ var _a;
316
317
  return {
317
318
  id,
318
319
  title: data.title,
@@ -325,7 +326,7 @@ const buildComponentSummary = (id, data) => {
325
326
  tags: data.tags ? data.tags : [],
326
327
  properties: data.properties,
327
328
  previews: data.previews,
328
- path: `/api/component/${id}.json`,
329
+ path: `${(_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : ''}/api/component/${id}.json`,
329
330
  };
330
331
  };
331
332
  exports.default = processComponents;
@@ -52,9 +52,10 @@ exports.registerHandlebarsHelpers = registerHandlebarsHelpers;
52
52
  * @returns Handlebars context object
53
53
  */
54
54
  const createHandlebarsContext = (data, previewData) => {
55
+ var _a, _b, _c, _d, _e;
55
56
  return {
56
- style: `<link rel="stylesheet" href="/api/component/shared.css"><link rel="stylesheet" href="/api/component/${data.id}.css">\n<link rel="stylesheet" href="/assets/css/preview.css">`,
57
- script: `<script src="/api/component/${data.id}.js"></script>\n<script src="/assets/js/preview.js"></script><script>var fields = ${JSON.stringify(data.properties)};</script>`,
57
+ style: `<link rel="stylesheet" href="${(_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : ''}/api/component/shared.css"><link rel="stylesheet" href="${(_b = process.env.HANDOFF_APP_BASE_PATH) !== null && _b !== void 0 ? _b : ''}/api/component/${data.id}.css">\n<link rel="stylesheet" href="${(_c = process.env.HANDOFF_APP_BASE_PATH) !== null && _c !== void 0 ? _c : ''}/assets/css/preview.css">`,
58
+ script: `<script src="${(_d = process.env.HANDOFF_APP_BASE_PATH) !== null && _d !== void 0 ? _d : ''}/api/component/${data.id}.js"></script>\n<script src="${(_e = process.env.HANDOFF_APP_BASE_PATH) !== null && _e !== void 0 ? _e : ''}/assets/js/preview.js"></script><script>var fields = ${JSON.stringify(data.properties)};</script>`,
58
59
  properties: previewData.values || {},
59
60
  fields: data.properties,
60
61
  title: data.title,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handoff-app",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Automated documentation toolchain for building client side documentation from figma",
5
5
  "author ": {
6
6
  "name": "Convertiv",
@@ -303,7 +303,7 @@ export const ComponentDisplay: React.FC<{
303
303
  className="h-7 px-3 hover:bg-gray-300 [&_svg]:size-3"
304
304
  onClick={() => {
305
305
  // open in new tab
306
- window.open('/api/component/' + previewUrl, '_blank');
306
+ window.open(`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/component/${previewUrl}`, '_blank');
307
307
  }}
308
308
  variant="ghost"
309
309
  >
@@ -333,7 +333,7 @@ export const ComponentDisplay: React.FC<{
333
333
  display: 'block',
334
334
  margin: '0 auto',
335
335
  }}
336
- src={`/api/component/` + previewUrl}
336
+ src={`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/component/${previewUrl}`}
337
337
  />
338
338
  </div>
339
339
  ) : (
@@ -539,9 +539,9 @@ const hasNestedProperties = (row: SlotMetadata): boolean => {
539
539
  );
540
540
  };
541
541
 
542
- const TableRows: React.FC<{
543
- rows: SlotMetadata[];
544
- openSheet: (field: SlotMetadata) => void;
542
+ const TableRows: React.FC<{
543
+ rows: SlotMetadata[];
544
+ openSheet: (field: SlotMetadata) => void;
545
545
  depth?: number;
546
546
  parentPath?: string;
547
547
  }> = ({
@@ -550,24 +550,24 @@ const TableRows: React.FC<{
550
550
  depth = 0,
551
551
  parentPath = '',
552
552
  }) => {
553
- return (
554
- <>
555
- {rows.map((row, i) => {
556
- if (!row) return null;
557
- const currentPath = parentPath ? `${parentPath}.${row.key || row.name}` : (row.key || row.name);
558
- return (
559
- <ExpandableTableRow
560
- key={`row-${currentPath}-${i}`}
561
- row={row}
562
- openSheet={openSheet}
563
- depth={depth}
564
- path={currentPath}
565
- />
566
- );
567
- })}
568
- </>
569
- );
570
- };
553
+ return (
554
+ <>
555
+ {rows.map((row, i) => {
556
+ if (!row) return null;
557
+ const currentPath = parentPath ? `${parentPath}.${row.key || row.name}` : (row.key || row.name);
558
+ return (
559
+ <ExpandableTableRow
560
+ key={`row-${currentPath}-${i}`}
561
+ row={row}
562
+ openSheet={openSheet}
563
+ depth={depth}
564
+ path={currentPath}
565
+ />
566
+ );
567
+ })}
568
+ </>
569
+ );
570
+ };
571
571
 
572
572
  const humanReadableSizeRule = (rule: string, value: any) => {
573
573
  switch (rule) {
@@ -597,9 +597,9 @@ const humanReadableSizeRule = (rule: string, value: any) => {
597
597
  * An expandable table row that can show nested properties
598
598
  * Supports deep nesting with visual indentation and expand/collapse
599
599
  */
600
- const ExpandableTableRow: React.FC<{
601
- row: SlotMetadata;
602
- openSheet: (field: SlotMetadata) => void;
600
+ const ExpandableTableRow: React.FC<{
601
+ row: SlotMetadata;
602
+ openSheet: (field: SlotMetadata) => void;
603
603
  depth: number;
604
604
  path: string;
605
605
  }> = ({
@@ -608,104 +608,104 @@ const ExpandableTableRow: React.FC<{
608
608
  depth,
609
609
  path,
610
610
  }) => {
611
- const [isExpanded, setIsExpanded] = useState(false);
612
- const nested = getNestedProperties(row);
613
- const canExpand = hasNestedProperties(row);
614
-
615
- const getSizeDisplay = (row: SlotMetadata) => {
616
- if (!row.rules) return '-';
617
-
618
- // Find the first applicable size rule
619
- const sizeRules = ['dimensions', 'content', 'maxSize'];
620
- const rule = Object.keys(row.rules).find((r) => sizeRules.includes(r));
621
-
622
- return rule ? humanReadableSizeRule(rule, row.rules[rule]) : '-';
623
- };
611
+ const [isExpanded, setIsExpanded] = useState(false);
612
+ const nested = getNestedProperties(row);
613
+ const canExpand = hasNestedProperties(row);
624
614
 
625
- // Calculate indent based on depth (each level adds 20px)
626
- const indentPx = depth * 20;
627
-
628
- // Get background color based on depth for visual hierarchy
629
- const getDepthBgClass = (depth: number) => {
630
- if (depth === 0) return '';
631
- if (depth === 1) return 'bg-gray-50/50 dark:bg-gray-800/30';
632
- if (depth === 2) return 'bg-gray-100/50 dark:bg-gray-800/50';
633
- return 'bg-gray-100/70 dark:bg-gray-800/70';
634
- };
615
+ const getSizeDisplay = (row: SlotMetadata) => {
616
+ if (!row.rules) return '-';
635
617
 
636
- const handleRowClick = (e: React.MouseEvent) => {
637
- // If clicking on the expand button area, don't open the sheet
638
- if ((e.target as HTMLElement).closest('.expand-toggle')) {
639
- return;
640
- }
641
- openSheet(row);
642
- };
618
+ // Find the first applicable size rule
619
+ const sizeRules = ['dimensions', 'content', 'maxSize'];
620
+ const rule = Object.keys(row.rules).find((r) => sizeRules.includes(r));
643
621
 
644
- const handleExpandToggle = (e: React.MouseEvent) => {
645
- e.stopPropagation();
646
- setIsExpanded(!isExpanded);
647
- };
622
+ return rule ? humanReadableSizeRule(rule, row.rules[rule]) : '-';
623
+ };
648
624
 
649
- return (
650
- <>
651
- <TableRow
652
- className={`h-10 cursor-pointer border-b-[0.5px] ${getDepthBgClass(depth)}`}
653
- onClick={handleRowClick}
654
- >
655
- <TableCell className="whitespace-nowrap border-l-[0.5px] border-r-[0.5px] px-4 py-1">
656
- <div className="flex items-center" style={{ paddingLeft: `${indentPx}px` }}>
657
- {canExpand ? (
658
- <button
659
- className="expand-toggle mr-2 flex h-5 w-5 items-center justify-center rounded hover:bg-gray-200 dark:hover:bg-gray-700"
660
- onClick={handleExpandToggle}
661
- aria-label={isExpanded ? 'Collapse' : 'Expand'}
662
- >
663
- {isExpanded ? (
664
- <ChevronDown className="h-4 w-4 text-gray-500" />
665
- ) : (
666
- <ChevronRight className="h-4 w-4 text-gray-500" />
667
- )}
668
- </button>
669
- ) : (
670
- <span className="mr-2 w-5" />
671
- )}
672
- <span className={depth > 0 ? 'text-gray-700 dark:text-gray-300' : ''}>
673
- {startCase(row.name)}
674
- </span>
675
- {canExpand && (
676
- <span className="ml-2 text-xs text-gray-400">
677
- ({Object.keys(nested || {}).length} {row.type === 'array' ? 'item fields' : 'fields'})
625
+ // Calculate indent based on depth (each level adds 20px)
626
+ const indentPx = depth * 20;
627
+
628
+ // Get background color based on depth for visual hierarchy
629
+ const getDepthBgClass = (depth: number) => {
630
+ if (depth === 0) return '';
631
+ if (depth === 1) return 'bg-gray-50/50 dark:bg-gray-800/30';
632
+ if (depth === 2) return 'bg-gray-100/50 dark:bg-gray-800/50';
633
+ return 'bg-gray-100/70 dark:bg-gray-800/70';
634
+ };
635
+
636
+ const handleRowClick = (e: React.MouseEvent) => {
637
+ // If clicking on the expand button area, don't open the sheet
638
+ if ((e.target as HTMLElement).closest('.expand-toggle')) {
639
+ return;
640
+ }
641
+ openSheet(row);
642
+ };
643
+
644
+ const handleExpandToggle = (e: React.MouseEvent) => {
645
+ e.stopPropagation();
646
+ setIsExpanded(!isExpanded);
647
+ };
648
+
649
+ return (
650
+ <>
651
+ <TableRow
652
+ className={`h-10 cursor-pointer border-b-[0.5px] ${getDepthBgClass(depth)}`}
653
+ onClick={handleRowClick}
654
+ >
655
+ <TableCell className="whitespace-nowrap border-l-[0.5px] border-r-[0.5px] px-4 py-1">
656
+ <div className="flex items-center" style={{ paddingLeft: `${indentPx}px` }}>
657
+ {canExpand ? (
658
+ <button
659
+ className="expand-toggle mr-2 flex h-5 w-5 items-center justify-center rounded hover:bg-gray-200 dark:hover:bg-gray-700"
660
+ onClick={handleExpandToggle}
661
+ aria-label={isExpanded ? 'Collapse' : 'Expand'}
662
+ >
663
+ {isExpanded ? (
664
+ <ChevronDown className="h-4 w-4 text-gray-500" />
665
+ ) : (
666
+ <ChevronRight className="h-4 w-4 text-gray-500" />
667
+ )}
668
+ </button>
669
+ ) : (
670
+ <span className="mr-2 w-5" />
671
+ )}
672
+ <span className={depth > 0 ? 'text-gray-700 dark:text-gray-300' : ''}>
673
+ {startCase(row.name)}
678
674
  </span>
679
- )}
680
- </div>
681
- </TableCell>
682
- <TableCell className="border-r-[0.5px] px-3.5 py-1">
683
- <Badge variant={getVariantForType(row.type)} className="rounded-xl px-2.5">
684
- {row.type}
685
- </Badge>
686
- {row.type === 'array' && row.items?.type && (
687
- <Badge variant="outline" className="ml-1 rounded-xl px-2 text-[10px]">
688
- {row.items.type}[]
675
+ {canExpand && (
676
+ <span className="ml-2 text-xs text-gray-400">
677
+ ({Object.keys(nested || {}).length} {row.type === 'array' ? 'item fields' : 'fields'})
678
+ </span>
679
+ )}
680
+ </div>
681
+ </TableCell>
682
+ <TableCell className="border-r-[0.5px] px-3.5 py-1">
683
+ <Badge variant={getVariantForType(row.type)} className="rounded-xl px-2.5">
684
+ {row.type}
689
685
  </Badge>
690
- )}
691
- </TableCell>
692
- <TableCell className="whitespace-nowrap border-l-[0.5px] border-r-[0.5px] px-4 py-1 text-gray-600 dark:text-gray-300">
693
- {getSizeDisplay(row)}
694
- </TableCell>
695
- <TableCell className="border-r-[0.5px] px-4 py-1 text-gray-600 dark:text-gray-300">
696
- <span className="slot-description line-clamp-1">{row.description || row.generic || '-'}</span>
697
- </TableCell>
698
- </TableRow>
699
-
700
- {/* Render nested properties when expanded */}
701
- {isExpanded && nested && nested.length > 0 && (
702
- <TableRows
703
- rows={nested}
704
- openSheet={openSheet}
705
- depth={depth + 1}
706
- parentPath={path}
707
- />
708
- )}
709
- </>
710
- );
711
- };
686
+ {row.type === 'array' && row.items?.type && (
687
+ <Badge variant="outline" className="ml-1 rounded-xl px-2 text-[10px]">
688
+ {row.items.type}[]
689
+ </Badge>
690
+ )}
691
+ </TableCell>
692
+ <TableCell className="whitespace-nowrap border-l-[0.5px] border-r-[0.5px] px-4 py-1 text-gray-600 dark:text-gray-300">
693
+ {getSizeDisplay(row)}
694
+ </TableCell>
695
+ <TableCell className="border-r-[0.5px] px-4 py-1 text-gray-600 dark:text-gray-300">
696
+ <span className="slot-description line-clamp-1">{row.description || row.generic || '-'}</span>
697
+ </TableCell>
698
+ </TableRow>
699
+
700
+ {/* Render nested properties when expanded */}
701
+ {isExpanded && nested && nested.length > 0 && (
702
+ <TableRows
703
+ rows={nested}
704
+ openSheet={openSheet}
705
+ depth={depth + 1}
706
+ parentPath={path}
707
+ />
708
+ )}
709
+ </>
710
+ );
711
+ };
@@ -9,7 +9,7 @@ import React from 'react';
9
9
  */
10
10
  export const getComponentList = async (): Promise<ComponentListObject[]> => {
11
11
  // Try to load the component from the public json
12
- let data = await fetch(`/api/components.json`).then((res) => res.json());
12
+ let data = await fetch(`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/components.json`).then((res) => res.json());
13
13
  return data as ComponentListObject[];
14
14
  };
15
15
 
@@ -206,6 +206,9 @@ export const staticBuildMenu = () => {
206
206
  // // Contents of docs
207
207
  const docRoot = path.join(process.env.HANDOFF_MODULE_PATH ?? '', 'config/docs');
208
208
  // Get the file list
209
+ if(!fs.existsSync(docRoot)) {
210
+ return [];
211
+ }
209
212
  const files = fs.readdirSync(docRoot);
210
213
  let list = files;
211
214
  const workingPages = path.resolve(process.env.HANDOFF_WORKING_PATH ?? '', 'pages');
@@ -277,7 +280,7 @@ export const staticBuildMenu = () => {
277
280
  title: metadata.menuTitle ?? metadata.title,
278
281
  external,
279
282
  weight: metadata.weight ?? 0,
280
- path: filepath,
283
+ path: `${filepath}`,
281
284
  subSections,
282
285
  };
283
286
  }
@@ -287,6 +290,7 @@ export const staticBuildMenu = () => {
287
290
  };
288
291
 
289
292
  const staticBuildComponentMenu = (type?: boolean | string) => {
293
+ const basePath = (process.env.HANDOFF_APP_BASE_PATH ?? '').replace(/^\//, '');
290
294
  let menu = [];
291
295
  let components = fetchComponents({ includeTokens: false });
292
296
  if (typeof type === 'string' && type !== '') {
@@ -305,7 +309,7 @@ const staticBuildComponentMenu = (type?: boolean | string) => {
305
309
  if (component.name) {
306
310
  title = component.name;
307
311
  }
308
- menuGroup.menu.push({ path: `system/component/${component.id}`, title });
312
+ menuGroup.menu.push({ path: `${basePath}/system/component/${component.id}`, title });
309
313
  });
310
314
  // sort the menu group by name alphabetical
311
315
  menuGroup.menu = menuGroup.menu.sort((a, b) => a.title.localeCompare(b.title));
@@ -317,22 +321,24 @@ const staticBuildComponentMenu = (type?: boolean | string) => {
317
321
  };
318
322
 
319
323
  const staticBuildTokensMenu = () => {
324
+ const basePath = (process.env.HANDOFF_APP_BASE_PATH ?? '').replace(/^\//, '');
325
+
320
326
  const menu = [
321
327
  {
322
328
  title: `Foundations`,
323
- path: `system/tokens/foundations`,
329
+ path: `${basePath}/system/tokens/foundations`,
324
330
  menu: [
325
331
  {
326
332
  title: `Colors`,
327
- path: `system/tokens/foundations/colors`,
333
+ path: `${basePath}/system/tokens/foundations/colors`,
328
334
  },
329
335
  {
330
336
  title: `Effects`,
331
- path: `system/tokens/foundations/effects`,
337
+ path: `${basePath}/system/tokens/foundations/effects`,
332
338
  },
333
339
  {
334
340
  title: `Typography`,
335
- path: `system/tokens/foundations/typography`,
341
+ path: `${basePath}/system/tokens/foundations/typography`,
336
342
  },
337
343
  ],
338
344
  },
@@ -352,14 +358,14 @@ const staticBuildTokensMenu = () => {
352
358
  if (component.name) {
353
359
  title = component.name;
354
360
  }
355
- componentMenuItems.push({ path: `system/tokens/components/${component.id}`, title });
361
+ componentMenuItems.push({ path: `${basePath}/system/tokens/components/${component.id}`, title });
356
362
  });
357
363
  });
358
364
 
359
365
  if (componentMenuItems.length > 0) {
360
366
  menu.push({
361
367
  title: `Components`,
362
- path: `system/tokens/components`,
368
+ path: `${basePath}/system/tokens/components`,
363
369
  menu: componentMenuItems,
364
370
  });
365
371
  }
@@ -368,9 +374,11 @@ const staticBuildTokensMenu = () => {
368
374
  };
369
375
 
370
376
  const staticBuildTokenMenu = () => {
377
+ const basePath = (process.env.HANDOFF_APP_BASE_PATH ?? '').replace(/^\//, '');
378
+
371
379
  let subSections = {
372
380
  title: 'Tokens',
373
- path: 'system/tokens',
381
+ path: `${basePath}/system/tokens`,
374
382
  menu: [],
375
383
  };
376
384
  const tokens = getTokens();
@@ -110,7 +110,7 @@ const GenericComponentPage = ({ menu, metadata, current, id, config, componentHo
110
110
  const [componentPreviews, setComponentPreviews] = useState<PreviewObject | [string, PreviewObject][]>();
111
111
 
112
112
  const fetchComponents = async () => {
113
- let data = await fetch(`/api/component/${id}.json`).then((res) => res.json());
113
+ let data = await fetch(`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/component/${id}.json`).then((res) => res.json());
114
114
  setComponent(data as PreviewObject);
115
115
  };
116
116
 
@@ -67,7 +67,7 @@ const ComponentsPage = ({ content, menu, metadata, current, config }: ComponentP
67
67
  // Fetch components from api
68
68
  const [components, setComponents] = useState<PreviewObject[]>(undefined);
69
69
  const fetchComponents = async () => {
70
- let data = await fetch(`/api/components.json`).then((res) => res.json());
70
+ let data = await fetch(`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/components.json`).then((res) => res.json());
71
71
  setComponents(data as PreviewObject[]);
72
72
  };
73
73
  useEffect(() => {
@@ -108,7 +108,7 @@ const ComponentsPage = ({ content, menu, metadata, current, config }: ComponentP
108
108
  );
109
109
  }
110
110
 
111
- const apiUrl = (window.location.origin && window.location.origin) + `/api/components.json`;
111
+ const apiUrl = (window.location.origin && window.location.origin) + `${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/components.json`;
112
112
  return (
113
113
  <Layout config={config} menu={menu} current={current} metadata={metadata}>
114
114
  <div className="flex flex-col gap-2 pb-7">
@@ -39,14 +39,14 @@ export const getStaticProps: GetStaticProps = async (context) => {
39
39
  ...fetchDocPageMarkdown('docs/', 'system', `/system`).props,
40
40
  components: components
41
41
  ? components.reduce(
42
- (acc, component) => ({
43
- ...acc,
44
- ...{
45
- [component]: fetchDocPageMetadataAndContent('docs/components/', component).metadata,
46
- },
47
- }),
48
- {}
49
- )
42
+ (acc, component) => ({
43
+ ...acc,
44
+ ...{
45
+ [component]: fetchDocPageMetadataAndContent('docs/components/', component).metadata,
46
+ },
47
+ }),
48
+ {}
49
+ )
50
50
  : null,
51
51
  } as ComponentPageDocumentationProps,
52
52
  },
@@ -61,14 +61,14 @@ const ComponentsPage = ({ content, menu, metadata, current, config }: ComponentP
61
61
  // Fetch components from api
62
62
  const [components, setComponents] = useState<PreviewObject[]>(undefined);
63
63
  const fetchComponents = async () => {
64
- let data = await fetch(`/api/components.json`).then((res) => res.json());
64
+ let data = await fetch(`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/components.json`).then((res) => res.json());
65
65
  setComponents(data as PreviewObject[]);
66
66
  };
67
67
  useEffect(() => {
68
68
  fetchComponents();
69
69
  }, []);
70
70
  if (!components) return <p>Loading...</p>;
71
- const apiUrl = (window.location.origin && window.location.origin) + `/api/components.json`;
71
+ const apiUrl = (window.location.origin && window.location.origin) + `${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/components.json`;
72
72
  return (
73
73
  <Layout config={config} menu={menu} current={current} metadata={metadata}>
74
74
  <div className="flex flex-col gap-2 pb-7">
package/src/index.ts CHANGED
@@ -55,6 +55,9 @@ class Handoff {
55
55
  this.exportsDirectory = config.exportsOutputDirectory ?? this.exportsDirectory;
56
56
  this.sitesDirectory = config.sitesOutputDirectory ?? this.exportsDirectory;
57
57
  [this.runtimeConfig, this._configFilePaths] = initRuntimeConfig(this);
58
+ if(this.config.app.base_path && !process.env.HANDOFF_APP_BASE_PATH) {
59
+ process.env.HANDOFF_APP_BASE_PATH = this.config.app.base_path ?? '';
60
+ }
58
61
  return this;
59
62
  }
60
63
 
@@ -118,9 +118,9 @@ function generateHtmlDocument(componentId: string, previewTitle: string, rendere
118
118
  <html lang="en">
119
119
  <head>
120
120
  <meta charset="UTF-8" />
121
- <link rel="stylesheet" href="/api/component/main.css" />
122
- <link rel="stylesheet" href="/api/component/${componentId}.css" />
123
- <link rel="stylesheet" href="/assets/css/preview.css" />
121
+ <link rel="stylesheet" href="${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/component/main.css" />
122
+ <link rel="stylesheet" href="${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/component/${componentId}.css" />
123
+ <link rel="stylesheet" href="${process.env.HANDOFF_APP_BASE_PATH ?? ''}/assets/css/preview.css" />
124
124
  <script id="${PLUGIN_CONSTANTS.PROPS_SCRIPT_ID}" type="application/json">${JSON.stringify(props)}</script>
125
125
  <script type="module">
126
126
  ${clientJs}
@@ -107,7 +107,7 @@ export const updateComponentSummaryApi = async (handoff: Handoff, componentData:
107
107
  }
108
108
 
109
109
  // Partial update: merge with existing data
110
- const apiPath = path.resolve(handoff.workingPath, 'public/api/components.json');
110
+ const apiPath = path.resolve(handoff.workingPath, `${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/components.json`);
111
111
  let existingData: ComponentListObject[] = [];
112
112
 
113
113
  if (fs.existsSync(apiPath)) {