lincd-cli
Version:
Command line tools for the lincd.js library
588 lines • 24.5 kB
JavaScript
;
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