piral-cli
Version:
The standard CLI for creating and building a Piral instance or a Pilet.
225 lines (192 loc) • 6.42 kB
text/typescript
import { DeclOptions, generateDeclaration, createDiffPlugin, Logger } from 'dets';
import { basename, dirname, extname, join, resolve } from 'path';
import { progress, log, logWarn, logVerbose, logInfo } from './log';
import { ForceOverwrite } from './enums';
import { retrievePiralRoot, retrievePiletsInfo, flattenExternals, validateSharedDependencies } from './package';
import { entryModuleExtensions, piralBaseRoot, packageJson } from './constants';
import { readText, getEntryFiles, matchFiles, createFileIfNotExists, readJson } from './io';
import { getModulePath } from '../external';
import { LogLevels } from '../types';
const piletApiName = 'PiletApi';
function findPiralBaseRoot(root: string, framework: string) {
const piralJson = `${framework}/package.json`;
if (piralJson !== piralBaseRoot) {
try {
const packageJsonPath = getModulePath(root, piralJson);
return dirname(packageJsonPath);
} catch {}
}
return root;
}
async function findPiralInstanceApi(root: string, piralInstance: string) {
if (piralInstance) {
const path = require.resolve(`${piralInstance}/${packageJson}`, {
paths: [join(root, 'node_modules')],
});
const appRoot = dirname(path);
const data = await readJson(appRoot, packageJson);
const subpath = data.types || data.typings;
if (subpath) {
return [
{
file: resolve(appRoot, subpath),
name: piletApiName,
},
];
}
}
return [];
}
function findPiralBaseApi(root: string, framework: string) {
// for some package managers, e.g., pnpm we need to first go into
// some specifics before being able to retrieve "piral-base"
const baseRoot = findPiralBaseRoot(root, framework);
try {
const packageJsonPath = getModulePath(baseRoot, piralBaseRoot);
const projectDir = dirname(packageJsonPath);
const project = require(packageJsonPath);
// By default support for piral-base < 0.15
const { piletApiTypings = 'lib/types.d.ts' } = project;
return [
{
file: resolve(projectDir, piletApiTypings),
name: piletApiName,
},
];
} catch (err) {
log('generalError_0002', `Could not find the root API of "piral-base" from "${root}": ${err}`);
return [];
}
}
function isString(n: any) {
return typeof n === 'string';
}
function findDeclaredTypings(root: string, shared: Array<string> = []) {
const types = shared.filter(isString).map((file) => resolve(root, file));
try {
const { typings, extraTypes } = require(resolve(root, 'package.json'));
if (extraTypes) {
if (isString(extraTypes)) {
return [resolve(root, extraTypes), ...types];
} else if (Array.isArray(extraTypes)) {
const items = extraTypes.filter(isString).map((file) => resolve(root, file));
return [...items, ...types];
}
}
if (typings) {
return [resolve(root, typings), ...types];
}
} catch {}
return types;
}
async function getAllFiles(entryModules: Array<string>) {
const files: Array<string> = [];
const pattern = '**/+(*.ts|*.tsx|*.js|*.jsx)';
const allFiles = await Promise.all(entryModules.map((m) => matchFiles(dirname(m), pattern)));
for (const found of allFiles) {
files.push(...found.filter((file) => !files.includes(file)));
}
return files;
}
async function getEntryModules(entryFiles: string) {
if (!entryModuleExtensions.includes(extname(entryFiles).toLowerCase())) {
const appFile = await readText(dirname(entryFiles), basename(entryFiles));
const entryModules = await getEntryFiles(appFile, dirname(entryFiles));
return entryModules;
}
return [entryFiles];
}
function createLogger(): Logger {
return {
error(message) {
throw new Error(message);
},
info(message) {
logInfo(message);
},
verbose(message) {
logVerbose(message);
},
warn(message) {
logWarn(message);
},
};
}
async function createDeclarationFile(options: DeclOptions, target: string, forceOverwrite: ForceOverwrite) {
progress('Bundling declaration file ...');
const result = await generateDeclaration(options);
progress('Writing declaration file ...');
await createFileIfNotExists(target, 'index.d.ts', result, forceOverwrite);
}
export async function createPiletDeclaration(
piralInstances: Array<string>,
root: string,
entry: string,
allowedImports: Array<string>,
target: string,
forceOverwrite: ForceOverwrite,
logLevel: LogLevels,
) {
const [piralInstance] = piralInstances;
const apis = await findPiralInstanceApi(root, piralInstance);
const [file] = apis.map((m) => m.file);
if (file) {
const files = await getAllFiles([entry]);
const types = findDeclaredTypings(root);
const options: DeclOptions = {
name: piralInstance,
root,
files,
types,
plugins: [createDiffPlugin(file)],
apis,
noModuleDeclaration: true,
imports: allowedImports.filter(m => !piralInstances.includes(m)),
logLevel,
logger: createLogger(),
};
try {
await createDeclarationFile(options, target, forceOverwrite);
return true;
} catch (ex) {
log('declarationCouldNotBeGenerated_0076', root, ex);
}
return false;
}
}
export async function createPiralDeclaration(
baseDir: string,
entry: string,
target: string,
forceOverwrite: ForceOverwrite,
logLevel: LogLevels,
) {
progress('Reading configuration ...');
const entryFiles = await retrievePiralRoot(baseDir, entry);
const { name, root, externals, framework, shared } = await retrievePiletsInfo(entryFiles);
const entryModules = await getEntryModules(entryFiles);
const files = await getAllFiles(entryModules);
const options: DeclOptions = {
name,
root,
files,
types: findDeclaredTypings(root, shared),
apis: findPiralBaseApi(root, framework),
noModuleDeclaration: true,
imports: flattenExternals(externals, true),
logLevel,
logger: createLogger(),
};
validateSharedDependencies(externals);
if (options.apis.length) {
try {
await createDeclarationFile(options, target, forceOverwrite);
return true;
} catch (ex) {
log('declarationCouldNotBeGenerated_0076', baseDir, ex);
}
} else {
log('declarationCouldNotBeGenerated_0076', baseDir, 'The main Pilet API interface could not be found.');
}
return false;
}