enablement-build-monorepo-version 6.0.0 → 8.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enablement-build-monorepo-version",
3
- "version": "6.0.0",
3
+ "version": "8.2.0",
4
4
  "description": "This detects changes in the children packages of a monorepo.",
5
5
  "type": "module",
6
6
  "module": "./src/index.mjs",
@@ -14,7 +14,7 @@
14
14
  ]
15
15
  },
16
16
  "dependencies": {
17
- "enablement-build-manifest": "1.4.0"
17
+ "enablement-build-manifest": "1.7.0"
18
18
  },
19
19
  "devDependencies": {
20
20
  "jest": "^30.4.2"
package/src/index.mjs CHANGED
@@ -44,6 +44,35 @@ async function updateRootVersionIfNeeded(options, changeList) {
44
44
  }
45
45
 
46
46
 
47
+
48
+ async function collectPackageEntries(prefixPath, scanFolder, packageFolder, hashOptions, current, seenDirs = new Set()) {
49
+ const dirPath = path.join(prefixPath, scanFolder);
50
+ const dirKey = path.resolve(dirPath);
51
+ if (!existsSync(dirPath) || !lstatSync(dirPath).isDirectory()) return;
52
+ if (seenDirs.has(dirKey)) return;
53
+ seenDirs.add(dirKey);
54
+
55
+ const hash = await hashElement(dirPath, hashOptions);
56
+ const children = hash.children || [];
57
+
58
+ for (let i = 0; i < children.length; i++) {
59
+ const child = children[i];
60
+ const name = child.name;
61
+ if (name.substring(0, 1) === "_" || name === "version") continue;
62
+
63
+ const childPath = path.join(dirPath, name);
64
+ if (!existsSync(childPath) || lstatSync(childPath).isFile()) continue;
65
+
66
+ const versionInfo = getCurrentVersion(prefixPath, scanFolder, name);
67
+ if (versionInfo.fullName) {
68
+ current[name] = { hash: child.hash, ...versionInfo, packageFolder };
69
+ continue;
70
+ }
71
+
72
+ await collectPackageEntries(prefixPath, path.join(scanFolder, name), packageFolder, hashOptions, current, seenDirs);
73
+ }
74
+ }
75
+
47
76
  async function main(options) {
48
77
  return new Promise((mainResolve, mainreject) => {
49
78
  const hashOptions = { encoding: 'hex', folders: { exclude: options.hashExcludeFolders }, files: { exclude: options.hashExcludeFiles } };
@@ -101,7 +130,7 @@ async function main(options) {
101
130
  }
102
131
  }
103
132
 
104
- // Load previous hashes, keyed by short package folder name.
133
+ // Load previous hashes, keyed by full package name.
105
134
  previous = loadHashFile(changeConfig);
106
135
  if (options.debug) console.log("PREVIOUS", JSON.stringify(previous));
107
136
 
@@ -131,16 +160,7 @@ async function main(options) {
131
160
  const children = hash.children;
132
161
  if (options.debug) console.log("HASH", JSON.stringify(children));
133
162
 
134
- for (let i = 0; i < children.length; i++) {
135
- let name = children[i].name;
136
- if (name.substring(0, 1) === "_" || name === "version") continue;
137
- if (lstatSync(path.join(options.prefixPath, packageFolder, name)).isFile()) continue;
138
- if (!existsSync(path.join(options.prefixPath, packageFolder, name))) continue;
139
-
140
- delete children[i].children;
141
- let v = getCurrentVersion(options.prefixPath, packageFolder, name);
142
- current[name] = { hash: children[i].hash, ...v };
143
- }
163
+ await collectPackageEntries(options.prefixPath, packageFolder, packageFolder, hashOptions, current, new Set());
144
164
 
145
165
  if (options.changed || options.version || options.tag) {
146
166
  let results = await compare(packageFolder, previous, current, dependencies, options);
@@ -194,16 +214,6 @@ async function main(options) {
194
214
  }
195
215
  }
196
216
 
197
- // save current hashes
198
- if (options.hash) {
199
- let names = Object.keys(current);
200
- for (let i = 0; i < names.length; i++) {
201
- if (current[names[i]].packageFolder !== packageFolder) continue;
202
- let v = getCurrentVersion(options.prefixPath, packageFolder, names[i]);
203
- current[names[i]].version = v.version;
204
- current[names[i]].fullName = v.fullName;
205
- }
206
- }
207
217
  resolve("OK");
208
218
  })
209
219
  .catch(error => {
@@ -233,7 +243,12 @@ async function main(options) {
233
243
  if (options.try) {
234
244
  console.log('\x1b[36m%s\x1b[0m', `[try] Would write updated hashes to ${changeConfig}`);
235
245
  } else {
236
- writeHashFile(changeConfig, current, { usesManifestHashFile });
246
+ const hashesByFullName = Object.fromEntries(
247
+ Object.entries(current)
248
+ .filter(([, entry]) => entry && entry.fullName)
249
+ .map(([, entry]) => [entry.fullName, entry])
250
+ );
251
+ writeHashFile(changeConfig, hashesByFullName, { usesManifestHashFile });
237
252
  console.log('folder hashes written successfully');
238
253
  }
239
254
  }
package/src/lib.mjs CHANGED
@@ -319,8 +319,8 @@ export async function compare(
319
319
  packages.forEach((name) => {
320
320
  if (current[name].packageFolder !== packageFolder) return;
321
321
  if (
322
- previous[name] === undefined ||
323
- current[name].hash !== previous[name].hash
322
+ previous[current[name].fullName] === undefined ||
323
+ current[name].hash !== previous[current[name].fullName].hash
324
324
  )
325
325
  changedNames.add(name);
326
326
  });
@@ -342,8 +342,9 @@ export async function compare(
342
342
  // Emit one result per package — no duplicates
343
343
  packages.forEach((name) => {
344
344
  if (current[name].packageFolder !== packageFolder) return;
345
- const isNew = previous[name] === undefined;
346
- const isDirect = !isNew && current[name].hash !== previous[name].hash;
345
+ const previousPackage = previous[current[name].fullName];
346
+ const isNew = previousPackage === undefined;
347
+ const isDirect = !isNew && current[name].hash !== previousPackage.hash;
347
348
  const status = isNew
348
349
  ? "NEW"
349
350
  : changedNames.has(name)
@@ -363,7 +364,7 @@ export async function compare(
363
364
  current[name].version,
364
365
  status,
365
366
  options,
366
- previous[name]?.hash,
367
+ previousPackage?.hash,
367
368
  current[name].hash,
368
369
  parent,
369
370
  current[name].fullName,
@@ -94,8 +94,8 @@ describe("index CLI hash loading", () => {
94
94
 
95
95
  const hashJson = JSON.parse(readFileSync(path.join(root, ".cicd", "hash.json"), "utf8"));
96
96
  expect(hashJson.version).toBe(2);
97
- expect(typeof hashJson.hash.example).toBe("string");
98
- expect(hashJson.hash.example).toBeTruthy();
97
+ expect(typeof hashJson.hash["@scope/example"]).toBe("string");
98
+ expect(hashJson.hash["@scope/example"]).toBeTruthy();
99
99
  } finally {
100
100
  rmSync(root, { recursive: true, force: true });
101
101
  }
@@ -112,8 +112,8 @@ describe("index CLI hash loading", () => {
112
112
 
113
113
  const hashJson = JSON.parse(readFileSync(path.join(root, ".cicd", "hash.json"), "utf8"));
114
114
  expect(hashJson.version).toBe(2);
115
- expect(typeof hashJson.hash.example).toBe("string");
116
- expect(hashJson.hash.example).toBeTruthy();
115
+ expect(typeof hashJson.hash["@scope/example"]).toBe("string");
116
+ expect(hashJson.hash["@scope/example"]).toBeTruthy();
117
117
  } finally {
118
118
  rmSync(root, { recursive: true, force: true });
119
119
  }
@@ -164,8 +164,8 @@ describe("index CLI --exclude", () => {
164
164
  expect(changed).not.toContain("skip-me");
165
165
 
166
166
  const hashJson = JSON.parse(readFileSync(path.join(root, ".cicd", "hash.json"), "utf8"));
167
- expect(hashJson.hash.example).toBeTruthy();
168
- expect(hashJson.hash["skip-me"]).toBeUndefined();
167
+ expect(hashJson.hash["@scope/example"]).toBeTruthy();
168
+ expect(hashJson.hash["@scope/skip-me"]).toBeUndefined();
169
169
  } finally {
170
170
  rmSync(root, { recursive: true, force: true });
171
171
  }
@@ -198,7 +198,49 @@ describe("index CLI --children defaulting", () => {
198
198
  expect(JSON.parse(changedMatch[1])).toContain("example");
199
199
 
200
200
  const hashJson = JSON.parse(readFileSync(path.join(root, ".cicd", "hash.json"), "utf8"));
201
- expect(hashJson.hash.example).toBeTruthy();
201
+ expect(hashJson.hash["@scope/example"]).toBeTruthy();
202
+ } finally {
203
+ rmSync(root, { recursive: true, force: true });
204
+ }
205
+ });
206
+ });
207
+
208
+ describe("index CLI nested workspace roots", () => {
209
+ it("skips non-package folders and discovers nested package roots under a child directory", () => {
210
+ const root = mkdtempSync(path.join(os.tmpdir(), "monorepo-version-"));
211
+ try {
212
+ mkdirSync(path.join(root, "ux", "portal", "profile"), { recursive: true });
213
+ mkdirSync(path.join(root, ".cicd"), { recursive: true });
214
+
215
+ writeFileSync(
216
+ path.join(root, "package.json"),
217
+ JSON.stringify(
218
+ { name: "tmp-monorepo", private: true, workspaces: ["ux/*"] },
219
+ null,
220
+ 2,
221
+ ) + "\n",
222
+ "utf8",
223
+ );
224
+
225
+ writeFileSync(
226
+ path.join(root, "ux", "portal", "profile", "package.json"),
227
+ JSON.stringify({ name: "@scope/profile", version: "1.0.0" }, null, 2) + "\n",
228
+ "utf8",
229
+ );
230
+
231
+ const stdout = execFileSync(
232
+ "node",
233
+ [cliPath, "--hash", "--changed", "--children", "ux", "--prefixPath", root],
234
+ { cwd: repoRoot, encoding: "utf8" },
235
+ );
236
+
237
+ const changedMatch = stdout.match(/CHANGED - (\[[^\n]*\])/);
238
+ expect(changedMatch).not.toBeNull();
239
+ expect(JSON.parse(changedMatch[1])).toContain("profile");
240
+
241
+ const hashJson = JSON.parse(readFileSync(path.join(root, ".cicd", "hash.json"), "utf8"));
242
+ expect(hashJson.hash["@scope/profile"]).toBeTruthy();
243
+ expect(hashJson.hash.undefined).toBeUndefined();
202
244
  } finally {
203
245
  rmSync(root, { recursive: true, force: true });
204
246
  }
@@ -83,7 +83,7 @@ describe("compare", () => {
83
83
  [name]: { hash, version: "1.0.0", fullName, packageFolder: folder },
84
84
  });
85
85
 
86
- const prev = (name, hash) => ({ [name]: { hash } });
86
+ const prev = (name, hash, fullName = "@s/" + name) => ({ [fullName]: { hash } });
87
87
 
88
88
  describe("status classification", () => {
89
89
  it("marks all packages UNCHANGED when hashes match", async () => {