UNPKG

standard-pkg

Version:

<p align="center"> <img alt="Logo" src="https://i.imgur.com/MsQMjew.png" width="420"> </p>

413 lines (345 loc) 13 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var util = require('util'); var fs = require('fs'); var fs__default = _interopDefault(fs); var _glob = _interopDefault(require('glob')); var parser = require('@babel/parser'); var traverse = _interopDefault(require('@babel/traverse')); var chalk = _interopDefault(require('chalk')); var path = require('path'); var minimist = _interopDefault(require('minimist')); var mkdirp = _interopDefault(require('mkdirp')); var babel = _interopDefault(require('@babel/core')); var babelPluginDynamicImportSyntax = _interopDefault(require('@babel/plugin-syntax-dynamic-import')); var babelPluginNullishCoalescingOperator = _interopDefault(require('@babel/plugin-syntax-nullish-coalescing-operator')); var babelPluginOptionalChaining = _interopDefault(require('@babel/plugin-syntax-optional-chaining')); var babelPluginBigint = _interopDefault(require('@babel/plugin-syntax-bigint')); var babelPluginImportMetaSyntax = _interopDefault(require('@babel/plugin-syntax-import-meta')); var babelPresetTypeScript = _interopDefault(require('@babel/preset-typescript')); var babelPluginImportRewrite = _interopDefault(require('@pika/babel-plugin-esm-import-rewrite')); const glob = util.promisify(_glob); // export const open = util.promisify(fs.open); // export const writeFile = util.promisify(fs.writeFile); // export const readlink = util.promisify(fs.readlink); // export const realpath = util.promisify(fs.realpath); // export const readdir = util.promisify(fs.readdir); // export const rename = util.promisify(fs.rename); // export const access = util.promisify(fs.access); // export const stat = util.promisify(fs.stat); // export const exists = util.promisify(fs.exists); // export const lstat = util.promisify(fs.lstat); // export const chmod = util.promisify(fs.chmod); // export const link = util.promisify(fs.link); // export const existsSync = fs.existsSync; // const readFileBuffer = util.promisify(fs.readFile); const readFile = path => { return util.promisify(fs.readFile)(path, { encoding: 'utf-8' }); }; // import stripBOM from 'strip-bom'; // export async function readJson(loc: string): Promise<Object> { // return (await readJsonAndFile(loc)).object; // } // export async function readJsonAndFile( // loc: string, // ): Promise<{ // object: Object, // content: string, // }> { // const file = await readFile(loc); // try { // return { // object: map(JSON.parse(stripBOM(file))), // content: file, // }; // } catch (err) { // err.message = `${loc}: ${err.message}`; // throw err; // } // } // const cr = '\r'.charCodeAt(0); // const lf = '\n'.charCodeAt(0); // async function getEolFromFile(path: string): Promise<string | void> { // if (!await exists(path)) { // return undefined; // } // const buffer = await readFileBuffer(path); // for (let i = 0; i < buffer.length; ++i) { // if (buffer[i] === cr) { // return '\r\n'; // } // if (buffer[i] === lf) { // return '\n'; // } // } // return undefined; // } // export async function writeFilePreservingEol(path: string, data: string): Promise<void> { // const eol = (await getEolFromFile(path)) || os.EOL; // if (eol !== '\n') { // data = data.replace(/\n/g, eol); // } // await writeFile(path, data); // } // @flow function getLineCol(node) { const loc = node.loc.start; return chalk.dim(`[${loc.line}:${loc.column}]`); } function validateDynamicImportArguments(path) { if (path.parent.arguments.length !== 1) { return new Set([`${getLineCol(path.node)} "\`import()\` only accepts 1 argument, but got ${path.parent.arguments.length}`]); } const [argNode] = path.parent.arguments; if (argNode.type !== 'StringLiteral') { return new Set([`${getLineCol(path.node)} Pika expects strings as \`import()\` arguments. Treating this as an absolute file path.`]); } return new Set(); } // @flow function getLineCol$1(node) { const loc = node.loc.start; return chalk.dim(`[${loc.line}:${loc.column}]`); } function validate(code, fileLoc, cwd, dist, ignoreExtensions) { const ast = parser.parse(code, { plugins: ['dynamicImport', 'importMeta'], sourceType: 'module' }); const errors = new Set(); function validateSpecifier(specifier, path$1) { const errors = new Set(); if (specifier.startsWith('./') || specifier.startsWith('../')) { if (!ignoreExtensions && !specifier.endsWith('.js')) { errors.add(`${getLineCol$1(path$1.node)} "${specifier}": Valid relative imports must include the ".js" file extension.`); } const absPathToImport = path.resolve(path.dirname(fileLoc), specifier); const assetsPath = path.join(cwd, 'assets'); if (!absPathToImport.startsWith(cwd)) { errors.add(`${getLineCol$1(path$1.node)} "${specifier}": Valid imports cannot reach outside of the current package.`); } else if (!absPathToImport.startsWith(assetsPath) && !absPathToImport.startsWith(dist)) { errors.add(`${getLineCol$1(path$1.node)} "${specifier}": Valid imports can only import from the dist directory or the sibling \`assets/\` directory.`); } return errors; } // NOTE(fks): Removed as "too opinionated" (rightfully!). // const parts = specifier.split('/').length; // if ((specifier.startsWith('@') && parts > 2) || (!specifier.startsWith('@') && parts > 1)) { // errors.add( // `${getLineCol(path.node)} "${specifier}": Avoid directly importing private files inside external packages.`, // ); // return errors; // } return errors; } traverse(ast, { Identifier(path) { if (path.node.name === '__dirname') { errors.add(`${getLineCol$1(path.node)} \`__dirname\` is not a valid ESM global. Use \`import.meta.url\` instead.`); } if (path.node.name === '__filename') { errors.add(`${getLineCol$1(path.node)} \`__filename\` is not a valid ESM global. Use \`import.meta.url\` instead.`); } if (path.node.name === 'require' && path.parent.type !== 'CallExpression') { errors.add(`${getLineCol$1(path.node)} \`require()\` is not a valid ESM global. Use \`import()\` instead.`); } if (path.node.name === 'module' && path.parent.type !== 'MemberExpression' && path.parent.type !== 'ObjectProperty') { errors.add(`${getLineCol$1(path.node)} \`module\` is not a valid ESM global. Use \`export\` instead.`); } // TODO: Lint against other node concepts? }, ImportDeclaration(path) { validateSpecifier(path.node.source.value, path).forEach(e => errors.add(e)); }, Import(path) { if (path.parent.type !== 'CallExpression') { errors.add(`${getLineCol$1(path.node)} \`import()\` should only be used/called directly.`); return; } const results = validateDynamicImportArguments(path); if (results.size > 0) { results.forEach(e => errors.add(e)); return; } validateSpecifier(path.parent.arguments[0].value, path).forEach(e => errors.add(e)); }, MetaProperty(path) { if (!path.parent.property || path.parent.property.name !== 'url') { errors.add(`${getLineCol$1(path.node)} \`url\` is the only \`import.meta\` property currently supported in spec.`); } } }); return errors; } /* $ standard-pkg [--src src/] [--dist dist-src/] - builds `src/` -> `dist-src/` - writes `src/` -> `dist-src/` - lints `dist-src/` $ standard-pkg [--src src/] - builds `src/` -> `dist-src/` - lints `dist-src/` - (does not write to disk) $ standard-pkg [--dist dist-src/] $ standard-pkg lint dist-src/ - lints `dist-src/` */ // const argv = yargs.command({ // command: 'build', // describe: 'describe', // builder: (yargs) => yargs.option('src', { // default: 'src/', // describe: 'x marks the spot', // type: 'string' // }).option('dist', { // default: 'dist/', // describe: 'x marks the spot', // type: 'string' // }), // handler: (argv) => undefined, // }).command({ // command: 'lint [dist]', // aliases: ['$0'], // describe: 'describe', // builder: (yargs) => yargs.option('src', { // default: 'src/', // describe: 'x marks the spot', // type: 'string' // }), // handler: (argv) => undefined // }).help(); // console.log(argv); function log(fileName, errors) { console.log(chalk.bold(fileName)); for (const error of errors) { console.log(' ', error.level === 2 ? '⚠️ ' : ' ', error.msg); } } class Lint { constructor(dist, { ignoreExtensions } = {}) { this.dist = dist; this.errors = new Map(); this.totalNum = 0; this.ignoreExtensions = ignoreExtensions || false; } addError(filename, msg, level = 2) { const errors = this.errors.get(filename) || []; errors.push({ msg, level }); this.errors.set(filename, errors); } async init() { const { dist } = this; const dir = path.join(dist, '..'); const files = await glob(`**/*`, { cwd: dist, absolute: true, nodir: true }); for (const fileLoc of files) { const relativePath = path.relative(path.join(dist, '..'), fileLoc); const extName = path.extname(fileLoc); if (extName === '.map') { continue; } if (fileLoc.includes('README')) { continue; } if (extName !== '.js') { this.addError(relativePath, 'Only JavaScript files are expected in your dist-src/ distribution.'); this.totalNum++; continue; } const fileContents = await readFile(fileLoc); const validateErrors = validate(fileContents, fileLoc, dir, dist, this.ignoreExtensions); for (const errMsg of validateErrors) { this.addError(relativePath, errMsg); } this.totalNum += validateErrors.size; } } summary() { if (this.totalNum === 0) { return; } console.log(``); for (const [filename, errors] of this.errors.entries()) { log(filename, errors); } console.log(``); console.log(chalk.red('✘'), `${this.totalNum} issues found.`); } exitCode() { return this.totalNum === 0 ? 0 : 1; } } class Build { constructor(dir, options = {}) { this.dir = dir; this.options = options; this.result = new Map(); } async init() { const { dir, options } = this; const files = (await glob(`**/*`, { cwd: dir, nodir: true, absolute: false, ignore: options.exclude || [] })).filter(filepath => !filepath.endsWith('.d.ts') && !filepath.includes('README')); for (const sourcePath of files) { const sourcePathAbs = path.join(dir, sourcePath); const transformedPath = sourcePath // .replace(path.join(dir, 'src/'), path.join(out, 'dist-src/')) .replace('.ts', '.js').replace('.tsx', '.js').replace('.jsx', '.js').replace('.mjs', '.js'); const resultSrc = await babel.transformFileAsync(sourcePathAbs, { presets: [[babelPresetTypeScript]], plugins: [[babelPluginImportRewrite, { addExtensions: true }], babelPluginDynamicImportSyntax, babelPluginImportMetaSyntax, babelPluginNullishCoalescingOperator, babelPluginOptionalChaining, babelPluginBigint] }); this.result.set(transformedPath, resultSrc.code); } return this.result; } async write(out, result = this.result) { for (const [filepath, contents] of result.entries()) { const transformedPathAbs = path.join(out, filepath); mkdirp.sync(path.dirname(transformedPathAbs)); fs__default.writeFileSync(transformedPathAbs, contents); } } } // export async function runBuild(args: Arguments): Promise<void> { // } async function run(argv) { var args = minimist(argv.slice(2)); const srcDir = path.resolve(process.cwd(), typeof args.src === 'string' ? args.src : 'src'); const distDir = path.resolve(process.cwd(), typeof args.dist === 'string' ? args.dist : 'lib'); if (args.src) { console.log(chalk.bold.dim(`»`), chalk(`Building ${path.relative(process.cwd(), srcDir)}${path.sep}${path.relative(process.cwd(), distDir)}${path.sep}...`)); const builder = new Build(srcDir); await builder.init(); await builder.write(distDir); } console.log(chalk.bold.dim(`»`), chalk(`Linting ${path.relative(process.cwd(), distDir)}${path.sep}...`)); const linter = new Lint(distDir); await linter.init(); if (linter.totalNum === 0) { // console.log(``); console.log(chalk.bold.green(`✓`), '0 issues found.'); } else { linter.summary(); } process.exit(linter.exitCode()); } exports.Build = Build; exports.Lint = Lint; exports.run = run; //# sourceMappingURL=index.js.map