UNPKG

@knapsack/app

Version:

Build Design Systems with Knapsack

502 lines • 20.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMeta = getMeta; exports.getHttpsCert = getHttpsCert; exports.writeKsMetaFile = writeKsMetaFile; exports.getKsMeta = getKsMeta; exports.writeTemplateMeta = writeTemplateMeta; exports.initAll = initAll; exports.hydrateAll = hydrateAll; exports.getDataStore = getDataStore; exports.savePrepNewDataStore = savePrepNewDataStore; exports.writeAppClientData = writeAppClientData; exports.ensureDataDirFilesFormatted = ensureDataDirFilesFormatted; exports.build = build; exports.testPatternRenders = testPatternRenders; const types_1 = require("@knapsack/types"); const file_utils_1 = require("@knapsack/file-utils"); const utils_1 = require("@knapsack/utils"); const cjs_1 = require("@knapsack/logger/cjs"); const path_1 = require("path"); const https_cert_1 = require("../lib/https-cert"); const server_utils_1 = require("../server/server-utils"); const log_1 = require("./log"); const misc_1 = require("../types/misc"); const routes_1 = require("../lib/routes"); const bootstrap_1 = require("../lib/bootstrap"); const cache_dir_1 = require("../lib/util/cache-dir"); const constants_1 = require("../lib/constants"); function getMeta({ config }) { return { version: config.version, hasKnapsackCloud: 'cloud' in config, knapsackCloudSiteId: config.cloud?.siteId ?? '', ksVersions: { app: constants_1.APP_CLIENT_VERSION, }, }; } /** * Gets the SSL cert to use for the dev server * If `cert` and `key` are provided in the config, they will be used * If not, we'll auto-generate an SSL cert * If auto-generating, we'll use mkcert to generate the cert * @see {@link getAutoGeneratedSSLCert} */ async function getHttpsCert(devServer) { if (!devServer || !devServer?.https) return null; if (!devServer.ssl) { // no manual cert, so auto-generate try { log_1.log.info('Using auto-generated SSL cert'); return await (0, https_cert_1.getAutoGeneratedSSLCert)(); } catch (error) { throw new Error(`Error auto-creating SSL cert. Consider setting "devServer.https" to "false" or using your own SSL cert by setting "devServer.ssl" to an object with "cert" and "key" properties pointing to the files on disk.`, { cause: error }); } } // manual cert, so use it try { log_1.log.info('Using SSL cert from config `devServer.ssl`', { certPath: (0, path_1.relative)(process.cwd(), devServer.ssl.cert), keyPath: (0, path_1.relative)(process.cwd(), devServer.ssl.key), }); const [cert, key] = await Promise.all([ (0, file_utils_1.readFile)(devServer.ssl.cert), (0, file_utils_1.readFile)(devServer.ssl.key), ]); return { cert, key }; } catch (error) { /** Should never happen b/c we check for files in {@link validateConfig} */ throw new Error(`Error getting manual SSL cert configured in knapsack.config.cjs at "devServer.ssl". Consider setting "devServer.https" to "false" or removing the "devServer.ssl" property to use auto-generated SSL certs.`, { cause: error }); } } const getMetaDir = (distDir) => (0, path_1.join)(distDir, 'meta'); async function writeKsMetaFile({ allPatterns, allAssetSetIds = [], distDir, demosById, }) { const demoUrls = allPatterns.reduce((cur, { id: patternId, templates }) => { // For every template within patterns templates.forEach(({ id: templateId, demoIds = [] }) => { // For every demoId within demos demoIds.forEach((demoId) => { // Find real demo object off of db.demos.byId const demo = demosById[demoId]; if (!demo) return; const { width, height } = demo; if (allAssetSetIds.length === 0) { cur.push({ patternId, templateId, demoId, width, height, url: (0, routes_1.createDemoUrl)({ patternId, templateId, demoId, assetSetId: '', }), }); } else { allAssetSetIds.forEach((assetSetId) => { cur.push({ patternId, templateId, demoId, assetSetId, width, height, url: (0, routes_1.createDemoUrl)({ patternId, templateId, demoId, assetSetId, }), }); }); } }); }); return cur; }, []); await (0, file_utils_1.writeJSON)({ path: (0, path_1.join)(distDir, 'ks-meta.json'), contents: { demoUrls }, minimize: true, }); } function getKsMeta() { const { config } = (0, bootstrap_1.findConfigAndBootstrap)(); const metaFile = (0, path_1.join)(getMetaDir(config.dist), 'ks-meta.json'); if (!(0, file_utils_1.existsSync)(metaFile)) { throw new Error(`Could not find meta file, run 'knapsack build' first, tried looking at ${metaFile}`); } return (0, file_utils_1.readJSONSync)(metaFile); } async function writeTemplateMeta({ templateRenderers, allPatterns, allAssetSetIds, distDir, demosById, }) { const getTime = (0, utils_1.timerInSeconds)(); const metaDir = getMetaDir(distDir); await writeKsMetaFile({ allPatterns, allAssetSetIds, distDir: metaDir, demosById, }); await Promise.all(templateRenderers .filter((t) => t.getTemplateMeta) .map(async (templateRenderer) => { const templateMetaFiles = []; await Promise.all(allPatterns.map(async (pattern) => Promise.all(pattern.templates .filter((t) => t.templateLanguageId === templateRenderer.id) .map(async (template) => { const files = await templateRenderer.getTemplateMeta({ pattern, template, }); if (files?.length > 0) { const dir = (0, path_1.join)(distDir, 'meta', pattern.id); files .map((file) => ({ ...file, path: (0, path_1.join)(dir, file.path), })) .forEach((file) => templateMetaFiles.push(file)); } })))); if (templateMetaFiles?.length > 0) { const filesToWrite = templateRenderer.alterTemplateMetaFiles ? await templateRenderer.alterTemplateMetaFiles({ files: templateMetaFiles, metaDir, }) : templateMetaFiles; const dirs = new Set(); filesToWrite.forEach((file) => dirs.add((0, path_1.dirname)(file.path))); await Promise.all([...dirs].map(async (dir) => { try { await (0, file_utils_1.ensureDir)(dir); } catch (e) { // sometimes fails if it already exists, but that's ok! Can probably remove warning once we feel better. const relativeDir = (0, path_1.relative)(process.cwd(), dir); log_1.log.warn(`Had an error that is probably ok but wanted to warn for now while running "ensureDir" in "writeTemplateMeta" for ${relativeDir}`, e); } })); const uniqueFilesToWrite = [...new Set(filesToWrite)]; await Promise.all(uniqueFilesToWrite .filter((file) => !!file.contents) .map(async (file) => { const filePath = (0, path_1.isAbsolute)(file.path) ? file.path : (0, path_1.join)(metaDir, file.path); return (0, file_utils_1.writeFile)({ path: filePath, contents: file.contents, isBase64: file.encoding === 'base64', }); })); } })).catch((err) => { throw new Error(`Error writing template meta`, { cause: err }); }); log_1.log.verbose(`writeTemplateMeta took ${getTime()}s`); return { metaDir, }; } async function initAll({ ksBrain, missingFileVerbosity: missingFileVerbosityOption = 'warn', isStartCmd = false, }) { const { config, patterns, assetSets, ...toInit } = ksBrain; const meta = getMeta({ config }); let missingFileVerbosity = missingFileVerbosityOption; if ((0, misc_1.isValidVerbosityOption)(process.env.MISSING_FILE_VERBOSITY)) { missingFileVerbosity = process.env.MISSING_FILE_VERBOSITY; } // do this first so we can register templates in codeSrcs await patterns.init({ missingFileVerbosity, }); await Promise.all([ ...Object.values(toInit).map((piece) => 'init' in piece ? piece.init({ missingFileVerbosity, }) : undefined), ...config.templateRenderers.map(async (templateRenderer) => { if (templateRenderer.init) { await templateRenderer.init({ missingFileVerbosity, }); } }), ]); if (config.plugins) { await Promise.all(config.plugins.filter((p) => p.init).map((p) => p.init(ksBrain))); } const discovery = { renderersById: Object.values(patterns.templateRenderers).reduce((acc, renderer) => { acc[renderer.id] = { meta: renderer.getMeta(), }; return acc; }, {}), assetSets: await assetSets.getData(), metaState: { isLocalDev: isStartCmd, meta, plugins: config.plugins?.map((p) => { let clientPluginPath; let cssPath; if (p.clientPluginPath) { if (p.clientPluginPath.startsWith('http') || p.clientPluginPath.startsWith('//')) { clientPluginPath = p.clientPluginPath; if (p.cssPath) { cssPath = p.cssPath; } } else { clientPluginPath = (0, path_1.join)(`/plugins/${p.id}`, p.clientPluginPath); if (p.cssPath) { cssPath = (0, path_1.join)(`/plugins/${p.id}`, p.cssPath); } } } return { id: p.id, hasContent: !!p.loadContent, clientPluginPath, cssPath, }; }) ?? [], }, }; if (config.plugins) { await Promise.all(config.plugins .filter((p) => p.onDataUpdated) .map((p) => p.onDataUpdated({ brain: ksBrain }))); } log_1.log.info('Done: Initializing'); return { discovery }; } async function hydrateAll({ ksBrain, }) { const { spinner } = await import('@clack/prompts'); const spin = spinner({ indicator: 'timer' }); spin.start('Hydrating...'); const [appClientDataNoMeta, discovery] = await Promise.all([ cache_dir_1.appClientDataFileHelper.read(), cache_dir_1.discoveryFileHelper.read(), ]); if (!appClientDataNoMeta) { throw new Error(`Could not find data from "knapsack build" - that should be ran before this command. It's data should be found in "${(0, path_1.relative)(process.cwd(), cache_dir_1.appClientDataFileHelper.path)}"`); } const { config, patterns, ...restOfBrain } = ksBrain; const meta = getMeta({ config }); discovery.metaState.meta = meta; // should be done before others await patterns.hydrate({ appClientData: appClientDataNoMeta, // hydrateData: await patterns.readHydrateData(), }); await Promise.all([ ...Object.values(restOfBrain).map(async (piece) => 'hydrate' in piece ? piece.hydrate({ appClientData: appClientDataNoMeta, // hydrateData: // 'readHydrateData' in piece // ? await piece.readHydrateData() // : undefined, }) : undefined), ...config.templateRenderers.map(async (templateRenderer) => { if (templateRenderer.hydrate) { await templateRenderer.hydrate({ appClientData: appClientDataNoMeta, hydrateData: await templateRenderer.readHydrateData(), }); } }), ]); if (config.plugins) { await Promise.all(config.plugins.map((p) => p.init?.(ksBrain))); } if (config.plugins) { await Promise.all(config.plugins .filter((p) => p.onDataUpdated) .map((p) => p.onDataUpdated({ brain: ksBrain }))); } spin.stop('Hydrated'); return { discovery, appClientDataNoMeta }; } async function getDataStore(ksBrain) { const { patterns, customPages, assetSets, navs, tokens, files, db } = ksBrain; const [patternsState, customPagesState, assetSetsState, navsState, tokensSrc, filesState, dbState,] = await Promise.all([ patterns.getData(), customPages.getData(), assetSets.getData(), navs.getData(), tokens.getData(), files.getData(), db.getData(), ]); return { patternsState, customPagesState, assetSetsState, navsState, tokensSrc, filesState, db: dbState, }; } async function savePrepNewDataStore({ ksBrain, state, }) { const { patterns, customPages, navs, tokens, files, db } = ksBrain; const configFiles = await Promise.all([ customPages.savePrep(state.customPagesState), navs.savePrep(state.navsState), patterns.savePrep(state.patternsState), tokens.savePrep(state.tokensSrc), files.savePrep(state.filesState), db.savePrep(state.db), ]) .then((results) => results.flat()) .then((ksFiles) => { return Promise.all(ksFiles.map(async (file) => { if (file.isDeleted) return file; return { ...file, // Do NOT format db.yml due to how slow prettier is with yaml files that large contents: file.path.endsWith('db.yml') ? file.contents : await (0, file_utils_1.formatCode)({ contents: file.contents, path: file.path, }), }; })); }); configFiles.forEach((configFile) => { if (configFile.encoding === 'base64') { if (!(0, utils_1.isBase64)(configFile.contents)) { console.log(configFile); throw new Error(`Pre-save check on Knapsack File "${configFile.path}" expected a base64 encoding and it is not.`); } } }); return { files: configFiles, }; } async function writeAppClientData({ appClientData, ksBrain, }) { const data = await savePrepNewDataStore({ ksBrain, state: appClientData, }); await (0, server_utils_1.saveFilesLocally)(data); } /** * This will read all the data, then run it through the formatters that would be used on PRs or local changes, then write those files back out. */ async function ensureDataDirFilesFormatted({ ksBrain, }) { const appClientData = await getDataStore(ksBrain); await writeAppClientData({ appClientData, ksBrain, }); } async function build({ config, ksBrain, skipDiscovery = false, }) { await (0, cjs_1.spinnerPromise)(Promise.all(Object.entries(ksBrain).map(async ([, piece]) => { if ('build' in piece) { const hydrateData = await piece.build(); // if (hydrateData && 'writeHydrateData' in piece) { // await piece.writeHydrateData(hydrateData); // } } })), { text: 'Building Core...', successText: 'Built Core', }); for (const renderer of config.templateRenderers) { await (0, cjs_1.spinnerPromise)(async () => { if ('build' in renderer && typeof renderer.build === 'function') { const hydrateData = await renderer.build(); if (hydrateData && 'writeHydrateData' in renderer) { await renderer.writeHydrateData(hydrateData); } } if (skipDiscovery) return; const discovery = await renderer.getDiscovery(); await (0, cache_dir_1.getRendererDiscoveryFileHelper)(renderer.id).write(discovery); }, { text: `Building ${renderer.id}...`, successText: `Built ${renderer.id}`, }); } } async function testPatternRenders(allPatterns, ksBrain) { const { demos: { byId: demosById }, } = await ksBrain.db.getData(); const tokensSrc = await ksBrain.tokens.getData(); const collectionsParentKey = tokensSrc?.$extensions?.['cloud.knapsack']?.global?.collectionsParentKey; const state = await ksBrain.patterns.getContentStateFromLocalJsonFiles(); const results = []; const { inlineDemoData } = await import('@knapsack/rendering-utils'); await Promise.all(allPatterns .filter((p) => p?.templates?.length > 0) .map(async (pattern) => Promise.all(pattern.templates // skip native patterns because they don't have any LOCAL templates .filter((t) => !(0, types_1.isRendererIdForNativeMobile)(t.templateLanguageId)) .filter((t) => t?.demoIds?.length > 0) .map(async (template) => { if (!template.demoIds) return []; return Promise.all(template.demoIds?.map(async (demoId) => { let demo = demosById[demoId]; if (!demo) { throw new Error(`Could not find demo "${demoId}" for pattern "${pattern.id}" template "${template.id}"`); } if ((0, types_1.isDemoWithData)(demo)) { demo = inlineDemoData({ demosById, demo, collectionsParentKey, }); } const result = await ksBrain.patterns.render({ patternId: pattern.id, templateId: template.id, demo, state, }); results.push({ ok: result.ok, patternId: pattern.id, templateId: template.id, demoId, message: result.message ?? '', }); })); })))).catch((err) => { log_1.log.error('Test error', err, 'test'); process.exit(1); }); results.forEach((result) => { const { ok, patternId, templateId, demoId, message } = result; if (!ok) { log_1.log.error(`fail - Pattern: ${patternId} - Template: ${templateId} - Demo: ${demoId} - ${message}`, null, 'test'); } log_1.log.info(`ok - Pattern: ${patternId} - Template: ${templateId} - Demo: ${demoId}`, null, 'test'); }); const fails = results.filter((p) => !p.ok).length; const ok = fails === 0; const msg = `${results.length} tests ran, ${fails} failed`; if (!ok) { log_1.log.error(msg, null, 'test'); throw new Error(msg); } log_1.log.info(msg, null, 'test'); } //# sourceMappingURL=commands.js.map