diff --git a/development/README.md b/development/README.md index e996786fc..a8e4eb6fc 100644 --- a/development/README.md +++ b/development/README.md @@ -1,5 +1,5 @@ # Development -Several files which are needed for developing on(!) MetaMask. +Several files which are needed for developing on MetaMask. -Usually each files contains information about its scope / usage. +Usually each file or directory contains information about its scope / usage. diff --git a/development/build/README.md b/development/build/README.md index 635b8d9cc..6c671be27 100644 --- a/development/build/README.md +++ b/development/build/README.md @@ -3,7 +3,7 @@ > _tl;dr_ `yarn dist` for prod, `yarn start` for local development This directory contains the MetaMask build system, which is used to build the MetaMask Extension such that it can be used in a supported browser. -From the repository root, the build system entry file is located at `development/build/index.js`. +From the repository root, the build system entry file is located at [`./development/build/index.js`](https://github.com/MetaMask/metamask-extension/blob/develop/development/build/index.js). Several package scripts invoke the build system. For example, `yarn start` creates a watched development build, and `yarn dist` creates a production build. @@ -13,7 +13,17 @@ For local development, building without `lavamoat` is faster and therefore prefe The build system is not a full-featured CLI, but rather a script that expects some command line arguments and environment variables. For instructions regarding environment variables, see [the main repository readme](../../README.md#building-locally). -Here follows basic usage information for the build system. +Generally speaking, the build system consists of [`gulp`](https://npmjs.com/package/gulp) tasks that either manipulate static assets or bundle source files using [Browserify](https://browserify.org/). +Production-ready zip files are written to the `./builds` directory, while "unpacked" extension builds +are written to the `./dist` directory. + +Our JavaScript source files are transformed using [Babel](https://babeljs.io/), specifically using +the [`babelify`](https://npmjs.com/package/babelify) Browserify transform. +Source file bundling tasks are implemented in the [`./development/build/scripts.js`](https://github.com/MetaMask/metamask-extension/blob/develop/development/build/scripts.js). + +> Locally implemented Browserify transforms, _some of which affect how we write JavaScript_, are listed and documented [here](./transforms/README.md). + +## Usage ```text Usage: yarn build [options] @@ -30,15 +40,21 @@ Commands: e2e tests. Options: - --beta-version If the build type is "beta", the beta version number. + --beta-version If the build type is "beta", the beta version number. [number] [default: 0] - --build-type The "type" of build to create. One of: "beta", "main" + --build-type The "type" of build to create. One of: "beta", "main" [string] [default: "main"] - --omit-lockdown Whether to omit SES lockdown files from the extension - bundle. Useful when linking dependencies that are - incompatible with lockdown. + --lint-fence-files Whether files with code fences should be linted after + fences have been removed by the code fencing transform. + The build will fail if linting fails. + Defaults to `false` if the entry task is `dev` or + `testDev`, and `true` otherwise. + [boolean] [default: ] + --omit-lockdown Whether to omit SES lockdown files from the extension + bundle. Useful when linking dependencies that are + incompatible with lockdown. [boolean] [default: false] - --skip-stats Whether to refrain from logging build progress. Mostly used - internally. + --skip-stats Whether to refrain from logging build progress. Mostly + used internally. [boolean] [default: false] ``` diff --git a/development/build/index.js b/development/build/index.js index 1dbb95752..01c9b50da 100755 --- a/development/build/index.js +++ b/development/build/index.js @@ -40,6 +40,7 @@ function defineAndRunBuildTasks() { isBeta, isLavaMoat, shouldIncludeLockdown, + shouldLintFenceFiles, skipStats, } = parseArgv(); @@ -74,6 +75,7 @@ function defineAndRunBuildTasks() { buildType, isLavaMoat, livereload, + shouldLintFenceFiles, }); const { clean, reload, zip } = createEtcTasks({ @@ -146,16 +148,22 @@ function parseArgv() { const NamedArgs = { BetaVersion: 'beta-version', BuildType: 'build-type', + LintFenceFiles: 'lint-fence-files', OmitLockdown: 'omit-lockdown', SkipStats: 'skip-stats', }; const argv = minimist(process.argv.slice(2), { - boolean: [NamedArgs.OmitLockdown, NamedArgs.SkipStats], + boolean: [ + NamedArgs.LintFenceFiles, + NamedArgs.OmitLockdown, + NamedArgs.SkipStats, + ], string: [NamedArgs.BuildType], default: { [NamedArgs.BetaVersion]: 0, [NamedArgs.BuildType]: BuildTypes.main, + [NamedArgs.LintFenceFiles]: true, [NamedArgs.OmitLockdown]: false, [NamedArgs.SkipStats]: false, }, @@ -182,6 +190,13 @@ function parseArgv() { throw new Error(`MetaMask build: Invalid build type: "${buildType}"`); } + // Manually default this to `false` for dev builds only. + const shouldLintFenceFiles = process.argv.includes( + `--${NamedArgs.LintFenceFiles}`, + ) + ? argv[NamedArgs.LintFenceFiles] + : !/dev/iu.test(entryTask); + return { betaVersion: String(betaVersion), buildType, @@ -189,6 +204,7 @@ function parseArgv() { isBeta: argv[NamedArgs.BuildType] === BuildTypes.beta, isLavaMoat: process.argv[0].includes('lavamoat'), shouldIncludeLockdown: argv[NamedArgs.OmitLockdown], + shouldLintFenceFiles, skipStats: argv[NamedArgs.SkipStats], }; } diff --git a/development/build/scripts.js b/development/build/scripts.js index baef60c13..da7e73e26 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -55,6 +55,7 @@ function createScriptTasks({ buildType, isLavaMoat, livereload, + shouldLintFenceFiles, }) { // internal tasks const core = { @@ -97,6 +98,7 @@ function createScriptTasks({ return `./app/scripts/${label}.js`; }), testing, + shouldLintFenceFiles, }), ); @@ -151,6 +153,7 @@ function createScriptTasks({ runInChildProcess(subtask, { buildType, isLavaMoat, + shouldLintFenceFiles, }), ); // make a parent task that runs each task in a child thread @@ -166,6 +169,7 @@ function createScriptTasks({ devMode, entryFilepath: `./app/scripts/${label}.js`, label, + shouldLintFenceFiles, }); } @@ -178,6 +182,7 @@ function createScriptTasks({ devMode, entryFilepath: `./app/scripts/${label}.js`, label, + shouldLintFenceFiles, }); } @@ -190,6 +195,7 @@ function createScriptTasks({ devMode, entryFilepath: `./app/scripts/${label}.js`, label, + shouldLintFenceFiles, }); } @@ -206,6 +212,7 @@ function createScriptTasks({ entryFilepath: `./app/scripts/${inpage}.js`, label: inpage, testing, + shouldLintFenceFiles, }), createNormalBundle({ buildType, @@ -215,6 +222,7 @@ function createScriptTasks({ entryFilepath: `./app/scripts/${contentscript}.js`, label: contentscript, testing, + shouldLintFenceFiles, }), ); } @@ -226,6 +234,7 @@ function createFactoredBuild({ devMode, entryFiles, testing, + shouldLintFenceFiles, }) { return async function () { // create bundler setup and apply defaults @@ -244,6 +253,7 @@ function createFactoredBuild({ envVars, minify, reloadOnChange, + shouldLintFenceFiles, }); // set bundle entries @@ -344,6 +354,7 @@ function createNormalBundle({ extraEntries = [], label, modulesToExpose, + shouldLintFenceFiles, testing, }) { return async function () { @@ -363,6 +374,7 @@ function createNormalBundle({ envVars, minify, reloadOnChange, + shouldLintFenceFiles, }); // set bundle entries @@ -409,7 +421,7 @@ function createBuildConfiguration() { function setupBundlerDefaults( buildConfiguration, - { buildType, devMode, envVars, minify, reloadOnChange }, + { buildType, devMode, envVars, minify, reloadOnChange, shouldLintFenceFiles }, ) { const { bundlerOpts } = buildConfiguration; @@ -417,7 +429,7 @@ function setupBundlerDefaults( // Source transforms transform: [ // Remove code that should be excluded from builds of the current type - createRemoveFencedCodeTransform(buildType), + createRemoveFencedCodeTransform(buildType, shouldLintFenceFiles), // Transpile top-level code babelify, // Inline `fs.readFileSync` files diff --git a/development/build/task.js b/development/build/task.js index aa04ce4e9..07cd6d0c3 100644 --- a/development/build/task.js +++ b/development/build/task.js @@ -48,7 +48,10 @@ function createTask(taskName, taskFn) { return task; } -function runInChildProcess(task, { buildType, isLavaMoat }) { +function runInChildProcess( + task, + { buildType, isLavaMoat, shouldLintFenceFiles }, +) { const taskName = typeof task === 'string' ? task : task.taskName; if (!taskName) { throw new Error( @@ -63,7 +66,15 @@ function runInChildProcess(task, { buildType, isLavaMoat }) { if (isLavaMoat) { childProcess = spawn( 'yarn', - ['build', taskName, '--build-type', buildType, '--skip-stats'], + [ + 'build', + taskName, + '--build-type', + buildType, + '--lint-fence-files', + shouldLintFenceFiles, + '--skip-stats', + ], { env: process.env, }, @@ -71,7 +82,15 @@ function runInChildProcess(task, { buildType, isLavaMoat }) { } else { childProcess = spawn( 'yarn', - ['build:dev', taskName, '--build-type', buildType, '--skip-stats'], + [ + 'build:dev', + taskName, + '--build-type', + buildType, + '--lint-fence-files', + shouldLintFenceFiles, + '--skip-stats', + ], { env: process.env, }, diff --git a/development/build/transforms/README.md b/development/build/transforms/README.md index b2c40abc1..4cbba5e5b 100644 --- a/development/build/transforms/README.md +++ b/development/build/transforms/README.md @@ -52,6 +52,13 @@ commands inside the parameter parentheses: ///: BEGIN:ONLY_INCLUDE_IN(beta,flask) ``` +### Gotchas + +By default, the transform will invoke ESLint on files that are modified by the transform. +This is our first line of defense against creating unsyntactic code using code fences, and the transform will error if linting fails. +(Live reloading will continue to work if enabled.) +To toggle this behavior via build system arguments, see [the build system readme](../README.md). + ### Code Fencing Syntax > In the specification, angle brackets, `< >`, indicate required tokens, while diff --git a/development/build/transforms/remove-fenced-code.js b/development/build/transforms/remove-fenced-code.js index fa23ada82..378972381 100644 --- a/development/build/transforms/remove-fenced-code.js +++ b/development/build/transforms/remove-fenced-code.js @@ -1,6 +1,7 @@ const path = require('path'); const { PassThrough, Transform } = require('stream'); const { BuildTypes } = require('../utils'); +const { lintTransformedFile } = require('./utils'); const hasOwnProperty = (obj, key) => Reflect.hasOwnProperty.call(obj, key); @@ -14,13 +15,18 @@ class RemoveFencedCodeTransform extends Transform { * A transform stream that calls {@link removeFencedCode} on the complete * string contents of the file read by Browserify. * + * Optionally lints the file if it was modified. + * * @param {string} filePath - The path to the file being transformed. - * @param {string} buildType - The type of the current build process.env. + * @param {string} buildType - The type of the current build process. + * @param {boolean} shouldLintTransformedFiles - Whether the file should be + * linted if modified by the transform. */ - constructor(filePath, buildType) { + constructor(filePath, buildType, shouldLintTransformedFiles) { super(); this.filePath = filePath; this.buildType = buildType; + this.shouldLintTransformedFiles = shouldLintTransformedFiles; this._fileBuffers = []; } @@ -35,14 +41,24 @@ class RemoveFencedCodeTransform extends Transform { // stream, immediately before the "end" event is emitted. // It applies the transform to the concatenated file contents. _flush(end) { - const [fileContent] = removeFencedCode( + const [fileContent, didModify] = removeFencedCode( this.filePath, this.buildType, Buffer.concat(this._fileBuffers).toString('utf8'), ); - this.push(fileContent); - end(); + const pushAndEnd = () => { + this.push(fileContent); + end(); + }; + + if (this.shouldLintTransformedFiles && didModify) { + lintTransformedFile(fileContent, this.filePath) + .then(pushAndEnd) + .catch((error) => end(error)); + } else { + pushAndEnd(); + } } } @@ -57,10 +73,19 @@ class RemoveFencedCodeTransform extends Transform { * For details on how the transform mutates source files, see * {@link removeFencedCode} and the documentation. * + * If specified (and by default), the transform will call ESLint on the text + * contents of any file that it modifies. The transform will error if such a + * file is ignored by ESLint, since linting is our first line of defense against + * making un-syntactic modifications to files using code fences. + * * @param {string} buildType - The type of the current build. + * @param {boolean} shouldLintTransformedFiles - Whether to lint transformed files. * @returns {(filePath: string) => Transform} The transform function. */ -function createRemoveFencedCodeTransform(buildType) { +function createRemoveFencedCodeTransform( + buildType, + shouldLintTransformedFiles = true, +) { if (!hasOwnProperty(BuildTypes, buildType)) { throw new Error( `Code fencing transform received unrecognized build type "${buildType}".`, @@ -81,7 +106,11 @@ function createRemoveFencedCodeTransform(buildType) { return new PassThrough(); } - return new RemoveFencedCodeTransform(filePath, buildType); + return new RemoveFencedCodeTransform( + filePath, + buildType, + shouldLintTransformedFiles, + ); }; } @@ -156,7 +185,7 @@ const directiveParsingRegex = /^([A-Z]+):([A-Z_]+)(?:\(((?:\w+,)*\w+)\))?$/u; * For details, please see the documentation. * * @param {string} filePath - The path to the file being transformed. - * @param {string} typeOfCurrentBuild - The type of the current build process. + * @param {string} typeOfCurrentBuild - The type of the current build. * @param {string} fileContent - The contents of the file being transformed. * @returns {[string, modified]} A tuple of the post-transform file contents and * a boolean indicating whether they were modified. diff --git a/development/build/transforms/remove-fenced-code.test.js b/development/build/transforms/remove-fenced-code.test.js index 15ce94811..10ee99add 100644 --- a/development/build/transforms/remove-fenced-code.test.js +++ b/development/build/transforms/remove-fenced-code.test.js @@ -4,6 +4,11 @@ const { createRemoveFencedCodeTransform, removeFencedCode, } = require('./remove-fenced-code'); +const transformUtils = require('./utils'); + +jest.mock('./utils', () => ({ + lintTransformedFile: jest.fn(), +})); // The test data is just strings. We get it from a function at the end of this // file because it takes up a lot of lines and is very distracting. @@ -17,8 +22,13 @@ Conditionally_Included describe('build/transforms/remove-fenced-code', () => { describe('createRemoveFencedCodeTransform', () => { + const { lintTransformedFile: lintTransformedFileMock } = transformUtils; const mockJsFileName = 'file.js'; + beforeEach(() => { + lintTransformedFileMock.mockImplementation(() => Promise.resolve()); + }); + it('rejects invalid build types', () => { expect(() => createRemoveFencedCodeTransform('foobar')).toThrow( /received unrecognized build type "foobar".$/u, @@ -37,6 +47,7 @@ describe('build/transforms/remove-fenced-code', () => { stream.on('end', () => { expect(streamOutput).toStrictEqual(fileContent); + expect(lintTransformedFileMock).not.toHaveBeenCalled(); resolve(); }); @@ -59,6 +70,11 @@ describe('build/transforms/remove-fenced-code', () => { stream.on('end', () => { expect(streamOutput).toStrictEqual(filePrefix); + expect(lintTransformedFileMock).toHaveBeenCalledTimes(1); + expect(lintTransformedFileMock).toHaveBeenCalledWith( + filePrefix, + mockJsFileName, + ); resolve(); }); @@ -86,6 +102,11 @@ describe('build/transforms/remove-fenced-code', () => { stream.on('end', () => { expect(streamOutput).toStrictEqual(filePrefix); + expect(lintTransformedFileMock).toHaveBeenCalledTimes(1); + expect(lintTransformedFileMock).toHaveBeenCalledWith( + filePrefix, + mockJsFileName, + ); resolve(); }); @@ -107,6 +128,57 @@ describe('build/transforms/remove-fenced-code', () => { stream.on('end', () => { expect(streamOutput).toStrictEqual(fileContent); + expect(lintTransformedFileMock).not.toHaveBeenCalled(); + resolve(); + }); + + stream.end(fileContent); + }); + }); + + it('skips linting for transformed file if shouldLintTransformedFiles is false', async () => { + const filePrefix = '// A comment\n'; + const fileContent = filePrefix.concat(getMinimalFencedCode()); + + const stream = createRemoveFencedCodeTransform( + 'main', + false, + )(mockJsFileName); + let streamOutput = ''; + + await new Promise((resolve) => { + stream.on('data', (data) => { + streamOutput = streamOutput.concat(data.toString('utf8')); + }); + + stream.on('end', () => { + expect(streamOutput).toStrictEqual(filePrefix); + expect(lintTransformedFileMock).not.toHaveBeenCalled(); + resolve(); + }); + + stream.end(fileContent); + }); + }); + + it('handles transformed file lint failure', async () => { + lintTransformedFileMock.mockImplementationOnce(() => + Promise.reject(new Error('lint failure')), + ); + + const filePrefix = '// A comment\n'; + const fileContent = filePrefix.concat(getMinimalFencedCode()); + + const stream = createRemoveFencedCodeTransform('main')(mockJsFileName); + + await new Promise((resolve) => { + stream.on('error', (error) => { + expect(error).toStrictEqual(new Error('lint failure')); + expect(lintTransformedFileMock).toHaveBeenCalledTimes(1); + expect(lintTransformedFileMock).toHaveBeenCalledWith( + filePrefix, + mockJsFileName, + ); resolve(); }); diff --git a/development/build/transforms/utils.js b/development/build/transforms/utils.js new file mode 100644 index 000000000..c438d5351 --- /dev/null +++ b/development/build/transforms/utils.js @@ -0,0 +1,68 @@ +const { ESLint } = require('eslint'); +const eslintrc = require('../../../.eslintrc.js'); + +/** + * The singleton ESLint instance. + * + * @type {ESLint} + */ +let eslintInstance; + +// We only need a single ESLint instance, and we only initialize it if necessary +const initializeESLint = () => { + if (!eslintInstance) { + eslintInstance = new ESLint({ baseConfig: eslintrc, useEslintrc: false }); + } +}; + +// Four spaces +const TAB = ' '; + +module.exports = { + lintTransformedFile, +}; + +/** + * Lints a transformed file by invoking ESLint programmatically on the string + * file contents. The path to the file must be specified so that the repository + * ESLint config can be applied properly. + * + * An error is thrown if linting produced any errors, or if the file is ignored + * by ESLint. Files linted by this function should never be ignored. + * + * @param {string} content - The file content. + * @param {string} filePath - The path to the file. + * @returns {Promise} Returns `undefined` or throws an error if linting produced + * any errors, or if the linted file is ignored. + */ +async function lintTransformedFile(content, filePath) { + initializeESLint(); + + const lintResult = ( + await eslintInstance.lintText(content, { filePath, warnIgnored: false }) + )[0]; + + // This indicates that the file is ignored, which should never be the case for + // a transformed file. + if (lintResult === undefined) { + throw new Error( + `MetaMask build: Transformed file "${filePath}" appears to be ignored by ESLint.`, + ); + } + + // This is the success case + if (lintResult.errorCount === 0) { + return; + } + + // Errors are stored in the messages array, and their "severity" is 2 + const errorsString = lintResult.messages + .filter(({ severity }) => severity === 2) + .reduce((allErrors, { message, ruleId }) => { + return allErrors.concat(`${TAB}${ruleId}\n${TAB}${message}\n\n`); + }, ''); + + throw new Error( + `MetaMask build: Lint errors encountered for transformed file "${filePath}":\n\n${errorsString}`, + ); +} diff --git a/development/build/transforms/utils.test.js b/development/build/transforms/utils.test.js new file mode 100644 index 000000000..ba273a15b --- /dev/null +++ b/development/build/transforms/utils.test.js @@ -0,0 +1,71 @@ +const { lintTransformedFile } = require('./utils'); + +let mockESLint; + +jest.mock('eslint', () => ({ + ESLint: class MockESLint { + constructor() { + if (mockESLint) { + throw new Error('Mock ESLint ref already assigned!'); + } + + // eslint-disable-next-line consistent-this + mockESLint = this; + + // eslint-disable-next-line jest/prefer-spy-on + this.lintText = jest.fn(); + } + }, +})); + +describe('transform utils', () => { + describe('lintTransformedFile', () => { + it('initializes the ESLint singleton', async () => { + expect(mockESLint).not.toBeDefined(); + + // This error is an artifact of how we're mocking the ESLint singleton, + // and won't actually occur in production. + await expect(() => lintTransformedFile()).rejects.toThrow( + `Cannot read property '0' of undefined`, + ); + expect(mockESLint).toBeDefined(); + }); + + it('returns if linting passes with no errors', async () => { + mockESLint.lintText.mockImplementationOnce(() => + Promise.resolve([{ errorCount: 0 }]), + ); + + expect( + await lintTransformedFile('/* JavaScript */', 'file.js'), + ).toBeUndefined(); + }); + + it('throws if the file is ignored by ESLint', async () => { + mockESLint.lintText.mockImplementationOnce(() => Promise.resolve([])); + + await expect(() => + lintTransformedFile('/* JavaScript */', 'file.js'), + ).rejects.toThrow( + /Transformed file "file\.js" appears to be ignored by ESLint\.$/u, + ); + }); + + it('throws if linting produced any errors', async () => { + const ruleId = 'some-eslint-rule'; + const message = 'You violated the rule!'; + + mockESLint.lintText.mockImplementationOnce(() => + Promise.resolve([ + { errorCount: 1, messages: [{ message, ruleId, severity: 2 }] }, + ]), + ); + + await expect(() => + lintTransformedFile('/* JavaScript */', 'file.js'), + ).rejects.toThrow( + /Lint errors encountered for transformed file "file\.js":\n\n {4}some-eslint-rule\n {4}You violated the rule!\n\n$/u, + ); + }); + }); +}); diff --git a/lavamoat/node/policy.json b/lavamoat/node/policy.json index d9eef720a..0cdbcf152 100644 --- a/lavamoat/node/policy.json +++ b/lavamoat/node/policy.json @@ -785,6 +785,42 @@ "console.log": true } }, + "@eslint/eslintrc": { + "builtin": { + "assert": true, + "fs.existsSync": true, + "fs.readFileSync": true, + "module.createRequire": true, + "module.createRequireFromPath": true, + "os.homedir": true, + "path.basename": true, + "path.dirname": true, + "path.extname": true, + "path.isAbsolute": true, + "path.join": true, + "path.relative": true, + "path.resolve": true, + "path.sep": true, + "util.inspect": true + }, + "globals": { + "__dirname": true, + "process.cwd": true, + "process.emitWarning": true, + "process.platform": true + }, + "packages": { + "ajv": true, + "debug": true, + "espree": true, + "globals": true, + "ignore": true, + "import-fresh": true, + "js-yaml": true, + "minimatch": true, + "strip-json-comments": true + } + }, "@gulp-sourcemaps/identity-map": { "packages": { "acorn": true, @@ -897,6 +933,11 @@ "acorn": true } }, + "acorn-jsx": { + "packages": { + "acorn": true + } + }, "acorn-node": { "packages": { "acorn": true, @@ -911,8 +952,14 @@ } }, "ajv": { + "globals": { + "console": true + }, "packages": { - "fast-deep-equal": true + "fast-deep-equal": true, + "fast-json-stable-stringify": true, + "json-schema-traverse": true, + "uri-js": true } }, "amdefine": { @@ -970,6 +1017,16 @@ "buffer-equal": true } }, + "are-we-there-yet": { + "builtin": { + "events.EventEmitter": true, + "util.inherits": true + }, + "packages": { + "delegates": true, + "readable-stream": true + } + }, "arr-diff": { "packages": { "arr-flatten": true, @@ -1329,6 +1386,7 @@ "anymatch": true, "async-each": true, "braces": true, + "fsevents": true, "glob-parent": true, "inherits": true, "is-binary-path": true, @@ -1580,6 +1638,16 @@ "through2": true } }, + "detect-libc": { + "builtin": { + "child_process.spawnSync": true, + "fs.readdirSync": true, + "os.platform": true + }, + "globals": { + "process.env": true + } + }, "detective": { "packages": { "acorn-node": true, @@ -1600,6 +1668,14 @@ "path-type": true } }, + "doctrine": { + "builtin": { + "assert": true + }, + "packages": { + "esutils": true + } + }, "dom-serializer": { "packages": { "domelementtype": true, @@ -1729,6 +1805,92 @@ "source-map": true } }, + "eslint": { + "builtin": { + "assert": true, + "fs.existsSync": true, + "fs.lstatSync": true, + "fs.readFileSync": true, + "fs.readdirSync": true, + "fs.statSync": true, + "fs.unlinkSync": true, + "fs.writeFile": true, + "fs.writeFileSync": true, + "path.extname": true, + "path.isAbsolute": true, + "path.join": true, + "path.normalize": true, + "path.relative": true, + "path.resolve": true, + "path.sep": true, + "util.format": true, + "util.inspect": true, + "util.promisify": true + }, + "globals": { + "__dirname": true, + "console.log": true, + "describe": true, + "it": true, + "process": true + }, + "packages": { + "@eslint/eslintrc": true, + "ajv": true, + "debug": true, + "doctrine": true, + "eslint-scope": true, + "eslint-utils": true, + "eslint-visitor-keys": true, + "espree": true, + "esquery": true, + "esutils": true, + "file-entry-cache": true, + "functional-red-black-tree": true, + "glob-parent": true, + "globals": true, + "ignore": true, + "imurmurhash": true, + "is-glob": true, + "json-stable-stringify-without-jsonify": true, + "levn": true, + "lodash": true, + "minimatch": true, + "natural-compare": true, + "regexpp": true + } + }, + "eslint-scope": { + "builtin": { + "assert": true + }, + "packages": { + "esrecurse": true, + "estraverse": true + } + }, + "eslint-utils": { + "packages": { + "eslint-visitor-keys": true + } + }, + "espree": { + "packages": { + "acorn": true, + "acorn-jsx": true, + "eslint-visitor-keys": true + } + }, + "esquery": { + "globals": { + "define": true + } + }, + "esrecurse": { + "packages": { + "estraverse": true + } + }, "event-emitter": { "packages": { "d": true, @@ -1911,7 +2073,9 @@ "flat-cache": { "builtin": { "fs.existsSync": true, + "fs.mkdirSync": true, "fs.readFileSync": true, + "fs.writeFileSync": true, "path.basename": true, "path.dirname": true, "path.resolve": true @@ -2020,6 +2184,45 @@ "process.version": true } }, + "fsevents": { + "builtin": { + "events.EventEmitter": true, + "fs.stat": true, + "path.join": true, + "util.inherits": true + }, + "globals": { + "__dirname": true, + "process.nextTick": true, + "process.platform": true, + "setImmediate": true + }, + "native": true, + "packages": { + "node-pre-gyp": true + } + }, + "gauge": { + "builtin": { + "util.format": true + }, + "globals": { + "clearInterval": true, + "process": true, + "setImmediate": true, + "setInterval": true + }, + "packages": { + "aproba": true, + "console-control-strings": true, + "has-unicode": true, + "object-assign": true, + "signal-exit": true, + "string-width": true, + "strip-ansi": true, + "wide-align": true + } + }, "get-assigned-identifiers": { "builtin": { "assert.equal": true @@ -2380,6 +2583,16 @@ "process.argv": true } }, + "has-unicode": { + "builtin": { + "os.type": true + }, + "globals": { + "process.env.LANG": true, + "process.env.LC_ALL": true, + "process.env.LC_CTYPE": true + } + }, "has-value": { "packages": { "get-value": true, @@ -2533,6 +2746,11 @@ "is-plain-object": true } }, + "is-fullwidth-code-point": { + "packages": { + "number-is-nan": true + } + }, "is-glob": { "packages": { "is-extglob": true @@ -2611,6 +2829,11 @@ "isarray": true } }, + "js-yaml": { + "globals": { + "esprima": true + } + }, "jsesc": { "globals": { "Buffer.isBuffer": true @@ -2688,6 +2911,12 @@ "flush-write-stream": true } }, + "levn": { + "packages": { + "prelude-ls": true, + "type-check": true + } + }, "lodash": { "globals": { "define": true @@ -2917,6 +3146,56 @@ "setTimeout": true } }, + "node-pre-gyp": { + "builtin": { + "events.EventEmitter": true, + "fs.existsSync": true, + "fs.readFileSync": true, + "fs.renameSync": true, + "path.dirname": true, + "path.existsSync": true, + "path.join": true, + "path.resolve": true, + "url.parse": true, + "url.resolve": true, + "util.inherits": true + }, + "globals": { + "__dirname": true, + "console.log": true, + "process.arch": true, + "process.cwd": true, + "process.env": true, + "process.platform": true, + "process.version.substr": true, + "process.versions": true + }, + "packages": { + "detect-libc": true, + "nopt": true, + "npmlog": true, + "rimraf": true, + "semver": true + } + }, + "nopt": { + "builtin": { + "path": true, + "stream.Stream": true, + "url": true + }, + "globals": { + "console": true, + "process.argv": true, + "process.env.DEBUG_NOPT": true, + "process.env.NOPT_DEBUG": true, + "process.platform": true + }, + "packages": { + "abbrev": true, + "osenv": true + } + }, "normalize-path": { "packages": { "remove-trailing-separator": true @@ -2932,6 +3211,22 @@ "once": true } }, + "npmlog": { + "builtin": { + "events.EventEmitter": true, + "util": true + }, + "globals": { + "process.nextTick": true, + "process.stderr": true + }, + "packages": { + "are-we-there-yet": true, + "console-control-strings": true, + "gauge": true, + "set-blocking": true + } + }, "object-copy": { "packages": { "copy-descriptor": true, @@ -2998,6 +3293,54 @@ "readable-stream": true } }, + "os-homedir": { + "builtin": { + "os.homedir": true + }, + "globals": { + "process.env": true, + "process.getuid": true, + "process.platform": true + } + }, + "os-tmpdir": { + "globals": { + "process.env.SystemRoot": true, + "process.env.TEMP": true, + "process.env.TMP": true, + "process.env.TMPDIR": true, + "process.env.windir": true, + "process.platform": true + } + }, + "osenv": { + "builtin": { + "child_process.exec": true, + "path": true + }, + "globals": { + "process.env.COMPUTERNAME": true, + "process.env.ComSpec": true, + "process.env.EDITOR": true, + "process.env.HOSTNAME": true, + "process.env.PATH": true, + "process.env.PROMPT": true, + "process.env.PS1": true, + "process.env.Path": true, + "process.env.SHELL": true, + "process.env.USER": true, + "process.env.USERDOMAIN": true, + "process.env.USERNAME": true, + "process.env.VISUAL": true, + "process.env.path": true, + "process.nextTick": true, + "process.platform": true + }, + "packages": { + "os-homedir": true, + "os-tmpdir": true + } + }, "parent-module": { "packages": { "callsites": true @@ -3580,6 +3923,12 @@ "process": true } }, + "set-blocking": { + "globals": { + "process.stderr": true, + "process.stdout": true + } + }, "set-value": { "packages": { "extend-shallow": true, @@ -3782,6 +4131,7 @@ }, "string-width": { "packages": { + "code-point-at": true, "emoji-regex": true, "is-fullwidth-code-point": true, "strip-ansi": true @@ -4040,6 +4390,11 @@ "through2": true } }, + "type-check": { + "packages": { + "prelude-ls": true + } + }, "typedarray-to-buffer": { "globals": { "Buffer.from": true @@ -4145,6 +4500,11 @@ "path": true } }, + "uri-js": { + "globals": { + "define": true + } + }, "urix": { "builtin": { "path.sep": true @@ -4348,6 +4708,11 @@ "isexe": true } }, + "wide-align": { + "packages": { + "string-width": true + } + }, "write": { "builtin": { "fs.createWriteStream": true,