const fs = require("fs"); const path = require("path"); let isInWatchMode = false; if (process.argv[2] === "--watch") { isInWatchMode = true; } const chokidar = require("chokidar"); const { debounce } = require("lodash"); const APP_STORE_PATH = path.join(__dirname, "..", "..", "app-store"); function getAppName(candidatePath) { function isValidAppName(candidatePath) { if (!candidatePath.startsWith("_") && !candidatePath.includes("/") && !candidatePath.includes("\\")) { return candidatePath; } } if (isValidAppName(candidatePath)) { // Already a dirname of an app return candidatePath; } // Get dirname of app from full path const dirName = path.relative(APP_STORE_PATH, candidatePath); return isValidAppName(dirName) ? dirName : null; } function generateFiles() { const browserOutput = [`import dynamic from "next/dynamic"`]; const serverOutput = []; const appDirs = []; fs.readdirSync(`${APP_STORE_PATH}`).forEach(function (dir) { if (fs.statSync(`${APP_STORE_PATH}/${dir}`).isDirectory()) { if (!getAppName(dir)) { return; } appDirs.push(dir); } }); function forEachAppDir(callback) { for (let i = 0; i < appDirs.length; i++) { callback(appDirs[i]); } } function getObjectExporter(objectName, { fileToBeImported, importBuilder, entryBuilder }) { const output = []; forEachAppDir((appName) => { if (fs.existsSync(path.join(APP_STORE_PATH, appName, fileToBeImported))) { output.push(importBuilder(appName)); } }); output.push(`export const ${objectName} = {`); forEachAppDir((dirName) => { if (fs.existsSync(path.join(APP_STORE_PATH, dirName, fileToBeImported))) { output.push(entryBuilder(dirName)); } }); output.push(`};`); return output; } serverOutput.push( ...getObjectExporter("apiHandlers", { fileToBeImported: "api/index.ts", importBuilder: (appName) => `const ${appName}_api = import("./${appName}/api");`, entryBuilder: (appName) => `${appName}:${appName}_api,`, }) ); browserOutput.push( ...getObjectExporter("appStoreMetadata", { fileToBeImported: "_metadata.ts", importBuilder: (appName) => `import { metadata as ${appName}_meta } from "./${appName}/_metadata";`, entryBuilder: (appName) => `${appName}:${appName}_meta,`, }) ); browserOutput.push( ...getObjectExporter("InstallAppButtonMap", { fileToBeImported: "components/InstallAppButton.tsx", importBuilder: (appName) => `const ${appName}_installAppButton = dynamic(() =>import("./${appName}/components/InstallAppButton"));`, entryBuilder: (appName) => `${appName}:${appName}_installAppButton,`, }) ); const banner = `/** This file is autogenerated using the command \`yarn app-store --watch\`. It automatically runs when using \`yarn dx\` Don't modify this file manually. **/ `; fs.writeFileSync(`${APP_STORE_PATH}/apps.server.generated.ts`, `${banner}${serverOutput.join("\n")}`); fs.writeFileSync(`${APP_STORE_PATH}/apps.browser.generated.tsx`, `${banner}${browserOutput.join("\n")}`); console.log("Generated `apps.server.generated.ts` and `apps.browser.generated.tsx`"); } const debouncedGenerateFiles = debounce(generateFiles); if (isInWatchMode) { chokidar .watch(APP_STORE_PATH) .on("addDir", (dirPath) => { const appName = getAppName(dirPath); if (appName) { console.log(`Added ${appName}`); debouncedGenerateFiles(); } }) .on("change", (filePath) => { if (filePath.endsWith("config.json")) { console.log("Config file changed"); debouncedGenerateFiles(); } }) .on("unlinkDir", (dirPath) => { const appName = getAppName(dirPath); if (appName) { console.log(`Removed ${appName}`); debouncedGenerateFiles(); } }); } else { generateFiles(); }