UNPKG

lincd-cli

Version:

Command line tools for the lincd.js library

588 lines 24.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.stripAnsi = exports.getFiles = exports.getLinkedTailwindColors = exports.flatten = exports.warn = exports.debugInfo = exports.debug = exports.log = exports.needsRebuilding = exports.generateScopedName = exports.generateScopedNameProduction = exports.execPromise = exports.execp = exports.getGruntConfig = exports.getModulePackageJSON = exports.getLastModifiedFile = exports.getLastCommitTime = exports.getLastModifiedSourceTime = exports.getLastBuildTime = exports.getLINCDDependencies = exports.getPackageJSON = exports.isImportWithMissingExtension = exports.isValidLINCDImport = exports.isImportOutsideOfPackage = exports.isInvalidLINCDImport = exports.getFileImports = void 0; const chalk_1 = __importDefault(require("chalk")); const child_process_1 = require("child_process"); const fs = __importStar(require("fs")); const module_1 = require("module"); const path = __importStar(require("path")); const typescript_1 = __importDefault(require("typescript")); const crypto = __importStar(require("crypto")); const find_nearest_package_json_1 = require("find-nearest-package-json"); const glob = __importStar(require("glob")); var gruntConfig; // Credit: https://gist.github.com/tinovyatkin/727ddbf7e7e10831a1eca9e4ff2fc32e const tsHost = typescript_1.default.createCompilerHost({ allowJs: true, noEmit: true, isolatedModules: true, resolveJsonModule: false, moduleResolution: typescript_1.default.ModuleResolutionKind.Classic, // we don't want node_modules incremental: true, noLib: true, noResolve: true, }, true); var getFileImports = async function (filePath) { try { const importing = []; const delintNode = (node) => { if (typescript_1.default.isImportDeclaration(node)) { const moduleName = node.moduleSpecifier.getText().replace(/['"]/g, ''); if (!moduleName.startsWith('node:') && !module_1.builtinModules.includes(moduleName)) importing.push(moduleName); } else typescript_1.default.forEachChild(node, delintNode); }; const sourceFile = tsHost.getSourceFile(filePath, typescript_1.default.ScriptTarget.Latest, (msg) => { throw new Error(`Failed to parse ${filePath}: ${msg}`); }); //check if its a directory, then we wanr that we can't parse it let stat = fs.lstatSync(filePath); if (stat.isDirectory()) { return importing; } if (!sourceFile) { console.warn(`Failed to find file ${filePath}`); return importing; } delintNode(sourceFile); return importing; } catch (err) { console.warn(`Error parsing file ${filePath}: ${err}`); return []; } }; exports.getFileImports = getFileImports; /** * * @param importPath The import path to check * @param curFileDepth How many folders deep the current file is (0 = src, 1 = src/foo, etc.) * @returns */ var isInvalidLINCDImport = function (importPath, curFileDepth) { return (importPath.includes('lincd') && (importPath.includes('/src/') || importPath.includes('/lib/'))); }; exports.isInvalidLINCDImport = isInvalidLINCDImport; var isImportOutsideOfPackage = function (importPath, curFileDepth) { if (importPath.includes('..')) { //the number of '..' in the import path should be less than or equal to the current file depth //if its bigger then the import is outside of the package return importPath.split('..').length - 1 > curFileDepth; } return false; }; exports.isImportOutsideOfPackage = isImportOutsideOfPackage; /** * * @param importPath The import path to check * @param curFileDepth How many folders deep the current file is (0 = src, 1 = src/foo, etc.) * @returns */ var isValidLINCDImport = function (importPath, curFileDepth) { const validLincdPath = importPath.includes('lincd') && !importPath.includes('/src/'); let validRelativePath = false; if (importPath.includes('..')) { // '../bad/path' from 'src/file.ts' should be invalid: // ^ should get split into ['', '/bad/path'], and the file depth is 0 // meaning that it'll be invalid. // And this should be true for all relative imports containing '..' validRelativePath = importPath.split('..').length - 1 <= curFileDepth; } return validLincdPath || validRelativePath; }; exports.isValidLINCDImport = isValidLINCDImport; var isImportWithMissingExtension = function (importPath) { //if a relative import then it needs an extension if (importPath.startsWith('../') || importPath.startsWith('./')) { //check if the last part of the import after the last slash has a file extension if (importPath.split('/').pop().split('.').length < 2) { //if it doesn't have an extension, then it's missing, so return true return true; } } return false; }; exports.isImportWithMissingExtension = isImportWithMissingExtension; var getPackageJSON = function (root = process.cwd(), error = true) { // console.log('Getting package.json from ' + chalk.cyan(root)); //log stack trace let packagePath = path.join(root, 'package.json'); if (fs.existsSync(packagePath)) { return JSON.parse(fs.readFileSync(packagePath, 'utf8')); } else if (root === process.cwd()) { if (error) { console.warn('Could not find package.json. Make sure you run this command from the root of a lincd module or a lincd yarn workspace'); process.exit(); } } }; exports.getPackageJSON = getPackageJSON; /** * Scans package.json for dependencies that are LINCD packages * Also looks into dependencies of dependencies * If no packageJson is given, it will attempt to obtain it from the current working directory * Returns an array of lincd packages, with each entry containing an array with the package name and the local path to the package * @param packageJson */ var getLINCDDependencies = function (packageJson, checkedPackages = new Set()) { if (!packageJson) { packageJson = (0, exports.getPackageJSON)(); } let dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies, }; let lincdPackagePaths = []; let firstTime = checkedPackages.size === 0; for (var dependency of Object.keys(dependencies)) { try { if (!checkedPackages.has(dependency)) { let [modulePackageJson, modulePath] = (0, exports.getModulePackageJSON)(dependency); checkedPackages.add(dependency); if (modulePackageJson === null || modulePackageJson === void 0 ? void 0 : modulePackageJson.lincd) { lincdPackagePaths.push([ modulePackageJson.name, modulePath, [ ...Object.keys({ ...modulePackageJson.dependencies, ...modulePackageJson.devDependencies, }), ], ]); //also check if this package has any dependencies that are lincd packages lincdPackagePaths = lincdPackagePaths.concat((0, exports.getLINCDDependencies)(modulePackageJson, checkedPackages)); } if (!modulePackageJson) { //this seems to only happen with yarn workspaces for some grunt related dependencies of lincd-cli // console.log(`could not find package.json of ${dependency}`); } } } catch (err) { console.log(`could not check if ${dependency} is a lincd package: ${err}`); } } if (firstTime) { // let dependencyMap:Map<string,Set<string>> = new Map(); let lincdPackageNames = new Set(lincdPackagePaths.map(([packageName, modulePath, pkgDependencies]) => packageName)); //remove lincd-cli from the list of lincd packages lincdPackageNames.delete('lincd-cli'); lincdPackagePaths.forEach(([packageName, modulePath, pkgDependencies], key) => { let lincdDependencies = pkgDependencies.filter((dependency) => lincdPackageNames.has(dependency)); if (packageName === 'lincd-cli') { //remove lincd-modules from the dependencies of lincd-cli (it's not a hard dependency, and it messes things up) lincdDependencies.splice(lincdDependencies.indexOf('lincd-modules'), 1); } // dependencyMap.set(packageName, new Set(lincdDependencies)); //update dependencies to be the actual lincd package objects lincdPackagePaths[key][2] = lincdDependencies; }); // //add the nested dependencies for each lincd package // for (let [packageName,pkgDependencies] of dependencyMap) { // pkgDependencies.forEach((dependency) => { // if (dependencyMap.has(dependency)) { // dependencyMap.get(dependency).forEach((nestedDependency) => { // pkgDependencies.add(nestedDependency); // }); // } // }); // } // // dependencyMap.forEach((dependencies,packageName) => { // //check for circular dependencies // if([...dependencies].some(dependency => { // return dependencyMap.get(dependency).has(packageName); // })) // { // console.warn(`Circular dependency detected between ${packageName} and ${dependency}`); // } // // }); // a simple sort with dependencyMap doesn't seem to work,so we start with LINCD (least dependencies) and from there add packages that have all their dependencies already added let sortedPackagePaths = []; let addedPackages = new Set(); let lincdItself = lincdPackagePaths.find(([packageName]) => { return packageName === 'lincd'; }); if (lincdItself) { sortedPackagePaths.push(lincdItself); addedPackages = new Set(['lincd']); } while (addedPackages.size !== lincdPackagePaths.length) { let startSize = addedPackages.size; lincdPackagePaths.forEach(([packageName, modulePath, pkgDependencies]) => { if (!addedPackages.has(packageName) && pkgDependencies.every((dependency) => addedPackages.has(dependency))) { sortedPackagePaths.push([packageName, modulePath, pkgDependencies]); addedPackages.add(packageName); } }); if (startSize === addedPackages.size) { console.warn('Could not sort lincd packages, circular dependencies?'); break; } } //sort the lincd packages by least dependent first // lincdPackagePaths = lincdPackagePaths.sort(([packageNameA],[packageNameB]) => { // //if package A depends on package B, then package B should come first // if (dependencyMap.get(packageNameA).has(packageNameB)) { // console.log(packageNameA+' depends on '+packageNameB+ ' (below)') // return 1; // } // console.log(packageNameA+' above '+packageNameB) // return -1; // }); return sortedPackagePaths; } return lincdPackagePaths; }; exports.getLINCDDependencies = getLINCDDependencies; const getLastBuildTime = (packagePath) => { return (0, exports.getLastModifiedFile)(packagePath + '/@(builds|lib|dist)/**/*.js'); }; exports.getLastBuildTime = getLastBuildTime; const getLastModifiedSourceTime = (packagePath) => { return (0, exports.getLastModifiedFile)(packagePath + '/@(src|data|css|modules)/**/*', { ignore: [ packagePath + '/**/*.css.json', packagePath + '/**/*.d.ts', packagePath + '/**/node_modules/**/*', packagePath + '/**/lib/**/*', packagePath + '/**/dist/**/*', ], }); }; exports.getLastModifiedSourceTime = getLastModifiedSourceTime; const getLastCommitTime = (packagePath) => { // console.log(`git log -1 --format=%ci -- ${packagePath}`); // process.exit(); return execPromise(`cd ${packagePath} && git log -1 --format="%h %ci" -- .`) .then(async (result) => { let commitId = result.substring(0, result.indexOf(' ')); let date = result.substring(commitId.length + 1); let lastCommitDate = new Date(date); let changes = await execPromise(`cd ${packagePath} && git show --stat --oneline ${commitId} -- .`); // log(packagePath, result, lastCommitDate); // log(changes); return { date: lastCommitDate, changes, commitId }; }) .catch(({ error, stdout, stderr }) => { debugInfo(chalk_1.default.red('Git error: ') + error.message.toString()); return null; }); }; exports.getLastCommitTime = getLastCommitTime; const getLastModifiedFile = (filePath, config = {}) => { var files = glob.sync(filePath, config); // console.log(files.join(" - ")); var lastModifiedName; var lastModified; var lastModifiedTime = 0; files.forEach((fileName) => { if (fs.lstatSync(fileName).isDirectory()) { // console.log("skipping directory "+fileName); return; } let mtime = fs.statSync(path.join(fileName)).mtime; let modifiedTime = mtime.getTime(); if (modifiedTime > lastModifiedTime) { // console.log(fileName,mtime); lastModifiedName = fileName; lastModified = mtime; lastModifiedTime = modifiedTime; } }); return { lastModified, lastModifiedName, lastModifiedTime }; }; exports.getLastModifiedFile = getLastModifiedFile; //from https://github.com/haalcala/node-packagejson/blob/master/index.js var getModulePackageJSON = function (module_name, work_dir) { if (!work_dir) { work_dir = process.cwd(); } else { work_dir = path.resolve(work_dir); } var package_json; if (fs.existsSync(path.resolve(work_dir, './node_modules'))) { var module_dir = path.resolve(work_dir, './node_modules/' + module_name); if (fs.existsSync(module_dir) && fs.existsSync(module_dir + '/package.json')) { package_json = JSON.parse(fs.readFileSync(module_dir + '/package.json', 'utf-8')); } } if (!package_json && work_dir != '/') { return (0, exports.getModulePackageJSON)(module_name, path.resolve(work_dir, '..')); } return [package_json, module_dir]; }; exports.getModulePackageJSON = getModulePackageJSON; var getGruntConfig = function (root = process.cwd(), error = true) { let gruntFile = path.join(root, 'Gruntfile.js'); if (fs.existsSync(gruntFile)) { return require(gruntFile)(); } else if (root === process.cwd()) { if (error) { console.warn('Could not find Gruntfile.js. Make sure you run this command from the root of a lincd module or a lincd yarn workspace'); process.exit(); } } }; exports.getGruntConfig = getGruntConfig; function execp(cmd, log = false, allowError = false, options = {}) { // opts || (opts = {}); if (log) console.log(chalk_1.default.cyan(cmd)); return new Promise((resolve, reject) => { var child = (0, child_process_1.exec)(cmd, options); child.stdout.pipe(process.stdout); child.stderr.pipe(process.stderr); // process.stdin.pipe(child.stdin); child.on('close', function (code) { if (code === 0) { resolve(null); } else { reject(); } // console.log('killing child'); // child.kill('SIGHUP'); // resolve(code); }); // child.on('data', function (result) { // // if (log) // // { // // console.log(result); // // } // resolve(result); // console.log('resolve data'); // // }); child.on('error', function (err) { if (!allowError) { // console.log('reject err'); reject(err); return; } else if (log) { console.warn(err); } // console.log('resolve err'); resolve(null); }); child.on('exit', function (code, signal) { if (code !== 0) { reject('Child process exited with error code ' + code); return; } // console.log('resolve exit'); resolve(null); }); }); } exports.execp = execp; function execPromise(command, log = false, allowError = false, options, pipeOutput = false) { return new Promise(function (resolve, reject) { if (log) console.log(chalk_1.default.cyan(command)); let child = (0, child_process_1.exec)(command, options, (error, stdout, stderr) => { if (error) { if (!allowError) { reject({ error, stdout, stderr }); return; } else if (log) { console.warn(error); } } //TODO: getting a typescript error for 'trim()', this worked before, is it still used? do we log anywhere? let result = stdout['trim'](); if (log) { // console.log(chalk"RESOLVING "+command); console.log(result); // console.log('ERRORS:'+(result.indexOf('Aborted due to warnings') !== -1)); // console.log('stderr:'+stderr); } resolve(result); }); if (pipeOutput) { child.stdout.pipe(process.stdout); child.stderr.pipe(process.stderr); } }); } exports.execPromise = execPromise; function generateScopedNameProduction(cssClassName, filepath, css) { //for app development we can use short unique hashes //but for webpack bundles of lincd modules, we need to ensure unique class names across bundles of many packages //generate a short unique hash based on cssClassName and filepath let hash = crypto .createHash('md5') .update(cssClassName + filepath) .digest('hex') .substring(0, 6); return hash; } exports.generateScopedNameProduction = generateScopedNameProduction; function generateScopedName(cssClassName, filepath, css) { // return cssClassName; var filename = path .basename(filepath) .replace(/\.module\.css$/, '') .replace(/\.css$/, ''); let resolved = path.resolve(filepath).replace(/[\w\-_\/]+\/file\:/, ''); let nearestPackageJson = (0, find_nearest_package_json_1.findNearestPackageJsonSync)(resolved); let packageName = nearestPackageJson ? nearestPackageJson.data.name : 'unknown'; return (packageName.replace(/[^a-zA-Z0-9_]+/g, '_') + '_' + filename + '_' + cssClassName); } exports.generateScopedName = generateScopedName; const needsRebuilding = async function (pkg, useGitForLastModified, log = false) { let lastModifiedSourceDate; let lastModifiedSourceName; if (useGitForLastModified) { const { changes, commitId, date } = await (0, exports.getLastCommitTime)(pkg.path); lastModifiedSourceDate = date; lastModifiedSourceName = commitId; } else { const { lastModified, lastModifiedName, lastModifiedTime } = (0, exports.getLastModifiedSourceTime)(pkg.path); lastModifiedSourceName = lastModifiedName; lastModifiedSourceDate = lastModified; } let lastModifiedBundle = (0, exports.getLastBuildTime)(pkg.path); let result = lastModifiedSourceDate && lastModifiedSourceDate.getTime() > lastModifiedBundle.lastModifiedTime; if (log) { console.log(chalk_1.default.cyan('Last modified source: ' + lastModifiedSourceName + ' on ' + lastModifiedSourceDate.toString())); console.log(chalk_1.default.cyan('Last build: ' + (lastModifiedBundle && typeof lastModifiedBundle.lastModified !== 'undefined' ? lastModifiedBundle.lastModified.toString() : 'never'))); } return result; }; exports.needsRebuilding = needsRebuilding; function log(...messages) { messages.forEach((message) => { console.log(chalk_1.default.cyan(message)); }); } exports.log = log; function debug(config, ...messages) { if (config.debug) { log(...messages); } } exports.debug = debug; function debugInfo(...messages) { // messages.forEach((message) => { // console.log(chalk.cyan('Info: ') + message); // }); //@TODO: let packages also use lincd.config.json? instead of gruntfile... // that way we can read "analyse" here and see if we need to log debug info // if(!gruntConfig) // { // gruntConfig = getGruntConfig(); // console.log(gruntConfig); // process.exit(); // } if (gruntConfig && gruntConfig.analyse === true) { messages.forEach((message) => { console.log(chalk_1.default.cyan('Info: ') + message); }); } } exports.debugInfo = debugInfo; function warn(...messages) { messages.forEach((message) => { console.log(chalk_1.default.red(message)); }); } exports.warn = warn; function flatten(arr) { return arr.reduce(function (a, b) { return b ? a.concat(b) : a; }, []); } exports.flatten = flatten; function getLinkedTailwindColors() { return { 'primary-color': 'var(--primary-color)', 'font-color': 'var(--font-color)', }; } exports.getLinkedTailwindColors = getLinkedTailwindColors; /** * Recursively get all files in a directory * https://stackoverflow.com/a/45130990/831465 * @param dir The directory to get files from * @returns A promise that resolves to an array of file paths */ async function getFiles(dir, filenameFilter) { const entries = await fs.promises.readdir(dir, { withFileTypes: true }); let files = await Promise.all(entries.map((entry) => { const res = path.resolve(dir, entry.name); return entry.isDirectory() ? getFiles(res, filenameFilter) : res; })); //flatten the array of arrays into a single array let flatFiles = flatten(files); if (filenameFilter) { //filter the files to only include those that match the filenameFilter flatFiles = flatFiles.filter((file) => file.includes(filenameFilter)); } return flatFiles; } exports.getFiles = getFiles; /** * Strip ANSI escape codes from a string * @param str The string to strip ANSI codes from * @returns The string with ANSI codes removed */ function stripAnsi(str) { return str.replace(/\x1b\[[0-9;]*m/g, ''); } exports.stripAnsi = stripAnsi; //# sourceMappingURL=utils.js.map