diff --git a/.changeset/lucky-cheetahs-relate.md b/.changeset/lucky-cheetahs-relate.md new file mode 100644 index 0000000..604d276 --- /dev/null +++ b/.changeset/lucky-cheetahs-relate.md @@ -0,0 +1,8 @@ +--- +"trace-deps": minor +"trace-pkg": minor +--- + +- Feature (`trace-pkg`): Add `conditions` option. +- Feature (`trace-deps`): Add `conditions` parameter to `traceFile`/`traceFiles` to support user runtime loading conditions. (See [#trace-deps/56](https://github.com/FormidableLabs/trace-deps/issues/56)) +- BREAKING: Remove `production` and `development` from set of default user conditions in package resolution. diff --git a/packages/trace-deps/README.md b/packages/trace-deps/README.md index 166b723..13f0fe7 100644 --- a/packages/trace-deps/README.md +++ b/packages/trace-deps/README.md @@ -26,6 +26,7 @@ _Parameters_: * `srcPath` (`string`): source file path to trace * `ignores` (`Array`): list of package prefixes to ignore tracing entirely +* `conditions` (`Array`): list of Node.js runtime import [user conditions](https://nodejs.org/api/packages.html#resolving-user-conditions) to trace in addition to our default built-in Node.js conditions of `import`, `require`, `node`, and `default`. * `allowMissing` (`Object.`): Mapping of (1) absolute source file paths and (2) package name or relative file path keys to permitted missing module prefixes values. * Source file keys must match the entire file path (e.g., `/FULL/PATH/TO/entry.js`) while package keys are the start of package name either alone or with the rest of the relative path to ultimate file (e.g., `lodash`, `@scope/pkg` or `@scope/pkg/some/path/to/file.js`). * Missing module prefix values may be the package name or any part of the relative path thereafter (e.g., `pkg`, `pkg/some`, `pkg/some/path/to/file.js`) @@ -69,6 +70,7 @@ _Parameters_: * `srcPaths` (`Array`): source file paths to trace * `ignores` (`Array`): list of package prefixes to ignore +* `conditions` (`Array`): list of Node.js runtime import user conditions to trace. * `allowMissing` (`Object.`): Mapping of source file paths and package names/paths to permitted missing module prefixes. * `bailOnMissing` (`boolean`): Throw error if missing static import. * `includeSourceMaps` (`boolean`): Include source map file paths from control comments @@ -111,7 +113,7 @@ Examples: * **Modern Node.js ESM / `package.json:exports` Support**: Node.js v12 and newer now support modern ESM, and `trace-deps` will correctly package your application in any Node.js runtime. Unfortunately, the implementation of how to [resolve an ESM import](https://nodejs.org/api/packages.html) in modern Node.js is quite complex. * **It's complicated**: For example, for the same import of `my-pkg`, a `require("my-pkg")` call in Node.js v10 might match a file specified in `package.json:main`, while `require("my-pkg")` in Node.js v12 might match a second file specified in `package.json:exports:".":require`, and `import "my-pkg"` in Node,js v12 might match a _third_ file specified in `package.json:exports:".":import`. Then, throw in [conditions](https://nodejs.org/api/packages.html#packages_conditional_exports), [subpaths](https://nodejs.org/api/packages.html#packages_subpath_exports), and even subpath conditions, and it becomes exceedingly difficult to statically analyze what is actually going to be imported at runtime by Node.js ahead of time, which is what `trace-deps` needs to do. 🤯 * **Our solution**: Our approach is to basically give up on trying to figure out the exact runtime conditions that will be used in module resolution, and instead package all reasonable conditions for a given module import. This means that maintain correctness at the cost of slightly larger zip sizes for libraries that ship multiple versions of exports. - * **Our implementation**: When `trace-deps` encounters a dependency, it resolves the file according to old CommonJS (reading `package.json:main`) and then in modern Node.js `package.json:exports` mode with each of the following built-in / suggested official conditions: `import`, `require`, `node`, `default`, `development`, and `production`. (We ignore `browser`). + * **Our implementation / conditions**: When `trace-deps` encounters a dependency, it resolves the file according to old CommonJS (reading `package.json:main`) and then in modern Node.js `package.json:exports` mode with each of the following built-in official conditions: `import`, `require`, `node`, `default`. We do not include any of the suggested user conditions (e.g., `production`, `development`, `browser`) by default. You can add additional user conditions using the `conditions` parameter. * **Missing Features**: `trace-deps` does not support the deprecated [subpath folder mappings](https://nodejs.org/api/packages.html#packages_subpath_folder_mappings) feature. Some advanced ESM features are still under development. * **Includes `package.json` files used in resolution**: As this is a Node.js-focused library, to follow the Node.js [module resolution algorithm](https://nodejs.org/api/modules.html#modules_all_together) which notably uses intermediate encountered `package.json` files to determine how to resolve modules. This means that we include a lot of `package.json` files that seemingly aren't directly imported (such as a `const pkg = require("mod/package.json")`) because they are needed for the list of all traced files to together resolve correctly if all on disk together. diff --git a/packages/trace-deps/lib/trace.js b/packages/trace-deps/lib/trace.js index 5b7dc91..cae5d22 100644 --- a/packages/trace-deps/lib/trace.js +++ b/packages/trace-deps/lib/trace.js @@ -30,21 +30,33 @@ const RESOLVE_EXTS = [".js", ".json"]; // https://github.com/FormidableLabs/trace-deps/issues/56 const CONDITIONS = [ // Node.js conditions + // // https://nodejs.org/api/packages.html#packages_conditional_exports + // > Node.js implements the following conditions, listed in order from most specific to + // > least specific as conditions should be defined: + // > - "node-addons" + // > - "node" + // > - "import" + // > - "require + // > - "default" + // NOTE: We don't include `node-addons` but could... "import", "require", "node", // Try `default` in both CJS + ESM modes. ["default", { require: true }], - ["default", { require: false }], + ["default", { require: false }] // Endorsed user conditions. - // https://nodejs.org/api/packages.html#packages_conditions_definitions // - // Note: We are ignoring - // - `browser` - "development", - "production" + // https://nodejs.org/api/packages.html#community-conditions-definitions + // > - "types" + // > - "deno" + // > - "browser" + // > - "development" + // > - "production" + // + // We do not default include any of these, but can be added with user-specified conditions. ]; // Exports that disable modern ESM. @@ -173,6 +185,7 @@ const _recurseDeps = async ({ srcPaths, depPaths = [], ignores = [], + conditions = [], allowMissing = {}, bailOnMissing = true, includeSourceMaps = false, @@ -203,6 +216,7 @@ const _recurseDeps = async ({ const traced = await traceFile({ srcPath: depPath, ignores, + conditions, allowMissing, bailOnMissing, includeSourceMaps, @@ -229,6 +243,7 @@ const _resolveDep = async ({ depName, basedir, srcPath, + conditions, dependencies, extraDepKeys, addMisses, @@ -339,13 +354,14 @@ const _resolveDep = async ({ } }); - CONDITIONS.forEach((cond) => { + CONDITIONS.concat(conditions).forEach((cond) => { let resolveOpts; if (Array.isArray(cond)) { resolveOpts = cond[1]; cond = cond[0]; } + let relPath; try { relPath = resolveExports.resolve(pkg, depName, { @@ -431,6 +447,7 @@ const _resolveDep = async ({ depName, basedir, srcPath, + conditions, dependencies, extraDepKeys, addMisses, @@ -487,6 +504,7 @@ const _resolveDep = async ({ * @param {*} opts options object * @param {string} opts.srcPath source file path to trace * @param {Array} opts.ignores list of package prefixes to ignore + * @param {Array} opts.conditions list of additional user conditions to trace * @param {Object} opts.allowMissing map packages to list of allowed missing package * @param {boolean} opts.bailOnMissing allow static dependencies to be missing * @param {boolean} opts.includeSourceMaps include source map paths in output @@ -499,6 +517,7 @@ const _resolveDep = async ({ const traceFile = async ({ srcPath, ignores = [], + conditions = [], allowMissing = {}, bailOnMissing = true, includeSourceMaps = false, @@ -594,6 +613,7 @@ const traceFile = async ({ depName, basedir, srcPath, + conditions, dependencies, extraDepKeys, addMisses, @@ -628,6 +648,7 @@ const traceFile = async ({ const recursed = await _recurseDeps({ depPaths, ignores, + conditions, allowMissing, bailOnMissing, includeSourceMaps, @@ -658,6 +679,7 @@ const traceFile = async ({ * @param {*} opts options object * @param {Array} opts.srcPaths source file paths to trace * @param {Array} opts.ignores list of package prefixes to ignore + * @param {Array} opts.conditions list of additional user conditions to trace * @param {Object} opts.allowMissing map packages to list of allowed missing package * @param {boolean} opts.bailOnMissing allow static dependencies to be missing * @param {boolean} opts.includeSourceMaps include source map paths in output @@ -669,6 +691,7 @@ const traceFile = async ({ const traceFiles = async ({ srcPaths, ignores = [], + conditions = [], allowMissing = {}, bailOnMissing = true, includeSourceMaps = false, @@ -682,6 +705,7 @@ const traceFiles = async ({ const results = await _recurseDeps({ srcPaths, ignores, + conditions, allowMissing, bailOnMissing, includeSourceMaps, diff --git a/packages/trace-deps/test/lib/trace.spec.js b/packages/trace-deps/test/lib/trace.spec.js index e0439a6..a7f7137 100644 --- a/packages/trace-deps/test/lib/trace.spec.js +++ b/packages/trace-deps/test/lib/trace.spec.js @@ -2098,12 +2098,10 @@ describe("lib/trace", () => { expect(dependencies).to.eql(fullPaths([ "node_modules/complicated/default.js", - "node_modules/complicated/development.js", "node_modules/complicated/import.mjs", "node_modules/complicated/local/two.mjs", "node_modules/complicated/main.js", "node_modules/complicated/package.json", - "node_modules/complicated/production.mjs", "node_modules/subdep/default.js", "node_modules/subdep/from-default.js", "node_modules/subdep/from-main.js", @@ -2149,11 +2147,9 @@ describe("lib/trace", () => { expect(dependencies).to.eql(fullPaths([ "node_modules/complicated/default.js", - "node_modules/complicated/development.js", "node_modules/complicated/local/one.js", "node_modules/complicated/main.js", "node_modules/complicated/package.json", - "node_modules/complicated/production.mjs", "node_modules/complicated/require.js", // Note: All of the import paths are to sub-paths, and **not** // the root package, so no defaults in play. @@ -2286,12 +2282,10 @@ describe("lib/trace", () => { const { dependencies, misses } = await traceFile({ srcPath }); expect(dependencies).to.eql(fullPaths([ - "node_modules/complicated/development.js", "node_modules/complicated/import.mjs", "node_modules/complicated/local/one.js", "node_modules/complicated/local/two.mjs", "node_modules/complicated/package.json", - "node_modules/complicated/production.mjs", "node_modules/complicated/require.js", "node_modules/subdep/default.js", "node_modules/subdep/from-require.js", @@ -2601,6 +2595,136 @@ describe("lib/trace", () => { expect(misses).to.eql({}); }); }); + + describe("user conditions", () => { + it("handles user conditions", async () => { + mock({ + "first.js": ` + const one = require("one"); + const dynamicTwo = () => import("two"); + const second = require("./second"); + `, + "second.js": ` + const one = require.resolve("one"); + + (async () => { + await import("three"); + })(); + `, + node_modules: { + one: { + "package.json": stringify({ + name: "one", + main: "index.js", + exports: { + ".": { + bespoke: "./bespoke.js", + production: "./production.js", + "default": "./default.js" + } + } + }), + "index.js": "module.exports = 'one';", + "default.js": "module.exports = 'default';", + "production.js": "module.exports = 'production';", + "bespoke.js": "module.exports = 'bespoke';" + }, + two: { + "package.json": stringify({ + name: "two", + main: "index.js", + exports: { + ".": { + bespoke: "./bespoke.js", + production: "./production.js", + require: "./require.js" + } + } + }), + "index.js": "module.exports = 'two';", + "require.js": "module.exports = 'require';", + "production.js": "module.exports = 'production';", + "bespoke.js": "module.exports = 'bespoke';" + }, + three: { + "package.json": stringify({ + name: "three", + main: "index.mjs", + type: "module", + exports: { + ".": { + // NOTE: If first, `import` will override `production` with a `production` + // condition in real Node.js runtime, so production _isn't_ matched here! + "import": "./import.mjs", + production: "./production.mjs" + } + } + }), + "index.mjs": ` + import three from "nested-three"; + export default three; + `, + "import.mjs": ` + import three from "nested-three"; + export default three; + `, + node_modules: { + "nested-three": { + "package.json": stringify({ + name: "nested-three", + main: "index.mjs", + type: "module", + exports: { + ".": { + bespoke: "./bespoke.mjs", + // NOTE: If a custom condition comes _before_ `import`, then it is actually + // matched in real Node.js runtime. + production: "./production.mjs", + "import": "./import.mjs" + } + } + }), + "index.mjs": "export const three = 'nested-three';", + "import.mjs": "export const msg = 'nested-import';", + "production.mjs": "export const msg = 'nested-production';", + "bespoke.mjs": "export const msg = 'nested-bespoke';" + } + } + } + } + }); + + const { dependencies, misses } = await traceFile({ + srcPath: "first.js", + conditions: [ + "bespoke", + "production" + ] + }); + expect(dependencies).to.eql(fullPaths([ + "node_modules/one/bespoke.js", + "node_modules/one/default.js", + "node_modules/one/index.js", + "node_modules/one/package.json", + "node_modules/one/production.js", + "node_modules/three/import.mjs", + "node_modules/three/index.mjs", + "node_modules/three/node_modules/nested-three/bespoke.mjs", + "node_modules/three/node_modules/nested-three/import.mjs", + "node_modules/three/node_modules/nested-three/index.mjs", + "node_modules/three/node_modules/nested-three/package.json", + "node_modules/three/node_modules/nested-three/production.mjs", + "node_modules/three/package.json", + "node_modules/two/bespoke.js", + "node_modules/two/index.js", + "node_modules/two/package.json", + "node_modules/two/production.js", + "node_modules/two/require.js", + "second.js" + ])); + expect(misses).to.eql({}); + }); + }); }); describe("traceFiles", () => { diff --git a/packages/trace-pkg/README.md b/packages/trace-pkg/README.md index ac33f9e..0c84507 100644 --- a/packages/trace-pkg/README.md +++ b/packages/trace-pkg/README.md @@ -70,6 +70,7 @@ Configuration options are generally global (`options.`) and/or per- - Can be overridden from CLI with `--concurrency ` - `options.includeSourceMaps` (`Boolean`): Include source map paths from files that are found during tracing (not inclusion via `include`) and present on-disk. Source map paths inferred but not found are ignored. (default: `false`). Please see [discussion below](#including-source-maps) to evaluate whether or not you should use this feature. - `options.ignores` (`Array`): A set of package path prefixes up to a directory level (e.g., `react` or `mod/lib`) to skip tracing on. This is particularly useful when you are excluding a package like `aws-sdk` that is already provided for your lambda. +- `options.conditions` (`Array`): list of Node.js runtime import [user conditions](https://nodejs.org/api/packages.html#resolving-user-conditions) to trace in addition to our default built-in Node.js conditions of `import`, `require`, `node`, and `default`. - `options.allowMissing` (`Object.>`): A way to allow certain packages to have potentially failing dependencies. Specify each object key as either (1) an source file path relative to `cwd` that begins with a `./` or (2) a package name and provide a value as an array of dependencies that _might_ be missing on disk. If the sub-dependency is found, then it is included in the bundle (this part distinguishes this option from `ignores`). If not, it is skipped without error. - `options.dynamic.resolutions` (`Object.>`): Handle dynamic import misses by providing a key to match misses on and an array of additional glob patterns to trace and include in the application bundle. - _Application source files_: If a miss is an application source file (e.g., not within `node_modules`), specify the **relative path** (from the package-level `cwd`) to it like `"./src/server/router.js": [/* array of patterns */]`. @@ -88,6 +89,7 @@ Configuration options are generally global (`options.`) and/or per- - `packages..trace` (`Array`): A list of [fast-glob][] glob patterns to match JS files that will be further traced to infer all imported dependencies via static analysis. Use this option to include your source code files that comprises your application. - `packages..includeSourceMaps` (`Boolean`): Additional configuration to override value of `options.includeSourceMaps`. - `packages..ignores` (`Array`): Additional configuration to merge with `options.ignores`. +- `packages..conditions` (`Array`): Additional configuration to merge with `options.conditions`. - `packages..allowMissing` (`Object.>`): Additional configuration to merge with `options.allowMissing`. Note that for source file paths, all of the paths are resolved to `cwd`, so if you provide both a global and package-level `cwd` the relative paths probably won't resolve as you would expect them to. - `packages..dynamic.resolutions` (`Object.>`): Additional configuration to merge with `options.dynamic.resolutions`. - `packages..dynamic.bail` (`Boolean`): Override `options.dynamic.bail` value. diff --git a/packages/trace-pkg/lib/config.js b/packages/trace-pkg/lib/config.js index 8eccbe4..34fb19d 100644 --- a/packages/trace-pkg/lib/config.js +++ b/packages/trace-pkg/lib/config.js @@ -72,6 +72,7 @@ const parseConfig = async ({ traceOptions: { includeSourceMaps: getBoolean(options.includeSourceMaps, pkgCfg.includeSourceMaps), ignores: smartConcat(options.ignores, pkgCfg.ignores), + conditions: smartConcat(options.conditions, pkgCfg.conditions), allowMissing: normalizeFileKeys({ cwd, map: smartMerge(options.allowMissing, pkgCfg.allowMissing) diff --git a/packages/trace-pkg/lib/worker/bundle.js b/packages/trace-pkg/lib/worker/bundle.js index 3809ee0..6d69b13 100644 --- a/packages/trace-pkg/lib/worker/bundle.js +++ b/packages/trace-pkg/lib/worker/bundle.js @@ -138,6 +138,7 @@ const bundle = async ({ traceOptions: { includeSourceMaps = false, ignores = [], + conditions = [], allowMissing = {}, dynamic: { resolutions = {} } } = {}, @@ -149,6 +150,7 @@ const bundle = async ({ srcPaths: tracePaths, includeSourceMaps, ignores, + conditions, allowMissing, extraImports: resolutions, bailOnMissing: true // TODO: Consider/ticket adding to misses and displaying together. diff --git a/packages/trace-pkg/test/lib/actions/package.spec.js b/packages/trace-pkg/test/lib/actions/package.spec.js index e4b281a..03b2efd 100644 --- a/packages/trace-pkg/test/lib/actions/package.spec.js +++ b/packages/trace-pkg/test/lib/actions/package.spec.js @@ -26,1175 +26,1209 @@ describe("lib/actions/package", () => { mock.restore(); }); - it("fails if any package contains zero matched files", async () => { - mock({ - src: { - "one.js": "module.exports = \"one\";", - "two.json": "{ \"msg\": \"two\" }" - } - }); - - await expect(createPackage({ - opts: { - config: { - packages: { - one: { - trace: [ - "src/one.js" - ] - }, - two: { - include: [ - "src/two.*" - ] - }, - "no-files": { - trace: [ - "no-match" - ], - include: [ - "also-no-match" - ] - } - } + describe("basic", () => { + it("fails if any package contains zero matched files", async () => { + mock({ + src: { + "one.js": "module.exports = \"one\";", + "two.json": "{ \"msg\": \"two\" }" } - } - })).to.eventually.be.rejectedWith( - "Did not find any matching files for bundle: \"no-files\"" - ); - }); - - it("packages across multiple different working directories", async () => { - mock({ - src: { - "one.js": "module.exports = require('./one/dep');", - one: { - "dep.js": "module.exports = require('./deeper/dep');", - deeper: { - "dep.js": "module.exports = \"deeper-dep\";" + }); + + await expect(createPackage({ + opts: { + config: { + packages: { + one: { + trace: [ + "src/one.js" + ] + }, + two: { + include: [ + "src/two.*" + ] + }, + "no-files": { + trace: [ + "no-match" + ], + include: [ + "also-no-match" + ] + } + } } - }, - "two.json": "{ \"msg\": \"two\" }", - "two.css": "body.two { background-color: pink; }", - three: { - "index.js": "module.exports = \"three\";" } - } + })).to.eventually.be.rejectedWith( + "Did not find any matching files for bundle: \"no-files\"" + ); }); - await createPackage({ - opts: { - config: { - options: { - cwd: "src" - }, - packages: { - "../../.build/one": { - cwd: "src/one", - trace: [ - "../one.js" - ] - }, - two: { - output: "../.build/two.zip", - include: [ - "two.*" - ] - }, - three: { - cwd: "src/three", - output: "../../.build/three.zip", - trace: [ - "index.js" - ], - include: [ - "index.js" // Double include (should only have one file in zip) - ] + it("packages across multiple different working directories", async () => { + mock({ + src: { + "one.js": "module.exports = require('./one/dep');", + one: { + "dep.js": "module.exports = require('./deeper/dep');", + deeper: { + "dep.js": "module.exports = \"deeper-dep\";" } + }, + "two.json": "{ \"msg\": \"two\" }", + "two.css": "body.two { background-color: pink; }", + three: { + "index.js": "module.exports = \"three\";" } } - } - }); + }); - expect(logStub).to.have.been.calledWithMatch("Created 3 packages:"); - - expect(await globby(".build/*.zip")).to.eql([ - ".build/one.zip", - ".build/three.zip", - ".build/two.zip" - ]); - expect(zipContents(".build/one.zip")).to.eql([ - "one.js", - "deeper/dep.js", - "dep.js" - ]); - expect(zipContents(".build/two.zip")).to.eql([ - "two.css", - "two.json" - ]); - expect(zipContents(".build/three.zip")).to.eql([ - "index.js" - ]); - }); - - it("outputs zip files to multiple different locations", async () => { - mock({ - src: { - "one.js": "module.exports = require('./one/dep');", - one: { - "dep.js": "module.exports = require('./deeper/dep');", - deeper: { - "dep.js": "module.exports = \"deeper-dep\";" - } - }, - "two.json": "{ \"msg\": \"two\" }", - "two.css": "body.two { background-color: pink; }" - } - }); - - await createPackage({ - opts: { - config: { - packages: { - "one.zip": { - trace: [ - "src/one.js" - ] + await createPackage({ + opts: { + config: { + options: { + cwd: "src" }, - two: { - include: [ - "src/two.*" - ] + packages: { + "../../.build/one": { + cwd: "src/one", + trace: [ + "../one.js" + ] + }, + two: { + output: "../.build/two.zip", + include: [ + "two.*" + ] + }, + three: { + cwd: "src/three", + output: "../../.build/three.zip", + trace: [ + "index.js" + ], + include: [ + "index.js" // Double include (should only have one file in zip) + ] + } } } } - } + }); + + expect(logStub).to.have.been.calledWithMatch("Created 3 packages:"); + + expect(await globby(".build/*.zip")).to.eql([ + ".build/one.zip", + ".build/three.zip", + ".build/two.zip" + ]); + expect(zipContents(".build/one.zip")).to.eql([ + "one.js", + "deeper/dep.js", + "dep.js" + ]); + expect(zipContents(".build/two.zip")).to.eql([ + "two.css", + "two.json" + ]); + expect(zipContents(".build/three.zip")).to.eql([ + "index.js" + ]); }); - expect(logStub).to.have.been.calledWithMatch("Created 2 packages:"); - - expect(await globby("*.zip")).to.eql([ - "one.zip", - "two.zip" - ]); - expect(zipContents("one.zip")).to.eql([ - "src/one.js", - "src/one/deeper/dep.js", - "src/one/dep.js" - ]); - expect(zipContents("two.zip")).to.eql([ - "src/two.css", - "src/two.json" - ]); - }); - - it("packages monorepos", async () => { - mock({ - "package.json": JSON.stringify({}), - functions: { - one: { - "index.js": "module.exports = require('./src/dep');", - src: { + it("outputs zip files to multiple different locations", async () => { + mock({ + src: { + "one.js": "module.exports = require('./one/dep');", + one: { "dep.js": "module.exports = require('./deeper/dep');", deeper: { - "dep.js": ` - require("local-nm-pkg"); - require("root-nm-pkg"); - module.exports = "deeper-dep"; - ` + "dep.js": "module.exports = \"deeper-dep\";" } }, - "package.json": JSON.stringify({ - main: "index.js" - }), - node_modules: { - "local-nm-pkg": { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": "module.exports = 'local-nm-pkg';" + "two.json": "{ \"msg\": \"two\" }", + "two.css": "body.two { background-color: pink; }" + } + }); + + await createPackage({ + opts: { + config: { + packages: { + "one.zip": { + trace: [ + "src/one.js" + ] + }, + two: { + include: [ + "src/two.*" + ] + } } } - }, - two: { - "index.js": "module.exports = require(\"local-nm-pkg-two\");", - src: { - "two.json": "{ \"msg\": \"two\" }", - "two.css": "body.two { background-color: pink; }" + } + }); + + expect(logStub).to.have.been.calledWithMatch("Created 2 packages:"); + + expect(await globby("*.zip")).to.eql([ + "one.zip", + "two.zip" + ]); + expect(zipContents("one.zip")).to.eql([ + "src/one.js", + "src/one/deeper/dep.js", + "src/one/dep.js" + ]); + expect(zipContents("two.zip")).to.eql([ + "src/two.css", + "src/two.json" + ]); + }); + + it("packages monorepos", async () => { + mock({ + "package.json": JSON.stringify({}), + functions: { + one: { + "index.js": "module.exports = require('./src/dep');", + src: { + "dep.js": "module.exports = require('./deeper/dep');", + deeper: { + "dep.js": ` + require("local-nm-pkg"); + require("root-nm-pkg"); + module.exports = "deeper-dep"; + ` + } + }, + "package.json": JSON.stringify({ + main: "index.js" + }), + node_modules: { + "local-nm-pkg": { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": "module.exports = 'local-nm-pkg';" + } + } }, - "package.json": { - main: "./index.js" + two: { + "index.js": "module.exports = require(\"local-nm-pkg-two\");", + src: { + "two.json": "{ \"msg\": \"two\" }", + "two.css": "body.two { background-color: pink; }" + }, + "package.json": { + main: "./index.js" + }, + node_modules: { + "local-nm-pkg-two": { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": "module.exports = 'local-nm-pkg-two';" + } + } }, node_modules: { - "local-nm-pkg-two": { + "root-nm-pkg": { "package.json": JSON.stringify({ - main: "index.js" + main: "main.js" }), - "index.js": "module.exports = 'local-nm-pkg-two';" + "main.js": "module.exports = 'root-nm-pkg';" } } - }, - node_modules: { - "root-nm-pkg": { - "package.json": JSON.stringify({ - main: "main.js" - }), - "main.js": "module.exports = 'root-nm-pkg';" - } } - } - }); - - await createPackage({ - opts: { - config: { - packages: { - one: { - cwd: "functions/one", - output: "../../.build/one.zip", - trace: [ - "index.js" - ] - }, - two: { - cwd: "functions/two", - output: "../../.build/two.zip", - trace: [ - "index.js" - ], - include: [ - "src/two.*" - ] + }); + + await createPackage({ + opts: { + config: { + packages: { + one: { + cwd: "functions/one", + output: "../../.build/one.zip", + trace: [ + "index.js" + ] + }, + two: { + cwd: "functions/two", + output: "../../.build/two.zip", + trace: [ + "index.js" + ], + include: [ + "src/two.*" + ] + } } } } - } + }); + + expect(logStub).to.have.been.calledWithMatch("Created 2 packages:"); + + expect(await globby(".build/*.zip")).to.eql([ + ".build/one.zip", + ".build/two.zip" + ]); + expect(zipContents(".build/one.zip")).to.eql([ + // Note: `root-nm-pkg` appears out of sorted order because collapsed + // from `../../node_modules/root-nm-pkg/index.js`. + "node_modules/root-nm-pkg/main.js", + "node_modules/root-nm-pkg/package.json", + "index.js", + "node_modules/local-nm-pkg/index.js", + "node_modules/local-nm-pkg/package.json", + "package.json", + "src/deeper/dep.js", + "src/dep.js" + ]); + expect(zipContents(".build/two.zip")).to.eql([ + "index.js", + "node_modules/local-nm-pkg-two/index.js", + "node_modules/local-nm-pkg-two/package.json", + "src/two.css", + "src/two.json" + ]); }); - - expect(logStub).to.have.been.calledWithMatch("Created 2 packages:"); - - expect(await globby(".build/*.zip")).to.eql([ - ".build/one.zip", - ".build/two.zip" - ]); - expect(zipContents(".build/one.zip")).to.eql([ - // Note: `root-nm-pkg` appears out of sorted order because collapsed - // from `../../node_modules/root-nm-pkg/index.js`. - "node_modules/root-nm-pkg/main.js", - "node_modules/root-nm-pkg/package.json", - "index.js", - "node_modules/local-nm-pkg/index.js", - "node_modules/local-nm-pkg/package.json", - "package.json", - "src/deeper/dep.js", - "src/dep.js" - ]); - expect(zipContents(".build/two.zip")).to.eql([ - "index.js", - "node_modules/local-nm-pkg-two/index.js", - "node_modules/local-nm-pkg-two/package.json", - "src/two.css", - "src/two.json" - ]); }); - it("traces source maps", async () => { - mock({ - src: { - "one.js": ` - module.exports = require('./one/dep'); + describe("includeSourceMaps", () => { + it("traces source maps", async () => { + mock({ + src: { + "one.js": ` + module.exports = require('./one/dep'); - //# sourceMappingURL=one.js.map - `, - "one.js.map": "{\"not\":\"real\"}", - one: { - "dep.js": ` - require(process.env.DYNAMIC_ONE); + //# sourceMappingURL=one.js.map + `, + "one.js.map": "{\"not\":\"real\"}", + one: { + "dep.js": ` + require(process.env.DYNAMIC_ONE); - module.exports = "dep"; + module.exports = "dep"; - //# sourceMappingURL=../one/dep.js.map - `, - "dep.js.map": "{\"not\":\"real\"}", - "extra-app-file.js": "module.exports = 'extra-app-file';" - }, - two: { - "index.js": "module.exports = require('./dep2');", - "dep2.js": ` - require(process.env.DYNAMIC_TWO); + //# sourceMappingURL=../one/dep.js.map + `, + "dep.js.map": "{\"not\":\"real\"}", + "extra-app-file.js": "module.exports = 'extra-app-file';" + }, + two: { + "index.js": "module.exports = require('./dep2');", + "dep2.js": ` + require(process.env.DYNAMIC_TWO); - module.exports = "dep"; + module.exports = "dep"; - //# sourceMappingURL=not/found/on/disk.js.map - ` - } - }, - node_modules: { - dep: { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": ` - require(process.env.DYNAMIC_ANOTHER_DEP); - - module.exports = "dep"; - ` + //# sourceMappingURL=not/found/on/disk.js.map + ` + } }, - "another-dep": { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": "module.exports = 'another-dep';" - } - } - }); + node_modules: { + dep: { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": ` + require(process.env.DYNAMIC_ANOTHER_DEP); - await createPackage({ - opts: { - config: { - options: { - includeSourceMaps: true, - dynamic: { - resolutions: { - "dep/index.js": [ - "another-dep" - ] - } - } + module.exports = "dep"; + ` }, - packages: { - "one.zip": { - trace: [ - "src/one.js" - ], - include: [ - // Directly include the map file to fully test out our - // cached file stats functionality. - "src/one/dep.js.map" - ], + "another-dep": { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": "module.exports = 'another-dep';" + } + } + }); + + await createPackage({ + opts: { + config: { + options: { + includeSourceMaps: true, dynamic: { resolutions: { - "./src/one/dep.js": [ - "dep", - "./extra-app-file.js" + "dep/index.js": [ + "another-dep" ] } } }, - two: { - // Different CWD, so dynamic.resolutions are relative to that. - cwd: "./src/two", - trace: [ - "index.js" - ], - dynamic: { - resolutions: { - "./dep2.js": [ - "dep" - ] + packages: { + "one.zip": { + trace: [ + "src/one.js" + ], + include: [ + // Directly include the map file to fully test out our + // cached file stats functionality. + "src/one/dep.js.map" + ], + dynamic: { + resolutions: { + "./src/one/dep.js": [ + "dep", + "./extra-app-file.js" + ] + } + } + }, + two: { + // Different CWD, so dynamic.resolutions are relative to that. + cwd: "./src/two", + trace: [ + "index.js" + ], + dynamic: { + resolutions: { + "./dep2.js": [ + "dep" + ] + } } } } } } - } + }); + + expect(logStub) + .to.have.been.calledWithMatch("Created 2 packages:").and + .to.have.been.calledWithMatch("WARN", "Missing source map files in two:"); + + expect(await globby("{,src/two/}*.zip")).to.eql([ + "one.zip", + "src/two/two.zip" + ]); + expect(zipContents("one.zip")).to.eql([ + "node_modules/another-dep/index.js", + "node_modules/another-dep/package.json", + "node_modules/dep/index.js", + "node_modules/dep/package.json", + "src/one.js", + "src/one.js.map", + "src/one/dep.js", + "src/one/dep.js.map", + "src/one/extra-app-file.js" + ]); + expect(zipContents("src/two/two.zip")).to.eql([ + "node_modules/another-dep/index.js", + "node_modules/another-dep/package.json", + "node_modules/dep/index.js", + "node_modules/dep/package.json", + "dep2.js", + "index.js" + ]); }); - - expect(logStub) - .to.have.been.calledWithMatch("Created 2 packages:").and - .to.have.been.calledWithMatch("WARN", "Missing source map files in two:"); - - expect(await globby("{,src/two/}*.zip")).to.eql([ - "one.zip", - "src/two/two.zip" - ]); - expect(zipContents("one.zip")).to.eql([ - "node_modules/another-dep/index.js", - "node_modules/another-dep/package.json", - "node_modules/dep/index.js", - "node_modules/dep/package.json", - "src/one.js", - "src/one.js.map", - "src/one/dep.js", - "src/one/dep.js.map", - "src/one/extra-app-file.js" - ]); - expect(zipContents("src/two/two.zip")).to.eql([ - "node_modules/another-dep/index.js", - "node_modules/another-dep/package.json", - "node_modules/dep/index.js", - "node_modules/dep/package.json", - "dep2.js", - "index.js" - ]); }); - it("ignores missing packages", async () => { - mock({ - src: { - "one.js": "module.exports = require('./one/dep');", - one: { - "dep.js": ` - require('missing-dep'); - require('missing/with/path/file.js'); - - module.exports = "dep"; - ` + describe("ignores", () => { + it("ignores missing packages", async () => { + mock({ + src: { + "one.js": "module.exports = require('./one/dep');", + one: { + "dep.js": ` + require('missing-dep'); + require('missing/with/path/file.js'); + + module.exports = "dep"; + ` + } } - } - }); + }); - await createPackage({ - opts: { - config: { - options: { - ignores: [ - "missing-dep" - ] - }, - packages: { - "one.zip": { - trace: [ - "src/one.js" - ], + await createPackage({ + opts: { + config: { + options: { ignores: [ - "missing/with" // could have `/path` added + "missing-dep" ] + }, + packages: { + "one.zip": { + trace: [ + "src/one.js" + ], + ignores: [ + "missing/with" // could have `/path` added + ] + } } } } - } - }); + }); - expect(logStub).to.have.been.calledWithMatch("Created 1 packages:"); + expect(logStub).to.have.been.calledWithMatch("Created 1 packages:"); - expect(await globby("*.zip")).to.eql([ - "one.zip" - ]); - expect(zipContents("one.zip")).to.eql([ - "src/one.js", - "src/one/dep.js" - ]); - }); + expect(await globby("*.zip")).to.eql([ + "one.zip" + ]); + expect(zipContents("one.zip")).to.eql([ + "src/one.js", + "src/one/dep.js" + ]); + }); - it("ignores skipped packages", async () => { - mock({ - src: { - "one.js": "module.exports = require('./one/dep');", - one: { - "dep.js": ` - require('present-but-skipped'); + it("ignores skipped packages", async () => { + mock({ + src: { + "one.js": "module.exports = require('./one/dep');", + one: { + "dep.js": ` + require('present-but-skipped'); - module.exports = "dep"; - ` + module.exports = "dep"; + ` + } + }, + node_modules: { + "present-but-skipped": { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": "module.exports = 'present-but-skipped';" + } } - }, - node_modules: { - "present-but-skipped": { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": "module.exports = 'present-but-skipped';" + }); + + await createPackage({ + opts: { + config: { + packages: { + "one.zip": { + trace: [ + "src/one.js" + ], + ignores: [ + "present-but-skipped" + ] + } + } + } } - } + }); + + expect(logStub).to.have.been.calledWithMatch("Created 1 packages:"); + + expect(await globby("*.zip")).to.eql([ + "one.zip" + ]); + expect(zipContents("one.zip")).to.eql([ + "src/one.js", + "src/one/dep.js" + ]); }); + }); - await createPackage({ - opts: { - config: { - packages: { - "one.zip": { - trace: [ - "src/one.js" - ], - ignores: [ - "present-but-skipped" + describe("conditions", () => { + it("handles user conditions", async () => { + mock({ + src: { + "one.js": "module.exports = require('./one/code');", + one: { + "code.js": ` + require('pkg'); + + module.exports = "code"; + ` + } + }, + node_modules: { + pkg: { + "package.json": JSON.stringify({ + name: "pkg", + main: "index.js", + exports: { + ".": { + // `import` will win over `global` and `package` below + "import": "./import.mjs", + "global-cond": "./global.js", + "package-cond": "./package.js", + "default": "./default.js" + } + } + }), + "index.js": ` + const nested = require("nested-pkg"); + + module.exports = nested; + `, + "global.js": ` + module.exports = "nested-pkg-global"; + `, + "default.js": ` + module.exports = "nested-pkg-default"; + `, + "package.js": ` + module.exports = "pkg-package"; + `, + "import.mjs": ` + const msg = "pkg-import"; + export default msg; + `, + node_modules: { + "nested-pkg": { + "package.json": JSON.stringify({ + name: "nested-pkg", + main: "index.js", + exports: { + ".": { + "global-cond": "./global.js", + "package-cond": "./package.js", + "import": "./import.mjs" + } + } + }), + "index.js": ` + module.exports = "nested-pkg-index"; + `, + "global.js": ` + module.exports = "nested-pkg-global"; + `, + "package.js": ` + module.exports = "nested-pkg-package"; + `, + "import.mjs": ` + const msg = "nested-pkg-import"; + export default msg; + ` + } + } + } + } + }); + + await createPackage({ + opts: { + config: { + options: { + conditions: [ + "global-cond" ] + }, + packages: { + "one.zip": { + trace: [ + "src/one.js" + ], + conditions: [ + "package-cond" + ] + } } } } - } + }); + + expect(logStub).to.have.been.calledWithMatch("Created 1 packages:"); + + expect(await globby("*.zip")).to.eql([ + "one.zip" + ]); + expect(zipContents("one.zip")).to.eql([ + "node_modules/pkg/default.js", + "node_modules/pkg/import.mjs", + "node_modules/pkg/index.js", + "node_modules/pkg/node_modules/nested-pkg/global.js", + "node_modules/pkg/node_modules/nested-pkg/import.mjs", + "node_modules/pkg/node_modules/nested-pkg/index.js", + "node_modules/pkg/node_modules/nested-pkg/package.js", + "node_modules/pkg/node_modules/nested-pkg/package.json", + "node_modules/pkg/package.json", + "src/one.js", + "src/one/code.js" + ]); }); - - expect(logStub).to.have.been.calledWithMatch("Created 1 packages:"); - - expect(await globby("*.zip")).to.eql([ - "one.zip" - ]); - expect(zipContents("one.zip")).to.eql([ - "src/one.js", - "src/one/dep.js" - ]); }); - it("skips allowed missing packages", async () => { - mock({ - src: { - "one.js": "module.exports = require('./one/dep');", - one: { - "dep.js": ` - require('dep'); - require('allowed-miss-from-app-sources'); - - module.exports = "dep"; - ` - } - }, - node_modules: { - dep: { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": ` - // Will be packaged. - require('present-but-allowed-to-be-missing'); - // WIll be omitted without error. - require('missing'); - - module.exports = "dep"; - ` + describe("allowMissing", () => { + it("skips allowed missing packages", async () => { + mock({ + src: { + "one.js": "module.exports = require('./one/dep');", + one: { + "dep.js": ` + require('dep'); + require('allowed-miss-from-app-sources'); + + module.exports = "dep"; + ` + } }, - "present-but-allowed-to-be-missing": { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": "module.exports = 'present-but-allowed-to-be-missing';" - } - } - }); + node_modules: { + dep: { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": ` + // Will be packaged. + require('present-but-allowed-to-be-missing'); + // WIll be omitted without error. + require('missing'); - await createPackage({ - opts: { - config: { - options: { - allowMissing: { - "./src/one/dep.js": [ - "allowed-miss-from-app-sources" - ], - dep: [ - "missing" - ] - } + module.exports = "dep"; + ` }, - packages: { - "one.zip": { - trace: [ - "src/one.js" - ], + "present-but-allowed-to-be-missing": { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": "module.exports = 'present-but-allowed-to-be-missing';" + } + } + }); + + await createPackage({ + opts: { + config: { + options: { allowMissing: { + "./src/one/dep.js": [ + "allowed-miss-from-app-sources" + ], dep: [ - "present-but-allowed-to-be-missing" + "missing" ] } + }, + packages: { + "one.zip": { + trace: [ + "src/one.js" + ], + allowMissing: { + dep: [ + "present-but-allowed-to-be-missing" + ] + } + } } } } - } + }); + + expect(logStub).to.have.been.calledWithMatch("Created 1 packages:"); + + expect(await globby("*.zip")).to.eql([ + "one.zip" + ]); + expect(zipContents("one.zip")).to.eql([ + "node_modules/dep/index.js", + "node_modules/dep/package.json", + "node_modules/present-but-allowed-to-be-missing/index.js", + "node_modules/present-but-allowed-to-be-missing/package.json", + "src/one.js", + "src/one/dep.js" + ]); }); - - expect(logStub).to.have.been.calledWithMatch("Created 1 packages:"); - - expect(await globby("*.zip")).to.eql([ - "one.zip" - ]); - expect(zipContents("one.zip")).to.eql([ - "node_modules/dep/index.js", - "node_modules/dep/package.json", - "node_modules/present-but-allowed-to-be-missing/index.js", - "node_modules/present-but-allowed-to-be-missing/package.json", - "src/one.js", - "src/one/dep.js" - ]); }); - it("resolves dynamic misses", async () => { - mock({ - src: { - "one.js": "module.exports = require('./one/dep');", - one: { - "dep.js": ` - require(process.env.DYNAMIC_ONE); + describe("dynamic.resolutions", () => { + it("resolves dynamic misses", async () => { + mock({ + src: { + "one.js": "module.exports = require('./one/dep');", + one: { + "dep.js": ` + require(process.env.DYNAMIC_ONE); - module.exports = "dep"; - `, - "extra-app-file.js": "module.exports = 'extra-app-file';" - }, - two: { - "index.js": "module.exports = require('./dep2');", - "dep2.js": ` - require(process.env.DYNAMIC_TWO); - require("missing-but-allowed-from-app-sources"); - - module.exports = "dep"; - ` - } - }, - node_modules: { - dep: { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": ` - require(process.env.DYNAMIC_ANOTHER_DEP); - - module.exports = "dep"; - ` + module.exports = "dep"; + `, + "extra-app-file.js": "module.exports = 'extra-app-file';" + }, + two: { + "index.js": "module.exports = require('./dep2');", + "dep2.js": ` + require(process.env.DYNAMIC_TWO); + require("missing-but-allowed-from-app-sources"); + + module.exports = "dep"; + ` + } }, - "another-dep": { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": "module.exports = 'another-dep';" - } - } - }); + node_modules: { + dep: { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": ` + require(process.env.DYNAMIC_ANOTHER_DEP); - await createPackage({ - opts: { - config: { - options: { - dynamic: { - resolutions: { - "dep/index.js": [ - "another-dep" - ] - } - } + module.exports = "dep"; + ` }, - packages: { - "one.zip": { - trace: [ - "src/one.js" - ], + "another-dep": { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": "module.exports = 'another-dep';" + } + } + }); + + await createPackage({ + opts: { + config: { + options: { dynamic: { resolutions: { - "./src/one/dep.js": [ - "dep", - "./extra-app-file.js" + "dep/index.js": [ + "another-dep" ] } } }, - two: { - // Different CWD, so dynamic.resolutions are relative to that. - cwd: "./src/two", - trace: [ - "index.js" - ], - allowMissing: { - // This tests different CWD for allowMissing. - "./dep2.js": [ - "missing-but-allowed-from-app-sources" - ] + packages: { + "one.zip": { + trace: [ + "src/one.js" + ], + dynamic: { + resolutions: { + "./src/one/dep.js": [ + "dep", + "./extra-app-file.js" + ] + } + } }, - dynamic: { - resolutions: { + two: { + // Different CWD, so dynamic.resolutions are relative to that. + cwd: "./src/two", + trace: [ + "index.js" + ], + allowMissing: { + // This tests different CWD for allowMissing. "./dep2.js": [ - "dep" + "missing-but-allowed-from-app-sources" ] + }, + dynamic: { + resolutions: { + "./dep2.js": [ + "dep" + ] + } } } } } } - } + }); + + expect(logStub).to.have.been.calledWithMatch("Created 2 packages:"); + + expect(await globby("{,src/two/}*.zip")).to.eql([ + "one.zip", + "src/two/two.zip" + ]); + expect(zipContents("one.zip")).to.eql([ + "node_modules/another-dep/index.js", + "node_modules/another-dep/package.json", + "node_modules/dep/index.js", + "node_modules/dep/package.json", + "src/one.js", + "src/one/dep.js", + "src/one/extra-app-file.js" + ]); + expect(zipContents("src/two/two.zip")).to.eql([ + "node_modules/another-dep/index.js", + "node_modules/another-dep/package.json", + "node_modules/dep/index.js", + "node_modules/dep/package.json", + "dep2.js", + "index.js" + ]); }); - expect(logStub).to.have.been.calledWithMatch("Created 2 packages:"); - - expect(await globby("{,src/two/}*.zip")).to.eql([ - "one.zip", - "src/two/two.zip" - ]); - expect(zipContents("one.zip")).to.eql([ - "node_modules/another-dep/index.js", - "node_modules/another-dep/package.json", - "node_modules/dep/index.js", - "node_modules/dep/package.json", - "src/one.js", - "src/one/dep.js", - "src/one/extra-app-file.js" - ]); - expect(zipContents("src/two/two.zip")).to.eql([ - "node_modules/another-dep/index.js", - "node_modules/another-dep/package.json", - "node_modules/dep/index.js", - "node_modules/dep/package.json", - "dep2.js", - "index.js" - ]); - }); + it("displays dynamic misses in report", async () => { + mock({ + src: { + "one.js": "module.exports = require('./one/dep');", + one: { + "dep.js": ` + require('dep'); - it("displays dynamic misses in report", async () => { - mock({ - src: { - "one.js": "module.exports = require('./one/dep');", - one: { - "dep.js": ` - require('dep'); + module.exports = "dep"; + ` + } + }, + node_modules: { + dep: { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": ` + require(process.env.DYNAMIC); - module.exports = "dep"; - ` - } - }, - node_modules: { - dep: { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": ` - require(process.env.DYNAMIC); - - module.exports = "dep"; - ` + module.exports = "dep"; + ` + } } - } - }); - - await createPackage({ - opts: { - report: true, - dryRun: true, - config: { - packages: { - "one.zip": { - trace: [ - "src/one.js" - ] + }); + + await createPackage({ + opts: { + report: true, + dryRun: true, + config: { + packages: { + "one.zip": { + trace: [ + "src/one.js" + ] + } } } } - } + }); + + const indexPath = path.normalize(path.resolve("node_modules/dep/index.js")); + + // Log output + expect(logStub) + .to.have.been.calledWithMatch("WARN", "Dynamic misses in one.zip:").and + .to.have.been.calledWithMatch( + "WARN", + `${indexPath}\n [2:14]: require(process.env.DYNAMIC)` + ); + + // Report output + expect(logStub).to.have.been.calledWithMatch(` + missed: + ${indexPath}: + - "[2:14]: require(process.env.DYNAMIC)" + `.trim().replace(/^ {4}/gm, "")); }); - const indexPath = path.normalize(path.resolve("node_modules/dep/index.js")); + it("errors on unresolved dynamic misses", async () => { + const errStub = sandbox.stub(console, "error"); - // Log output - expect(logStub) - .to.have.been.calledWithMatch("WARN", "Dynamic misses in one.zip:").and - .to.have.been.calledWithMatch( - "WARN", - `${indexPath}\n [2:12]: require(process.env.DYNAMIC)` - ); + mock({ + src: { + "one.js": "module.exports = require('./one/dep');", + one: { + "dep.js": ` + require(process.env.DYNAMIC_ONE); - // Report output - expect(logStub).to.have.been.calledWithMatch(` - missed: - ${indexPath}: - - "[2:12]: require(process.env.DYNAMIC)" - `.trim().replace(/^ {2}/gm, "")); - }); - - it("errors on unresolved dynamic misses", async () => { - const errStub = sandbox.stub(console, "error"); - - mock({ - src: { - "one.js": "module.exports = require('./one/dep');", - one: { - "dep.js": ` - require(process.env.DYNAMIC_ONE); + module.exports = "dep"; + `, + "extra-app-file.js": "module.exports = 'extra-app-file';" + }, + two: { + "index.js": "module.exports = require('./dep2');", + "dep2.js": ` + require(process.env.DYNAMIC_TWO); - module.exports = "dep"; - `, - "extra-app-file.js": "module.exports = 'extra-app-file';" + module.exports = "dep"; + ` + } }, - two: { - "index.js": "module.exports = require('./dep2');", - "dep2.js": ` - require(process.env.DYNAMIC_TWO); + node_modules: { + dep: { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": ` + require(process.env.DYNAMIC_ANOTHER_DEP); - module.exports = "dep"; - ` - } - }, - node_modules: { - dep: { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": ` - require(process.env.DYNAMIC_ANOTHER_DEP); - - module.exports = "dep"; - ` - }, - "another-dep": { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": "module.exports = 'another-dep';" + module.exports = "dep"; + ` + }, + "another-dep": { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": "module.exports = 'another-dep';" + } } - } - }); + }); - await expect(createPackage({ - opts: { - config: { - options: { - dynamic: { - bail: true - } - }, - packages: { - "one.zip": { - trace: [ - "src/one.js" - ] - }, - two: { - // Different CWD, so dynamic.resolutions are relative to that. - cwd: "./src/two", - trace: [ - "index.js" - ], + await expect(createPackage({ + opts: { + config: { + options: { dynamic: { - bail: true, - resolutions: { - "./dep2.js": [ - "dep" - ], - "dep/index.js": [ - "another-dep" - ] + bail: true + } + }, + packages: { + "one.zip": { + trace: [ + "src/one.js" + ] + }, + two: { + // Different CWD, so dynamic.resolutions are relative to that. + cwd: "./src/two", + trace: [ + "index.js" + ], + dynamic: { + bail: true, + resolutions: { + "./dep2.js": [ + "dep" + ], + "dep/index.js": [ + "another-dep" + ] + } } } } } } - } - })).to.eventually.be.rejectedWith("Unresolved dynamic misses"); + })).to.eventually.be.rejectedWith("Unresolved dynamic misses"); - expect(errStub).to.be.calledWithMatch("ERROR", "Unresolved dynamic misses in one.zip: ["); - }); + expect(errStub).to.be.calledWithMatch("ERROR", "Unresolved dynamic misses in one.zip: ["); + }); - it("allows unresolved dynamic misses with package override of bail", async () => { - mock({ - src: { - "one.js": "module.exports = require('./one/dep');", - one: { - "dep.js": ` - require(process.env.DYNAMIC_ONE); + it("allows unresolved dynamic misses with package override of bail", async () => { + mock({ + src: { + "one.js": "module.exports = require('./one/dep');", + one: { + "dep.js": ` + require(process.env.DYNAMIC_ONE); - module.exports = "dep"; - `, - "extra-app-file.js": "module.exports = 'extra-app-file';" - }, - two: { - "index.js": "module.exports = require('./dep2');", - "dep2.js": ` - require(process.env.DYNAMIC_TWO); + module.exports = "dep"; + `, + "extra-app-file.js": "module.exports = 'extra-app-file';" + }, + two: { + "index.js": "module.exports = require('./dep2');", + "dep2.js": ` + require(process.env.DYNAMIC_TWO); - module.exports = "dep"; - ` - } - }, - node_modules: { - dep: { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": ` - require(process.env.DYNAMIC_ANOTHER_DEP); - - module.exports = "dep"; - ` + module.exports = "dep"; + ` + } }, - "another-dep": { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": "module.exports = 'another-dep';" - } - } - }); + node_modules: { + dep: { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": ` + require(process.env.DYNAMIC_ANOTHER_DEP); - await createPackage({ - opts: { - config: { - options: { - dynamic: { - bail: true, - resolutions: { - "dep/index.js": [ - "another-dep" - ] - } - } + module.exports = "dep"; + ` }, - packages: { - "one.zip": { - trace: [ - "src/one.js" - ], + "another-dep": { + "package.json": JSON.stringify({ + main: "index.js" + }), + "index.js": "module.exports = 'another-dep';" + } + } + }); + + await createPackage({ + opts: { + config: { + options: { dynamic: { + bail: true, resolutions: { - "./src/one/dep.js": [ - "dep", - "./extra-app-file.js" + "dep/index.js": [ + "another-dep" ] } } }, - two: { - // Different CWD, so dynamic.resolutions are relative to that. - cwd: "./src/two", - trace: [ - "index.js" - ], - dynamic: { - // Allow unresolved misses. - bail: false + packages: { + "one.zip": { + trace: [ + "src/one.js" + ], + dynamic: { + resolutions: { + "./src/one/dep.js": [ + "dep", + "./extra-app-file.js" + ] + } + } + }, + two: { + // Different CWD, so dynamic.resolutions are relative to that. + cwd: "./src/two", + trace: [ + "index.js" + ], + dynamic: { + // Allow unresolved misses. + bail: false + } } } } } - } + }); + + expect(logStub) + .to.have.been.calledWithMatch("Created 2 packages:").and + .to.have.been.calledWithMatch("WARN", "Dynamic misses in two:"); + + expect(await globby("{,src/two/}*.zip")).to.eql([ + "one.zip", + "src/two/two.zip" + ]); + expect(zipContents("one.zip")).to.eql([ + "node_modules/another-dep/index.js", + "node_modules/another-dep/package.json", + "node_modules/dep/index.js", + "node_modules/dep/package.json", + "src/one.js", + "src/one/dep.js", + "src/one/extra-app-file.js" + ]); + expect(zipContents("src/two/two.zip")).to.eql([ + "dep2.js", + "index.js" + ]); }); - - expect(logStub) - .to.have.been.calledWithMatch("Created 2 packages:").and - .to.have.been.calledWithMatch("WARN", "Dynamic misses in two:"); - - expect(await globby("{,src/two/}*.zip")).to.eql([ - "one.zip", - "src/two/two.zip" - ]); - expect(zipContents("one.zip")).to.eql([ - "node_modules/another-dep/index.js", - "node_modules/another-dep/package.json", - "node_modules/dep/index.js", - "node_modules/dep/package.json", - "src/one.js", - "src/one/dep.js", - "src/one/extra-app-file.js" - ]); - expect(zipContents("src/two/two.zip")).to.eql([ - "dep2.js", - "index.js" - ]); }); - it("errors on collapsed files in zip bundle", async () => { - const errStub = sandbox.stub(console, "error"); - - mock({ - "a-file.js": ` - module.exports = "a file (at project root)"; - `, - packages: { - one: { - "index.js": ` - // Root level dep import. - require("root-dep"); - - // Application sources conflicts. - require("../../a-file"); // Root - require("./a-file"); // In packages/one - - // Transitive nested dep import. - module.exports = require("./lib/nested"); - `, - "a-file.js": ` - module.exports = "a file (in packages/one)"; - `, - lib: { - "nested.js": ` - module.exports = require("dep"); - ` - }, - node_modules: { - dep: { - "package.json": JSON.stringify({ - main: "index.js", - version: "2.0.0" - }), - "index.js": ` - module.exports = "dep"; + describe("collapsed", () => { + it("errors on collapsed files in zip bundle", async () => { + const errStub = sandbox.stub(console, "error"); + + mock({ + "a-file.js": ` + module.exports = "a file (at project root)"; + `, + packages: { + one: { + "index.js": ` + // Root level dep import. + require("root-dep"); + + // Application sources conflicts. + require("../../a-file"); // Root + require("./a-file"); // In packages/one + + // Transitive nested dep import. + module.exports = require("./lib/nested"); + `, + "a-file.js": ` + module.exports = "a file (in packages/one)"; + `, + lib: { + "nested.js": ` + module.exports = require("dep"); ` + }, + node_modules: { + dep: { + "package.json": JSON.stringify({ + main: "index.js", + version: "2.0.0" + }), + "index.js": ` + module.exports = "dep"; + ` + } } } - } - }, - node_modules: { - dep: { - "package.json": JSON.stringify({ - main: "index.js", - version: "1.0.0" - }), - "index.js": ` - module.exports = "dep"; - ` }, - "root-dep": { - "package.json": JSON.stringify({ - main: "index.js", - version: "1.0.0" - }), - "index.js": ` - // Forces root-level dep package. - module.exports = require("dep"); - ` - } - } - }); - - await expect(createPackage({ - opts: { - config: { - packages: { - one: { - cwd: "packages/one", - trace: [ - "index.js" - ] - } - } - } - } - })).to.eventually.be.rejectedWith("Collapsed file conflicts"); - - expect(logStub) - .to.be.calledWithMatch( - "WARN", - "Collapsed sources in one (1 conflicts, 2 files): a-file.js" - ).and - .to.be.calledWithMatch( - "WARN", - "Collapsed dependencies in one (1 packages, 2 conflicts, 4 files): dep" - ); - - expect(errStub).to.be.calledWithMatch( - "ERROR", - "Collapsed file conflicts in one: (3 total conflicts)" - ); - }); - - it("has no collapsed files in zip bundle from root", async () => { - mock({ - "a-file.js": ` - module.exports = "a file (at project root)"; - `, - packages: { - one: { - "index.js": ` - // Root level dep import. - require("root-dep"); - - // Application sources conflicts. - require("../../a-file"); // Root - require("./a-file"); // In packages/one - - // Transitive nested dep import. - module.exports = require("./lib/nested"); - `, - "a-file.js": ` - module.exports = "a file (in packages/one)"; - `, - lib: { - "nested.js": ` - module.exports = require("dep"); + node_modules: { + dep: { + "package.json": JSON.stringify({ + main: "index.js", + version: "1.0.0" + }), + "index.js": ` + module.exports = "dep"; ` }, - node_modules: { - dep: { - "package.json": JSON.stringify({ - main: "index.js", - version: "2.0.0" - }), - "index.js": ` - module.exports = "dep"; - ` - } + "root-dep": { + "package.json": JSON.stringify({ + main: "index.js", + version: "1.0.0" + }), + "index.js": ` + // Forces root-level dep package. + module.exports = require("dep"); + ` } } - }, - node_modules: { - dep: { - "package.json": JSON.stringify({ - main: "index.js", - version: "1.0.0" - }), - "index.js": ` - module.exports = "dep"; - ` - }, - "root-dep": { - "package.json": JSON.stringify({ - main: "index.js", - version: "1.0.0" - }), - "index.js": ` - // Forces root-level dep package. - module.exports = require("dep"); - ` - } - } - }); - - await createPackage({ - opts: { - config: { - packages: { - one: { - trace: [ - "packages/one/index.js" - ] + }); + + await expect(createPackage({ + opts: { + config: { + packages: { + one: { + cwd: "packages/one", + trace: [ + "index.js" + ] + } } } } - } + })).to.eventually.be.rejectedWith("Collapsed file conflicts"); + + expect(logStub) + .to.be.calledWithMatch( + "WARN", + "Collapsed sources in one (1 conflicts, 2 files): a-file.js" + ).and + .to.be.calledWithMatch( + "WARN", + "Collapsed dependencies in one (1 packages, 2 conflicts, 4 files): dep" + ); + + expect(errStub).to.be.calledWithMatch( + "ERROR", + "Collapsed file conflicts in one: (3 total conflicts)" + ); }); - - expect(logStub) - .to.have.been.calledWithMatch("Created 1 packages:"); - - expect(await globby("**/*.zip")).to.eql([ - "one.zip" - ]); - expect(zipContents("one.zip")).to.eql([ - "a-file.js", - "node_modules/dep/index.js", - "node_modules/dep/package.json", - "node_modules/root-dep/index.js", - "node_modules/root-dep/package.json", - "packages/one/a-file.js", - "packages/one/index.js", - "packages/one/lib/nested.js", - "packages/one/node_modules/dep/index.js", - "packages/one/node_modules/dep/package.json" - ]); - }); - - it("warns on collapsed files in zip bundle with bail=false", async () => { - mock({ - "a-file.js": ` - module.exports = "a file (at project root)"; - `, - packages: { - one: { - "index.js": ` - // Root level dep import. - require("root-dep"); - - // Application sources conflicts. - require("../../a-file"); // Root - require("./a-file"); // In packages/one - - // Transitive nested dep import. - module.exports = require("./lib/nested"); - `, - "a-file.js": ` - module.exports = "a file (in packages/one)"; - `, - lib: { - "nested.js": ` - module.exports = require("@scope/dep"); - ` - }, - node_modules: { - "@scope": { + it("has no collapsed files in zip bundle from root", async () => { + mock({ + "a-file.js": ` + module.exports = "a file (at project root)"; + `, + packages: { + one: { + "index.js": ` + // Root level dep import. + require("root-dep"); + + // Application sources conflicts. + require("../../a-file"); // Root + require("./a-file"); // In packages/one + + // Transitive nested dep import. + module.exports = require("./lib/nested"); + `, + "a-file.js": ` + module.exports = "a file (in packages/one)"; + `, + lib: { + "nested.js": ` + module.exports = require("dep"); + ` + }, + node_modules: { dep: { "package.json": JSON.stringify({ main: "index.js", @@ -1206,10 +1240,8 @@ describe("lib/actions/package", () => { } } } - } - }, - node_modules: { - "@scope": { + }, + node_modules: { dep: { "package.json": JSON.stringify({ main: "index.js", @@ -1218,70 +1250,171 @@ describe("lib/actions/package", () => { "index.js": ` module.exports = "dep"; ` + }, + "root-dep": { + "package.json": JSON.stringify({ + main: "index.js", + version: "1.0.0" + }), + "index.js": ` + // Forces root-level dep package. + module.exports = require("dep"); + ` + } + } + }); + + await createPackage({ + opts: { + config: { + packages: { + one: { + trace: [ + "packages/one/index.js" + ] + } + } } - }, - "root-dep": { - "package.json": JSON.stringify({ - main: "index.js", - version: "1.0.0" - }), - "index.js": ` - // Forces root-level dep package. - module.exports = require("@scope/dep"); - ` } - } + }); + + + expect(logStub) + .to.have.been.calledWithMatch("Created 1 packages:"); + + expect(await globby("**/*.zip")).to.eql([ + "one.zip" + ]); + expect(zipContents("one.zip")).to.eql([ + "a-file.js", + "node_modules/dep/index.js", + "node_modules/dep/package.json", + "node_modules/root-dep/index.js", + "node_modules/root-dep/package.json", + "packages/one/a-file.js", + "packages/one/index.js", + "packages/one/lib/nested.js", + "packages/one/node_modules/dep/index.js", + "packages/one/node_modules/dep/package.json" + ]); }); - await createPackage({ - opts: { - config: { - options: { - collapsed: { - bail: false + it("warns on collapsed files in zip bundle with bail=false", async () => { + mock({ + "a-file.js": ` + module.exports = "a file (at project root)"; + `, + packages: { + one: { + "index.js": ` + // Root level dep import. + require("root-dep"); + + // Application sources conflicts. + require("../../a-file"); // Root + require("./a-file"); // In packages/one + + // Transitive nested dep import. + module.exports = require("./lib/nested"); + `, + "a-file.js": ` + module.exports = "a file (in packages/one)"; + `, + lib: { + "nested.js": ` + module.exports = require("@scope/dep"); + ` + }, + node_modules: { + "@scope": { + dep: { + "package.json": JSON.stringify({ + main: "index.js", + version: "2.0.0" + }), + "index.js": ` + module.exports = "dep"; + ` + } + } + } + } + }, + node_modules: { + "@scope": { + dep: { + "package.json": JSON.stringify({ + main: "index.js", + version: "1.0.0" + }), + "index.js": ` + module.exports = "dep"; + ` } }, - packages: { - one: { - cwd: "packages/one", - trace: [ - "index.js" - ] + "root-dep": { + "package.json": JSON.stringify({ + main: "index.js", + version: "1.0.0" + }), + "index.js": ` + // Forces root-level dep package. + module.exports = require("@scope/dep"); + ` + } + } + }); + + await createPackage({ + opts: { + config: { + options: { + collapsed: { + bail: false + } + }, + packages: { + one: { + cwd: "packages/one", + trace: [ + "index.js" + ] + } } } } - } + }); + + expect(logStub) + .to.have.been.calledWithMatch("Created 1 packages:").and + .to.be.calledWithMatch( + "WARN", + "Collapsed sources in one (1 conflicts, 2 files): a-file.js" + ).and + .to.be.calledWithMatch( + "WARN", + "Collapsed dependencies in one (1 packages, 2 conflicts, 4 files): @scope/dep" + ); + + expect(await globby("**/*.zip")).to.eql([ + "packages/one/one.zip" + ]); + expect(zipContents("packages/one/one.zip")).to.eql([ + // NOTE: These 3 files are collapsed and overwritten on expansion. + "a-file.js", + "node_modules/@scope/dep/index.js", + "node_modules/@scope/dep/package.json", + + // Unique files. + "node_modules/root-dep/index.js", + "node_modules/root-dep/package.json", + "a-file.js", + "index.js", + "lib/nested.js", + "node_modules/@scope/dep/index.js", + "node_modules/@scope/dep/package.json" + ]); }); - - expect(logStub) - .to.have.been.calledWithMatch("Created 1 packages:").and - .to.be.calledWithMatch( - "WARN", - "Collapsed sources in one (1 conflicts, 2 files): a-file.js" - ).and - .to.be.calledWithMatch( - "WARN", - "Collapsed dependencies in one (1 packages, 2 conflicts, 4 files): @scope/dep" - ); - - expect(await globby("**/*.zip")).to.eql([ - "packages/one/one.zip" - ]); - expect(zipContents("packages/one/one.zip")).to.eql([ - // NOTE: These 3 files are collapsed and overwritten on expansion. - "a-file.js", - "node_modules/@scope/dep/index.js", - "node_modules/@scope/dep/package.json", - - // Unique files. - "node_modules/root-dep/index.js", - "node_modules/root-dep/package.json", - "a-file.js", - "index.js", - "lib/nested.js", - "node_modules/@scope/dep/index.js", - "node_modules/@scope/dep/package.json" - ]); }); // https://github.com/FormidableLabs/trace-pkg/issues/11