UNPKG

handoff-app

Version:

Automated documentation toolchain for building client side documentation from figma

175 lines (174 loc) 8.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.readComponentMetadataApi = exports.readComponentApi = exports.updateComponentSummaryApi = exports.writeComponentApi = exports.getAPIPath = void 0; const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); /** * Merges values from a source object into a target object, returning a new object. * For each key present in either object: * - If the key is listed in preserveKeys and the source value is undefined, null, or an empty string, * the target's value is preserved. * - Otherwise, the value from the source is used (even if undefined, null, or empty string). * This is useful for partial updates where some properties should not be overwritten unless explicitly set. * * @param target - The original object to merge into * @param source - The object containing new values * @param preserveKeys - Keys for which the target's value should be preserved if the source value is undefined, null, or empty string * @returns A new object with merged values */ function updateObject(target, source, preserveKeys = []) { // Collect all unique keys from both target and source const allKeys = Array.from(new Set([...Object.keys(target), ...Object.keys(source)])); return allKeys.reduce((acc, key) => { const sourceValue = source[key]; const targetValue = target[key]; // Preserve existing values for specified keys when source value is undefined if (preserveKeys.includes(key) && (sourceValue === undefined || sourceValue === null || sourceValue === '')) { acc[key] = targetValue; } else { acc[key] = sourceValue; } return acc; }, Object.assign({}, target)); } const getAPIPath = (handoff) => { const apiPath = path_1.default.resolve(handoff.workingPath, `public/api`); const componentPath = path_1.default.resolve(handoff.workingPath, `public/api/component`); // Ensure the public API path exists if (!fs_extra_1.default.existsSync(componentPath)) { fs_extra_1.default.mkdirSync(componentPath, { recursive: true }); } return apiPath; }; exports.getAPIPath = getAPIPath; /** * Build the preview API from the component data * @param handoff * @param componentData */ const writeComponentSummaryAPI = (handoff, componentData) => __awaiter(void 0, void 0, void 0, function* () { componentData.sort((a, b) => a.title.localeCompare(b.title)); yield fs_extra_1.default.writeFile(path_1.default.resolve((0, exports.getAPIPath)(handoff), 'components.json'), JSON.stringify(componentData, null, 2)); }); 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 = []) { const outputDirPath = path_1.default.resolve((0, exports.getAPIPath)(handoff), 'component'); const outputFilePath = path_1.default.resolve(outputDirPath, `${id}.json`); if (fs_extra_1.default.existsSync(outputFilePath)) { const existingJson = yield fs_extra_1.default.readFile(outputFilePath, 'utf8'); if (existingJson) { try { const existingData = JSON.parse(existingJson); // Special case: always allow page to be cleared when undefined // This handles the case where page slices are removed const finalPreserveKeys = component.page === undefined ? preserveKeys.filter((key) => key !== 'page') : preserveKeys; const mergedData = updateObject(existingData, component, finalPreserveKeys); yield fs_extra_1.default.writeFile(outputFilePath, JSON.stringify(mergedData, null, 2)); return; } catch (_) { // Unable to parse existing file } } } if (!fs_extra_1.default.existsSync(outputDirPath)) { fs_extra_1.default.mkdirSync(outputDirPath, { recursive: true }); } yield fs_extra_1.default.writeFile(outputFilePath, JSON.stringify(component, null, 2)); }); exports.writeComponentApi = writeComponentApi; /** * Update the main component summary API with the new component data * @param handoff * @param componentData */ const updateComponentSummaryApi = (handoff_1, componentData_1, ...args_1) => __awaiter(void 0, [handoff_1, componentData_1, ...args_1], void 0, function* (handoff, componentData, isFullRebuild = false) { if (isFullRebuild) { // Full rebuild: replace the entire file yield writeComponentSummaryAPI(handoff, componentData); return; } // Partial update: merge with existing data const apiPath = path_1.default.resolve(handoff.workingPath, 'public/api/components.json'); let existingData = []; if (fs_extra_1.default.existsSync(apiPath)) { try { const existing = yield fs_extra_1.default.readFile(apiPath, 'utf8'); existingData = JSON.parse(existing); } catch (_a) { // Corrupt or missing JSON — treat as empty existingData = []; } } // Replace existing entries with same ID const incomingIds = new Set(componentData.map((c) => c.id)); const merged = [...componentData, ...existingData.filter((c) => !incomingIds.has(c.id))]; // Always write the file (even if merged is empty) yield writeComponentSummaryAPI(handoff, merged); }); exports.updateComponentSummaryApi = updateComponentSummaryApi; /** * Read the component API data * @param handoff * @param id * @returns */ const readComponentApi = (handoff, id) => __awaiter(void 0, void 0, void 0, function* () { const outputFilePath = path_1.default.resolve((0, exports.getAPIPath)(handoff), 'component', `${id}.json`); if (fs_extra_1.default.existsSync(outputFilePath)) { try { const existingJson = yield fs_extra_1.default.readFile(outputFilePath, 'utf8'); if (existingJson) { return JSON.parse(existingJson); } } catch (_) { // Unable to parse existing file } } return null; }); exports.readComponentApi = readComponentApi; /** * Read the component metadata/summary from the component JSON file * @param handoff * @param id * @returns The component summary or null if not found */ const readComponentMetadataApi = (handoff, id) => __awaiter(void 0, void 0, void 0, function* () { const componentData = yield (0, exports.readComponentApi)(handoff, id); if (!componentData) { return null; } // Construct the summary from the full component data return { id, title: componentData.title, description: componentData.description, type: componentData.type, group: componentData.group, image: componentData.image ? componentData.image : '', figma: componentData.figma ? componentData.figma : '', figmaComponentId: componentData.figmaComponentId, categories: componentData.categories ? componentData.categories : [], tags: componentData.tags ? componentData.tags : [], properties: componentData.properties, previews: componentData.previews, path: `/api/component/${id}.json`, }; }); exports.readComponentMetadataApi = readComponentMetadataApi; exports.default = writeComponentSummaryAPI;