From 14bbfe8524d82932eea338b997f356121f3e24cb Mon Sep 17 00:00:00 2001 From: Ryan Roemer Date: Sat, 3 Sep 2022 14:15:27 -0700 Subject: [PATCH 01/10] Remove dev/prod fromd default conditions. --- .changeset/lucky-cheetahs-relate.md | 6 ++++++ packages/trace-deps/README.md | 2 +- packages/trace-deps/lib/trace.js | 24 ++++++++++++++++------ packages/trace-deps/test/lib/trace.spec.js | 10 ++++----- 4 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 .changeset/lucky-cheetahs-relate.md diff --git a/.changeset/lucky-cheetahs-relate.md b/.changeset/lucky-cheetahs-relate.md new file mode 100644 index 0000000..d41de1a --- /dev/null +++ b/.changeset/lucky-cheetahs-relate.md @@ -0,0 +1,6 @@ +--- +"trace-deps": minor +--- + +- TODO: FEATURE NOTES +- 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..67994a6 100644 --- a/packages/trace-deps/README.md +++ b/packages/trace-deps/README.md @@ -111,7 +111,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. * **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..91b0283 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. diff --git a/packages/trace-deps/test/lib/trace.spec.js b/packages/trace-deps/test/lib/trace.spec.js index e0439a6..3b0c3cc 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,10 @@ describe("lib/trace", () => { expect(misses).to.eql({}); }); }); + + describe("user conditions", () => { + it("TODO(conditions)"); // TODO: IMPLEMENT + }); }); describe("traceFiles", () => { From 9776bf708974c11f946d4c344372cf5f5777ba52 Mon Sep 17 00:00:00 2001 From: Ryan Roemer Date: Sat, 3 Sep 2022 14:52:58 -0700 Subject: [PATCH 02/10] Add initial test for conditions. --- packages/trace-deps/test/lib/trace.spec.js | 104 ++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/packages/trace-deps/test/lib/trace.spec.js b/packages/trace-deps/test/lib/trace.spec.js index 3b0c3cc..93326c2 100644 --- a/packages/trace-deps/test/lib/trace.spec.js +++ b/packages/trace-deps/test/lib/trace.spec.js @@ -2597,7 +2597,109 @@ describe("lib/trace", () => { }); describe("user conditions", () => { - it("TODO(conditions)"); // TODO: IMPLEMENT + 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" + }), + "index.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", + "import": "./import.mjs", + production: "./production.mjs" + } + } + }), + "index.mjs": "export const three = 'three';", + "import.mjs": "export const msg = 'import';", + "production.mjs": "export const msg = 'production';", + "bespoke.mjs": "export const msg = 'bespoke';" + } + } + } + } + }); + + // TODO(conditions): Add bespoke, production conditions. + const { dependencies, misses } = await traceFile({ + srcPath: "first.js" + }); + expect(dependencies).to.eql(fullPaths([ + "node_modules/one/default.js", + "node_modules/one/index.js", + "node_modules/one/package.json", + "node_modules/three/index.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/package.json", + "node_modules/two/index.js", + "node_modules/two/package.json", + "node_modules/two/require.js", + "second.js" + ])); + expect(misses).to.eql({}); + }); }); }); From 4dd00320f4531eca18d29aa47fd202c8c41ce1b2 Mon Sep 17 00:00:00 2001 From: Ryan Roemer Date: Tue, 6 Sep 2022 12:01:09 -0700 Subject: [PATCH 03/10] Still figuring out. --- .changeset/lucky-cheetahs-relate.md | 2 +- packages/trace-deps/README.md | 5 +++-- packages/trace-deps/lib/trace.js | 14 +++++++++++++- packages/trace-deps/test/lib/trace.spec.js | 18 ++++++++++++++---- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/.changeset/lucky-cheetahs-relate.md b/.changeset/lucky-cheetahs-relate.md index d41de1a..2195ea5 100644 --- a/.changeset/lucky-cheetahs-relate.md +++ b/.changeset/lucky-cheetahs-relate.md @@ -2,5 +2,5 @@ "trace-deps": minor --- -- TODO: FEATURE NOTES +- Feature: Add `conditions` parameter to `traceFile`/`traceFiles` to support user runtime loading conditions. - 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 67994a6..1c57ce1 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,7 +70,7 @@ _Parameters_: * `srcPaths` (`Array`): source file paths to trace * `ignores` (`Array`): list of package prefixes to ignore -* `allowMissing` (`Object.`): Mapping of source file paths and package names/paths to permitted missing module prefixes. +* `conditions` (`Array`): list of Node.js runtime import user conditions to trace. * `bailOnMissing` (`boolean`): Throw error if missing static import. * `includeSourceMaps` (`boolean`): Include source map file paths from control comments * `extraImports` (`Object.`): Mapping of files to additional imports to trace. @@ -111,7 +112,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 / 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. + * **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 91b0283..3d7c063 100644 --- a/packages/trace-deps/lib/trace.js +++ b/packages/trace-deps/lib/trace.js @@ -185,6 +185,7 @@ const _recurseDeps = async ({ srcPaths, depPaths = [], ignores = [], + conditions = [], allowMissing = {}, bailOnMissing = true, includeSourceMaps = false, @@ -215,6 +216,7 @@ const _recurseDeps = async ({ const traced = await traceFile({ srcPath: depPath, ignores, + conditions, allowMissing, bailOnMissing, includeSourceMaps, @@ -241,6 +243,7 @@ const _resolveDep = async ({ depName, basedir, srcPath, + conditions, dependencies, extraDepKeys, addMisses, @@ -351,7 +354,8 @@ const _resolveDep = async ({ } }); - CONDITIONS.forEach((cond) => { + console.log("TODO HERE", { depName, srcPath, conditions }) + CONDITIONS.concat(conditions).forEach((cond) => { let resolveOpts; if (Array.isArray(cond)) { resolveOpts = cond[1]; @@ -443,6 +447,7 @@ const _resolveDep = async ({ depName, basedir, srcPath, + conditions, dependencies, extraDepKeys, addMisses, @@ -499,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 @@ -511,6 +517,7 @@ const _resolveDep = async ({ const traceFile = async ({ srcPath, ignores = [], + conditions = [], allowMissing = {}, bailOnMissing = true, includeSourceMaps = false, @@ -606,6 +613,7 @@ const traceFile = async ({ depName, basedir, srcPath, + conditions, dependencies, extraDepKeys, addMisses, @@ -640,6 +648,7 @@ const traceFile = async ({ const recursed = await _recurseDeps({ depPaths, ignores, + conditions, allowMissing, bailOnMissing, includeSourceMaps, @@ -670,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 @@ -681,6 +691,7 @@ const traceFile = async ({ const traceFiles = async ({ srcPaths, ignores = [], + conditions = [], allowMissing = {}, bailOnMissing = true, includeSourceMaps = false, @@ -694,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 93326c2..a6dd2b2 100644 --- a/packages/trace-deps/test/lib/trace.spec.js +++ b/packages/trace-deps/test/lib/trace.spec.js @@ -2665,8 +2665,9 @@ describe("lib/trace", () => { exports: { ".": { bespoke: "./bespoke.mjs", - "import": "./import.mjs", - production: "./production.mjs" + // TODO: Why does order matter. Not picked up if behind `import`??? + production: "./production.mjs", + "import": "./import.mjs" } } }), @@ -2680,21 +2681,30 @@ describe("lib/trace", () => { } }); - // TODO(conditions): Add bespoke, production conditions. const { dependencies, misses } = await traceFile({ - srcPath: "first.js" + 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/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" ])); From a957407dc75e2b5d896b1bd4c1e918f12b420a9c Mon Sep 17 00:00:00 2001 From: Ryan Roemer Date: Thu, 8 Sep 2022 15:12:38 -0700 Subject: [PATCH 04/10] Lint --- packages/trace-deps/lib/trace.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trace-deps/lib/trace.js b/packages/trace-deps/lib/trace.js index 3d7c063..3dd4dd7 100644 --- a/packages/trace-deps/lib/trace.js +++ b/packages/trace-deps/lib/trace.js @@ -354,7 +354,7 @@ const _resolveDep = async ({ } }); - console.log("TODO HERE", { depName, srcPath, conditions }) + console.log("TODO HERE", { depName, srcPath, conditions }); CONDITIONS.concat(conditions).forEach((cond) => { let resolveOpts; if (Array.isArray(cond)) { From c504513bf8e0baa0bd5a2ff640979fdbd8529d3a Mon Sep 17 00:00:00 2001 From: Ryan Roemer Date: Thu, 8 Sep 2022 15:37:39 -0700 Subject: [PATCH 05/10] Enumerate weird runtime Node.js user conditions case. --- packages/trace-deps/lib/trace.js | 2 +- packages/trace-deps/test/lib/trace.spec.js | 26 +++++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/trace-deps/lib/trace.js b/packages/trace-deps/lib/trace.js index 3dd4dd7..cae5d22 100644 --- a/packages/trace-deps/lib/trace.js +++ b/packages/trace-deps/lib/trace.js @@ -354,7 +354,6 @@ const _resolveDep = async ({ } }); - console.log("TODO HERE", { depName, srcPath, conditions }); CONDITIONS.concat(conditions).forEach((cond) => { let resolveOpts; if (Array.isArray(cond)) { @@ -362,6 +361,7 @@ const _resolveDep = async ({ cond = cond[0]; } + let relPath; try { relPath = resolveExports.resolve(pkg, depName, { diff --git a/packages/trace-deps/test/lib/trace.spec.js b/packages/trace-deps/test/lib/trace.spec.js index a6dd2b2..a7f7137 100644 --- a/packages/trace-deps/test/lib/trace.spec.js +++ b/packages/trace-deps/test/lib/trace.spec.js @@ -2650,12 +2650,24 @@ describe("lib/trace", () => { "package.json": stringify({ name: "three", main: "index.mjs", - type: "module" + 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({ @@ -2665,16 +2677,17 @@ describe("lib/trace", () => { exports: { ".": { bespoke: "./bespoke.mjs", - // TODO: Why does order matter. Not picked up if behind `import`??? + // 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 = 'three';", - "import.mjs": "export const msg = 'import';", - "production.mjs": "export const msg = 'production';", - "bespoke.mjs": "export const msg = 'bespoke';" + "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';" } } } @@ -2694,6 +2707,7 @@ describe("lib/trace", () => { "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", From b1b01f4f3fa9032e83b2b6b6e272fed923773885 Mon Sep 17 00:00:00 2001 From: Ryan Roemer Date: Thu, 8 Sep 2022 15:55:10 -0700 Subject: [PATCH 06/10] Add trace-pkg to changeset --- .changeset/lucky-cheetahs-relate.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/lucky-cheetahs-relate.md b/.changeset/lucky-cheetahs-relate.md index 2195ea5..0cf7782 100644 --- a/.changeset/lucky-cheetahs-relate.md +++ b/.changeset/lucky-cheetahs-relate.md @@ -1,5 +1,6 @@ --- "trace-deps": minor +"trace-pkg": minor --- - Feature: Add `conditions` parameter to `traceFile`/`traceFiles` to support user runtime loading conditions. From 40c4841bed3043c8f4ad97962ebbd01922232132 Mon Sep 17 00:00:00 2001 From: Ryan Roemer Date: Fri, 9 Sep 2022 19:35:25 -0700 Subject: [PATCH 07/10] trace-pkg: Add conditions option. Refactor tests into suites --- .changeset/lucky-cheetahs-relate.md | 3 +- packages/trace-pkg/README.md | 2 + packages/trace-pkg/lib/config.js | 1 + packages/trace-pkg/lib/worker/bundle.js | 2 + .../test/lib/actions/package.spec.js | 2140 +++++++++-------- 5 files changed, 1083 insertions(+), 1065 deletions(-) diff --git a/.changeset/lucky-cheetahs-relate.md b/.changeset/lucky-cheetahs-relate.md index 0cf7782..604d276 100644 --- a/.changeset/lucky-cheetahs-relate.md +++ b/.changeset/lucky-cheetahs-relate.md @@ -3,5 +3,6 @@ "trace-pkg": minor --- -- Feature: Add `conditions` parameter to `traceFile`/`traceFiles` to support user runtime loading conditions. +- 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-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..f52032d 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.ignconditionsores, 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..c123f11 100644 --- a/packages/trace-pkg/test/lib/actions/package.spec.js +++ b/packages/trace-pkg/test/lib/actions/package.spec.js @@ -26,1175 +26,1088 @@ 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"; - ` - } - }, - node_modules: { - "present-but-skipped": { - "package.json": JSON.stringify({ - main: "index.js" - }), - "index.js": "module.exports = 'present-but-skipped';" + module.exports = "dep"; + ` + } + }, + 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" - ] + }); + + 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(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("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")); - - // 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)` - ); - - // 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"); - 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); - 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 +1119,8 @@ describe("lib/actions/package", () => { } } } - } - }, - node_modules: { - "@scope": { + }, + node_modules: { dep: { "package.json": JSON.stringify({ main: "index.js", @@ -1218,70 +1129,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"); + ` } - }, - "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: { + packages: { + one: { + trace: [ + "packages/one/index.js" + ] + } + } + } + } + }); + + + 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 From a5e955a1647ba559e25802cef92605cefe8fd87f Mon Sep 17 00:00:00 2001 From: Ryan Roemer Date: Fri, 9 Sep 2022 19:36:39 -0700 Subject: [PATCH 08/10] Placeholder for tests --- packages/trace-pkg/test/lib/actions/package.spec.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/trace-pkg/test/lib/actions/package.spec.js b/packages/trace-pkg/test/lib/actions/package.spec.js index c123f11..d919c4d 100644 --- a/packages/trace-pkg/test/lib/actions/package.spec.js +++ b/packages/trace-pkg/test/lib/actions/package.spec.js @@ -533,6 +533,11 @@ describe("lib/actions/package", () => { }); }); + describe("conditions", () => { + it("TODO global conditions"); // TODO: IMPLEMENT + it("TODO package conditions"); // TODO: IMPLEMENT + }); + describe("allowMissing", () => { it("skips allowed missing packages", async () => { mock({ From b5df233b4d619a9b10230e2edb6eef12c57f9c93 Mon Sep 17 00:00:00 2001 From: Ryan Roemer Date: Fri, 9 Sep 2022 20:29:06 -0700 Subject: [PATCH 09/10] Add conditions test --- packages/trace-pkg/lib/config.js | 2 +- .../test/lib/actions/package.spec.js | 120 +++++++++++++++++- 2 files changed, 119 insertions(+), 3 deletions(-) diff --git a/packages/trace-pkg/lib/config.js b/packages/trace-pkg/lib/config.js index f52032d..34fb19d 100644 --- a/packages/trace-pkg/lib/config.js +++ b/packages/trace-pkg/lib/config.js @@ -72,7 +72,7 @@ const parseConfig = async ({ traceOptions: { includeSourceMaps: getBoolean(options.includeSourceMaps, pkgCfg.includeSourceMaps), ignores: smartConcat(options.ignores, pkgCfg.ignores), - conditions: smartConcat(options.ignconditionsores, pkgCfg.conditions), + conditions: smartConcat(options.conditions, pkgCfg.conditions), allowMissing: normalizeFileKeys({ cwd, map: smartMerge(options.allowMissing, pkgCfg.allowMissing) diff --git a/packages/trace-pkg/test/lib/actions/package.spec.js b/packages/trace-pkg/test/lib/actions/package.spec.js index d919c4d..03b2efd 100644 --- a/packages/trace-pkg/test/lib/actions/package.spec.js +++ b/packages/trace-pkg/test/lib/actions/package.spec.js @@ -534,8 +534,124 @@ describe("lib/actions/package", () => { }); describe("conditions", () => { - it("TODO global conditions"); // TODO: IMPLEMENT - it("TODO package conditions"); // TODO: IMPLEMENT + 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" + ]); + }); }); describe("allowMissing", () => { From 9b1bb5b75f387ea3deeb3af88a9f250fe744cf2d Mon Sep 17 00:00:00 2001 From: Ryan Roemer Date: Fri, 9 Sep 2022 20:30:19 -0700 Subject: [PATCH 10/10] Add back in accidentally removed option --- packages/trace-deps/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/trace-deps/README.md b/packages/trace-deps/README.md index 1c57ce1..13f0fe7 100644 --- a/packages/trace-deps/README.md +++ b/packages/trace-deps/README.md @@ -71,6 +71,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 * `extraImports` (`Object.`): Mapping of files to additional imports to trace.