UNPKG

lincd-cli

Version:

Command line tools for the lincd.js library

1,089 lines (1,087 loc) 95.3 kB
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