yam-flow 2.4.0 → 2.5.1

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.
@@ -1,6 +1,7 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import fsp from 'node:fs/promises';
3
3
  import path from 'node:path';
4
+ import { compareStableText } from './stable-order.js';
4
5
 
5
6
  export const INSTALL_RECEIPT_NAME = '.yam-flow-install-receipt.json';
6
7
  export const INSTALL_LOCK_NAME = '.yam-flow-install.lock';
@@ -211,7 +212,7 @@ async function collectTreeManifest(root: string, prefix = ''): Promise<InstallFi
211
212
  const files: InstallFileDigest[] = [];
212
213
  async function walk(current: string, relative: string): Promise<void> {
213
214
  const entries = await fsp.readdir(current, { withFileTypes: true });
214
- entries.sort((left, right) => left.name.localeCompare(right.name));
215
+ entries.sort((left, right) => compareStableText(left.name, right.name));
215
216
  for (const entry of entries) {
216
217
  const absolute = path.join(current, entry.name);
217
218
  const nextRelative = relative ? path.join(relative, entry.name) : entry.name;
@@ -226,7 +227,7 @@ async function collectTreeManifest(root: string, prefix = ''): Promise<InstallFi
226
227
  }
227
228
 
228
229
  await walk(root, '');
229
- return files.sort((left, right) => left.path.localeCompare(right.path));
230
+ return files.sort((left, right) => compareStableText(left.path, right.path));
230
231
  }
231
232
 
232
233
  async function expectedInstallManifest(sourceRoot: string, skills: string[]) {
@@ -253,7 +254,7 @@ async function expectedInstallManifest(sourceRoot: string, skills: string[]) {
253
254
  });
254
255
  }
255
256
  }
256
- return files.sort((left, right) => left.path.localeCompare(right.path));
257
+ return files.sort((left, right) => compareStableText(left.path, right.path));
257
258
  }
258
259
 
259
260
  async function copyTree(source: string, target: string): Promise<void> {
@@ -278,8 +279,12 @@ async function copyTree(source: string, target: string): Promise<void> {
278
279
  }
279
280
 
280
281
  function manifestDigest(files: InstallFileDigest[]) {
282
+ return digestManifestFiles([...files].sort((left, right) => compareStableText(left.path, right.path)));
283
+ }
284
+
285
+ function digestManifestFiles(files: InstallFileDigest[]) {
281
286
  const digest = createHash('sha256');
282
- for (const file of [...files].sort((left, right) => left.path.localeCompare(right.path))) {
287
+ for (const file of files) {
283
288
  digest.update(file.path);
284
289
  digest.update('\0');
285
290
  digest.update(String(file.bytes));
@@ -290,6 +295,20 @@ function manifestDigest(files: InstallFileDigest[]) {
290
295
  return digest.digest('hex');
291
296
  }
292
297
 
298
+ function allowsLegacyManifestOrder(version: unknown) {
299
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(String(version || ''));
300
+ if (!match) return false;
301
+ const major = Number(match[1]);
302
+ const minor = Number(match[2]);
303
+ const patch = Number(match[3]);
304
+ return major < 2 || (major === 2 && (minor < 5 || (minor === 5 && patch === 0)));
305
+ }
306
+
307
+ function receiptManifestDigestMatches(files: InstallFileDigest[], digest: unknown, version: unknown) {
308
+ if (digest === manifestDigest(files)) return true;
309
+ return allowsLegacyManifestOrder(version) && digest === digestManifestFiles(files);
310
+ }
311
+
293
312
  function manifestIssues(expected: InstallFileDigest[], actual: InstallFileDigest[], label: string) {
294
313
  const issues: string[] = [];
295
314
  const expectedByPath = new Map(expected.map((file) => [file.path, file]));
@@ -379,7 +398,7 @@ function ownershipReceiptIssues(
379
398
  if (value.integrity?.file_count !== files.length) {
380
399
  issues.push('install receipt file count does not match its manifest');
381
400
  }
382
- if (value.integrity?.source_digest !== manifestDigest(files)) {
401
+ if (!receiptManifestDigestMatches(files, value.integrity?.source_digest, value.package?.version)) {
383
402
  issues.push('install receipt source digest does not match its manifest');
384
403
  }
385
404
  if (Array.isArray(value.skills)) {
@@ -0,0 +1,4 @@
1
+ export function compareStableText(left: string, right: string): number {
2
+ if (left === right) return 0;
3
+ return left < right ? -1 : 1;
4
+ }
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { constants as fsConstants } from 'node:fs';
3
3
  import fsp from 'node:fs/promises';
4
4
  import path from 'node:path';
5
+ import { compareStableText } from './stable-order.js';
5
6
 
6
7
  export interface InspectedImageFile {
7
8
  path: string;
@@ -102,7 +103,7 @@ export async function upsertUeyeAsset(input: {
102
103
  };
103
104
  if (existingIndex >= 0) manifest.assets[existingIndex] = entry;
104
105
  else manifest.assets.push(entry);
105
- manifest.assets.sort((left, right) => left.id.localeCompare(right.id));
106
+ manifest.assets.sort((left, right) => compareStableText(left.id, right.id));
106
107
  manifest.updated_at = new Date().toISOString();
107
108
  await writeJsonAtomic(manifestPath, manifest);
108
109
  const warnings = assetWarnings(entry);
@@ -205,7 +206,7 @@ export async function archiveUeyeRevision(input: {
205
206
  } else {
206
207
  history.revisions.push(entry);
207
208
  }
208
- history.revisions.sort((left, right) => left.round - right.round || left.artifact_id.localeCompare(right.artifact_id));
209
+ history.revisions.sort((left, right) => left.round - right.round || compareStableText(left.artifact_id, right.artifact_id));
209
210
  history.updated_at = new Date().toISOString();
210
211
  await writeJsonAtomic(manifestPath, history);
211
212
  return {
package/yam.manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yam",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
4
4
  "principles": [
5
5
  "Direction before execution.",
6
6
  "Start fast.",