@knapsack/app
Version:
Build Design Systems on top of knapsack, by Basalt
252 lines (232 loc) • 7.46 kB
text/typescript
import fs, { readFile } from 'fs-extra';
import { KnapsackFile } from '@knapsack/core';
import portfinder from 'portfinder';
import findCacheDir from 'find-cache-dir';
import { isAbsolute, join, dirname, relative } from 'path';
import { readJson } from '../server/server-utils';
import { flattenArray, flattenNestedArray, timer } from '../lib/utils';
import * as log from './log';
import { KnapsackBrain, Patterns } from '../schemas/main-types';
import {
KnapsackConfig,
KnapsackTemplateRenderer,
} from '../schemas/knapsack-config';
import { KnapsackPattern } from '../schemas/patterns';
import { KnapsackMeta } from '../schemas/misc';
export async function getMeta(config: KnapsackConfig): Promise<KnapsackMeta> {
const { version, name } = await readJson(
join(__dirname, '../../package.json'),
);
const cacheDir = findCacheDir({ name, create: true });
const serverPort = await portfinder.getPortPromise({ port: 3999 });
const websocketsPort = await portfinder.getPortPromise();
return {
websocketsPort,
serverPort,
knapsackVersion: version,
cacheDir,
version: config.version,
hasKnapsackCloud: 'cloud' in config,
};
}
export async function writeTemplateMeta({
templateRenderers,
allPatterns,
distDir,
}: {
templateRenderers: KnapsackTemplateRenderer[];
allPatterns: KnapsackPattern[];
distDir: string;
}): Promise<void> {
const getTime = timer();
const metaDir = join(distDir, 'meta');
try {
await fs.emptyDir(metaDir);
} catch (e) {
const msg = `'Could not empty the meta directory in the dist directory': ${e.message}`;
log.error(msg, e);
throw new Error(e);
}
await Promise.all(
templateRenderers
.filter(t => t.getTemplateMeta)
.map(async templateRenderer => {
const templateMetaFiles: KnapsackFile[] = [];
await Promise.all(
allPatterns.map(async pattern => {
return 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 = join(distDir, 'meta', pattern.id);
files
.map(file => ({
...file,
path: 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<string>();
filesToWrite.forEach(file => dirs.add(dirname(file.path)));
await Promise.all(
[...dirs].map(async dir => {
try {
await fs.ensureDir(dir);
} catch (e) {
// sometimes fails if it already exists, but that's ok! Can probably remove warning once we feel better.
const relativeDir = relative(process.cwd(), dir);
log.warn(
`Had an error that is probably ok but wanted to warn for now while running "ensureDir" in "writeTemplateMeta" for ${relativeDir}`,
e,
);
}
}),
);
// To ensure that we don't try to write to the same file twice, we'll do this synchronous
filesToWrite.forEach(file => {
const filePath = isAbsolute(file.path)
? file.path
: join(metaDir, file.path);
fs.writeFileSync(filePath, file.contents, {
encoding: file.encoding,
});
});
}
}),
).catch(err => {
const msg = `Error writeTemplateMeta: ${err.message}`;
log.error(msg, err);
throw new Error(err);
});
log.verbose(`writeTemplateMeta took ${getTime()}s`);
}
export async function initAll(ksBrain: KnapsackBrain): Promise<KnapsackMeta> {
log.info('Initializing...');
const { config, patterns } = ksBrain;
const meta = await getMeta(config);
await patterns.init({ cacheDir: meta.cacheDir });
await Promise.all(
config.templateRenderers.map(async templateRenderer => {
if (templateRenderer.init) {
await templateRenderer.init({
config,
patterns,
cacheDir: meta.cacheDir,
});
log.info('Init done', null, `templateRenderer:${templateRenderer.id}`);
}
}),
);
if (config.plugins) {
await Promise.all(
config.plugins.filter(p => p.init).map(p => p.init(ksBrain)),
);
}
await writeTemplateMeta({
templateRenderers: config.templateRenderers,
allPatterns: patterns.allPatterns,
distDir: config.dist,
});
log.info('Done: Initializing');
return meta;
}
export async function build({
config,
patterns,
}: {
config: KnapsackConfig;
patterns: Patterns;
}): Promise<void> {
const getTime = timer();
log.info('Building...');
await writeTemplateMeta({
allPatterns: patterns.allPatterns,
templateRenderers: config.templateRenderers,
distDir: config.dist,
});
await Promise.all(
config.templateRenderers.map(async templateRenderer => {
if (!templateRenderer.build) return;
await templateRenderer.build({
templatePaths: patterns.getAllTemplatePaths({
templateLanguageId: templateRenderer.id,
}),
});
log.info('Built', null, `templateRenderer:${templateRenderer.id}`);
}),
);
log.info('Knapsack built', null, 'build');
log.verbose(`Build took ${getTime()}s`);
}
export async function testPatternRenders(
allPatterns: KnapsackPattern[],
patterns: Patterns,
): Promise<void> {
const results = [];
await Promise.all(
allPatterns.map(async pattern =>
Promise.all(
pattern.templates.map(async template => {
return Promise.all(
template.demos.map(async demo => {
const result = await patterns.render({
patternId: pattern.id,
templateId: template.id,
demo: template.demosById[demo],
});
results.push({
ok: result.ok,
patternId: pattern.id,
templateId: template.id,
});
}),
);
}),
),
),
).catch(err => {
log.error('Test error', err, 'test');
process.exit(1);
});
let exitCode = 0;
results.forEach(result => {
const { ok, patternId, templateId } = result;
if (!ok) {
exitCode = 1;
log.error(
`fail - Pattern: ${patternId} - Template: ${templateId}`,
null,
'test',
);
}
log.info(
`ok - Pattern: ${patternId} - Template: ${templateId}`,
null,
'test',
);
});
const ok = exitCode === 0;
const fails = results.filter(p => p.ok).length;
const msg = `${results.length} tests ran, ${fails} failed`;
if (!ok) {
log.error(msg, null, 'test');
process.exit(1);
}
log.info(msg, null, 'test');
}