UNPKG

@knapsack/app

Version:

Build Design Systems with Knapsack

387 lines • 17.3 kB
"use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _RendererBase_hydrateDataFilePath, _RendererBase_publicPath, _RendererBase_outputDir, _RendererBase_userConfigDir, _RendererBase_dataDir; Object.defineProperty(exports, "__esModule", { value: true }); exports.RendererBase = void 0; // eslint-disable-next-line max-classes-per-file const chokidar_1 = __importDefault(require("chokidar")); const utils_1 = require("@knapsack/utils"); const types_1 = require("@knapsack/types"); const file_utils_1 = require("@knapsack/file-utils"); const json_schema_to_typescript_1 = require("json-schema-to-typescript"); const fs_extra_1 = __importDefault(require("fs-extra")); const events_1 = require("../../../server/events"); const log_1 = require("../../../cli/log"); const cache_dir_1 = require("../../../lib/util/cache-dir"); const gitRoot = (0, file_utils_1.findGitRoot)(); /** * Used in property accessors to indicate that the property is not yet set */ class TooEarlyError extends Error { constructor(propName) { super(`You cannot access ${propName} this early (likely trying in constructor) because it is not yet set from '.setConfig()'. Try in 'init', 'hyrdrate', 'build', or 'watch' methods`); } } class RendererBase { // end lazy props constructor({ id, language, codeSrcsUserConfig = [], }) { this.codeSrcs = new Map(); this.codeSrcsUserConfig = []; // start all the lazy props that are set in `setConfig()` _RendererBase_hydrateDataFilePath.set(this, void 0); _RendererBase_publicPath.set(this, void 0); _RendererBase_outputDir.set(this, void 0); _RendererBase_userConfigDir.set(this, void 0); _RendererBase_dataDir.set(this, void 0); // eslint-disable-next-line @typescript-eslint/class-methods-use-this this.changeCase = (str) => (0, utils_1.pascalCase)(str); this.resolvePath = (path) => (0, file_utils_1.resolvePath)({ path: typeof path === 'string' ? path : path.path, pkgPathAliases: this.pkgPathAliases, resolveFromDir: typeof path !== 'string' && path.resolveFromDir ? path.resolveFromDir : this.dataDir, }); /** @deprecated use async `resolvePath` instead */ this.resolvePathSync = (path) => (0, file_utils_1.resolvePathSync)({ path: typeof path === 'string' ? path : path.path, pkgPathAliases: this.pkgPathAliases, resolveFromDir: typeof path !== 'string' && path.resolveFromDir ? path.resolveFromDir : this.dataDir, }); this.writeHydrateData = async (data) => { await (0, file_utils_1.writeJSON)({ path: this.hydrateDataFilePath, contents: data, minimize: true, }); }; this.readHydrateData = async () => { return (0, file_utils_1.readJSON)(this.hydrateDataFilePath); }; this.id = id; this.language = language; this.logPrefix = this.id; this.pkgPathAliases = {}; this.codeSrcsUserConfig = codeSrcsUserConfig; this.log = { inspect: log_1.log.inspect, info: (msg, extra, prefix) => { log_1.log.info(msg, extra, prefix || this.logPrefix); }, verbose: (msg, extra, prefix) => { log_1.log.verbose(msg, extra, prefix || this.logPrefix); }, warn: (msg, extra, prefix) => { log_1.log.warn(msg, extra, prefix || this.logPrefix); }, error: (msg, extra, prefix) => { log_1.log.error(msg, extra, prefix || this.logPrefix); }, }; } /** * This is ran after constructor and before any other methods * We do this to pass in things that the users do no pass into the constructor in `knapsack.config.js` */ setConfig({ userConfigDir, dataDir, }) { (0, file_utils_1.assertsIsAbsolutePath)(userConfigDir); (0, file_utils_1.assertsIsAbsolutePath)(dataDir); __classPrivateFieldSet(this, _RendererBase_userConfigDir, userConfigDir, "f"); __classPrivateFieldSet(this, _RendererBase_dataDir, dataDir, "f"); __classPrivateFieldSet(this, _RendererBase_hydrateDataFilePath, (0, file_utils_1.join)(cache_dir_1.ksCacheDir, `hydrate.renderer-${this.id}.json`), "f"); __classPrivateFieldSet(this, _RendererBase_outputDir, (0, file_utils_1.join)(cache_dir_1.ksCacheDir, `knapsack-renderer-${this.id}`), "f"); __classPrivateFieldSet(this, _RendererBase_publicPath, `/${(0, file_utils_1.relative)(cache_dir_1.ksCacheDir, this.outputDir)}/`, "f"); fs_extra_1.default.ensureDirSync(this.outputDir); } async init(_) { Object.entries(this.pkgPathAliases).forEach(([alias, path]) => { const info = (0, file_utils_1.getPathType)(alias); if (info.type !== 'package' && info.type !== 'package-sub-path') { throw new Error(`Aliased path, "${alias}" must be a package or package sub-path, but was "${info.type}". Was used to alias to this path: "${path}"`); } this.addCodeSrc({ path }); }); if (this.codeSrcsUserConfig.length === 0) return; const allPathsResult = await Promise.all(this.codeSrcsUserConfig.map(async ({ path, filter = () => true }) => { const allPaths = await (0, file_utils_1.globPkgsAndFiles)({ paths: [path], cwd: __classPrivateFieldGet(this, _RendererBase_userConfigDir, "f"), }); return allPaths.filter((p) => filter(p.path)); })); const allPaths = allPathsResult.flat(); await Promise.all(allPaths.map(({ path }) => this.addCodeSrc({ path }))); } async build() { const codeSrcs = Object.fromEntries(this.codeSrcs.entries()); return { codeSrcs, }; } async hydrate({ hydrateData, }) { this.codeSrcs = new Map((0, utils_1.entries)(hydrateData.codeSrcs)); } /** * directory path where `knapsack.config.js` can be found * non-absolute paths in `knapsack.config.js` will be relative from this */ get userConfigDir() { const it = __classPrivateFieldGet(this, _RendererBase_userConfigDir, "f"); if (!it) throw new TooEarlyError('userConfigDir'); return it; } get dataDir() { const it = __classPrivateFieldGet(this, _RendererBase_dataDir, "f"); if (!it) throw new TooEarlyError('dataDir'); return it; } get outputDir() { const it = __classPrivateFieldGet(this, _RendererBase_outputDir, "f"); if (!it) throw new TooEarlyError('outputDir'); return it; } get publicPath() { const it = __classPrivateFieldGet(this, _RendererBase_publicPath, "f"); if (!it) throw new TooEarlyError('publicPath'); return it; } get hydrateDataFilePath() { const it = __classPrivateFieldGet(this, _RendererBase_hydrateDataFilePath, "f"); if (!it) throw new TooEarlyError('hydrateDataFilePath'); return it; } onChange() { events_1.knapsackEvents.emitRequestRendererClientReload(); } async watch() { const templatePaths = []; await Promise.all(this.getCodeSrcs().map(async ({ path }) => { const { absolutePath } = await this.resolvePath({ path }); templatePaths.push(absolutePath); })); if (templatePaths.length === 0) return; const watcher = chokidar_1.default.watch(templatePaths, { ignoreInitial: true, }); watcher .on('change', (path) => { events_1.knapsackEvents.emitPatternTemplateChanged({ path }); this.onChange(); }) .on('error', (error) => { log_1.log.error(new Error(`Error watching: ${error.message}`, { cause: error }), '', `renderer:${this.id}`); }); watcher.on('ready', () => { log_1.log.verbose('Watching these files:', watcher.getWatched(), `renderer:${this.id}`); }); events_1.knapsackEvents.onShutdown(() => watcher.close()); } normalizeTemplateInfo(opt) { return (0, types_1.normalizeTemplateInfo)({ rendererId: this.id, ...opt }); } normalizeDemo({ demo, state, }) { switch (demo.type) { case 'data': { const { patternId, templateId } = demo; const pattern = state.patterns[patternId]; if (!pattern) { throw new Error(`Could not find pattern: ${patternId}`); } const template = pattern.templates.find((t) => t.id === templateId); if (!template) { throw new Error(`Could not find template: ${templateId}`); } if (template.path) { const info = (0, file_utils_1.getPathType)(template.path); if (info.type === 'absolute') { throw new Error(`Absolute paths are not allowed to be stored in demo paths: "${template.path}"`); } return this.normalizeTemplateInfo({ path: info.path, alias: template.alias, }); } return this.normalizeTemplateInfo({ alias: template.alias, }); } case 'data-w-template-info': { const info = demo.templateInfo; if ((0, types_1.isTemplateInfoWithCodeSrcPath)(info) && !this.codeSrcs.has(info.codeSrcPath)) { throw new Error(`Could not find codeSrc: "${info.codeSrcPath}" for demo: ${JSON.stringify(demo)}`); } return info; } default: { const _exhaustiveCheck = demo; throw new Error(`Unhandled demo type at rendererBase.normalizeDemo`); } } } async addCodeSrc({ path, relativePathsFrom = 'data-dir', }) { if (!path) return; // @todo handle how `this.pkgPathAliases` can create multiple redundant CodeSrcs and therefore templates - https://linear.app/knapsack/issue/KSP-6115/pkgpathaliases-can-cause-duplicate-codesrcs // const aliasThisPathUses = Object.entries(this.pkgPathAliases).find( // ([alias, pkgPath]) => { // return path.startsWith(pkgPath); // }, // ); if (this.codeSrcs.has(path)) return; const resolveFromDir = relativePathsFrom === 'data-dir' ? this.dataDir : this.userConfigDir; const { absolutePath, exists } = await this.resolvePath({ path, resolveFromDir, }); if (!exists) throw new Error(`File not found: "${path}"`); const pathInfo = (0, file_utils_1.getPathType)( // makes sure absolute paths don't get in b/c we turn them to relative paths (0, file_utils_1.getPathType)(path).type === 'absolute' ? (0, file_utils_1.relative)(resolveFromDir, absolutePath) : path); switch (pathInfo.type) { case 'package': { this.codeSrcs.set(pathInfo.path, { type: 'package', path: pathInfo.path, pathFromOutputDir: pathInfo.path, pkgName: pathInfo.pkgName, rendererId: this.id, }); return; } case 'package-sub-path': { this.codeSrcs.set(pathInfo.path, { type: 'package-sub-path', path: pathInfo.path, pathFromOutputDir: pathInfo.path, pkgName: pathInfo.pkgName, subPath: pathInfo.subPath, rendererId: this.id, }); return; } case 'relative': { this.codeSrcs.set(pathInfo.path, { type: 'relative-from-data-dir', path: pathInfo.path, pathFromOutputDir: (0, file_utils_1.relative)(this.outputDir, absolutePath), rendererId: this.id, }); return; } case 'absolute': { throw new Error(`Absolute paths are not allowed: ${path}`); } default: { const _exhaustiveCheck = pathInfo; throw new Error(`Unhandled path type: ${JSON.stringify(pathInfo)}`); } } } getCodeSrcs({ includePrototypePaths = false, } = {}) { const proto = this.getMeta().prototypingTemplate; const codeSrcs = [...this.codeSrcs.values()]; if (!proto) return codeSrcs; if (includePrototypePaths) { return codeSrcs; } return codeSrcs.filter((codeSrc) => { return codeSrc.path !== proto.path; }); } async getUnusedTemplatePaths({ globPaths, state, cwd = gitRoot || process.cwd(), }) { const allPaths = await (0, file_utils_1.globby)(globPaths, { absolute: true, deep: 20, ignore: ['**/node_modules/**'], // respect .gitignore gitignore: true, cwd, }); const paths = new Set(); Object.values(state.patterns).forEach((pattern) => { pattern.templates.forEach(({ path, templateLanguageId }) => { if (templateLanguageId === this.id) { paths.add(path); } }); pattern.templateDemos.forEach(({ templateLanguageId, templateInfo: { path } }) => { if (templateLanguageId === this.id) { paths.add(path); } }); }); const usedAbsolutePaths = new Set(); await Promise.all(Array.from(paths).map(async (path) => { const { exists, absolutePath } = await this.resolvePath(path); if (exists) { usedAbsolutePaths.add(absolutePath); } })); const unusedPaths = allPaths.filter((path) => !usedAbsolutePaths.has(path)); return { unusedPaths, usedPaths: Array.from(usedAbsolutePaths), }; } static async convertSchemaToTypeScriptDefs({ schema, title, description = '', patternId, templateId, preBanner, postBanner, }) { const theSchema = { ...schema, additionalProperties: false, description, title, }; const bannerComment = ` /** * patternId: "${patternId}" templateId: "${templateId}" * This file was automatically generated by Knapsack. * DO NOT MODIFY IT BY HAND. * Instead, adjust it's spec, by either: * 1) go to "/patterns/${patternId}/${templateId}" and use the UI to edit the spec * 2) OR edit the "knapsack.pattern.${patternId}.json" file's "spec.props". * Run Knapsack again to regenerate this file. */`.trim(); const typeDefs = await (0, json_schema_to_typescript_1.compile)(theSchema, theSchema.title, { bannerComment: [preBanner, bannerComment, postBanner] .filter(Boolean) .join('\n\n'), style: { singleQuote: true, }, }); return typeDefs .split('\n') .map((line) => line.replace('export type', 'type')) .join('\n'); } } exports.RendererBase = RendererBase; _RendererBase_hydrateDataFilePath = new WeakMap(), _RendererBase_publicPath = new WeakMap(), _RendererBase_outputDir = new WeakMap(), _RendererBase_userConfigDir = new WeakMap(), _RendererBase_dataDir = new WeakMap(); //# sourceMappingURL=renderer-base.js.map