lincd-cli
Version:
Command line tools for the lincd.js library
1,089 lines (1,087 loc) • 95.3 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chalk from 'chalk';
import { exec } from 'child_process';
import depcheck from 'depcheck';
import { getEnvFile } from 'env-cmd/dist/get-env-vars.js';
import fs from 'fs-extra';
import path, { dirname } from 'path';
import { debugInfo, execp, execPromise, getFileImports, getFiles, getLastCommitTime, getPackageJSON, isImportOutsideOfPackage, isImportWithMissingExtension, isInvalidLINCDImport, needsRebuilding, } from './utils.js';
import { statSync } from 'fs';
import { findNearestPackageJson } from 'find-nearest-package-json';
import { LinkedFileStorage } from 'lincd/utils/LinkedFileStorage';
// import pkg from 'lincd/utils/LinkedFileStorage';
// const { LinkedFileStorage } = pkg;
// const config = require('lincd-server/site.webpack.config');
import { glob } from 'glob';
import webpack from 'webpack';
import stagedGitFiles from 'staged-git-files';
import ora from 'ora';
//@ts-ignore
let dirname__ = typeof __dirname !== 'undefined' ? __dirname : dirname(import.meta.url).replace('file:/', '');
var variables = {};
export const createApp = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...args_1], void 0, function* (name, basePath = process.cwd()) {
if (!name) {
console.warn('Please provide a name as the first argument');
}
let { hyphenName, camelCaseName, underscoreName } = setNameVariables(name);
let targetFolder = path.join(basePath, hyphenName);
if (!fs.existsSync(targetFolder)) {
fs.mkdirSync(targetFolder);
}
fs.copySync(path.join(dirname__, '..', '..', 'defaults', 'app-with-backend'), targetFolder);
//make sure the data folder exists (even though its empty).. copying empty folders does not work with fs.copySync
fs.mkdirSync(path.join(targetFolder, 'data'), { recursive: true });
fs.mkdirSync(path.join(targetFolder, 'data/uploads/resized'), {
recursive: true,
});
fs.renameSync(path.join(targetFolder, 'gitignore.template'), path.join(targetFolder, '.gitignore'));
fs.renameSync(path.join(targetFolder, 'yarnrc.yml.template'), path.join(targetFolder, '.yarnrc.yml'));
// fs.copySync(path.join(__dirname, '..', 'defaults', 'app'), targetFolder);
log('Creating new LINCD application \'' + name + '\'');
//replace variables in some copied files
yield replaceVariablesInFolder(targetFolder);
let hasYarn = yield hasYarnInstalled();
let installCommand = hasYarn
? 'export NODE_OPTIONS="--no-network-family-autoselection" && yarn install'
: 'npm install';
yield execp(`cd ${hyphenName} && ${installCommand}`, true).catch((err) => {
console.warn('Could not install dependencies or start application');
});
log(`Your LINCD App is ready at ${chalk.blueBright(targetFolder)}`, `To start, run\n${chalk.blueBright(`cd ${hyphenName}`)} and then ${chalk.blueBright((hasYarn ? 'yarn' : 'npm') + ' start')}`);
});
function logHelp() {
execp('yarn exec lincd help');
}
function log(...messages) {
messages.forEach((message) => {
console.log(chalk.cyan('Info: ') + message);
});
}
function progressUpdate(message) {
process.stdout.write(' \r');
process.stdout.write(message + '\r');
}
export function warn(...messages) {
messages.forEach((message) => {
console.log(chalk.redBright('Warning: ') + message);
// console.log(chalk.red(message));
});
}
export function developPackage(target, mode) {
if (!target)
target = 'es6';
if (mode !== 'production')
mode = '';
else if (target !== 'es6')
log('target must be es6 when developing for production');
if (target == 'es5' || target == 'es6') {
// log('> Starting continuous development build for '+target+' target')
log('starting continuous development build');
log('grunt dev' +
(target ? '-' + target : '') +
(mode ? '-' + mode : '') +
' --color');
var command = exec('grunt dev' +
(target ? '-' + target : '') +
(mode ? '-' + mode : '') +
' --color');
command.stdout.pipe(process.stdout);
command.stderr.pipe(process.stderr);
}
else {
console.warn('unknown build target. Use es5 or es6');
}
}
function checkWorkspaces(rootPath, workspaces, res) {
// console.log('checking workspaces at '+rootPath+": "+workspaces.toString());
if (workspaces.packages) {
workspaces = workspaces.packages;
}
workspaces.forEach((workspace) => {
let workspacePath = path.join(rootPath, workspace.replace('/*', ''));
if (workspace.indexOf('/*') !== -1) {
// console.log(workspacePath);
if (fs.existsSync(workspacePath)) {
let folders = fs.readdirSync(workspacePath);
folders.forEach((folder) => {
if (folder !== './' && folder !== '../') {
checkPackagePath(rootPath, path.join(workspacePath, folder), res);
}
});
}
}
else {
checkPackagePath(rootPath, workspacePath, res);
}
});
}
function checkPackagePath(rootPath, packagePath, res) {
let packageJsonPath = path.join(packagePath, 'package.json');
// console.log('checking '+packagePath);
if (fs.existsSync(packageJsonPath)) {
var pack = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
//some packages are not true lincd packages, but we still want them to be re-built automatically. This is what lincd_util is for
if (pack && pack.workspaces) {
checkWorkspaces(packagePath, pack.workspaces, res);
}
else if (pack && pack.lincd === true) {
res.push({
path: packagePath,
packageName: pack.name,
});
}
}
}
function runOnPackagesGroupedByDependencies(lincdPackages, onBuildStack, onStackEnd, sync = false) {
let dependencies = new Map();
//get dependencies of each package
let leastDependentPackage;
lincdPackages.forEach((pkg) => {
var pack = getPackageJSON(pkg.path);
if (pack) {
//get lincd related dependencies and get the actual package details from the package map by removing '@dacore/' from the package name
let packageDependencies = Object.keys(pack.dependencies)
.filter((dependency) => lincdPackages.has(dependency))
.map((dependency) => {
return lincdPackages.has(dependency)
? lincdPackages.get(dependency)
: dependency;
});
// console.log(package.packageName,packageDependencies.map())
dependencies.set(pkg, packageDependencies);
}
});
dependencies.forEach((PackageDependencies, pkg) => {
if (!PackageDependencies.some((dependency) => {
return (typeof dependency !== 'string' &&
lincdPackages.has(dependency.packageName));
})) {
leastDependentPackage = pkg;
}
});
let startStack = [leastDependentPackage];
const runPackage = (runFunction, pck) => {
return runFunction(pck)
.catch((errorObj) => {
if (errorObj.error) {
let { error, stdout, stderr } = errorObj;
warn('Uncaught exception whilst running parallel function on ' +
pck.packageName, (error === null || error === void 0 ? void 0 : error.message) ? error.message : error === null || error === void 0 ? void 0 : error.toString());
}
else {
warn('Uncaught exception whilst running parallel function on ' +
pck.packageName, errorObj === null || errorObj === void 0 ? void 0 : errorObj.toString());
process.exit();
}
// warn(chalk.red(pck.packageName+' failed:'));
// console.log(stdout);
})
.then((res) => {
done.add(pck);
return res;
});
};
let done = new Set();
let results = [];
let runStack = (stack) => __awaiter(this, void 0, void 0, function* () {
let runFunction = onBuildStack(stack, dependencies);
let stackPromise;
if (sync) {
//build the stack in parallel
stackPromise = Promise.resolve(true);
stack.forEach((pck) => {
stackPromise = stackPromise.then(() => {
return runPackage(runFunction, pck);
});
});
}
else {
//build the stack in parallel
stackPromise = Promise.all(stack.map((pck) => {
return runPackage(runFunction, pck);
}));
}
//wait till stack is completed
let stackResults = yield stackPromise;
results = results.concat(stackResults);
//clear stack for next round
stack = [];
//find those packages who have all their dependencies already built and add them to the stack
lincdPackages.forEach((pkg) => {
let deps = dependencies.get(pkg);
//if the package is not done yet
//but every dependency is now done OR was not something we can build (some @dacore dependencies may not be local)
if (!done.has(pkg) &&
deps.every((dependency) => {
return (typeof dependency !== 'string' &&
(done.has(dependency) || !lincdPackages.has(dependency.packageName)));
})) {
stack.push(pkg);
}
});
if (stack.length <= 0 && done.size < lincdPackages.size) {
console.log(chalk.red('Only ' +
done.size +
' out of ' +
lincdPackages.size +
' packages have been built'));
console.log('ALL remaining packages have dependencies that have not been met. This may point to ' +
chalk.red('circular dependencies.'));
console.log('Already built: ' +
Array.from(done)
.map((p) => chalk.green(p.packageName))
.join(', '));
console.log(chalk.blue('\nTo solve this issue') +
': find the circular dependencies below and fix the dependencies:\n\n');
//TODO: actually find and name the packages that have circular dependencies
// let circular = [];
// lincdPackages.forEach((pkg) => {
// if (!done.has(pkg))
// {
// let deps = dependencies.get(pkg);
// if (deps.some(dependency => {
// //return true if this dependency (indirectly) depends on the package whos' dependency it is
// return hasDependency(dependency,pkg,dependencies)
// }))
// {
// circular.push(pkg);
// }
// process.exit();
// }
// });
lincdPackages.forEach((pkg) => {
let deps = dependencies.get(pkg);
if (!done.has(pkg)) {
console.log(chalk.red(pkg.packageName) +
' has not been built yet. Unbuilt dependencies:\n' +
deps
.filter((dependency) => {
return !Array.from(done).some((p) => {
// console.log(p.packageName,dependency.packageName,p===dependency)
return p === dependency;
});
})
.map((p) => chalk.red('\t- ' +
((p === null || p === void 0 ? void 0 : p.packageName) ? p.packageName : p.toString()) +
'\n'))
.join(' '));
// console.log(chalk.red(pkg.packageName)+' has not been built yet. Built dependencies:\n' + deps.filter(dependency => {
// return Array.from(done).some(p => p.packageName === pkg.packageName)
// }).map(p => chalk.green('\t- '+p.packageName+'\n')).join(" "))
// console.log(chalk.red(pkg.packageName)+' has not been built yet. Built dependencies:\n' + deps.filter(dependency => done.has(pkg)).map(p => chalk.green('\t- '+p.packageName+'\n')).join(" "))
}
});
}
//if more to be built, iterate
if (stack.length > 0) {
return runStack(stack);
}
else {
onStackEnd(dependencies, results.filter(Boolean));
}
});
//starts the process
runStack(startStack);
}
function hasDependency(pkg, childPkg, dependencies, depth = 1) {
console.log('Does ' + pkg.packageName + ' have dep ' + childPkg.packageName + ' ?');
let deps = dependencies.get(pkg);
if (deps.some((dependency) => {
console.log(dependency.packageName, childPkg.packageName, dependency === childPkg);
if (depth === 2)
return false;
// return dependency === childPkg;
return (dependency === childPkg ||
hasDependency(dependency, childPkg, dependencies, depth++));
})) {
console.log('##YES');
return true;
}
console.log('going up');
return false;
}
export function buildAll(options) {
console.log('Building all LINCD packages of this repository in order of dependencies');
let lincdPackages = getLocalLincdPackageMap();
let startFrom;
//by default start building
let building = true;
let from = options === null || options === void 0 ? void 0 : options.from;
let sync = (options === null || options === void 0 ? void 0 : options.sync) || false;
// console.log('from', from);
// console.log('sync', sync);
// process.exit();
//option to start from a specific package in the stack
if (from) {
startFrom = from;
//if we have a startFrom, then we havnt started the build process yet
building = startFrom ? false : true;
//clear targets
// target = '';
// target2 = '';
console.log(chalk.blue('Will skip builds until ' + startFrom));
// return async (pkg) => {};
}
// if (target2 == 'from') {
// startFrom = target3;
// //if we have a startFrom, then we havnt started the build process yet
// building = startFrom ? false : true;
//
// //clear targets
// target2 = '';
// target3 = '';
// console.log(chalk.blue('Will skip builds until ' + startFrom));
//
// // return async (pkg) => {};
// }
let done = new Set();
let failedModules = [];
progressUpdate(lincdPackages.size + ' packages left');
let packagesLeft = lincdPackages.size;
// let packagesLeft = lincdPackages.size - done.size;
runOnPackagesGroupedByDependencies(lincdPackages, (packageGroup, dependencies) => {
if (done.size > 0) {
debugInfo(chalk.magenta('\n-------\nThese packages are next, since all their dependencies have now been build:'));
// log(stack);
}
debugInfo('Now building: ' + chalk.blue(packageGroup.map((i) => i.packageName)));
return (pkg) => __awaiter(this, void 0, void 0, function* () {
let command;
let skipping = false;
//if we're skipping builds until a certain package
if (!building) {
//if the package name matches the package we're supposed to start from then start building packages
if (pkg.packageName == startFrom || pkg.packageName == startFrom) {
building = true;
}
//else still waiting for the package
else {
log(chalk.blue('skipping ' + pkg.packageName));
command = Promise.resolve(true);
skipping = true;
}
}
//unless told otherwise, build the package
if (!command) {
command = buildPackage(null, null, path.join(process.cwd(), pkg.path), false);
// command = execPromise(
// 'cd ' + pkg.path + ' && yarn exec lincd build',
// // (target ? ' ' + target : '') +
// // (target2 ? ' ' + target2 : ''),
// false,
// false,
// {},
// false,
// );
log(chalk.cyan('Building ' + pkg.packageName));
process.stdout.write(packagesLeft + ' packages left\r');
}
return command.then(res => {
//empty string or true is success
//false is success with warnings
//any other string is the build error text
//undefined result means it failed
// if (res !== '' && res !== true && res !== false) {
if (typeof res === 'undefined') {
failedModules.push(pkg.packageName);
let dependentModules = getDependentPackages(dependencies, pkg);
if (dependentModules.length > 0) {
printBuildResults(failedModules, done);
console.log('Stopping build process because an error occurred whilst building ' +
pkg.packageName +
', which ' +
dependentModules.length +
' other packages depend on.'); //"+dependentModules.map(d => d.packageName).join(", ")));
log('Run ' +
chalk.greenBright(`lincd build-all --from=${pkg.packageName}`) +
' to build only the remaining packages'); //"+dependentModules.map(d => d.packageName).join(", ")));
process.exit(1);
}
}
else {
if (!skipping) {
log(chalk.green('Built ' + pkg.packageName) + (res === false ? chalk.redBright(' (with warnings)') : ''));
}
done.add(pkg);
packagesLeft--;
// log(chalk.magenta(packagesLeft + ' packages left'));
process.stdout.write(packagesLeft + ' packages left\r');
if (packagesLeft == 0) {
printBuildResults(failedModules, done);
if (failedModules.length > 0) {
process.exit(1);
}
}
return res;
}
})
.catch(({ error, stdout, stderr }) => {
warn(chalk.red('Failed to build ' + pkg.packageName));
console.log(stdout);
process.exit(1);
// let dependentModules = getDependentP
});
//undefined result means it failed
/*if (typeof res === 'undefined')
{
// .catch(({ error,stdout,stderr }) => {
//this prints out the webpack output, including the build errors
// warn('Failed to build ' + pkg.packageName);
// console.log(stdout);
failedModules.push(pkg.packageName);
let dependentModules = getDependentPackages(dependencies,pkg);
if (dependentModules.length > 0)
{
printBuildResults(failedModules,done);
console.log(
'Stopping build process because an error occurred whilst building ' +
pkg.packageName +
', which ' +
dependentModules.length +
' other packages depend on.',
); //"+dependentModules.map(d => d.packageName).join(", ")));
log(
'Run ' +
chalk.greenBright(`lincd build-all --from=${pkg.packageName}`) +
' to build only the remaining packages',
); //"+dependentModules.map(d => d.packageName).join(", ")));
process.exit(1);
}
}
else //true is successful build, false is successful but with warnings
{
//successful build
// })
// .then((res) => {
if (!skipping)
{
log(chalk.green('Built ' + pkg.packageName)+(res === false ? chalk.redBright(' (with warnings)') : ''));
}
done.add(pkg);
packagesLeft--;
// log(chalk.magenta(packagesLeft + ' packages left'));
process.stdout.write(packagesLeft + ' packages left\r');
if (packagesLeft == 0)
{
printBuildResults(failedModules,done);
if (failedModules.length > 0)
{
process.exit(1);
}
}
return res;
}*/
// }).catch(err => {
// console.log(err);
// })
});
}, (dependencies) => {
//if no more packages to build but we never started building...
if (!building) {
console.log(chalk.red('Could not find the package to start from. Please provide a correct package name or package name to build from'));
}
else {
//Detecting cyclical dependencies that caused some packages not to be build
let first = true;
lincdPackages.forEach((pkg) => {
if (!done.has(pkg)) {
let deps = dependencies.get(pkg);
if (first) {
console.log(chalk.red('CYCLICAL DEPENDENCIES? - could not build some packages because they depend on each other.'));
first = false;
}
//print the cyclical dependencies
console.log(chalk.red(pkg.packageName) +
' depends on ' +
deps
.filter((dependency) => {
return typeof dependency !== 'string';
})
.map((d) => {
return done.has(d)
? d.packageName
: chalk.red(d.packageName);
})
.join(', '));
//also print some information why these packages have not been moved into the stack
let stringDependencies = deps.filter((d) => typeof d === 'string');
if (stringDependencies.length > 0) {
console.log(chalk.red('And it depends on these package(s) - which seem not to be proper packages :' +
stringDependencies.join(', ')));
console.log(chalk.red('Could you remove this from dependencies? Should it be a devDependency?'));
}
}
});
}
}, sync);
}
function getDependentPackages(dependencies, pkg) {
let dependentModules = [];
dependencies.forEach((dModuleDependencies, dModule) => {
if (dModuleDependencies.indexOf(pkg) !== -1) {
dependentModules.push(dModule);
}
});
return dependentModules;
}
/**
* Returns a map of the packages that this repository manages (so no packages found through the workspaces who's path contains ../ )
* @param rootPath
*/
function getLocalLincdPackageMap(rootPath = './') {
let map = new Map();
getLincdPackages(rootPath).forEach((pkg) => {
if (pkg.path.indexOf('../') === -1 && pkg.path.indexOf('..\\') === -1) {
// console.log(package.path);
map.set(pkg.packageName, pkg);
}
});
return map;
}
function getLocalLincdModules(rootPath = './') {
return getLincdPackages(rootPath).filter((pkg) => {
return pkg.path.indexOf('..\\') === -1;
});
}
export function getLincdPackages(rootPath = process.cwd()) {
let pack = getPackageJSON();
if (!pack || !pack.workspaces) {
for (let i = 0; i <= 3; i++) {
rootPath = path.join(process.cwd(), ...Array(i).fill('..'));
pack = getPackageJSON(rootPath);
if (pack && pack.workspaces) {
// log('Found workspace at '+packagePath);
break;
}
}
}
if (!pack || !pack.workspaces) {
warn(chalk.red('Could not find package workspaces. Make sure you run this command from a yarn workspace.'));
logHelp();
process.exit();
}
// console.log(pack.workspaces);
let res = [];
checkWorkspaces(rootPath, pack.workspaces, res);
return res;
}
function setVariable(name, replacement) {
//prepare name for regexp
name = name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
variables[name] = replacement;
}
var replaceVariablesInFile = (filePath) => __awaiter(void 0, void 0, void 0, function* () {
var fileContent = yield fs.readFile(filePath, 'utf8').catch((err) => {
console.warn(chalk.red('Could not read file ' + filePath));
});
if (fileContent) {
var newContent = replaceCurlyVariables(fileContent);
return fs.writeFile(filePath, newContent);
}
else {
return Promise.resolve();
}
});
var replaceCurlyVariables = function (string) {
// var reg = new RegExp('\\$\\{'+key+'\\}','g');
for (var key in variables) {
string = string.replace(new RegExp('\\$\\{' + key + '\\}', 'g'), variables[key]);
}
return string;
};
const capitalize = (str) => str.charAt(0).toUpperCase() + str.toLowerCase().slice(1);
const camelCase = (str) => {
let string = str.replace(/[^A-Za-z0-9]/g, ' ').split(' ');
if (string.length > 1) {
return string.reduce((result, word) => result + capitalize(word));
}
return str;
};
export const createOntology = (prefix_1, uriBase_1, ...args_1) => __awaiter(void 0, [prefix_1, uriBase_1, ...args_1], void 0, function* (prefix, uriBase, basePath = process.cwd()) {
if (!prefix) {
console.warn('Please provide a suggested prefix as the first argument');
return;
}
let sourceFolder = getSourceFolder(basePath);
let targetFolder = ensureFolderExists(sourceFolder, 'ontologies');
if (!uriBase) {
uriBase = 'http://lincd.org/ont/' + prefix + '/';
}
setVariable('uri_base', uriBase);
let { hyphenName, camelCaseName, underscoreName } = setNameVariables(prefix);
//copy ontology accessor file
log('Creating files for ontology \'' + prefix + '\'');
let targetFile = path.join(targetFolder, hyphenName + '.ts');
fs.copySync(path.join(dirname__, '..', '..', 'defaults', 'package', 'src', 'ontologies', 'example-ontology.ts'), targetFile);
//copy data files
let targetDataFile = path.join(targetFolder, '..', 'data', hyphenName + '.json');
let targetDataFile2 = path.join(targetFolder, '..', 'data', hyphenName + '.json.d.ts');
fs.copySync(path.join(dirname__, '..', '..', 'defaults', 'package', 'src', 'data', 'example-ontology.json'), targetDataFile);
fs.copySync(path.join(dirname__, '..', '..', 'defaults', 'package', 'src', 'data', 'example-ontology.json.d.ts'), targetDataFile2);
yield replaceVariablesInFiles(targetFile, targetDataFile, targetDataFile2);
log(`Prepared a new ontology data files in ${chalk.magenta(targetDataFile.replace(basePath, ''))}`, `And an ontology accessor file in ${chalk.magenta(targetFile.replace(basePath, ''))}`);
//if this is not a lincd app (but a lincd package instead)
if (!sourceFolder.includes('frontend')) {
//then also add an import to index
let indexPath = addLineToIndex(`import './ontologies/${hyphenName}.js';`, 'ontologies');
log(`Added an import of this file from ${chalk.magenta(indexPath)}`);
}
});
const addLineToIndex = function (line, insertMatchString, root = process.cwd(), insertAtStart = false) {
//import ontology in index
let indexPath = ['index.ts', 'index.tsx']
.map((f) => path.join(root, 'src', f))
.find((indexFileName) => {
return fs.existsSync(indexFileName);
});
if (indexPath) {
let indexContents = fs.readFileSync(indexPath, 'utf-8');
let lines = indexContents.split(/\n/g);
let newContents;
for (var key in lines) {
//if the match string is found
if (lines[key].indexOf(insertMatchString) !== -1) {
//add the new line after this line
lines[key] += `\n${line}`;
newContents = lines.join('\n');
// log("Found at "+key,lines,newContents);
break;
}
}
if (!newContents) {
if (insertAtStart) {
newContents = `${line}\n${indexContents}`;
}
else {
newContents = `${indexContents}\n${line}`;
}
// log("Added at end",newContents);
}
fs.writeFileSync(indexPath, newContents);
}
return indexPath;
};
const replaceVariablesInFiles = function (...files) {
return Promise.all(files.map((file) => {
return replaceVariablesInFile(file);
}));
};
const replaceVariablesInFolder = function (folder) {
//get all files in folder, including files that start with a dot
glob(folder + '/**/*', { dot: true, nodir: true }, function (err, files) {
return __awaiter(this, void 0, void 0, function* () {
if (err) {
console.log('Error', err);
}
else {
// console.log(files);
yield Promise.all(files.map((file) => {
return replaceVariablesInFile(file);
}));
}
});
});
};
const replaceVariablesInFilesWithRoot = function (root, ...files) {
return replaceVariablesInFiles(...files.map((f) => path.join(root, f)));
};
const hasYarnInstalled = function () {
return __awaiter(this, void 0, void 0, function* () {
let version = (yield execPromise('yarn --version').catch((err) => {
console.log('yarn probably not working');
return '';
}));
return version.toString().match(/[0-9]+/);
});
};
const ensureFolderExists = function (...folders) {
let target;
folders.forEach((folder) => {
target = target ? path.join(target, folder) : path.join(folder);
if (!fs.existsSync(target)) {
fs.mkdirSync(target);
}
});
return target;
/*let targetFolder = path.join(...folders);
let parentDirectory = folders.slice(0, folders.length - 1);
if (!fs.existsSync(targetFolder)) {
if (fs.existsSync(path.join(...parentDirectory))) {
fs.mkdirSync(targetFolder);
} else {
warn(
`Please run this command from the root of your package. This command expects ${parentDirectory.toString()} to exists from that folder`,
);
}
}
return targetFolder;*/
};
export const setNameVariables = function (name) {
let hyphenName = name.replace(/[-_\s]+/g, '-');
let camelCaseName = camelCase(name); //some-package --> someModule
let underscoreName = name.replace(/[-\s]+/g, '_');
let plainName = name.replace(/[-\s]+/g, '');
//longer similar variables names should come before the shorter ones
setVariable('underscore_name', underscoreName);
setVariable('hyphen_name', hyphenName);
setVariable('camel_name', camelCaseName);
setVariable('name', name);
setVariable('plain_name', plainName);
return { hyphenName, camelCaseName, underscoreName, plainName };
};
function getSourceFolder(basePath = process.cwd()) {
//LINCD App
if (fs.existsSync(path.join(basePath, 'frontend', 'src'))) {
return path.join(basePath, 'frontend', 'src');
}
//LINCD package
if (fs.existsSync(path.join(basePath, 'src'))) {
return path.join(basePath, 'src');
}
else {
console.warn('Cannot find source folder');
return path.join(basePath, 'src');
}
}
/**
* get __dirname for either ESM/CJS
*/
export const getScriptDir = () => {
return dirname__;
// // @ts-ignore
// if (typeof __dirname !== 'undefined')
// {
// // @ts-ignore
// return __dirname;
// }
// else
// {
// // @ts-ignore
// return dirname(import.meta.url).replace('file:/','');
// }
};
export const createShape = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...args_1], void 0, function* (name, basePath = process.cwd()) {
let sourceFolder = getSourceFolder(basePath);
let targetFolder = ensureFolderExists(sourceFolder, 'shapes');
let { hyphenName, camelCaseName, underscoreName } = setNameVariables(name);
//copy default shape file
// log("Creating files for shape '" + name + "'");
let targetFile = path.join(targetFolder, hyphenName + '.ts');
fs.copySync(path.join(getScriptDir(), '..', '..', 'defaults', 'shape.ts'), targetFile);
//replace variables in some of the copied files
yield replaceVariablesInFiles(targetFile);
log(`Created a new shape class template in ${chalk.magenta(targetFile.replace(basePath, ''))}`);
//if this is NOT a lincd app (but a lincd package)
let indexPath;
if (!sourceFolder.includes('frontend')) {
indexPath = addLineToIndex(`import './shapes/${hyphenName}.js';`, 'shapes');
log(`Added an import of this file from ${chalk.magenta(indexPath)}`);
}
});
export const createSetComponent = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...args_1], void 0, function* (name, basePath = process.cwd()) {
let targetFolder = ensureFolderExists(basePath, 'src', 'components');
let { hyphenName, camelCaseName, underscoreName } = setNameVariables(name);
//copy default shape file
log('Creating files for set component \'' + name + '\'');
let targetFile = path.join(targetFolder, hyphenName + '.tsx');
fs.copySync(path.join(getScriptDir(), '..', '..', 'defaults', 'set-component.tsx'), targetFile);
let targetFile2 = path.join(targetFolder, hyphenName + '.scss');
fs.copySync(path.join(getScriptDir(), '..', '..', 'defaults', 'component.scss'), targetFile2);
//replace variables in some of the copied files
yield replaceVariablesInFiles(targetFile, targetFile2);
let indexPath = addLineToIndex(`import './components/${hyphenName}.js';`, 'components');
log(`Created a new set component in ${chalk.magenta(targetFile.replace(basePath, ''))}`, `Created a new stylesheet in ${chalk.magenta(targetFile2.replace(basePath, ''))}`, `Added an import of this file from ${chalk.magenta(indexPath)}`);
});
export const createComponent = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...args_1], void 0, function* (name, basePath = process.cwd()) {
let sourceFolder = getSourceFolder(basePath);
let targetFolder = ensureFolderExists(sourceFolder, 'components');
let { hyphenName, camelCaseName, underscoreName } = setNameVariables(name);
//copy default shape file
log('Creating files for component \'' + name + '\'');
let targetFile = path.join(targetFolder, hyphenName + '.tsx');
fs.copySync(path.join(getScriptDir(), '..', 'defaults', 'component.tsx'), targetFile);
let targetFile2 = path.join(targetFolder, hyphenName + '.scss');
fs.copySync(path.join(getScriptDir(), '..', 'defaults', 'component.scss'), targetFile2);
//replace variables in some of the copied files
yield replaceVariablesInFiles(targetFile, targetFile2);
log(`Created a new component template in ${chalk.magenta(targetFile.replace(basePath, ''))}`, `Created component stylesheet template in ${chalk.magenta(targetFile2.replace(basePath, ''))}`);
//if this is not a lincd app (but a lincd package instead)
if (!sourceFolder.includes('frontend')) {
//then also add an import to index
let indexPath = addLineToIndex(`import './components/${hyphenName}.js';`, 'components');
log(`Added an import of this file from ${chalk.magenta(indexPath)}`);
}
});
//read the source of all ts/tsx files in the src folder
//if there is an import that imports a lincd package with /src/ in it, then warn
//if there is an import that imports something from outside the src folder, then warn
export const checkImports = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (sourceFolder = getSourceFolder(), depth = 0, // Used to check if the import is outside the src folder
invalidImports = new Map()) {
const dir = fs.readdirSync(sourceFolder);
// Start checking each file in the source folder
for (const file of dir) {
const filename = path.join(sourceFolder, file);
// File is either a directory, or not a .ts(x)
// INFO: For future use - if this part fails, it could be due to user permissions
// i.e. the program not having access to check the file metadata
if (!filename.match(/\.tsx?$/)) {
try {
if (statSync(filename).isDirectory()) {
yield checkImports(filename, depth + 1, invalidImports);
}
else {
// Ignore all files that aren't one of the following:
// - .ts
// - .tsx
continue;
}
}
catch (e) {
console.log(e);
}
}
const allImports = yield getFileImports(filename);
if (!invalidImports.has(filename)) {
invalidImports.set(filename, []);
}
allImports.forEach((i) => {
if (isImportOutsideOfPackage(i, depth)) {
invalidImports.get(filename).push({
type: 'outside_package',
importPath: i
});
}
if (isInvalidLINCDImport(i, depth)) {
invalidImports.get(filename).push({
type: 'lincd',
importPath: i
});
}
if (isImportWithMissingExtension(i)) {
invalidImports.get(filename).push({
type: 'missing_extension',
importPath: i
});
}
});
}
let res = '';
//check if invalidImports has any
let flat = [...invalidImports.values()].flat();
// All recursion must have finished, display any errors
if (depth === 0 && flat.length > 0) {
res += chalk.red('Invalid imports found.\n');
invalidImports.forEach((value, key) => {
// res += '- '+chalk.blueBright(key.split('/').pop()) + ':\n';
value.forEach(({ type, importPath }) => {
let message = key.split('/').pop() + ' imports from \'' + importPath + '\'';
if (type === 'outside_package') {
message += ' which is outside the package source root';
}
if (type === 'lincd') {
message += ' which should not contain /src/ or /lib/ in the import path';
}
if (type === 'missing_extension') {
message += ' which should end with a file extension. Like .js or .scss';
}
res += chalk.red(message + '\n');
});
});
throw res;
// process.exit(1);
}
else if (depth === 0 && invalidImports.size === 0) {
// console.info('All imports OK');
// process.exit(0);
return true;
}
});
export const depCheckStaged = () => __awaiter(void 0, void 0, void 0, function* () {
console.log('Checking dependencies of staged files');
stagedGitFiles(function (err, results) {
return __awaiter(this, void 0, void 0, function* () {
const packages = new Set();
yield Promise.all(results.map((file) => __awaiter(this, void 0, void 0, function* () {
// console.log('STAGED: ', file.filename);
let root = yield findNearestPackageJson(file.filename);
packages.add(root.path);
})));
[...packages].forEach((packageRoot) => {
const pack = JSON.parse(fs.readFileSync(packageRoot, 'utf8'));
const srcPath = packageRoot.replace('package.json', '');
console.log('Checking dependencies of ' + chalk.blue(pack.name) + ':');
return depCheck(process.cwd() + '/' + srcPath);
// console.log('check dependencies of ' + pack.name);
//
// console.log('ROOT of ' + file.filename + ': ' + root.path);
// console.log('ROOT of ' + file.filename + ': ' + root.data);
});
});
});
});
export const depCheck = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (packagePath = process.cwd()) {
return new Promise((resolve, reject) => {
depcheck(packagePath, {}, (results) => {
if (results.missing) {
let lincdPackages = getLocalLincdModules();
let missing = Object.keys(results.missing);
//filter out missing types, if it builds we're not too concerned about that at the moment?
//especially things like @types/react, @types/react-dom, @types/node (they are added elsewhere?)
// missing = missing.filter(m => m.indexOf('@types/') === 0);
//currently react is not an explicit dependency, but we should add it as a peer dependency
missing.splice(missing.indexOf('react'), 1);
let missingLincdPackages = missing.filter((missingDep) => {
return lincdPackages.some((lincdPackage) => {
return lincdPackage.packageName === missingDep;
});
});
//currently just missing LINCD packages cause a hard failure exit code
if (missingLincdPackages.length > 0) {
reject(chalk.red(packagePath.split('/').pop() +
'\n[ERROR] These LINCD packages are imported but they are not listed in package.json:\n- ' +
missingLincdPackages.join(',\n- ')));
}
else if (missing.length > 0) {
resolve(chalk.redBright('warning: ' + packagePath.split('/').pop() +
' is missing dependencies:\n - ' +
missing.join('\n - ')));
}
else {
resolve(true);
}
}
// if(Object.keys(results.invalidFiles).length > 0) {
// console.warn(chalk.red("Invalid files:\n")+Object.keys(results.invalidFiles).join(",\n"));
// }
// if(Object.keys(results.invalidDirs).length > 0) {
// console.warn(chalk.red("Invalid dirs:\n")+results.invalidDirs.toString());
// }
// if(results.unused) {
// console.warn("Unused dependencies: "+results.missing.join(", "));
// }
});
});
});
export const ensureEnvironmentLoaded = () => __awaiter(void 0, void 0, void 0, function* () {
if (!process.env.NODE_ENV) {
//load env-cmd for development environment
let { GetEnvVars } = yield import('env-cmd');
let envCmdrcPath = path.join(process.cwd(), '.env-cmdrc.json');
if (!fs.existsSync(envCmdrcPath)) {
console.warn('No .env-cmdrc.json found in this folder. Are you running this command from the root of a LINCD app?');
process.exit();
}
let vars = yield GetEnvVars({
envFile: {
filePath: envCmdrcPath,
},
});
let environments = Object.keys(vars);
//if _main is present, load it first
if (environments.includes('_main')) {
process.env = Object.assign(Object.assign({}, process.env), vars._main);
}
//if --env is passed, load that environment
let args = process.argv.splice(2);
if (args.includes('--env')) {
let envIndex = args.indexOf('--env');
let env = args[envIndex + 1];
env.split(',').forEach((singleEnvironment) => {
if (environments.includes(singleEnvironment)) {
console.log('Environment: ' + singleEnvironment);
process.env = Object.assign(Object.assign({}, process.env), vars[singleEnvironment]);
}
else {
console.warn('Environment ' +
singleEnvironment +
' not found in .env-cmdrc.json. Available environments: ' +
environments.join(', '));
}
});
}
else {
//chose development by default
process.env = Object.assign(Object.assign({}, process.env), vars.development);
console.log('No environment specified, using development');
}
}
});
export const startServer = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (initOnly = false, ServerClass = null) {
yield ensureEnvironmentLoaded();
let lincdConfig = (yield import(path.join(process.cwd(), 'lincd.config.js'))).default;
// function scssLoadcall(source, filename) {
// return 'console.log("SCSS CALL: ' + filename + '");\n' + source;
// process.exit();
// }
// hook.hook('.scss', scssLoadcall);
// hook.hook('.css', scssLoadcall);
// import.meta.
// // hook.hook('*.css', scssLoadcall);
// // hook.hook('Body.module.css', scssLoadcall);
// hook.hook('.module.css', scssLoadcall);
if (!ServerClass) {
//@ts-ignore
ServerClass = (yield import('lincd-server/shapes/LincdServer')).LincdServer;
}
yield import(path.join(process.cwd(), 'scripts', 'storage-config.js'));
let server = new ServerClass(Object.assign({ loadAppComponent: () => __awaiter(void 0, void 0, void 0, function* () { return (yield import(path.join(process.cwd(), 'src', 'App'))).default; }) }, lincdConfig));
//Important to use slice, because when using clusers, child processes need to be able to read the same arguments
let args = process.argv.slice(2);
//if --initOnly is passed, only initialize the server and don't start it
if (args.includes('--initOnly') || initOnly) {
return server.initOnly();
}
else {
return server.start();
}
});
export const buildApp = () => __awaiter(void 0, void 0, void 0, function* () {
yield ensureEnvironmentLoaded();
const webpackAppConfig = yield (yield import('./config-webpack-app.js')).getWebpackAppConfig();
console.log(chalk.magenta(`Building ${process.env.NODE_ENV} app bundles`));
return new Promise((resolve, reject) => {
webpack(webpackAppConfig, (err, stats) => __awaiter(void 0, void 0, void 0, function* () {
if (err) {
console.error(err.stack || err);
process.exit(1);
}
const info = stats.toJson();
if (stats.hasErrors()) {
console.log('Finished running webpack with errors.');
info.errors.forEach((e) => console.error(e));
// process.exit(1);
reject();
}
else {
console.log(stats.toString({
chunks: false,
assets: true,
entrypoints: false,
modules: false,
moduleAssets: false,
colors: true,
}));
console.log('App build process finished');
resolve(true);
// console.log(
// chalk.green('\t'+Object.keys(stats.compilation.assets).join('\n\t')),
// );
//build metadata (JSON-LD files containing metadata about the lincd components, shapes & ontologies in this app or its packages)
// let updatedPaths = await buildMetadata();
// console.log(chalk.green("Updated metadata:\n")+" - "+updatedPaths.map(p => chalk.magenta(p.replace(process.cwd(),''))).join("\n - "));
}
// process.exit();
}));
}).then(() => __awaiter(void 0, void 0, void 0, function* () {
// make sure environment is not development for storage config
// and if we want to upload to storage, we need set S3_BUCKET_ENDPOINT
if (process.env.NODE_ENV === 'development' || !process.env.S3_BUCKET_ENDPOINT) {
console.warn('Upload build to storage skip in development