enablement-build-monorepo-version 8.4.9 → 8.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@ npx enablement-build-monorepo-version@latest [flags]
14
14
 
15
15
  1. **Hash** — Each subfolder under the configured `children` directories is hashed (source files only; `node_modules`, `dist`, etc. are excluded).
16
16
  2. **Compare** — Hashes are diffed against state in `--hashFile`, which defaults to `{configDir}/hash.json`.
17
- 3. **Propagate** — Any package whose hash changed is marked `CHANGED`. All transitive dependents (read from `{configDir}/manifest.json` `dependencies` when available, or from `--dependencies` when explicitly provided) are also marked `CHANGED` via a breadth-first traversal.
17
+ 3. **Propagate** — After all workspace folders have been scanned, any package whose hash changed is marked `CHANGED`. All transitive dependents, including those in other workspace folders, are also marked `CHANGED` via a breadth-first traversal. The graph comes from `{configDir}/manifest.json` `dependents` (or inverted `dependencies` for manifests without `dependents`), or from the Nx graph supplied explicitly with `--dependencies`.
18
18
  4. **Version** — The next semver is determined from the current version found in each package's version manifest (`package.json`, `version.json`, or `pyproject.toml`) using conventional-commit rules.
19
19
  5. **Output** — Azure DevOps `##vso[task.setvariable]` lines are written to stdout for consumption by downstream pipeline steps.
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enablement-build-monorepo-version",
3
- "version": "8.4.9",
3
+ "version": "8.4.10",
4
4
  "description": "This detects changes in the children packages of a monorepo.",
5
5
  "type": "module",
6
6
  "module": "./src/index.mjs",
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "minimatch": "~10.1.1",
18
- "enablement-build-manifest": "1.8.9"
18
+ "enablement-build-manifest": "1.8.10"
19
19
  },
20
20
  "devDependencies": {
21
21
  "jest": "^30.4.2"
package/src/index.mjs CHANGED
@@ -9,7 +9,7 @@ const packageMetadata = JSON.parse(
9
9
  );
10
10
  const packageVersion = packageMetadata.version;
11
11
 
12
- import { dependencyMap, compare, getCurrentVersion, updateVersion, retagToPackageNames, detectOldFormatTags, resolveWorkspaceChildren, excludeChildren, applyInitScripts, RELEASE_SCRIPTS, emitVariable, hashElement, resolveHashFilePath, ensureHashFileExists, loadHashFile, writeHashFile } from "./lib.mjs";
12
+ import { dependencyMap, parseDependencyMap, compare, getCurrentVersion, updateVersion, retagToPackageNames, detectOldFormatTags, resolveWorkspaceChildren, excludeChildren, applyInitScripts, RELEASE_SCRIPTS, emitVariable, hashElement, resolveHashFilePath, ensureHashFileExists, loadHashFile, writeHashFile } from "./lib.mjs";
13
13
 
14
14
 
15
15
  function bumpPatchVersion(version, forcedSuffix) {
@@ -162,7 +162,11 @@ async function main(options) {
162
162
  if (shouldPreferManifestDependencies) {
163
163
  const manifestText = readFileSync(manifestDependencyPath, 'utf8');
164
164
  const manifest = JSON.parse(manifestText || '{}');
165
- dependencies = manifest.dependencies || {};
165
+ dependencies = manifest.dependents ?? parseDependencyMap(
166
+ Object.fromEntries(Object.entries(manifest.dependencies || {}).map(
167
+ ([name, targets]) => [name, targets.map(target => ({ target }))],
168
+ )),
169
+ );
166
170
  if (options.debug) console.log(`Loaded dependencies from ${manifestDependencyPath}\n`, JSON.stringify(dependencies));
167
171
  } else if (options.dependencies) {
168
172
  if (existsSync(options.dependencies)) {
@@ -211,6 +215,14 @@ async function main(options) {
211
215
 
212
216
  await collectPackageEntries(options.prefixPath, packageFolder, packageFolder, hashOptions, current, new Set());
213
217
 
218
+ resolve("OK");
219
+ }).catch(reject);
220
+ }));
221
+ });
222
+
223
+ Promise.all(scanlist).then(async () => {
224
+ const pendingOperations = [];
225
+ for (const packageFolder of children.split(',')) {
214
226
  if (options.changed || options.version || options.tag) {
215
227
  let results = await compare(packageFolder, previous, current, dependencies, options);
216
228
  if (options.debug) console.log("COMPARE", JSON.stringify(results));
@@ -231,14 +243,14 @@ async function main(options) {
231
243
  if (results[i].value.changed) {
232
244
  emitVariable(safeName, results[i].value.version, options.platform);
233
245
  if (options.saveVersion) {
234
- scanlist.push(updateVersion(packageFolder, results[i].value.name, results[i].value.version, options));
246
+ pendingOperations.push(updateVersion(packageFolder, results[i].value.name, results[i].value.version, options));
235
247
  }
236
248
  } else {
237
249
  emitVariable(safeName, results[i].value.previous, options.platform);
238
250
  }
239
251
  }
240
252
  if (options.tag) {
241
- scanlist.push(new Promise((resolve, reject) => {
253
+ pendingOperations.push(new Promise((resolve, reject) => {
242
254
  let v = results[i].value.version;
243
255
  if (!v) v = getCurrentVersion(options.prefixPath, packageFolder, results[i].value.name)?.version;
244
256
  const shortName = (results[i].value.fullName || results[i].value.name).replace(/^@[^/]+\//, '');
@@ -257,22 +269,15 @@ async function main(options) {
257
269
  });
258
270
  return;
259
271
  }
272
+ resolve(rev);
260
273
  });
261
274
  }));
262
275
  }
263
276
  }
264
277
  }
265
278
 
266
- resolve("OK");
267
- })
268
- .catch(error => {
269
- console.error('hashing failed:', error);
270
- resolve("OK");
271
- });
272
- }));
273
- });
274
-
275
- Promise.all(scanlist).then(async () => {
279
+ }
280
+ await Promise.all(pendingOperations);
276
281
  await updateRootVersionIfNeeded(options, changeList);
277
282
 
278
283
  if (options.changed) {
@@ -339,7 +344,7 @@ async function main(options) {
339
344
  } else {
340
345
  finish();
341
346
  }
342
- }, mainreject);
347
+ }).catch(mainreject);
343
348
  });
344
349
  }
345
350
 
package/src/lib.mjs CHANGED
@@ -317,7 +317,6 @@ export async function compare(
317
317
  // Collect directly-changed/new packages
318
318
  const changedNames = new Set();
319
319
  packages.forEach((name) => {
320
- if (current[name].packageFolder !== packageFolder) return;
321
320
  if (
322
321
  previous[current[name].fullName] === undefined ||
323
322
  current[name].hash !== previous[current[name].fullName].hash
@@ -120,6 +120,65 @@ describe("index CLI hash loading", () => {
120
120
  });
121
121
  });
122
122
 
123
+ describe("index CLI cross-folder propagation", () => {
124
+ it.each(["dependents", "dependencies", "nx"])(
125
+ "includes unchanged consumers across folders using %s",
126
+ graphSource => {
127
+ const root = setupWorkspace();
128
+ try {
129
+ const packages = [
130
+ ["ux/apps", "admin-ux", "@icg/x-saas-ux"],
131
+ ["ux/packages", "saas-package", "@lit/lit-saas-package"],
132
+ ["ux/saas", "manage-role", "@lit/lit-saas-manage-role"],
133
+ ["ux/apps", "unrelated", "@scope/unrelated"],
134
+ ];
135
+ for (const [folder, name, fullName] of packages) {
136
+ const directory = path.join(root, folder, name);
137
+ mkdirSync(directory, { recursive: true });
138
+ writeFileSync(path.join(directory, "package.json"), JSON.stringify({ name: fullName, version: "1.0.0" }));
139
+ writeFileSync(path.join(directory, "index.js"), "export default 1;\n");
140
+ }
141
+ const dependencies = {
142
+ "@icg/x-saas-ux": ["@lit/lit-saas-package"],
143
+ "@lit/lit-saas-package": ["@lit/lit-saas-manage-role"],
144
+ "@lit/lit-saas-manage-role": [],
145
+ };
146
+ const dependents = {
147
+ "@lit/lit-saas-manage-role": ["@lit/lit-saas-package"],
148
+ "@lit/lit-saas-package": ["@icg/x-saas-ux"],
149
+ };
150
+ writeFileSync(path.join(root, ".cicd/manifest.json"), JSON.stringify(
151
+ graphSource === "dependents" ? { dependencies, dependents } :
152
+ graphSource === "dependencies" ? { dependencies } : { dependencies: {} },
153
+ ));
154
+ const graphPath = path.join(root, "dependencies.json");
155
+ writeFileSync(graphPath, JSON.stringify({ graph: {
156
+ dependencies: Object.fromEntries(Object.entries(dependencies).map(
157
+ ([name, targets]) => [name, targets.map(target => ({ target }))],
158
+ )),
159
+ } }));
160
+ const run = (folders, saveHash = false) => execFileSync("node", [
161
+ cliPath, "--changed", "--children", folders, "--prefixPath", root,
162
+ ...(saveHash ? ["--hash"] : []),
163
+ ...(graphSource === "nx" ? ["--dependencies", graphPath] : []),
164
+ ], { cwd: repoRoot, encoding: "utf8" });
165
+ run("ux/apps,ux/packages,ux/saas", true);
166
+ writeFileSync(path.join(root, "ux/saas/manage-role/index.js"), "export default 2;\n");
167
+ for (const folders of ["ux/apps,ux/packages,ux/saas", "ux/saas,ux/packages,ux/apps"]) {
168
+ const stdout = run(folders);
169
+ const changed = JSON.parse(stdout.match(/CHANGED - (\[[^\n]*\])/)[1]);
170
+ expect(changed.sort()).toEqual(["admin-ux", "manage-role", "saas-package"]);
171
+ expect(stdout).toContain("variable=ux_apps;");
172
+ expect(stdout).toContain("variable=ux_packages;");
173
+ expect(stdout).toContain("variable=ux_saas;");
174
+ }
175
+ } finally {
176
+ rmSync(root, { recursive: true, force: true });
177
+ }
178
+ },
179
+ );
180
+ });
181
+
123
182
  describe("index CLI --exclude", () => {
124
183
  function setupWorkspaceWithTwoRoots() {
125
184
  const root = mkdtempSync(path.join(os.tmpdir(), "monorepo-version-"));
@@ -256,7 +256,7 @@ describe("compare", () => {
256
256
  expect(results[0].value.name).toBe("comp-a");
257
257
  });
258
258
 
259
- it("does not propagate cross-folder dependency changes into result", async () => {
259
+ it("propagates cross-folder changes while emitting only the requested folder", async () => {
260
260
  // leaf is in 'components', parent is in 'saas'
261
261
  // when comparing 'components', only comp packages appear in output
262
262
  const current = {
@@ -274,6 +274,9 @@ describe("compare", () => {
274
274
  );
275
275
  expect(results).toHaveLength(1);
276
276
  expect(results[0].value.name).toBe("leaf");
277
+ const parentResults = await compare("saas", previous, current, deps, opts);
278
+ expect(parentResults).toHaveLength(1);
279
+ expect(parentResults[0].value).toMatchObject({ name: "parent", changed: true });
277
280
  });
278
281
  });
279
282