handoff-app
Version:
Automated documentation toolchain for building client side documentation from figma
661 lines (652 loc) • 31.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
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.devApp = exports.watchApp = void 0;
const chalk_1 = __importDefault(require("chalk"));
const chokidar_1 = __importDefault(require("chokidar"));
const cross_spawn_1 = __importDefault(require("cross-spawn"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const gray_matter_1 = __importDefault(require("gray-matter"));
const http_1 = require("http");
const next_1 = __importDefault(require("next"));
const path_1 = __importDefault(require("path"));
const url_1 = require("url");
const ws_1 = __importDefault(require("ws"));
const config_1 = require("./config");
const pipeline_1 = require("./pipeline");
const builder_1 = __importStar(require("./transformers/preview/component/builder"));
/**
* Creates a WebSocket server that broadcasts messages to connected clients.
* Designed for development mode to help with hot-reloading.
*
* @param port - Optional port number for the WebSocket server; defaults to 3001.
* @returns A function that accepts a message string and broadcasts it to all connected clients.
*/
const createWebSocketServer = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (port = 3001) {
const wss = new ws_1.default.Server({ port });
// Heartbeat function to mark a connection as alive.
const heartbeat = function () {
this.isAlive = true;
};
// Setup a new connection
wss.on('connection', (ws) => {
const extWs = ws;
extWs.isAlive = true;
extWs.send(JSON.stringify({ type: 'WELCOME' }));
extWs.on('error', (error) => console.error('WebSocket error:', error));
extWs.on('pong', heartbeat);
});
// Periodically ping clients to ensure they are still connected
const pingInterval = setInterval(() => {
wss.clients.forEach((client) => {
const extWs = client;
if (!extWs.isAlive) {
console.log(chalk_1.default.yellow('Terminating inactive client'));
return client.terminate();
}
extWs.isAlive = false;
client.ping();
});
}, 30000);
// Clean up the interval when the server closes
wss.on('close', () => {
clearInterval(pingInterval);
});
console.log(chalk_1.default.green(`WebSocket server started on ws://localhost:${port}`));
// Return a function to broadcast a message to all connected clients
return (message) => {
console.log(chalk_1.default.green(`Broadcasting message to ${wss.clients.size} client(s)`));
wss.clients.forEach((client) => {
if (client.readyState === ws_1.default.OPEN) {
client.send(message);
}
});
};
});
/**
* Gets the working public directory path for a given handoff instance
* Checks for both project-specific and default public directories
*
* @param handoff - The handoff instance containing working path and figma project configuration
* @returns The resolved path to the public directory if it exists, null otherwise
*/
const getWorkingPublicPath = (handoff) => {
const paths = [
path_1.default.resolve(handoff.workingPath, `public-${handoff.config.figma_project_id}`),
path_1.default.resolve(handoff.workingPath, `public`),
];
for (const path of paths) {
if (fs_extra_1.default.existsSync(path)) {
return path;
}
}
return null;
};
/**
* Gets the application path for a given handoff instance
* @param handoff - The handoff instance containing module path and figma project configuration
* @returns The resolved path to the application directory
*/
const getAppPath = (handoff) => {
return path_1.default.resolve(handoff.modulePath, '.handoff', `${handoff.config.figma_project_id}`);
};
/**
* Copy the public dir from the working dir to the module dir
* @param handoff
*/
const mergePublicDir = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
const appPath = getAppPath(handoff);
const workingPublicPath = getWorkingPublicPath(handoff);
if (workingPublicPath) {
fs_extra_1.default.copySync(workingPublicPath, path_1.default.resolve(appPath, 'public'), { overwrite: true });
}
});
/**
* Publish the mdx files from the working dir to the module dir
* @param handoff
*/
const publishMDX = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
console.log(chalk_1.default.yellow('Merging MDX files...'));
const appPath = getAppPath(handoff);
const pages = path_1.default.resolve(handoff.workingPath, `pages`);
if (fs_extra_1.default.existsSync(pages)) {
// Find all mdx files in path
const files = fs_extra_1.default.readdirSync(pages);
for (const file of files) {
if (file.endsWith('.mdx')) {
// transform the file
transformMdx(path_1.default.resolve(pages, file), path_1.default.resolve(appPath, 'pages', file), file.replace('.mdx', ''));
}
else if (fs_extra_1.default.lstatSync(path_1.default.resolve(pages, file)).isDirectory()) {
// Recursion - find all mdx files in sub directories
const subFiles = fs_extra_1.default.readdirSync(path_1.default.resolve(pages, file));
for (const subFile of subFiles) {
if (subFile.endsWith('.mdx')) {
// transform the file
const target = path_1.default.resolve(appPath, 'pages', file);
if (!fs_extra_1.default.existsSync(target)) {
fs_extra_1.default.mkdirSync(target, { recursive: true });
}
transformMdx(path_1.default.resolve(pages, file, subFile), path_1.default.resolve(appPath, 'pages', file, subFile), file);
}
else if (fs_extra_1.default.lstatSync(path_1.default.resolve(pages, file, subFile)).isDirectory()) {
const thirdFiles = fs_extra_1.default.readdirSync(path_1.default.resolve(pages, file, subFile));
for (const thirdFile of thirdFiles) {
if (thirdFile.endsWith('.mdx')) {
const target = path_1.default.resolve(appPath, 'pages', file, subFile);
if (!fs_extra_1.default.existsSync(target)) {
fs_extra_1.default.mkdirSync(target, { recursive: true });
}
transformMdx(path_1.default.resolve(pages, file, subFile, thirdFile), path_1.default.resolve(appPath, 'pages', file, subFile, thirdFile), file);
}
}
}
}
}
}
}
});
/**
* Remove the frontmatter from the mdx file, convert it to an import, and
* add the metadata to the export. Then write the file to the destination.
* @param src
* @param dest
* @param id
*/
const transformMdx = (src, dest, id) => {
var _a, _b, _c, _d, _e, _f, _g;
const content = fs_extra_1.default.readFileSync(src);
const { data, content: body } = (0, gray_matter_1.default)(content);
const title = (_a = data.title) !== null && _a !== void 0 ? _a : '';
const description = data.description ? data.description.replace(/(\r\n|\n|\r)/gm, '') : '';
const metaDescription = (_b = data.metaDescription) !== null && _b !== void 0 ? _b : '';
const metaTitle = (_c = data.metaTitle) !== null && _c !== void 0 ? _c : '';
const weight = (_d = data.weight) !== null && _d !== void 0 ? _d : 0;
const image = (_e = data.image) !== null && _e !== void 0 ? _e : '';
const menuTitle = (_f = data.menuTitle) !== null && _f !== void 0 ? _f : '';
const enabled = (_g = data.enabled) !== null && _g !== void 0 ? _g : true;
const wide = data.wide ? 'true' : 'false';
const mdxHeader = `// This file is auto-generated by transformMdx(). Do not edit manually.
// Source: ${src}
// Generated at: ${new Date().toISOString()}
`;
const mdx = `${mdxHeader}import { getClientRuntimeConfig, getCurrentSection, staticBuildMenu } from '@handoff/app/components/util';
import fs from 'fs-extra';
import matter from 'gray-matter';
import { MDXRemote } from 'next-mdx-remote';
import { serialize } from 'next-mdx-remote/serialize';
import path from 'path';
export async function getStaticProps() {
const mdxFilePath = path.join(process.env.HANDOFF_WORKING_PATH, 'pages', '${id}.mdx');
const mdxSource = fs.readFileSync(mdxFilePath, 'utf8');
const { data, content: body } = matter(mdxSource); // extract frontmatter and body
const mdx = await serialize(body); // serialize only the body
const menu = staticBuildMenu();
const config = getClientRuntimeConfig();
return {
props: {
mdx,
menu,
config,
current: getCurrentSection(menu, "/${id}") ?? [],
title: "${title}",
description: "${description}",
image: "${image}",
},
};
}
import MarkdownLayout from "@handoff/app/components/Layout/Markdown";
import { Hero } from "@handoff/app/components/Hero";
const components = { Hero };
export default function Layout(props) {
return (
<MarkdownLayout
menu={props.menu}
metadata={{
description: "${description}",
metaDescription: "${metaDescription}",
metaTitle: "${metaTitle}",
title: "${title}",
}}
wide={${wide}}
config={props.config}
current={props.current}
>
<MDXRemote {...props.mdx} components={components} />
</MarkdownLayout>
);
}`;
fs_extra_1.default.writeFileSync(dest.replaceAll('.mdx', '.tsx'), mdx, 'utf-8');
};
/**
* Performs cleanup of the application directory by removing the existing app directory if it exists.
* This is typically used before rebuilding the application to ensure a clean state.
*
* @param handoff - The Handoff instance containing configuration and working paths
* @returns Promise that resolves when cleanup is complete
*/
const performCleanup = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
const appPath = getAppPath(handoff);
// Clean project app dir
if (fs_extra_1.default.existsSync(appPath)) {
yield fs_extra_1.default.rm(appPath, { recursive: true });
}
});
const publishTokensApi = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
const apiPath = path_1.default.resolve(path_1.default.join(handoff.workingPath, 'public/api'));
if (!fs_extra_1.default.existsSync(apiPath)) {
fs_extra_1.default.mkdirSync(apiPath, { recursive: true });
}
const tokens = yield handoff.getDocumentationObject();
fs_extra_1.default.writeFileSync(path_1.default.join(apiPath, 'tokens.json'), JSON.stringify(tokens, null, 2));
if (!fs_extra_1.default.existsSync(path_1.default.join(apiPath, 'tokens'))) {
fs_extra_1.default.mkdirSync(path_1.default.join(apiPath, 'tokens'));
}
for (const type in tokens) {
if (type === 'timestamp')
continue;
for (const group in tokens[type]) {
fs_extra_1.default.writeFileSync(path_1.default.join(apiPath, 'tokens', `${group}.json`), JSON.stringify(tokens[type][group], null, 2));
}
}
});
const prepareProjectApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c, _d, _e;
const srcPath = path_1.default.resolve(handoff.modulePath, 'src', 'app');
const appPath = getAppPath(handoff);
// Publish tokens API
publishTokensApi(handoff);
// Prepare project app dir
yield fs_extra_1.default.promises.mkdir(appPath, { recursive: true });
yield fs_extra_1.default.copy(srcPath, appPath, { overwrite: true });
yield mergePublicDir(handoff);
yield publishMDX(handoff);
// Prepare project app configuration
const handoffProjectId = (_a = handoff.config.figma_project_id) !== null && _a !== void 0 ? _a : '';
const handoffAppBasePath = (_b = handoff.config.app.base_path) !== null && _b !== void 0 ? _b : '';
const handoffWorkingPath = path_1.default.resolve(handoff.workingPath);
const handoffModulePath = path_1.default.resolve(handoff.modulePath);
const handoffExportPath = path_1.default.resolve(handoff.workingPath, handoff.exportsDirectory, handoff.config.figma_project_id);
const nextConfigPath = path_1.default.resolve(appPath, 'next.config.mjs');
const handoffUseReferences = (_c = handoff.config.useVariables) !== null && _c !== void 0 ? _c : false;
const handoffWebsocketPort = (_e = (_d = handoff.config.app.ports) === null || _d === void 0 ? void 0 : _d.websocket) !== null && _e !== void 0 ? _e : 3001;
const nextConfigContent = (yield fs_extra_1.default.readFile(nextConfigPath, 'utf-8'))
.replace(/basePath:\s+\'\'/g, `basePath: '${handoffAppBasePath}'`)
.replace(/HANDOFF_PROJECT_ID:\s+\'\'/g, `HANDOFF_PROJECT_ID: '${handoffProjectId}'`)
.replace(/HANDOFF_APP_BASE_PATH:\s+\'\'/g, `HANDOFF_APP_BASE_PATH: '${handoffAppBasePath}'`)
.replace(/HANDOFF_WORKING_PATH:\s+\'\'/g, `HANDOFF_WORKING_PATH: '${handoffWorkingPath}'`)
.replace(/HANDOFF_MODULE_PATH:\s+\'\'/g, `HANDOFF_MODULE_PATH: '${handoffModulePath}'`)
.replace(/HANDOFF_EXPORT_PATH:\s+\'\'/g, `HANDOFF_EXPORT_PATH: '${handoffExportPath}'`)
.replace(/HANDOFF_WEBSOCKET_PORT:\s+\'\'/g, `HANDOFF_WEBSOCKET_PORT: '${handoffWebsocketPort}'`)
.replace(/%HANDOFF_MODULE_PATH%/g, handoffModulePath);
yield fs_extra_1.default.writeFile(nextConfigPath, nextConfigContent);
return appPath;
});
const persistRuntimeCache = (handoff) => {
const destination = path_1.default.resolve(handoff.workingPath, handoff.exportsDirectory, handoff.config.figma_project_id, 'runtime.cache.json');
fs_extra_1.default.writeFileSync(destination, JSON.stringify(Object.assign({ config: (0, config_1.getClientConfig)(handoff) }, handoff.integrationObject), null, 2), 'utf-8');
};
/**
* Build the next js application
* @param handoff
* @returns
*/
const buildApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
if (!fs_extra_1.default.existsSync(path_1.default.resolve(handoff.workingPath, handoff.exportsDirectory, handoff.config.figma_project_id, 'tokens.json'))) {
throw new Error('Tokens not exported. Run `handoff-app fetch` first.');
}
// Perform cleanup
yield performCleanup(handoff);
// Build components
yield (0, pipeline_1.buildComponents)(handoff);
// Prepare app
const appPath = yield prepareProjectApp(handoff);
persistRuntimeCache(handoff);
// Build app
const buildResult = cross_spawn_1.default.sync('npx', ['next', 'build'], {
cwd: appPath,
stdio: 'inherit',
env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'production' }),
});
if (buildResult.status !== 0) {
let errorMsg = `Next.js build failed with exit code ${buildResult.status}`;
if (buildResult.error) {
errorMsg += `\nSpawn error: ${buildResult.error.message}`;
}
throw new Error(errorMsg);
}
// Ensure output root directory exists
const outputRoot = path_1.default.resolve(handoff.workingPath, handoff.sitesDirectory);
if (!fs_extra_1.default.existsSync(outputRoot)) {
fs_extra_1.default.mkdirSync(outputRoot, { recursive: true });
}
// Clean the project output directory (if exists)
const output = path_1.default.resolve(outputRoot, handoff.config.figma_project_id);
if (fs_extra_1.default.existsSync(output)) {
fs_extra_1.default.removeSync(output);
}
// Copy the build files into the project output directory
fs_extra_1.default.copySync(path_1.default.resolve(appPath, 'out'), output);
});
/**
* Watch the next js application
* @param handoff
*/
const watchApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h;
const tokensJsonFilePath = handoff.getTokensFilePath();
if (!fs_extra_1.default.existsSync(tokensJsonFilePath)) {
throw new Error('Tokens not exported. Run `handoff-app fetch` first.');
}
// Initial processing of the components
yield (0, builder_1.default)(handoff);
const appPath = yield prepareProjectApp(handoff);
// Include any changes made within the app source during watch
chokidar_1.default
.watch(path_1.default.resolve(handoff.modulePath, 'src', 'app'), {
ignored: /(^|[\/\\])\../, // ignore dotfiles
persistent: true,
ignoreInitial: true,
})
.on('all', (event, path) => __awaiter(void 0, void 0, void 0, function* () {
switch (event) {
case 'add':
case 'change':
case 'unlink':
yield prepareProjectApp(handoff);
break;
}
}));
// // does a ts config exist?
// let tsconfigPath = 'tsconfig.json';
// config.typescript = {
// ...config.typescript,
// tsconfigPath,
// };
const dev = true;
const hostname = 'localhost';
const port = (_b = (_a = handoff.config.app.ports) === null || _a === void 0 ? void 0 : _a.app) !== null && _b !== void 0 ? _b : 3000;
// when using middleware `hostname` and `port` must be provided below
const app = (0, next_1.default)({
dev,
dir: appPath,
hostname,
port,
// conf: config,
});
const handle = app.getRequestHandler();
// purge out cache
const moduleOutput = path_1.default.resolve(appPath, 'out');
if (fs_extra_1.default.existsSync(moduleOutput)) {
fs_extra_1.default.removeSync(moduleOutput);
}
app.prepare().then(() => {
(0, http_1.createServer)((req, res) => __awaiter(void 0, void 0, void 0, function* () {
try {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
if (!req.url)
throw new Error('No url');
const parsedUrl = (0, url_1.parse)(req.url, true);
const { pathname, query } = parsedUrl;
yield handle(req, res, parsedUrl);
}
catch (err) {
console.error('Error occurred handling', req.url, err);
res.statusCode = 500;
res.end('internal server error');
}
}))
.once('error', (err) => {
console.error(err);
process.exit(1);
})
.listen(port, () => {
console.log(`> Ready on http://${hostname}:${port}`);
});
});
const wss = yield createWebSocketServer((_d = (_c = handoff.config.app.ports) === null || _c === void 0 ? void 0 : _c.websocket) !== null && _d !== void 0 ? _d : 3001);
const chokidarConfig = {
ignored: /(^|[\/\\])\../, // ignore dotfiles
persistent: true,
ignoreInitial: true,
};
let debounce = false;
if (fs_extra_1.default.existsSync(path_1.default.resolve(handoff.workingPath, 'exportables'))) {
chokidar_1.default.watch(path_1.default.resolve(handoff.workingPath, 'exportables'), chokidarConfig).on('all', (event, path) => __awaiter(void 0, void 0, void 0, function* () {
switch (event) {
case 'add':
case 'change':
case 'unlink':
if (path.includes('json') && !debounce) {
console.log(chalk_1.default.yellow('Exportables changed. Handoff will fetch new tokens...'));
debounce = true;
yield handoff.fetch();
debounce = false;
}
break;
}
}));
}
if (fs_extra_1.default.existsSync(path_1.default.resolve(handoff.workingPath, 'public'))) {
chokidar_1.default.watch(path_1.default.resolve(handoff.workingPath, 'public'), chokidarConfig).on('all', (event, path) => __awaiter(void 0, void 0, void 0, function* () {
switch (event) {
case 'add':
case 'change':
case 'unlink':
if (!debounce) {
debounce = true;
console.log(chalk_1.default.yellow('Public directory changed. Handoff will ingest the new data...'));
yield mergePublicDir(handoff);
wss(JSON.stringify({ type: 'reload' }));
debounce = false;
}
break;
}
}));
}
let runtimeComponentsWatcher = null;
let runtimeConfigurationWatcher = null;
const entryTypeToSegment = (type) => {
return {
js: builder_1.ComponentSegment.JavaScript,
scss: builder_1.ComponentSegment.Style,
templates: builder_1.ComponentSegment.Previews,
}[type];
};
const watchRuntimeComponents = (runtimeComponentPathsToWatch) => {
persistRuntimeCache(handoff);
if (runtimeComponentsWatcher) {
runtimeComponentsWatcher.close();
}
if (runtimeComponentPathsToWatch.size > 0) {
const pathsToWatch = Array.from(runtimeComponentPathsToWatch.keys());
runtimeComponentsWatcher = chokidar_1.default.watch(pathsToWatch, { ignoreInitial: true });
runtimeComponentsWatcher.on('all', (event, file) => __awaiter(void 0, void 0, void 0, function* () {
if (handoff.getConfigFilePaths().includes(file)) {
return;
}
switch (event) {
case 'add':
case 'change':
case 'unlink':
if (!debounce) {
debounce = true;
let segmentToUpdate = undefined;
const matchingPath = runtimeComponentPathsToWatch.get(file);
if (matchingPath) {
const entryType = runtimeComponentPathsToWatch.get(matchingPath);
segmentToUpdate = entryTypeToSegment(entryType);
}
const componentDir = path_1.default.basename(path_1.default.dirname(path_1.default.dirname(file)));
yield (0, builder_1.default)(handoff, componentDir, segmentToUpdate);
debounce = false;
}
break;
}
}));
}
};
const watchRuntimeConfiguration = () => {
if (runtimeConfigurationWatcher) {
runtimeConfigurationWatcher.close();
}
if (handoff.getConfigFilePaths().length > 0) {
runtimeConfigurationWatcher = chokidar_1.default.watch(handoff.getConfigFilePaths(), { ignoreInitial: true });
runtimeConfigurationWatcher.on('all', (event, file) => __awaiter(void 0, void 0, void 0, function* () {
switch (event) {
case 'add':
case 'change':
case 'unlink':
if (!debounce) {
debounce = true;
file = path_1.default.dirname(path_1.default.dirname(file));
handoff.reload();
watchRuntimeComponents(getRuntimeComponentsPathsToWatch());
yield (0, builder_1.default)(handoff, path_1.default.basename(file));
debounce = false;
}
break;
}
}));
}
};
const getRuntimeComponentsPathsToWatch = () => {
var _a, _b, _c;
const result = new Map();
for (const runtimeComponentId of Object.keys((_b = (_a = handoff.integrationObject) === null || _a === void 0 ? void 0 : _a.entries.components) !== null && _b !== void 0 ? _b : {})) {
for (const runtimeComponentVersion of Object.keys(handoff.integrationObject.entries.components[runtimeComponentId])) {
const runtimeComponent = handoff.integrationObject.entries.components[runtimeComponentId][runtimeComponentVersion];
for (const [runtimeComponentEntryType, runtimeComponentEntryPath] of Object.entries((_c = runtimeComponent.entries) !== null && _c !== void 0 ? _c : {})) {
const normalizedComponentEntryPath = runtimeComponentEntryPath;
if (fs_extra_1.default.existsSync(normalizedComponentEntryPath)) {
const entryType = runtimeComponentEntryType;
if (fs_extra_1.default.statSync(normalizedComponentEntryPath).isFile()) {
result.set(path_1.default.dirname(normalizedComponentEntryPath), entryType);
}
else {
result.set(normalizedComponentEntryPath, entryType);
}
}
}
}
}
return result;
};
/*
if (fs.existsSync(path.resolve(handoff.workingPath, 'handoff.config.json'))) {
chokidar.watch(path.resolve(handoff.workingPath, 'handoff.config.json'), { ignoreInitial: true }).on('all', async (event, file) => {
console.log(chalk.yellow('handoff.config.json changed. Please restart server to see changes...'));
if (!debounce) {
debounce = true;
handoff.reload();
watchRuntimeComponents(getRuntimeComponentsPathsToWatch());
watchRuntimeConfiguration();
await processComponents(handoff, undefined, sharedStyles, documentationObject.components);
debounce = false;
}
});
}
*/
watchRuntimeComponents(getRuntimeComponentsPathsToWatch());
watchRuntimeConfiguration();
if (((_f = (_e = handoff.integrationObject) === null || _e === void 0 ? void 0 : _e.entries) === null || _f === void 0 ? void 0 : _f.integration) && fs_extra_1.default.existsSync((_h = (_g = handoff.integrationObject) === null || _g === void 0 ? void 0 : _g.entries) === null || _h === void 0 ? void 0 : _h.integration)) {
const stat = yield fs_extra_1.default.stat(handoff.integrationObject.entries.integration);
chokidar_1.default
.watch(stat.isDirectory() ? handoff.integrationObject.entries.integration : path_1.default.dirname(handoff.integrationObject.entries.integration), chokidarConfig)
.on('all', (event, file) => __awaiter(void 0, void 0, void 0, function* () {
switch (event) {
case 'add':
case 'change':
case 'unlink':
if (!debounce) {
debounce = true;
yield handoff.getSharedStyles();
debounce = false;
}
}
}));
}
if (fs_extra_1.default.existsSync(path_1.default.resolve(handoff.workingPath, 'pages'))) {
chokidar_1.default.watch(path_1.default.resolve(handoff.workingPath, 'pages'), chokidarConfig).on('all', (event, path) => __awaiter(void 0, void 0, void 0, function* () {
switch (event) {
case 'add':
case 'change':
case 'unlink':
if (path.endsWith('.mdx')) {
publishMDX(handoff);
}
console.log(chalk_1.default.yellow(`Doc page ${event}ed. Please reload browser to see changes...`), path);
break;
}
}));
}
});
exports.watchApp = watchApp;
/**
* Watch the next js application
* @param handoff
*/
const devApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b;
if (!fs_extra_1.default.existsSync(path_1.default.resolve(handoff.workingPath, handoff.exportsDirectory, handoff.config.figma_project_id, 'tokens.json'))) {
throw new Error('Tokens not exported. Run `handoff-app fetch` first.');
}
// Prepare app
const appPath = yield prepareProjectApp(handoff);
// Purge app cache
const moduleOutput = path_1.default.resolve(appPath, 'out');
if (fs_extra_1.default.existsSync(moduleOutput)) {
fs_extra_1.default.removeSync(moduleOutput);
}
persistRuntimeCache(handoff);
// Run
const devResult = cross_spawn_1.default.sync('npx', ['next', 'dev', '--port', String((_b = (_a = handoff.config.app.ports) === null || _a === void 0 ? void 0 : _a.app) !== null && _b !== void 0 ? _b : 3000)], {
cwd: appPath,
stdio: 'inherit',
env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'development' }),
});
if (devResult.status !== 0) {
let errorMsg = `Next.js dev failed with exit code ${devResult.status}`;
if (devResult.error) {
errorMsg += `\nSpawn error: ${devResult.error.message}`;
}
throw new Error(errorMsg);
}
});
exports.devApp = devApp;
exports.default = buildApp;