Repository: endojs/endo
Pull request #2373 (2024-07-23 19:42:54)

refactor(bundle-source): Node.js conditions
## Description

The upcoming release was planned to introduce a `-t,--tags` flag to the `bundle-source` command. I’ve found that the analogous flag in the Node.js CLI is `-C,--condition` and elected to take this opportunity to make these consistent, before releasing the new feature.

### Security Considerations

None.

### Scaling Considerations

None.

### Documentation Considerations

Usage docs update. Not yet documented in README.

### Testing Considerations

Covered with a CLI test.

### Compatibility Considerations

This feature is not yet released, so the change to `bundle-source` has no compatibility concerns.

The change to `@endo/compartment-mapper` deprecates the `tags` option to all functions and aliases `conditions` for backward-compatibility.

### Upgrade Considerations

None.

dckc: help me find the ci log of the test running, plz?
kriskowal: Looks like I’ve got some ironing to do.

Patch:
diff --git a/packages/bundle-source/NEWS.md b/packages/bundle-source/NEWS.md
index a110edc50..f86e77ebe 100644
--- a/packages/bundle-source/NEWS.md
+++ b/packages/bundle-source/NEWS.md
@@ -11,8 +11,8 @@
 - Adds a `-f,--format` command flag to specify other module formats.
 - Adds a new `endoScript` module format.
 - Adds a no-cache, bundle-to-stdout mode.
-- Adds a `-t,--tag` command flag to specify export/import conditions like
-  `"browser"`.
+- Adds a `-C,--condition` command flag to specify export/import conditions like
+  `"development"` or `"browser"`.
 
 # v3.2.1 (2024-03-20)
 
diff --git a/packages/bundle-source/cache.js b/packages/bundle-source/cache.js
index 6506dbfc7..74220fa76 100644
--- a/packages/bundle-source/cache.js
+++ b/packages/bundle-source/cache.js
@@ -20,7 +20,7 @@ const { Fail, quote: q } = assert;
  * @property {number} bundleSize
  * @property {boolean} noTransforms
  * @property {ModuleFormat} format
- * @property {string[]} tags
+ * @property {string[]} conditions
  * @property {{ relative: string, absolute: string }} moduleSource
  * @property {Array<{ relativePath: string, mtime: string, size: number }>} contents
  */
@@ -53,6 +53,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
    * @param {object} [options]
    * @param {boolean} [options.noTransforms]
    * @param {string[]} [options.tags]
+   * @param {string[]} [options.conditions]
    * @param {ModuleFormat} [options.format]
    */
   const add = async (rootPath, targetName, log = defaultLog, options = {}) => {
@@ -61,10 +62,10 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
     const {
       noTransforms = false,
       format = 'endoZipBase64',
-      tags = [],
+      conditions = [],
     } = options;
 
-    tags.sort();
+    conditions.sort();
 
     const statsByPath = new Map();
 
@@ -93,7 +94,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
 
     const bundle = await bundleSource(
       rootPath,
-      { ...bundleOptions, noTransforms, format },
+      { ...bundleOptions, noTransforms, format, conditions },
       {
         ...readPowers,
         read: loggedRead,
@@ -123,7 +124,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
       ),
       noTransforms,
       format,
-      tags,
+      conditions,
     };
 
     await metaWr.atomicWriteText(JSON.stringify(meta, null, 2));
@@ -154,6 +155,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
    * @param {boolean} [options.noTransforms]
    * @param {ModuleFormat} [options.format]
    * @param {string[]} [options.tags]
+   * @param {string[]} [options.conditions]
    * @returns {Promise<BundleMeta>}
    */
   const validate = async (
@@ -167,9 +169,9 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
     const {
       noTransforms: expectedNoTransforms,
       format: expectedFormat,
-      tags: expectedTags = [],
+      conditions: expectedConditions = [],
     } = options;
-    expectedTags.sort();
+    expectedConditions.sort();
     if (!meta) {
       const metaJson = await loadMetaText(targetName, log);
       if (metaJson) {
@@ -194,15 +196,15 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
       moduleSource: { absolute: moduleSource },
       format = 'endoZipBase64',
       noTransforms = false,
-      tags = [],
+      conditions = [],
     } = meta;
-    tags.sort();
+    conditions.sort();
     assert.equal(bundleFileName, toBundleName(targetName));
     assert.equal(format, expectedFormat);
     assert.equal(noTransforms, expectedNoTransforms);
-    assert.equal(tags.length, expectedTags.length);
-    tags.forEach((tag, index) => {
-      assert.equal(tag, expectedTags[index]);
+    assert.equal(conditions.length, expectedConditions.length);
+    conditions.forEach((tag, index) => {
+      assert.equal(tag, expectedConditions[index]);
     });
     if (rootOpt) {
       moduleSource === cwd.neighbor(rootOpt).absolute() ||
@@ -250,7 +252,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
    * @param {object} [options]
    * @param {boolean} [options.noTransforms]
    * @param {ModuleFormat} [options.format]
-   * @param {string[]} [options.tags]
+   * @param {string[]} [options.conditions]
    * @returns {Promise<BundleMeta>}
    */
   const validateOrAdd = async (
@@ -269,7 +271,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
         meta = await validate(targetName, rootPath, log, meta, {
           format: options.format,
           noTransforms: options.noTransforms,
-          tags: options.tags,
+          conditions: options.conditions,
         });
         const {
           bundleTime,
@@ -277,7 +279,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
           contents,
           noTransforms,
           format = 'endoZipBase64',
-          tags = [],
+          conditions = [],
         } = meta;
         log(
           `${wr}`,
@@ -291,8 +293,8 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
           noTransforms ? 'w/o transforms' : 'with transforms',
           'with format',
           format,
-          'and tags',
-          JSON.stringify(tags),
+          'and conditions',
+          JSON.stringify(conditions),
         );
       } catch (invalid) {
         meta = undefined;
@@ -309,7 +311,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
         contents,
         noTransforms,
         format = 'endoZipBase64',
-        tags = [],
+        conditions = [],
       } = meta;
       log(
         `${wr}`,
@@ -322,8 +324,8 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
         noTransforms ? 'w/o transforms' : 'with transforms',
         'with format',
         format,
-        'and tags',
-        JSON.stringify(tags),
+        'and conditions',
+        JSON.stringify(conditions),
       );
     }
 
@@ -338,7 +340,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
    * @param {object} [options]
    * @param {boolean} [options.noTransforms]
    * @param {ModuleFormat} [options.format]
-   * @param {string[]} [options.tags]
+   * @param {string[]} [options.conditions]
    */
   const load = async (
     rootPath,
diff --git a/packages/bundle-source/src/main.js b/packages/bundle-source/src/main.js
index 4f31983c1..11bdd02c0 100644
--- a/packages/bundle-source/src/main.js
+++ b/packages/bundle-source/src/main.js
@@ -11,7 +11,7 @@ const USAGE = `\
 bundle-source [-Tft] <entry.js>
 bundle-source [-Tft] --cache-js|--cache-json <cache/> (<entry.js> <bundle-name>)*
   -f,--format endoZipBase64*|nestedEvaluate|getExport
-  -t,--tag <tag> (browser, node, &c)
+  -C,--condition <condition> (browser, node, development, &c)
   -T,--no-transforms`;
 
 const options = /** @type {const} */ ({
@@ -33,9 +33,9 @@ const options = /** @type {const} */ ({
     short: 'f',
     multiple: false,
   },
-  tag: {
+  condition: {
     type: 'string',
-    short: 't',
+    short: 'C',
     multiple: true,
   },
   // deprecated
@@ -58,7 +58,7 @@ export const main = async (args, { loadModule, pid, log }) => {
   const {
     values: {
       format: moduleFormat = 'endoZipBase64',
-      tag: tags = [],
+      condition: conditions = [],
       'no-transforms': noTransforms,
       'cache-json': cacheJson,
       'cache-js': cacheJs,
@@ -94,7 +94,7 @@ export const main = async (args, { loadModule, pid, log }) => {
     const bundle = await bundleSource(entryPath, {
       noTransforms,
       format,
-      tags,
+      conditions,
     });
     process.stdout.write(JSON.stringify(bundle));
     process.stdout.write('\n');
@@ -125,7 +125,7 @@ export const main = async (args, { loadModule, pid, log }) => {
     await cache.validateOrAdd(bundleRoot, bundleName, undefined, {
       noTransforms,
       format,
-      tags,
+      conditions,
     });
   }
 };
diff --git a/packages/bundle-source/src/script.js b/packages/bundle-source/src/script.js
index 2e430927b..3e08e8347 100644
--- a/packages/bundle-source/src/script.js
+++ b/packages/bundle-source/src/script.js
@@ -37,7 +37,7 @@ export async function bundleScript(
     dev = false,
     cacheSourceMaps = false,
     noTransforms = false,
-    tags = [],
+    conditions = [],
     commonDependencies,
   } = options;
   const powers = { ...readPowers, ...grantedPowers };
@@ -70,7 +70,7 @@ export async function bundleScript(
 
   const source = await makeBundle(powers, entry, {
     dev,
-    tags,
+    conditions,
     commonDependencies,
     parserForLanguage,
     moduleTransforms,
diff --git a/packages/bundle-source/src/types.js b/packages/bundle-source/src/types.js
index 836f1b432..087e0adab 100644
--- a/packages/bundle-source/src/types.js
+++ b/packages/bundle-source/src/types.js
@@ -70,7 +70,7 @@ export {};
  * @property {boolean} [noTransforms] - when true, generates a bundle with the
  * original sources instead of SES-shim specific ESM and CJS. This may become
  * default in a future major version.
- * @property {string[]} [tags] - conditions for package.json conditional
+ * @property {string[]} [conditions] - conditions for package.json conditional
  * exports and imports.
  */
 
diff --git a/packages/bundle-source/src/zip-base64.js b/packages/bundle-source/src/zip-base64.js
index e7bd1e29d..dbf7cb008 100644
--- a/packages/bundle-source/src/zip-base64.js
+++ b/packages/bundle-source/src/zip-base64.js
@@ -22,7 +22,7 @@ const readPowers = makeReadPowers({ fs, url, crypto });
  * @param {boolean} [options.dev]
  * @param {boolean} [options.cacheSourceMaps]
  * @param {boolean} [options.noTransforms]
- * @param {string[]} [options.tags]
+ * @param {string[]} [options.condditions]
  * @param {Record<string, string>} [options.commonDependencies]
  * @param {object} [grantedPowers]
  * @param {(bytes: string | Uint8Array) => string} [grantedPowers.computeSha512]
@@ -40,7 +40,7 @@ export async function bundleZipBase64(
     dev = false,
     cacheSourceMaps = false,
     noTransforms = false,
-    tags = [],
+    conditions = [],
     commonDependencies,
   } = options;
   const powers = { ...readPowers, ...grantedPowers };
@@ -73,7 +73,7 @@ export async function bundleZipBase64(
 
   const compartmentMap = await mapNodeModules(powers, entry, {
     dev,
-    tags,
+    conditions,
     commonDependencies,
   });
 
diff --git a/packages/compartment-mapper/NEWS.md b/packages/compartment-mapper/NEWS.md
index d98df7d92..d710cf475 100644
--- a/packages/compartment-mapper/NEWS.md
+++ b/packages/compartment-mapper/NEWS.md
@@ -22,6 +22,8 @@ User-visible changes to the compartment mapper:
   The new `import-lite.js` does not entrain `node-modules.js` and composes
   with potential alternative package discovery, storage, and locks.
 - Adds JSON module support to `makeBundle`.
+- Aliases and deprecates `tags` in favor of `conditions` to align with Node.js
+  terminology.
 
 # 0.9.0 (2023-08-07)
 
diff --git a/packages/compartment-mapper/README.md b/packages/compartment-mapper/README.md
index 5d2a88d24..68ed240a5 100644
--- a/packages/compartment-mapper/README.md
+++ b/packages/compartment-mapper/README.md
@@ -164,27 +164,15 @@ in a parent directory, under `node_modules`.
 The `main`, `browser`, and `exports` properties determine the modules each
 package exports to other compartments.
 
-The `exports` property describes [package entry points] and can be influenced
-by build _tags_.
-Currently, the only tag supported by the compartment mapper is `import`, which
-indicates that the module map should use ESM modules over CommonJS modules or
-other variants.
-
-> TODO
->
-> A future version may reveal other tags like `browser` to prepare an
-> application for use in a web client.
-> For this case, the compartment mapper would prepare a JSON manifest like an
-> `importmap` (if not precisely an `importmap`).
-> The "compartment map" would be consistent except when the dependency graph
-> changes so updates could be automated with a `postinstall` script.
-> Preparing a web application for production would follow a process similar to
-> creating an archive, but with the `browser` build tag.
-
-The `browser` and `require` tags are well-known but not yet supported.
-The `browser` tag will apply for compartment maps generated for use on the web.
-The `require` tag is a fallback for environments that do not support ESM and
-will never apply.
+The `exports` property describes [package entry points][] and can be influenced
+by build _conditions_.
+Currently, the only conditions supported by the compartment mapper are
+`import`, `browser`, and `endo`.
+The `imports` condition indicates that the module map should use ESM modules
+over CommonJS modules or other variants, and `endo`.
+The `browser` condition also draws in the `browser` property from
+`package.json` instead of `main`.
+The `endo` condition only indicates that this tool is in use.
 
 If no `exports` apply to the root of the compartment namespace (`"."`),
 the `main` property serves as a default.
@@ -284,7 +272,7 @@ Node.js platform.
 > For browser applications, the compartment mapper would use the translator
 > modules in two modes.
 > During development, the compartment mapper would be able to load the
-> translator in the client, with the `browser` tag.
+> translator in the client, with the `browser` condition.
 > The compartment mapper would also be able to run the translator in a separate
 > non-browser compartment during bundling, so the translator can be excluded
 > from the production application and archived applications.
@@ -392,18 +380,19 @@ The compartment map shape:
 // CompartmentMap describes how to prepare compartments
 // to run an application.
 type CompartmentMap = {
-  tags: Tags,
+  conditions: Conditions,
   entry: Entry,
   compartments: Record<CompartmentName, Compartment>,
   realms: Record<RealmName, Realm>, // TODO
 };
 
-// Tags are the build tags for the compartment.
+// Conditions influence which modules are selected
+// to represent the implementation of various modules.
 // These may include terms like "browser", meaning
 // each compartment uses the implementation of each
 // module suitable for use in a browser environment.
-type Tags = Array<Tag>;
-type Tag = string;
+type Conditions = Array<Condition>;
+type Condition = string;
 
 // Entry is a reference to the module that is the module to initially import.
 type Entry = CompartmentModule;
diff --git a/packages/compartment-mapper/src/archive-lite.js b/packages/compartment-mapper/src/archive-lite.js
index f48f62e0c..d7a70f19b 100644
--- a/packages/compartment-mapper/src/archive-lite.js
+++ b/packages/compartment-mapper/src/archive-lite.js
@@ -281,7 +281,7 @@ export const makeArchiveCompartmentMap = (compartmentMap, sources) => {
   const archiveSources = renameSources(sources, compartmentRenames);
 
   const archiveCompartmentMap = {
-    tags: [],
+    conditions: [],
     entry: {
       compartment: archiveEntryCompartmentName,
       module: entryModuleSpecifier,
diff --git a/packages/compartment-mapper/src/archive.js b/packages/compartment-mapper/src/archive.js
index 9eab2f1d3..f3d209fac 100644
--- a/packages/compartment-mapper/src/archive.js
+++ b/packages/compartment-mapper/src/archive.js
@@ -72,11 +72,11 @@ export const makeAndHashArchive = async (
  * @returns {Promise<Uint8Array>}
  */
 export const makeArchive = async (powers, moduleLocation, options = {}) => {
-  const { dev, tags, commonDependencies, policy } = options;
+  const { dev, tags, conditions = tags, commonDependencies, policy } = options;
 
   const compartmentMap = await mapNodeModules(powers, moduleLocation, {
     dev,
-    tags,
+    conditions,
     commonDependencies,
     policy,
   });
@@ -95,11 +95,11 @@ export const makeArchive = async (powers, moduleLocation, options = {}) => {
  * @returns {Promise<Uint8Array>}
  */
 export const mapLocation = async (powers, moduleLocation, options = {}) => {
-  const { dev, tags, commonDependencies, policy } = options;
+  const { dev, tags, conditions = tags, commonDependencies, policy } = options;
 
   const compartmentMap = await mapNodeModules(powers, moduleLocation, {
     dev,
-    tags,
+    conditions,
     commonDependencies,
     policy,
   });
@@ -114,11 +114,11 @@ export const mapLocation = async (powers, moduleLocation, options = {}) => {
  * @returns {Promise<string>}
  */
 export const hashLocation = async (powers, moduleLocation, options = {}) => {
-  const { dev, tags, commonDependencies, policy } = options;
+  const { dev, tags, conditions = tags, commonDependencies, policy } = options;
 
   const compartmentMap = await mapNodeModules(powers, moduleLocation, {
     dev,
-    tags,
+    conditions,
     commonDependencies,
     policy,
   });
@@ -140,10 +140,10 @@ export const writeArchive = async (
   moduleLocation,
   options = {},
 ) => {
-  const { dev, tags, commonDependencies, policy } = options;
+  const { dev, tags, conditions = tags, commonDependencies, policy } = options;
   const compartmentMap = await mapNodeModules(readPowers, moduleLocation, {
     dev,
-    tags,
+    conditions,
     commonDependencies,
     policy,
   });
diff --git a/packages/compartment-mapper/src/bundle.js b/packages/compartment-mapper/src/bundle.js
index bbee7f336..c7bcd78d0 100644
--- a/packages/compartment-mapper/src/bundle.js
+++ b/packages/compartment-mapper/src/bundle.js
@@ -224,14 +224,15 @@ export const makeBundle = async (readPowers, moduleLocation, options) => {
   const {
     moduleTransforms,
     dev,
-    tags: tagsOption,
+    tags,
+    conditions: conditionsOption = tags,
     searchSuffixes,
     commonDependencies,
     sourceMapHook = undefined,
     parserForLanguage: parserForLanguageOption = {},
     languageForExtension: languageForExtensionOption = {},
   } = options || {};
-  const tags = new Set(tagsOption);
+  const conditions = new Set(conditionsOption);
 
   const parserForLanguage = Object.freeze(
     Object.assign(
@@ -258,7 +259,7 @@ export const makeBundle = async (readPowers, moduleLocation, options) => {
   const compartmentMap = await compartmentMapForNodeModules(
     read,
     packageLocation,
-    tags,
+    conditions,
     packageDescriptor,
     moduleSpecifier,
     { dev, commonDependencies },
diff --git a/packages/compartment-mapper/src/capture-lite.js b/packages/compartment-mapper/src/capture-lite.js
index 0ba4efbc6..f584218e1 100644
--- a/packages/compartment-mapper/src/capture-lite.js
+++ b/packages/compartment-mapper/src/capture-lite.js
@@ -227,7 +227,7 @@ const captureCompartmentMap = (compartmentMap, sources) => {
   const captureSources = renameSources(sources, compartmentRenames);
 
   const captureCompartmentMap = {
-    tags: [],
+    conditions: [],
     entry: {
       compartment: captureEntryCompartmentName,
       module: entryModuleSpecifier,
diff --git a/packages/compartment-mapper/src/compartment-map.js b/packages/compartment-mapper/src/compartment-map.js
index 8b23459fe..ef5fd024b 100644
--- a/packages/compartment-mapper/src/compartment-map.js
+++ b/packages/compartment-mapper/src/compartment-map.js
@@ -85,20 +85,20 @@ const assertEmptyObject = (object, message) => {
 };
 
 /**
- * @param {unknown} tags
+ * @param {unknown} conditions
  * @param {string} url
  */
-const assertTags = (tags, url) => {
-  if (tags === undefined) return;
+const assertConditions = (conditions, url) => {
+  if (conditions === undefined) return;
   assert(
-    Array.isArray(tags),
-    `tags must be an array, got ${tags} in ${q(url)}`,
+    Array.isArray(conditions),
+    `conditions must be an array, got ${conditions} in ${q(url)}`,
   );
-  for (const [index, value] of enumerate(tags)) {
+  for (const [index, value] of enumerate(conditions)) {
     assert.typeof(
       value,
       'string',
-      `tags[${index}] must be a string, got ${value} in ${q(url)}`,
+      `conditions[${index}] must be a string, got ${value} in ${q(url)}`,
     );
   }
 };
@@ -480,14 +480,20 @@ export const assertCompartmentMap = (
       url,
     )}`,
   );
-  const { tags, entry, compartments, ...extra } = Object(compartmentMap);
+  const {
+    tags,
+    conditions = tags,
+    entry,
+    compartments,
+    ...extra
+  } = Object(compartmentMap);
   assertEmptyObject(
     extra,
     `Compartment map must not have extra properties, got ${q(
       Object.keys(extra),
     )} in ${q(url)}`,
   );
-  assertTags(tags, url);
+  assertConditions(conditions, url);
   assertEntry(entry, url);
   assertCompartments(compartments, url);
 };
diff --git a/packages/compartment-mapper/src/import.js b/packages/compartment-mapper/src/import.js
index 578a1f726..151b4c398 100644
--- a/packages/compartment-mapper/src/import.js
+++ b/packages/compartment-mapper/src/import.js
@@ -48,10 +48,10 @@ export const loadLocation = async (
   moduleLocation,
   options = {},
 ) => {
-  const { dev, tags, commonDependencies, policy } = options;
+  const { dev, tags, conditions = tags, commonDependencies, policy } = options;
   const compartmentMap = await mapNodeModules(readPowers, moduleLocation, {
     dev,
-    tags,
+    conditions,
     commonDependencies,
     policy,
   });
diff --git a/packages/compartment-mapper/src/infer-exports.js b/packages/compartment-mapper/src/infer-exports.js
index 436a14577..f0be444a9 100644
--- a/packages/compartment-mapper/src/infer-exports.js
+++ b/packages/compartment-mapper/src/infer-exports.js
@@ -57,15 +57,15 @@ function* interpretBrowserField(name, browser, main = 'index.js') {
 /**
  * @param {string} name - the name of the referrer package.
  * @param {object} exports - the `exports` field from a package.json.
- * @param {Set<string>} tags - build tags about the target environment
+ * @param {Set<string>} conditions - build conditions about the target environment
  * for selecting relevant exports, e.g., "browser" or "node".
  * @yields {[string, string]}
  * @returns {Generator<[string, string]>}
  */
-function* interpretExports(name, exports, tags) {
+function* interpretExports(name, exports, conditions) {
   if (isArray(exports)) {
     for (const section of exports) {
-      const results = [...interpretExports(name, section, tags)];
+      const results = [...interpretExports(name, section, conditions)];
       if (results.length > 0) {
         yield* results;
         break;
@@ -84,12 +84,12 @@ function* interpretExports(name, exports, tags) {
   for (const [key, value] of entries(exports)) {
     if (key.startsWith('./') || key === '.') {
       if (name === '.') {
-        yield* interpretExports(key, value, tags);
+        yield* interpretExports(key, value, conditions);
       } else {
-        yield* interpretExports(join(name, key), value, tags);
+        yield* interpretExports(join(name, key), value, conditions);
       }
-    } else if (tags.has(key)) {
-      yield* interpretExports(name, value, tags);
+    } else if (conditions.has(key)) {
+      yield* interpretExports(name, value, conditions);
       // Take only the first matching tag.
       break;
     }
@@ -110,7 +110,7 @@ function* interpretExports(name, exports, tags) {
  * @param {string} packageDescriptor.main
  * @param {string} [packageDescriptor.module]
  * @param {object} [packageDescriptor.exports]
- * @param {Set<string>} tags - build tags about the target environment
+ * @param {Set<string>} conditions - build conditions about the target environment
  * for selecting relevant exports, e.g., "browser" or "node".
  * @param {LanguageForExtension} types - an object to populate
  * with any recognized module's type, if implied by a tag.
@@ -118,12 +118,12 @@ function* interpretExports(name, exports, tags) {
  */
 export const inferExportsEntries = function* inferExportsEntries(
   { main, module, exports },
-  tags,
+  conditions,
   types,
 ) {
   // From lowest to highest precedence, such that later entries override former
   // entries.
-  if (module !== undefined && tags.has('import')) {
+  if (module !== undefined && conditions.has('import')) {
     // In this one case, the key "module" has carried a hint that the
     // referenced module is an ECMASCript module, and that hint may be
     // necessary to override whatever type might be inferred from the module
@@ -135,7 +135,7 @@ export const inferExportsEntries = function* inferExportsEntries(
     yield ['.', relativize(main)];
   }
   if (exports !== undefined) {
-    yield* interpretExports('.', exports, tags);
+    yield* interpretExports('.', exports, conditions);
   }
   // TODO Otherwise, glob 'files' for all '.js', '.cjs', and '.mjs' entry
   // modules, taking care to exclude node_modules.
@@ -151,20 +151,20 @@ export const inferExportsEntries = function* inferExportsEntries(
  * package's module map, like `./index.js`.
  *
  * @param {object} descriptor - the parsed body of a package.json file.
- * @param {Set<string>} tags - build tags about the target environment
+ * @param {Set<string>} conditions - build conditions about the target environment
  * for selecting relevant exports, e.g., "browser" or "node".
  * @param {LanguageForExtension} types - an object to populate
  * with any recognized module's type, if implied by a tag.
  * @returns {Record<string, string>}
  */
-export const inferExports = (descriptor, tags, types) =>
-  fromEntries(inferExportsEntries(descriptor, tags, types));
+export const inferExports = (descriptor, conditions, types) =>
+  fromEntries(inferExportsEntries(descriptor, conditions, types));
 
 export const inferExportsAndAliases = (
   descriptor,
   externalAliases,
   internalAliases,
-  tags,
+  conditions,
   types,
 ) => {
   const { name, type, main, module, exports, browser } = descriptor;
@@ -172,7 +172,7 @@ export const inferExportsAndAliases = (
   // collect externalAliases from exports and main/module
   assign(
     externalAliases,
-    fromEntries(inferExportsEntries(descriptor, tags, types)),
+    fromEntries(inferExportsEntries(descriptor, conditions, types)),
   );
 
   // expose default module as package root
@@ -188,7 +188,7 @@ export const inferExportsAndAliases = (
   }
 
   // if present, allow "browser" field to populate moduleMap
-  if (tags.has('browser') && browser !== undefined) {
+  if (conditions.has('browser') && browser !== undefined) {
     for (const [specifier, target] of interpretBrowserField(
       name,
       browser,
diff --git a/packages/compartment-mapper/src/node-modules.js b/packages/compartment-mapper/src/node-modules.js
index c4290b2d6..f57d1a2f0 100644
--- a/packages/compartment-mapper/src/node-modules.js
+++ b/packages/compartment-mapper/src/node-modules.js
@@ -276,7 +276,7 @@ const inferParsers = (
  * @param {object} packageDetails
  * @param {string} packageDetails.packageLocation
  * @param {object} packageDetails.packageDescriptor
- * @param {Set<string>} tags
+ * @param {Set<string>} conditions
  * @param {boolean} dev
  * @param {CommonDependencyDescriptors} commonDependencyDescriptors
  * @param {Map<string, Array<string>>} preferredPackageLogicalPathMap
@@ -289,7 +289,7 @@ const graphPackage = async (
   canonical,
   graph,
   { packageLocation, packageDescriptor },
-  tags,
+  conditions,
   dev,
   commonDependencyDescriptors,
   preferredPackageLogicalPathMap = new Map(),
@@ -359,7 +359,7 @@ const graphPackage = async (
         dependencyLocations,
         packageLocation,
         name,
-        tags,
+        conditions,
         preferredPackageLogicalPathMap,
         childLogicalPath,
         optional,
@@ -388,7 +388,7 @@ const graphPackage = async (
     packageDescriptor,
     externalAliases,
     internalAliases,
-    tags,
+    conditions,
     types,
   );
 
@@ -453,7 +453,7 @@ const graphPackage = async (
  * @param {Record<string, string>} dependencyLocations
  * @param {string} packageLocation - location of the package of interest.
  * @param {string} name - name of the package of interest.
- * @param {Set<string>} tags
+ * @param {Set<string>} conditions
  * @param {Map<string, Array<string>>} preferredPackageLogicalPathMap
  * @param {Array<string>} [childLogicalPath]
  * @param {boolean} [optional] - whether the dependency is optional
@@ -466,7 +466,7 @@ const gatherDependency = async (
   dependencyLocations,
   packageLocation,
   name,
-  tags,
+  conditions,
   preferredPackageLogicalPathMap,
   childLogicalPath = [],
   optional = false,
@@ -501,7 +501,7 @@ const gatherDependency = async (
     canonical,
     graph,
     dependency,
-    tags,
+    conditions,
     false,
     commonDependencyDescriptors,
     preferredPackageLogicalPathMap,
@@ -521,7 +521,7 @@ const gatherDependency = async (
  * @param {MaybeReadFn} maybeRead
  * @param {CanonicalFn} canonical
  * @param {string} packageLocation - location of the main package.
- * @param {Set<string>} tags
+ * @param {Set<string>} conditions
  * @param {object} mainPackageDescriptor - the parsed contents of the main
  * package.json, which was already read when searching for the package.json.
  * @param {boolean} dev - whether to use devDependencies from this package (and
@@ -532,7 +532,7 @@ const graphPackages = async (
   maybeRead,
   canonical,
   packageLocation,
-  tags,
+  conditions,
   mainPackageDescriptor,
   dev,
   commonDependencies = {},
@@ -551,10 +551,10 @@ const graphPackages = async (
 
   const packageDescriptor = await readDescriptor(packageLocation);
 
-  tags = new Set(tags || []);
-  tags.add('import');
-  tags.add('default');
-  tags.add('endo');
+  conditions = new Set(conditions || []);
+  conditions.add('import');
+  conditions.add('default');
+  conditions.add('endo');
 
   if (packageDescriptor === undefined) {
     throw Error(
@@ -589,7 +589,7 @@ const graphPackages = async (
       packageLocation,
       packageDescriptor,
     },
-    tags,
+    conditions,
     dev,
     commonDependencyDescriptors,
   );
@@ -603,7 +603,7 @@ const graphPackages = async (
  * @param {string} entryPackageLocation
  * @param {string} entryModuleSpecifier
  * @param {Graph} graph
- * @param {Set<string>} tags - build tags about the target environment
+ * @param {Set<string>} conditions - build conditions about the target environment
  * for selecting relevant exports, e.g., "browser" or "node".
  * @param {import('./types.js').Policy} [policy]
  * @returns {CompartmentMapDescriptor}
@@ -612,7 +612,7 @@ const translateGraph = (
   entryPackageLocation,
   entryModuleSpecifier,
   graph,
-  tags,
+  conditions,
   policy,
 ) => {
   /** @type {Record<string, CompartmentDescriptor>} */
@@ -728,7 +728,7 @@ const translateGraph = (
   }
 
   return {
-    tags: [...tags],
+    conditions: [...conditions],
     entry: {
       compartment: entryPackageLocation,
       module: entryModuleSpecifier,
@@ -740,7 +740,7 @@ const translateGraph = (
 /**
  * @param {ReadFn | ReadPowers | MaybeReadPowers} readPowers
  * @param {string} packageLocation
- * @param {Set<string>} tags
+ * @param {Set<string>} conditions
  * @param {object} packageDescriptor
  * @param {string} moduleSpecifier
  * @param {object} [options]
@@ -752,7 +752,7 @@ const translateGraph = (
 export const compartmentMapForNodeModules = async (
   readPowers,
   packageLocation,
-  tags,
+  conditions,
   packageDescriptor,
   moduleSpecifier,
   options = {},
@@ -763,7 +763,7 @@ export const compartmentMapForNodeModules = async (
     maybeRead,
     canonical,
     packageLocation,
-    tags,
+    conditions,
     packageDescriptor,
     dev,
     commonDependencies,
@@ -789,7 +789,7 @@ export const compartmentMapForNodeModules = async (
     packageLocation,
     moduleSpecifier,
     graph,
-    tags,
+    conditions,
     policy,
   );
 
@@ -800,7 +800,8 @@ export const compartmentMapForNodeModules = async (
  * @param {ReadFn | ReadPowers | MaybeReadPowers} readPowers
  * @param {string} moduleLocation
  * @param {object} [options]
- * @param {Set<string>} [options.tags]
+ * @param {Set<string>} [options.tags] deprecated in favor of `conditions`
+ * @param {Set<string>} [options.conditions]
  * @param {boolean} [options.dev]
  * @param {object} [options.commonDependencies]
  * @param {object} [options.policy]
@@ -811,7 +812,13 @@ export const mapNodeModules = async (
   moduleLocation,
   options = {},
 ) => {
-  const { tags = new Set(), dev = false, commonDependencies, policy } = options;
+  const {
+    tags = new Set(),
+    conditions = tags,
+    dev = false,
+    commonDependencies,
+    policy,
+  } = options;
 
   const { maybeRead } = unpackReadPowers(readPowers);
 
@@ -830,7 +837,7 @@ export const mapNodeModules = async (
   return compartmentMapForNodeModules(
     readPowers,
     packageLocation,
-    tags,
+    conditions,
     packageDescriptor,
     moduleSpecifier,
     { dev, commonDependencies, policy },
diff --git a/packages/compartment-mapper/src/types.js b/packages/compartment-mapper/src/types.js
index c10fd28f2..800c663df 100644
--- a/packages/compartment-mapper/src/types.js
+++ b/packages/compartment-mapper/src/types.js
@@ -17,7 +17,8 @@ export {};
  * Compartments, each corresponding to Node.js style packaged modules.
  *
  * @typedef {object} CompartmentMapDescriptor
- * @property {Array<string>} tags
+ * @property {Array<string>} [tags] is deprecated in favor of `conditions`
+ * @property {Array<string>} conditions
  * @property {EntryDescriptor} entry
  * @property {Record<string, CompartmentDescriptor>} compartments
  */
@@ -424,7 +425,8 @@ export {};
  * @property {Record<string, any>} [modules]
  * @property {boolean} [dev]
  * @property {SomePolicy} [policy]
- * @property {Set<string>} [tags]
+ * @property {Set<string>} [tags] deprecated in favor of `conditions`
+ * @property {Set<string>} [conditions]
  * @property {CaptureSourceLocationHook} [captureSourceLocation]
  * @property {ExitModuleImportHook} [importHook]
  * @property {Array<string>} [searchSuffixes]
@@ -627,7 +629,8 @@ export {};
  * @property {Record<string, any>} [modules]
  * @property {boolean} [dev]
  * @property {SomePolicy} [policy]
- * @property {Set<string>} [tags]
+ * @property {Set<string>} [tags] deprecated in favor of `conditions`
+ * @property {Set<string>} [conditions]
  * @property {ExitModuleImportHook} [importHook]
  * @property {Array<string>} [searchSuffixes]
  * @property {Record<string, string>} [commonDependencies]


Test patch:
diff --git a/packages/bundle-source/test/tags-command.test.js b/packages/bundle-source/test/tags-command.test.js
index ead4ccd88..c0a4065ff 100644
--- a/packages/bundle-source/test/tags-command.test.js
+++ b/packages/bundle-source/test/tags-command.test.js
@@ -40,11 +40,11 @@ const bundleSource = async (...args) => {
   return JSON.parse(bundleText);
 };
 
-test('bundle-source with --format and --tag', async t => {
+test('bundle-source with --format and --condition', async t => {
   const compartment = new Compartment();
   {
     const bundle = await bundleSource(
-      '--tag',
+      '--condition',
       'b',
       '--format',
       'endoScript',
@@ -55,7 +55,7 @@ test('bundle-source with --format and --tag', async t => {
   }
   {
     const bundle = await bundleSource(
-      '--tag',
+      '--condition',
       'a',
       '--format',
       'endoScript',
diff --git a/packages/compartment-mapper/test/infer-exports.test.js b/packages/compartment-mapper/test/infer-exports.test.js
index 9793a4e02..ee8189cc4 100644
--- a/packages/compartment-mapper/test/infer-exports.test.js
+++ b/packages/compartment-mapper/test/infer-exports.test.js
@@ -2,11 +2,11 @@ import test from 'ava';
 import { inferExports } from '../src/infer-exports.js';
 
 function scaffold(cases) {
-  const tags = new Set(['node', 'import', 'default']);
+  const conditions = new Set(['node', 'import', 'default']);
   cases.forEach(pkg => {
     test(`infer-exports for ${pkg.name}`, t => {
       const types = {};
-      const exports = inferExports(pkg, tags, types);
+      const exports = inferExports(pkg, conditions, types);
       t.snapshot({ types, exports });
     });
   });
diff --git a/packages/compartment-mapper/test/main.test.js b/packages/compartment-mapper/test/main.test.js
index ab1806d88..28a5e8179 100644
--- a/packages/compartment-mapper/test/main.test.js
+++ b/packages/compartment-mapper/test/main.test.js
@@ -207,7 +207,7 @@ scaffold(
     });
   },
   1,
-  { tags: new Set(['browser']) },
+  { conditions: new Set(['browser']) },
 );
 
 scaffold(
diff --git a/packages/compartment-mapper/test/policy.test.js b/packages/compartment-mapper/test/policy.test.js
index 72f1e8757..547fcd0c9 100644
--- a/packages/compartment-mapper/test/policy.test.js
+++ b/packages/compartment-mapper/test/policy.test.js
@@ -200,7 +200,7 @@ scaffold(
       },
     },
     // 'eve' defines a browser override for 'dan' pointing to 'hackity'
-    tags: new Set(['browser']),
+    conditions: new Set(['browser']),
   },
 );
 
@@ -222,7 +222,7 @@ scaffold(
     addGlobals: globals,
     policy,
     // This turns alice malicious - attempting to redirect alice.js to an outside module
-    tags: new Set(['browser']),
+    conditions: new Set(['browser']),
   },
 );
 
diff --git a/packages/compartment-mapper/test/scaffold.js b/packages/compartment-mapper/test/scaffold.js
index 7876649d4..bf1303283 100644
--- a/packages/compartment-mapper/test/scaffold.js
+++ b/packages/compartment-mapper/test/scaffold.js
@@ -87,6 +87,7 @@ export function scaffold(
     policy,
     knownFailure = false,
     tags = undefined,
+    conditions = tags,
     searchSuffixes = undefined,
     commonDependencies = undefined,
     additionalOptions = {},
@@ -130,7 +131,7 @@ export function scaffold(
     const application = await loadLocation(readPowers, fixture, {
       dev: true,
       policy,
-      tags,
+      conditions,
       searchSuffixes,
       commonDependencies,
       ...additionalOptions,
@@ -154,7 +155,7 @@ export function scaffold(
       modules,
       Compartment,
       dev: true,
-      tags,
+      conditions,
       searchSuffixes,
       commonDependencies,
       ...additionalOptions,
@@ -172,7 +173,7 @@ export function scaffold(
         modules,
         dev: true,
         policy,
-        tags,
+        conditions,
         searchSuffixes,
         commonDependencies,
         ...additionalOptions,
@@ -209,7 +210,7 @@ export function scaffold(
         modules,
         dev: true,
         policy,
-        tags,
+        conditions,
         searchSuffixes,
         commonDependencies,
         ...additionalOptions,
@@ -254,7 +255,7 @@ export function scaffold(
         modules: { builtin: true },
         dev: true,
         policy,
-        tags,
+        conditions,
         searchSuffixes,
         commonDependencies,
         ...additionalOptions,
@@ -307,7 +308,7 @@ export function scaffold(
         policy,
         modules,
         dev: true,
-        tags,
+        conditions,
         searchSuffixes,
         commonDependencies,
         sourceMapHook,
@@ -344,7 +345,7 @@ export function scaffold(
         modules,
         Compartment,
         dev: true,
-        tags,
+        conditions,
         searchSuffixes,
         commonDependencies,
         ...additionalOptions,
@@ -353,7 +354,7 @@ export function scaffold(
       const archiveBytes = await makeArchive(readPowers, fixture, {
         modules,
         dev: true,
-        tags,
+        conditions,
         searchSuffixes,
         commonDependencies,
         ...additionalOptions,
@@ -366,7 +367,7 @@ export function scaffold(
         {
           modules,
           dev: true,
-          tags,
+          conditions,
           computeSha512,
           expectedSha512,
           ...additionalOptions,
@@ -384,7 +385,7 @@ export function scaffold(
         modules,
         Compartment,
         dev: true,
-        tags,
+        conditions,
         searchSuffixes,
         commonDependencies,
         ...additionalOptions,
@@ -393,7 +394,7 @@ export function scaffold(
       const archive = await makeArchive(readPowers, fixture, {
         modules,
         dev: true,
-        tags,
+        conditions,
         searchSuffixes,
         commonDependencies,
         ...additionalOptions,