@neocompose/cli 0.10.7 → 0.11.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/neo.mjs +935 -154
  3. package/package.json +1 -1
package/dist/neo.mjs CHANGED
@@ -11837,12 +11837,13 @@ var init_project_source_parser = __esm({
11837
11837
  });
11838
11838
 
11839
11839
  // ../packages/neoscript-language/src/project-schema-contract.generated.ts
11840
- var PROJECT_SCHEMA_MANIFEST_FORMAT_VERSION, PROJECT_SCHEMA_MANIFEST_CONTRACT_VERSION, NEO_PROJECT_SOURCE_RECORD_CONTRACT;
11840
+ var PROJECT_SCHEMA_MANIFEST_FORMAT_VERSION, PROJECT_SCHEMA_MANIFEST_CONTRACT_VERSION, PROJECT_FILE_UPLOAD_BATCH_SIZE, NEO_PROJECT_SOURCE_RECORD_CONTRACT;
11841
11841
  var init_project_schema_contract_generated = __esm({
11842
11842
  "../packages/neoscript-language/src/project-schema-contract.generated.ts"() {
11843
11843
  "use strict";
11844
11844
  PROJECT_SCHEMA_MANIFEST_FORMAT_VERSION = 3;
11845
11845
  PROJECT_SCHEMA_MANIFEST_CONTRACT_VERSION = "3.5";
11846
+ PROJECT_FILE_UPLOAD_BATCH_SIZE = 32;
11846
11847
  NEO_PROJECT_SOURCE_RECORD_CONTRACT = {
11847
11848
  "recordFields": {
11848
11849
  "member": [
@@ -12101,6 +12102,7 @@ var init_project_schema_contract_generated = __esm({
12101
12102
  "ObjectPlacementTile",
12102
12103
  "TileInstance",
12103
12104
  "ObjectCollider",
12105
+ "SortingGroup",
12104
12106
  "SortingLayer",
12105
12107
  "SmartTile",
12106
12108
  "SmartTileRule",
@@ -19506,8 +19508,8 @@ function memberSymbol(member, schemaKey, environment, field) {
19506
19508
  `${field}.source`
19507
19509
  );
19508
19510
  const overrideOf = nullableString(member.overrideOf, `${field}.overrideOf`);
19509
- const inheritedMember = overrideOf ? environment.members.get(overrideOf) : void 0;
19510
- const inheritedOwner = inheritedMember ? optionalRecord(inheritedMember.owner) : void 0;
19511
+ const inheritedMember2 = overrideOf ? environment.members.get(overrideOf) : void 0;
19512
+ const inheritedOwner = inheritedMember2 ? optionalRecord(inheritedMember2.owner) : void 0;
19511
19513
  const inheritedTypeId = inheritedOwner?.kind === "classMember" && typeof inheritedOwner.classId === "string" ? inheritedOwner.classId : void 0;
19512
19514
  const inheritedType = inheritedTypeId ? environment.classes.get(inheritedTypeId) : void 0;
19513
19515
  const common = {
@@ -23439,6 +23441,7 @@ function isSchemaSystemMetadata(value) {
23439
23441
  "objectPlacementTile",
23440
23442
  "tileInstance",
23441
23443
  "objectCollider",
23444
+ "sortingGroup",
23442
23445
  "sortingLayer",
23443
23446
  "smartTile",
23444
23447
  "smartTileRule",
@@ -26740,6 +26743,7 @@ var init_validate = __esm({
26740
26743
  "objectPlacementTile",
26741
26744
  "tileInstance",
26742
26745
  "objectCollider",
26746
+ "sortingGroup",
26743
26747
  "sortingLayer",
26744
26748
  "smartTile",
26745
26749
  "smartTileRule",
@@ -29257,6 +29261,7 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29257
29261
  const fieldType = partial ? requiredTypeArgument(declaration.type, 0) : declaration.type;
29258
29262
  const storedStatic = declaration.modifiers.includes("static") && !declaration.modifiers.includes("readonly");
29259
29263
  const settings = annotation(declaration.annotations, "settings");
29264
+ const inherited = inheritedMember(context, commonInput.overrideOf);
29260
29265
  const storesSelectionArray = context.enumIdsByName.has(fieldType.name) || fieldType.name === "Dialogue" || Boolean(settings && argument(settings, "collection"));
29261
29266
  const common = {
29262
29267
  ...commonInput,
@@ -29288,11 +29293,12 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29288
29293
  return { ...common, kind: name };
29289
29294
  }
29290
29295
  if (name === "int") {
29296
+ const inheritedInt = inherited?.kind === "int" ? inherited : void 0;
29291
29297
  return {
29292
29298
  ...common,
29293
29299
  kind: "int",
29294
- min: numberArgument(settings, "min"),
29295
- max: numberArgument(settings, "max")
29300
+ min: numberArgument(settings, "min") ?? inheritedInt?.min ?? null,
29301
+ max: numberArgument(settings, "max") ?? inheritedInt?.max ?? null
29296
29302
  };
29297
29303
  }
29298
29304
  if (name === "string") {
@@ -29307,20 +29313,24 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29307
29313
  const min = numberTextArgument(settings, "min");
29308
29314
  const max = numberTextArgument(settings, "max");
29309
29315
  if (name === "decimal") {
29316
+ const inheritedDecimal = inherited?.kind === "decimal" ? inherited : void 0;
29310
29317
  return {
29311
29318
  ...common,
29312
29319
  kind: "decimal",
29313
- min,
29314
- max,
29315
- decimalPoints: numberArgument(settings, "decimalPoints")
29320
+ min: min ?? inheritedDecimal?.min ?? null,
29321
+ max: max ?? inheritedDecimal?.max ?? null,
29322
+ decimalPoints: numberArgument(settings, "decimalPoints") ?? inheritedDecimal?.decimalPoints ?? null
29316
29323
  };
29317
29324
  }
29325
+ const inheritedFloat = inherited?.kind === "float" ? inherited : void 0;
29326
+ const numericMin = min === null ? null : Number(min);
29327
+ const numericMax = max === null ? null : Number(max);
29318
29328
  return {
29319
29329
  ...common,
29320
29330
  kind: "float",
29321
- min: min === null ? null : Number(min),
29322
- max: max === null ? null : Number(max),
29323
- decimalPoints: numberArgument(settings, "decimalPoints")
29331
+ min: numericMin ?? inheritedFloat?.min ?? null,
29332
+ max: numericMax ?? inheritedFloat?.max ?? null,
29333
+ decimalPoints: numberArgument(settings, "decimalPoints") ?? inheritedFloat?.decimalPoints ?? null
29324
29334
  };
29325
29335
  }
29326
29336
  const primitive3 = primitiveMemberKind(name);
@@ -29440,6 +29450,10 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29440
29450
  `Unsupported field type ${JSON.stringify(name)} on ${ownerClass.name}.${declaration.name}.`
29441
29451
  );
29442
29452
  }
29453
+ function inheritedMember(context, memberId) {
29454
+ if (memberId === null) return void 0;
29455
+ return context.loweredMembers.get(memberId) ?? context.baseMembers.get(memberId);
29456
+ }
29443
29457
  function lowerListIndexes(ownerClass, declaration) {
29444
29458
  const declared = /* @__PURE__ */ new Set();
29445
29459
  return declaration.annotations.filter((entry) => entry.name === "index").map((entry) => {
@@ -29633,7 +29647,6 @@ function lowerInterface(context, declaration) {
29633
29647
  function lowerEnum(context, declaration) {
29634
29648
  const id2 = materializedId(declaration, "enum", declaration.name);
29635
29649
  const base = context.baseEnums.get(id2);
29636
- const baseOptions = new Map(base?.options.map((entry) => [entry.id, entry]));
29637
29650
  return {
29638
29651
  id: id2,
29639
29652
  source: sourceIdentity2(declaration, "enum", declaration.name),
@@ -29641,13 +29654,15 @@ function lowerEnum(context, declaration) {
29641
29654
  ...declaration.docsText === void 0 ? {} : { docsText: declaration.docsText },
29642
29655
  options: declaration.options.map((option) => {
29643
29656
  const optionId = materializedId(option, "enum-option", option.name);
29644
- const baseOption = baseOptions.get(optionId);
29645
29657
  return {
29646
29658
  id: optionId,
29647
29659
  key: optionId,
29648
29660
  name: option.name,
29649
29661
  ...option.docsText === void 0 ? {} : { docsText: option.docsText },
29650
- text: option.textExpression === null ? baseOption?.text ?? option.name : stringExpression(option.textExpression),
29662
+ // An option with no `= "..."` is labelled by its own name. Preserving
29663
+ // the pulled record's text existed to carry a localized-text id
29664
+ // through a lowering; P39 section 1.3 removed that shape.
29665
+ text: option.textExpression === null ? option.name : stringExpression(option.textExpression),
29651
29666
  source: sourceIdentity2(option, "enumOption", option.name)
29652
29667
  };
29653
29668
  }),
@@ -31185,7 +31200,7 @@ ${members.map((member) => indentNeoSourceNonEmptyLines(member, 2)).join("\n\n")}
31185
31200
  }
31186
31201
  function emitEnum(value) {
31187
31202
  const options = value.options.map(
31188
- (option) => `${emitDocsText(option.docsText)}${id(option.id)}${option.name}${option.text !== option.name && !looksLikeId(option.text) ? ` = ${quote(option.text)}` : ""},`
31203
+ (option) => `${emitDocsText(option.docsText)}${id(option.id)}${option.name}${option.text === option.name ? "" : ` = ${quote(option.text)}`},`
31189
31204
  );
31190
31205
  return `${emitDocsText(value.docsText)}${id(value.id)}enum ${value.name} {
31191
31206
  ${options.map((option) => indentNeoSourceNonEmptyLines(option, 2)).join("\n")}
@@ -31989,11 +32004,6 @@ function assertUniqueEmittedPaths(files) {
31989
32004
  paths.set(key, file);
31990
32005
  }
31991
32006
  }
31992
- function looksLikeId(value) {
31993
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
31994
- value
31995
- );
31996
- }
31997
32007
  function assertIdentifier(value, kind) {
31998
32008
  if (!isValidNeoIdentifier(value)) {
31999
32009
  throw new Error(
@@ -33730,6 +33740,7 @@ var init_world_system_kinds_generated = __esm({
33730
33740
  Tile: "tile",
33731
33741
  ObjectBase: "objectBase",
33732
33742
  ObjectCollider: "objectCollider",
33743
+ SortingGroup: "sortingGroup",
33733
33744
  LayerGroupBase: "layerGroupBase",
33734
33745
  SpriteObject: "spriteObject",
33735
33746
  ObjectPlacementTile: "objectPlacementTile",
@@ -36085,6 +36096,22 @@ var init_world_system_classes_generated = __esm({
36085
36096
  ],
36086
36097
  worldKind: "objectCollider"
36087
36098
  },
36099
+ {
36100
+ classId: "system_69150540-b653-4bd0-a4fa-43a9f39da72b",
36101
+ name: "NeoSortingGroup",
36102
+ isAbstract: false,
36103
+ schemaFields: [
36104
+ {
36105
+ memberId: "system_fb90f48f-fcc0-4c98-bc22-70a8ea01170e",
36106
+ memberKind: "bool",
36107
+ defaultValue: false,
36108
+ docsText: "Sort this group against the scene root, ignoring any enclosing sorting group. Maps to SortingGroup.sortAtRoot.",
36109
+ required: true,
36110
+ schemaKey: "SortAtRoot"
36111
+ }
36112
+ ],
36113
+ worldKind: "sortingGroup"
36114
+ },
36088
36115
  {
36089
36116
  classId: "system_bf076e9b-c8da-47dc-bae3-7527d0c307a8",
36090
36117
  name: "NeoLayerGroupBase",
@@ -36111,6 +36138,42 @@ var init_world_system_classes_generated = __esm({
36111
36138
  memberKind: "sprite",
36112
36139
  required: true,
36113
36140
  schemaKey: "Sprite"
36141
+ },
36142
+ {
36143
+ memberId: "system_9fcab37a-9743-4e35-8eee-80cb560f1433",
36144
+ memberKind: "bool",
36145
+ defaultValue: false,
36146
+ docsText: "Mirrors the sprite horizontally about its own centre. Maps to SpriteRenderer.flipX.",
36147
+ required: true,
36148
+ schemaKey: "FlipX"
36149
+ },
36150
+ {
36151
+ memberId: "system_ddd09f08-1656-4404-b36b-5570a4c01fcf",
36152
+ memberKind: "bool",
36153
+ defaultValue: false,
36154
+ docsText: "Mirrors the sprite vertically about its own centre. Maps to SpriteRenderer.flipY.",
36155
+ required: true,
36156
+ schemaKey: "FlipY"
36157
+ },
36158
+ {
36159
+ memberId: "system_f2a8c86d-ace5-421b-9475-0f3f6f97b2a6",
36160
+ memberKind: "enum",
36161
+ defaultValue: ["system_9d607a4f-60c3-4347-94fc-f24b538bf468"],
36162
+ docsText: "How this sprite reacts to sprite masks. Maps to SpriteRenderer.maskInteraction. The web canvas has no real sprite mask, so anything other than None previews as a distinguishable treatment rather than as the mask itself \u2014 only Unity renders the real thing.",
36163
+ enumId: "system_4e6c4d6f-1d0b-402a-ad3a-be92242dceec",
36164
+ multiselect: false,
36165
+ required: true,
36166
+ schemaKey: "MaskInteraction"
36167
+ },
36168
+ {
36169
+ memberId: "system_6f32f1f2-83ea-42fc-8647-34ed0946b7f1",
36170
+ memberKind: "int",
36171
+ defaultValue: null,
36172
+ docsText: "Nudges this sprite's draw order within its object. Added to the order derived from the object's layer group \u2014 it does not replace it. Leave unset for the default order.",
36173
+ minValue: -32768,
36174
+ maxValue: 32767,
36175
+ required: false,
36176
+ schemaKey: "SortingOrder"
36114
36177
  }
36115
36178
  ],
36116
36179
  worldKind: "spriteObject"
@@ -49650,7 +49713,6 @@ var init_localization_types = __esm({
49650
49713
  LOCALIZED_TEXT_LINK_KINDS = [
49651
49714
  "member-value",
49652
49715
  "member-default-value",
49653
- "member-enum-option",
49654
49716
  "dialogue-description",
49655
49717
  "dialogue-node-text",
49656
49718
  "dialogue-choice-text",
@@ -51426,7 +51488,7 @@ var init_http = __esm({
51426
51488
  }
51427
51489
  return new _NeoApiClient(apiBaseUrl, token);
51428
51490
  }
51429
- async post(path, body, headers) {
51491
+ async post(path, body, headers, options = {}) {
51430
51492
  const url = new URL(path, this.apiBaseUrl).toString();
51431
51493
  const response = await fetch(url, {
51432
51494
  method: "POST",
@@ -51435,7 +51497,8 @@ var init_http = __esm({
51435
51497
  "Content-Type": "application/json",
51436
51498
  ...headers
51437
51499
  },
51438
- body: body === void 0 ? void 0 : JSON.stringify(body)
51500
+ body: body === void 0 ? void 0 : JSON.stringify(body),
51501
+ signal: options.signal
51439
51502
  });
51440
51503
  const text = await response.text();
51441
51504
  let parsed = null;
@@ -59521,13 +59584,13 @@ function ownedObjectChildMember(row, sourceMember, key, ctx) {
59521
59584
  return memberForCustomSchemaValue(classId, key, ctx);
59522
59585
  }
59523
59586
  function freshCloneValueId() {
59524
- const randomUUID6 = globalThis.crypto?.randomUUID;
59525
- if (randomUUID6 === void 0) {
59587
+ const randomUUID7 = globalThis.crypto?.randomUUID;
59588
+ if (randomUUID7 === void 0) {
59526
59589
  throw new NSGetterRuntimeError(
59527
59590
  "Class.Clone cannot mint a value id because crypto.randomUUID is unavailable."
59528
59591
  );
59529
59592
  }
59530
- return randomUUID6.call(globalThis.crypto);
59593
+ return randomUUID7.call(globalThis.crypto);
59531
59594
  }
59532
59595
  function parseDialogueMemoryPointer(pointer) {
59533
59596
  if (typeof pointer !== "string") return null;
@@ -66453,80 +66516,154 @@ function ensureProjectFileBinaryChangesV4(args) {
66453
66516
  });
66454
66517
  }
66455
66518
  }
66519
+ function prepareProjectFilePushesV4(args) {
66520
+ const prepared = [];
66521
+ for (const binary of args.binaryChanges) {
66522
+ if (binary.action !== "create" && binary.action !== "upload") continue;
66523
+ const recordId = args.assignedIds.get(binary.fileId) ?? binary.fileId;
66524
+ const change = args.changes.find(
66525
+ (candidate) => candidate.recordKind === "project-file" && candidate.recordId === recordId
66526
+ );
66527
+ if (change === void 0) {
66528
+ throw new Error(
66529
+ `Project file ${recordId} has upload bytes but no source change.`
66530
+ );
66531
+ }
66532
+ if (!isObjectRecord2(change.nextData)) {
66533
+ throw new Error(
66534
+ `Project file ${recordId} has upload bytes but its source change has no record data.`
66535
+ );
66536
+ }
66537
+ const absolute = join14(args.workspace.root, binary.path);
66538
+ const bytes = new Uint8Array(readFileSync12(absolute));
66539
+ const digest = sha256Bytes(bytes);
66540
+ if (binary.localSha256 !== null && digest !== binary.localSha256) {
66541
+ throw new Error(
66542
+ `Project file ${binary.path} changed after status was computed; run push again.`
66543
+ );
66544
+ }
66545
+ const mimeType = binary.mimeType;
66546
+ if (mimeType === null) {
66547
+ throw new Error(`Project file ${binary.path} has no MIME type.`);
66548
+ }
66549
+ prepared.push({
66550
+ uploadToken: `${recordId}-${prepared.length}`,
66551
+ recordId,
66552
+ replaceFileId: binary.action === "upload" ? recordId : null,
66553
+ name: basename2(binary.path),
66554
+ fileType: binary.kind,
66555
+ mimeType,
66556
+ byteLength: bytes.byteLength,
66557
+ contentSha256: digest,
66558
+ audioDurationSeconds: binary.kind === "audio" && typeof change.nextData.audioDurationSeconds === "number" ? change.nextData.audioDurationSeconds : null,
66559
+ bytes
66560
+ });
66561
+ }
66562
+ return prepared;
66563
+ }
66456
66564
  async function stageProjectFilePushesV4(args) {
66565
+ const prepared = args.prepared ?? prepareProjectFilePushesV4({
66566
+ workspace: args.workspace,
66567
+ changes: args.changes,
66568
+ binaryChanges: args.binaryChanges,
66569
+ assignedIds: args.assignedIds
66570
+ });
66457
66571
  const staged = [];
66458
66572
  const cleanupKeys = [];
66459
66573
  const put = args.put ?? fetch;
66574
+ const interruptController = new AbortController();
66575
+ const interrupt = () => interruptController.abort(new ProjectFilePushCancelledError());
66576
+ process.once("SIGINT", interrupt);
66577
+ const signal = combineSignals(args.signal, interruptController.signal);
66578
+ const totalBytes = prepared.reduce((sum, file) => sum + file.byteLength, 0);
66579
+ let completedBytes = 0;
66580
+ let completedFiles = 0;
66581
+ const report = () => args.onProgress?.({
66582
+ completedFiles,
66583
+ totalFiles: prepared.length,
66584
+ completedBytes,
66585
+ totalBytes
66586
+ });
66587
+ report();
66460
66588
  try {
66461
- for (const binary of args.binaryChanges) {
66462
- if (binary.action !== "create" && binary.action !== "upload") continue;
66463
- const recordId = args.assignedIds.get(binary.fileId) ?? binary.fileId;
66464
- const change = args.changes.find(
66465
- (candidate) => candidate.recordKind === "project-file" && candidate.recordId === recordId
66589
+ for (let offset = 0; offset < prepared.length; offset += PROJECT_FILE_UPLOAD_BATCH_SIZE) {
66590
+ const batch = prepared.slice(
66591
+ offset,
66592
+ offset + PROJECT_FILE_UPLOAD_BATCH_SIZE
66466
66593
  );
66467
- if (change === void 0 || !isObjectRecord2(change.nextData)) {
66468
- throw new Error(
66469
- `Project file ${recordId} has upload bytes but no source change.`
66470
- );
66471
- }
66472
- const absolute = join14(args.workspace.root, binary.path);
66473
- const bytes = new Uint8Array(readFileSync12(absolute));
66474
- const digest = sha256Bytes(bytes);
66475
- if (binary.localSha256 !== null && digest !== binary.localSha256) {
66476
- throw new Error(
66477
- `Project file ${binary.path} changed after status was computed; run push again.`
66478
- );
66479
- }
66480
- const mimeType = binary.mimeType;
66481
- if (mimeType === null) {
66482
- throw new Error(`Project file ${binary.path} has no MIME type.`);
66483
- }
66484
- const name = basename2(binary.path);
66485
- const replaceFileId = binary.action === "upload" ? recordId : null;
66486
- const presign = await args.client.post(
66594
+ const presign = await postWithTimeout(
66595
+ args.client,
66487
66596
  versionPath2(args.workspace, "upload"),
66488
66597
  {
66489
66598
  route: "projectFile",
66490
66599
  metadata: {
66491
- projectFileId: recordId,
66492
- replaceFileId,
66493
- contentSha256: digest,
66494
- deferredSourceCommit: true
66600
+ deferredSourceCommit: true,
66601
+ uploads: batch.map((file) => ({
66602
+ uploadToken: file.uploadToken,
66603
+ projectFileId: file.recordId,
66604
+ replaceFileId: file.replaceFileId,
66605
+ name: file.name,
66606
+ contentSha256: file.contentSha256
66607
+ }))
66495
66608
  },
66496
- files: [{ name, size: bytes.byteLength, type: mimeType }]
66497
- }
66609
+ files: batch.map((file) => ({
66610
+ name: file.uploadToken,
66611
+ size: file.byteLength,
66612
+ type: file.mimeType
66613
+ }))
66614
+ },
66615
+ signal
66498
66616
  );
66499
- const entry = Array.isArray(presign.files) ? presign.files.find(isObjectRecord2) : void 0;
66500
- const fileInfo = isObjectRecord2(entry?.file) ? entry.file : {};
66501
- const objectInfo = isObjectRecord2(fileInfo.objectInfo) ? fileInfo.objectInfo : {};
66502
- if (typeof entry?.signedUrl !== "string" || typeof objectInfo.key !== "string") {
66503
- throw new Error("Upload presign response is missing signedUrl/key.");
66617
+ const entries = readPresignEntries(presign, batch);
66618
+ for (const entry of entries) cleanupKeys.push(entry.storageKey);
66619
+ const batchController = new AbortController();
66620
+ try {
66621
+ await mapWithConcurrency(entries, UPLOAD_CONCURRENCY, async (entry) => {
66622
+ try {
66623
+ await putWithRetry(
66624
+ put,
66625
+ entry,
66626
+ combineSignals(signal, batchController.signal)
66627
+ );
66628
+ } catch (error) {
66629
+ batchController.abort(error);
66630
+ throw error;
66631
+ }
66632
+ staged.push({
66633
+ uploadToken: entry.file.uploadToken,
66634
+ file: {
66635
+ recordId: entry.file.recordId,
66636
+ replaceFileId: entry.file.replaceFileId,
66637
+ name: entry.file.name,
66638
+ fileType: entry.file.fileType,
66639
+ mimeType: entry.file.mimeType,
66640
+ byteLength: entry.file.byteLength,
66641
+ storageKey: entry.storageKey,
66642
+ contentSha256: entry.file.contentSha256,
66643
+ audioDurationSeconds: entry.file.audioDurationSeconds
66644
+ }
66645
+ });
66646
+ completedFiles += 1;
66647
+ completedBytes += entry.file.byteLength;
66648
+ report();
66649
+ });
66650
+ } catch (error) {
66651
+ batchController.abort(error);
66652
+ throw error;
66504
66653
  }
66505
- cleanupKeys.push(objectInfo.key);
66506
- const headers = uploadHeaders(entry, objectInfo, mimeType);
66507
- const response = await put(entry.signedUrl, {
66508
- method: "PUT",
66509
- headers,
66510
- body: bytes
66511
- });
66512
- if (!response.ok) {
66654
+ }
66655
+ const byUploadToken = new Map(
66656
+ staged.map((entry) => [entry.uploadToken, entry.file])
66657
+ );
66658
+ return prepared.map((file) => {
66659
+ const result = byUploadToken.get(file.uploadToken);
66660
+ if (result === void 0) {
66513
66661
  throw new Error(
66514
- `Storage PUT failed (${response.status}): ${await response.text()}`
66662
+ `Uploaded project file ${file.name} (${file.uploadToken}) is missing from the staged result.`
66515
66663
  );
66516
66664
  }
66517
- staged.push({
66518
- recordId,
66519
- replaceFileId,
66520
- name,
66521
- fileType: binary.kind,
66522
- mimeType,
66523
- byteLength: bytes.byteLength,
66524
- storageKey: objectInfo.key,
66525
- contentSha256: digest,
66526
- audioDurationSeconds: binary.kind === "audio" && typeof change.nextData.audioDurationSeconds === "number" ? change.nextData.audioDurationSeconds : null
66527
- });
66528
- }
66529
- return staged;
66665
+ return result;
66666
+ });
66530
66667
  } catch (error) {
66531
66668
  if (cleanupKeys.length > 0) {
66532
66669
  try {
@@ -66537,8 +66674,145 @@ async function stageProjectFilePushesV4(args) {
66537
66674
  } catch {
66538
66675
  }
66539
66676
  }
66677
+ if (interruptController.signal.aborted || args.signal?.aborted === true) {
66678
+ throw new ProjectFilePushCancelledError();
66679
+ }
66540
66680
  throw error;
66681
+ } finally {
66682
+ process.removeListener("SIGINT", interrupt);
66683
+ }
66684
+ }
66685
+ function readPresignEntries(presign, batch) {
66686
+ const entries = Array.isArray(presign.files) ? presign.files.filter(isObjectRecord2) : [];
66687
+ return batch.map((file) => {
66688
+ const entry = entries.find((candidate) => {
66689
+ const info = isObjectRecord2(candidate.file) ? candidate.file : {};
66690
+ return info.name === file.uploadToken;
66691
+ });
66692
+ const fileInfo = isObjectRecord2(entry?.file) ? entry.file : {};
66693
+ const objectInfo = isObjectRecord2(fileInfo.objectInfo) ? fileInfo.objectInfo : {};
66694
+ if (typeof entry?.signedUrl !== "string") {
66695
+ throw new Error(
66696
+ `Upload presign response is missing signedUrl for ${file.name}.`
66697
+ );
66698
+ }
66699
+ if (typeof objectInfo.key !== "string") {
66700
+ throw new Error(
66701
+ `Upload presign response is missing a storage key for ${file.name}.`
66702
+ );
66703
+ }
66704
+ return {
66705
+ file,
66706
+ signedUrl: entry.signedUrl,
66707
+ storageKey: objectInfo.key,
66708
+ headers: uploadHeaders(entry, objectInfo, file.mimeType)
66709
+ };
66710
+ });
66711
+ }
66712
+ async function postWithTimeout(client, path, body, signal) {
66713
+ for (let attempt = 1; attempt <= PRESIGN_ATTEMPTS; attempt += 1) {
66714
+ try {
66715
+ return await client.post(path, body, void 0, {
66716
+ signal: combineSignals(signal, AbortSignal.timeout(PRESIGN_TIMEOUT_MS))
66717
+ });
66718
+ } catch (error) {
66719
+ if (signal.aborted || !isRetryableRequestError(error) || attempt === PRESIGN_ATTEMPTS) {
66720
+ throw error;
66721
+ }
66722
+ await waitForRetry(attempt, signal);
66723
+ }
66724
+ }
66725
+ throw new Error("Upload presign failed after all retry attempts.");
66726
+ }
66727
+ async function putWithRetry(put, entry, signal) {
66728
+ let lastError;
66729
+ for (let attempt = 1; attempt <= STORAGE_PUT_ATTEMPTS; attempt += 1) {
66730
+ let response;
66731
+ try {
66732
+ response = await put(entry.signedUrl, {
66733
+ method: "PUT",
66734
+ headers: entry.headers,
66735
+ body: Buffer.from(entry.file.bytes),
66736
+ signal: combineSignals(
66737
+ signal,
66738
+ AbortSignal.timeout(STORAGE_PUT_TIMEOUT_MS)
66739
+ )
66740
+ });
66741
+ } catch (error) {
66742
+ if (signal.aborted || attempt === STORAGE_PUT_ATTEMPTS) throw error;
66743
+ lastError = error;
66744
+ await waitForRetry(attempt, signal);
66745
+ continue;
66746
+ }
66747
+ if (response.ok) return;
66748
+ if (isRetryableStatus(response.status) && attempt < STORAGE_PUT_ATTEMPTS) {
66749
+ lastError = new Error(`Storage PUT failed (${response.status}).`);
66750
+ await waitForRetry(attempt, signal);
66751
+ continue;
66752
+ }
66753
+ throw new Error(
66754
+ `Storage PUT failed (${response.status}): ${await response.text()}`
66755
+ );
66756
+ }
66757
+ throw lastError instanceof Error ? lastError : new Error(`Storage PUT failed for ${entry.file.name}.`);
66758
+ }
66759
+ function isRetryableStatus(status) {
66760
+ return status === 408 || status === 429 || status >= 500;
66761
+ }
66762
+ function isRetryableRequestError(error) {
66763
+ if (isObjectRecord2(error) && typeof error.status === "number") {
66764
+ return isRetryableStatus(error.status);
66765
+ }
66766
+ if (isObjectRecord2(error) && error.name === "TimeoutError") return true;
66767
+ if (!(error instanceof TypeError) || error.message !== "fetch failed") {
66768
+ return false;
66769
+ }
66770
+ if (!isObjectRecord2(error.cause) || typeof error.cause.code !== "string") {
66771
+ return false;
66541
66772
  }
66773
+ return RETRYABLE_NETWORK_ERROR_CODES.has(error.cause.code);
66774
+ }
66775
+ async function waitForRetry(attempt, signal) {
66776
+ await new Promise((resolve4, reject) => {
66777
+ const abort = () => {
66778
+ clearTimeout(timeout);
66779
+ reject(signal.reason);
66780
+ };
66781
+ const timeout = setTimeout(
66782
+ () => {
66783
+ signal.removeEventListener("abort", abort);
66784
+ resolve4();
66785
+ },
66786
+ 200 * 2 ** (attempt - 1)
66787
+ );
66788
+ signal.addEventListener("abort", abort, { once: true });
66789
+ });
66790
+ }
66791
+ async function mapWithConcurrency(values, concurrency, run) {
66792
+ let nextIndex = 0;
66793
+ let firstError;
66794
+ const worker = async () => {
66795
+ while (firstError === void 0) {
66796
+ const index = nextIndex;
66797
+ nextIndex += 1;
66798
+ if (index >= values.length) return;
66799
+ try {
66800
+ await run(values[index]);
66801
+ } catch (error) {
66802
+ firstError ??= error;
66803
+ }
66804
+ }
66805
+ };
66806
+ await Promise.all(
66807
+ Array.from({ length: Math.min(concurrency, values.length) }, worker)
66808
+ );
66809
+ if (firstError !== void 0) throw firstError;
66810
+ }
66811
+ function combineSignals(...signals) {
66812
+ const defined = signals.filter(
66813
+ (signal) => signal !== void 0
66814
+ );
66815
+ return defined.length === 1 ? defined[0] : AbortSignal.any(defined);
66542
66816
  }
66543
66817
  function uploadHeaders(entry, objectInfo, mimeType) {
66544
66818
  const headers = { "Content-Type": mimeType };
@@ -66560,11 +66834,431 @@ function uploadHeaders(entry, objectInfo, mimeType) {
66560
66834
  function versionPath2(workspace, suffix) {
66561
66835
  return `/api/projects/${workspace.config.projectId}/versions/${workspace.config.versionId}/${suffix}`;
66562
66836
  }
66837
+ var UPLOAD_CONCURRENCY, PRESIGN_TIMEOUT_MS, PRESIGN_ATTEMPTS, STORAGE_PUT_TIMEOUT_MS, STORAGE_PUT_ATTEMPTS, ProjectFilePushCancelledError, RETRYABLE_NETWORK_ERROR_CODES;
66563
66838
  var init_project_file_push = __esm({
66564
66839
  "src/project-source/project-file-push.ts"() {
66565
66840
  "use strict";
66566
66841
  init_project_files();
66567
66842
  init_projection();
66843
+ init_src();
66844
+ UPLOAD_CONCURRENCY = 6;
66845
+ PRESIGN_TIMEOUT_MS = 3e4;
66846
+ PRESIGN_ATTEMPTS = 3;
66847
+ STORAGE_PUT_TIMEOUT_MS = 12e4;
66848
+ STORAGE_PUT_ATTEMPTS = 3;
66849
+ ProjectFilePushCancelledError = class extends Error {
66850
+ constructor() {
66851
+ super("Project file upload was cancelled.");
66852
+ this.name = "ProjectFilePushCancelledError";
66853
+ }
66854
+ };
66855
+ RETRYABLE_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
66856
+ "ECONNRESET",
66857
+ "ECONNREFUSED",
66858
+ "EHOSTUNREACH",
66859
+ "ENETUNREACH",
66860
+ "ENOTFOUND",
66861
+ "EPIPE",
66862
+ "ETIMEDOUT"
66863
+ ]);
66864
+ }
66865
+ });
66866
+
66867
+ // src/project-source/trusted-commit-verification.ts
66868
+ import { randomUUID as randomUUID5 } from "node:crypto";
66869
+ import { tmpdir } from "node:os";
66870
+ import { join as join15 } from "node:path";
66871
+ function verifyProjectSourceCommitAgainstStateV4(args) {
66872
+ const root = join15(tmpdir(), `neo-source-verify-virtual-${randomUUID5()}`);
66873
+ const workspace = {
66874
+ root,
66875
+ config: {
66876
+ formatVersion: 4,
66877
+ apiBaseUrl: "https://trusted-server.invalid",
66878
+ projectId: args.projectId,
66879
+ versionId: args.versionId,
66880
+ profile: "editor"
66881
+ },
66882
+ state: { records: { ...args.stateRecords } }
66883
+ };
66884
+ const assignments = validateAssignments(args.pendingIdAssignments);
66885
+ const pendingIdByAssignedId = new Map(
66886
+ [...assignments].map(([pendingId2, assignedId]) => [assignedId, pendingId2])
66887
+ );
66888
+ const trustedPendingProjectFiles = /* @__PURE__ */ new Map();
66889
+ const stagedRecordIds = /* @__PURE__ */ new Set();
66890
+ for (const file of args.stagedFiles) {
66891
+ if (stagedRecordIds.has(file.recordId)) {
66892
+ throw new ProjectSourceCommitVerificationError(
66893
+ "Source commit contains duplicate verified staged project files."
66894
+ );
66895
+ }
66896
+ stagedRecordIds.add(file.recordId);
66897
+ const metadata = {
66898
+ mimeType: file.mimeType,
66899
+ byteLength: file.byteLength,
66900
+ sha256: file.contentSha256
66901
+ };
66902
+ trustedPendingProjectFiles.set(file.recordId, metadata);
66903
+ const pendingId2 = pendingIdByAssignedId.get(file.recordId);
66904
+ if (pendingId2 !== void 0) {
66905
+ trustedPendingProjectFiles.set(pendingId2, metadata);
66906
+ }
66907
+ }
66908
+ const status = computeWorkspaceStatus(workspace, {
66909
+ skipProjectBinaryInspection: true,
66910
+ writeProjectAnalysisCache: () => void 0,
66911
+ trustedPendingProjectFiles,
66912
+ virtualSourceFiles: args.files
66913
+ });
66914
+ const blockingErrors = status.parseErrors.filter(isBlockingSchemaSourceError);
66915
+ if (status.conflictedFiles.length > 0 || blockingErrors.length > 0) {
66916
+ const first = blockingErrors[0];
66917
+ throw new ProjectSourceCommitVerificationError(
66918
+ first ? `Trusted source lowering failed: ${first.file}:${first.line}:${first.column} ${first.message}` : `Trusted source lowering found conflict markers in ${status.conflictedFiles[0]}.`
66919
+ );
66920
+ }
66921
+ const usedAssignments = /* @__PURE__ */ new Set();
66922
+ const expectedChanges = status.changes.map((change) => {
66923
+ const rewrittenData = change.nextData === void 0 ? void 0 : rewritePending(change.nextData, assignments, usedAssignments);
66924
+ return {
66925
+ recordKind: change.recordKind,
66926
+ recordId: rewritePending(change.recordId, assignments, usedAssignments),
66927
+ operation: change.kind,
66928
+ // Source syntax does not author project ownership. The CLI stamps the
66929
+ // authenticated project id on creates immediately before transport;
66930
+ // reproduce that server-known field instead of comparing the
66931
+ // lowerer's empty construction placeholder.
66932
+ nextData: change.kind === "create" && isObjectRecord2(rewrittenData) ? { ...rewrittenData, projectId: args.projectId } : rewrittenData,
66933
+ expectedBaseContentHash: change.kind === "create" ? null : change.casBaseHash ?? null
66934
+ };
66935
+ });
66936
+ const expectedSeeds = [...status.staticValueSeeds].map(
66937
+ ([memberId, seed]) => ({
66938
+ memberId: rewritePending(memberId, assignments, usedAssignments),
66939
+ value: rewritePending(seed.value, assignments, usedAssignments),
66940
+ classId: seed.classId === null ? null : rewritePending(seed.classId, assignments, usedAssignments),
66941
+ ...seed.valueId === void 0 ? {} : {
66942
+ valueId: rewritePending(seed.valueId, assignments, usedAssignments)
66943
+ },
66944
+ ...seed.values === void 0 ? {} : {
66945
+ values: rewritePending(seed.values, assignments, usedAssignments)
66946
+ },
66947
+ ...seed.bindingMembers === void 0 ? {} : {
66948
+ bindingMembers: rewritePending(
66949
+ seed.bindingMembers,
66950
+ assignments,
66951
+ usedAssignments
66952
+ )
66953
+ },
66954
+ ...seed.localizedTexts === void 0 ? {} : {
66955
+ localizedTexts: rewritePending(
66956
+ seed.localizedTexts,
66957
+ assignments,
66958
+ usedAssignments
66959
+ )
66960
+ }
66961
+ })
66962
+ );
66963
+ for (const pendingId2 of assignments.keys()) {
66964
+ if (!usedAssignments.has(pendingId2)) {
66965
+ throw new ProjectSourceCommitVerificationError(
66966
+ `Pending id assignment ${pendingId2} is not present in the verified source manifest.`
66967
+ );
66968
+ }
66969
+ }
66970
+ compareChanges(expectedChanges, args.changes);
66971
+ compareSeeds(expectedSeeds, args.staticValueSeeds);
66972
+ }
66973
+ function validateAssignments(value) {
66974
+ const assignments = /* @__PURE__ */ new Map();
66975
+ const assignedIds = /* @__PURE__ */ new Set();
66976
+ for (const [pendingId2, assignedId] of Object.entries(value)) {
66977
+ if (!pendingId2.startsWith("__pending__:")) {
66978
+ throw new ProjectSourceCommitVerificationError(
66979
+ `Pending id assignment key ${pendingId2} is not a pending identity.`
66980
+ );
66981
+ }
66982
+ if (pendingMemberValueMemberId(pendingId2) === null) {
66983
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(
66984
+ assignedId
66985
+ )) {
66986
+ throw new ProjectSourceCommitVerificationError(
66987
+ `Pending id assignment for ${pendingId2} is not a UUID v4.`
66988
+ );
66989
+ }
66990
+ }
66991
+ if (assignedIds.has(assignedId)) {
66992
+ throw new ProjectSourceCommitVerificationError(
66993
+ `Pending id assignments reuse durable id ${assignedId}.`
66994
+ );
66995
+ }
66996
+ assignments.set(pendingId2, assignedId);
66997
+ assignedIds.add(assignedId);
66998
+ }
66999
+ assertMemberValueAssignmentsAreDerived(assignments);
67000
+ return assignments;
67001
+ }
67002
+ function assertMemberValueAssignmentsAreDerived(assignments) {
67003
+ for (const [pendingId2, assignedId] of assignments) {
67004
+ const memberLocator = pendingMemberValueMemberId(pendingId2);
67005
+ if (memberLocator === null) continue;
67006
+ const memberId = memberLocator.startsWith("__pending__:") ? assignments.get(memberLocator) : memberLocator;
67007
+ if (memberId === void 0) {
67008
+ throw new ProjectSourceCommitVerificationError(
67009
+ `Member value assignment ${pendingId2} names member ${memberLocator}, which has no pending id assignment.`
67010
+ );
67011
+ }
67012
+ const derived = derivedMemberValueId(memberId);
67013
+ if (assignedId !== derived) {
67014
+ throw new ProjectSourceCommitVerificationError(
67015
+ `Member value assignment ${pendingId2} is ${assignedId}, but member ${memberId} owns value ${derived}.`
67016
+ );
67017
+ }
67018
+ }
67019
+ }
67020
+ function positionIndependentPendingKey(pendingId2) {
67021
+ const parts = pendingId2.split(":");
67022
+ if (parts.length !== 6) return null;
67023
+ const [prefix, kind, uri, line, character, label] = parts;
67024
+ if (prefix !== "__pending__") return null;
67025
+ if (!/^\d+$/u.test(line) || !/^\d+$/u.test(character)) return null;
67026
+ return `${prefix}:${kind}:${uri}:${label}`;
67027
+ }
67028
+ function positionIndependentAssignments(assignments) {
67029
+ const candidates = /* @__PURE__ */ new Map();
67030
+ for (const pendingId2 of assignments.keys()) {
67031
+ const key = positionIndependentPendingKey(pendingId2);
67032
+ if (key === null) continue;
67033
+ candidates.set(key, [...candidates.get(key) ?? [], pendingId2]);
67034
+ }
67035
+ const resolved = /* @__PURE__ */ new Map();
67036
+ for (const [key, pendingIds] of candidates) {
67037
+ if (pendingIds.length !== 1) continue;
67038
+ resolved.set(key, pendingIds[0]);
67039
+ }
67040
+ return resolved;
67041
+ }
67042
+ function rewritePending(value, assignments, used, byPosition) {
67043
+ const positionless = byPosition ?? positionIndependentAssignments(assignments);
67044
+ if (typeof value === "string") {
67045
+ const exact = assignments.get(value);
67046
+ if (exact !== void 0) {
67047
+ used.add(value);
67048
+ return exact;
67049
+ }
67050
+ if (value.startsWith("__pending__:")) {
67051
+ const key = positionIndependentPendingKey(value);
67052
+ const relocated = key === null ? void 0 : positionless.get(key);
67053
+ if (relocated !== void 0) {
67054
+ used.add(relocated);
67055
+ return assignments.get(relocated);
67056
+ }
67057
+ throw new ProjectSourceCommitVerificationError(
67058
+ `Verified source identity ${value} has no pending id assignment.`
67059
+ );
67060
+ }
67061
+ let rewritten = value;
67062
+ for (const [pendingId2, assignedId] of assignments) {
67063
+ if (!rewritten.includes(pendingId2)) continue;
67064
+ rewritten = rewritten.replaceAll(pendingId2, assignedId);
67065
+ used.add(pendingId2);
67066
+ }
67067
+ return rewritten;
67068
+ }
67069
+ if (Array.isArray(value)) {
67070
+ return value.map(
67071
+ (entry) => rewritePending(entry, assignments, used, positionless)
67072
+ );
67073
+ }
67074
+ if (isObjectRecord2(value)) {
67075
+ const result = {};
67076
+ for (const [key, entry] of Object.entries(value)) {
67077
+ const rewrittenKey = key.startsWith("__pending__:") ? rewritePending(key, assignments, used, positionless) : key;
67078
+ result[rewrittenKey] = rewritePending(
67079
+ entry,
67080
+ assignments,
67081
+ used,
67082
+ positionless
67083
+ );
67084
+ }
67085
+ return result;
67086
+ }
67087
+ return value;
67088
+ }
67089
+ function compareChanges(expected, received) {
67090
+ const expectedByKey = new Map(
67091
+ expected.map((change) => [
67092
+ `${change.recordKind}:${change.recordId}`,
67093
+ change
67094
+ ])
67095
+ );
67096
+ const receivedByKey = new Map(
67097
+ received.map((change) => [
67098
+ `${change.recordKind}:${change.recordId}`,
67099
+ change
67100
+ ])
67101
+ );
67102
+ if (expectedByKey.size !== expected.length || receivedByKey.size !== received.length) {
67103
+ throw new ProjectSourceCommitVerificationError(
67104
+ "Source commit contains duplicate semantic record changes."
67105
+ );
67106
+ }
67107
+ for (const [key, expectedChange] of expectedByKey) {
67108
+ const actual = receivedByKey.get(key);
67109
+ if (actual === void 0) {
67110
+ throw new ProjectSourceCommitVerificationError(
67111
+ `Source commit omitted semantic change ${key}.`
67112
+ );
67113
+ }
67114
+ if (actual.operation !== expectedChange.operation || (actual.expectedBaseContentHash ?? null) !== expectedChange.expectedBaseContentHash) {
67115
+ throw new ProjectSourceCommitVerificationError(
67116
+ `Source commit changed operation or CAS base for ${key}.`
67117
+ );
67118
+ }
67119
+ const expectedData = comparisonData(
67120
+ expectedChange.recordKind,
67121
+ expectedChange.nextData
67122
+ );
67123
+ const actualData = comparisonData(actual.recordKind, actual.nextData);
67124
+ if (canonicalStringify(expectedData) !== canonicalStringify(actualData)) {
67125
+ const difference = firstDifference(expectedData, actualData);
67126
+ throw new ProjectSourceCommitVerificationError(
67127
+ `Source commit nextData for ${key} does not match trusted lowering${difference === null ? "." : ` at ${difference.path}: trusted ${formatDifferenceValue(difference.trusted)}, submitted ${formatDifferenceValue(difference.submitted)}.`}`
67128
+ );
67129
+ }
67130
+ if (!isObjectRecord2(actual.intent) || actual.intent.type !== `${actual.recordKind}.${actual.operation}`) {
67131
+ throw new ProjectSourceCommitVerificationError(
67132
+ `Source commit intent for ${key} does not match its semantic operation.`
67133
+ );
67134
+ }
67135
+ receivedByKey.delete(key);
67136
+ }
67137
+ const extra = receivedByKey.keys().next().value;
67138
+ if (extra !== void 0) {
67139
+ throw new ProjectSourceCommitVerificationError(
67140
+ `Source commit added semantic change ${extra} absent from trusted lowering.`
67141
+ );
67142
+ }
67143
+ }
67144
+ function firstDifference(trusted, submitted, path = "$") {
67145
+ if (Object.is(trusted, submitted)) return null;
67146
+ if (Array.isArray(trusted) && Array.isArray(submitted)) {
67147
+ const count = Math.max(trusted.length, submitted.length);
67148
+ for (let index = 0; index < count; index += 1) {
67149
+ if (index >= trusted.length) {
67150
+ return {
67151
+ path: `${path}[${index}]`,
67152
+ trusted: ABSENT_DIFFERENCE_VALUE,
67153
+ submitted: submitted[index]
67154
+ };
67155
+ }
67156
+ if (index >= submitted.length) {
67157
+ return {
67158
+ path: `${path}[${index}]`,
67159
+ trusted: trusted[index],
67160
+ submitted: ABSENT_DIFFERENCE_VALUE
67161
+ };
67162
+ }
67163
+ const difference = firstDifference(
67164
+ trusted[index],
67165
+ submitted[index],
67166
+ `${path}[${index}]`
67167
+ );
67168
+ if (difference !== null) return difference;
67169
+ }
67170
+ }
67171
+ if (isObjectRecord2(trusted) && isObjectRecord2(submitted)) {
67172
+ const keys = [
67173
+ .../* @__PURE__ */ new Set([...Object.keys(trusted), ...Object.keys(submitted)])
67174
+ ].sort();
67175
+ for (const key of keys) {
67176
+ const trustedHasKey = Object.hasOwn(trusted, key);
67177
+ const submittedHasKey = Object.hasOwn(submitted, key);
67178
+ const childPath = `${path}.${key}`;
67179
+ if (!trustedHasKey || !submittedHasKey) {
67180
+ return {
67181
+ path: childPath,
67182
+ trusted: trustedHasKey ? trusted[key] : ABSENT_DIFFERENCE_VALUE,
67183
+ submitted: submittedHasKey ? submitted[key] : ABSENT_DIFFERENCE_VALUE
67184
+ };
67185
+ }
67186
+ const difference = firstDifference(
67187
+ trusted[key],
67188
+ submitted[key],
67189
+ childPath
67190
+ );
67191
+ if (difference !== null) return difference;
67192
+ }
67193
+ }
67194
+ return { path, trusted, submitted };
67195
+ }
67196
+ function formatDifferenceValue(value) {
67197
+ if (value === ABSENT_DIFFERENCE_VALUE) return "<absent>";
67198
+ const serialized = JSON.stringify(value);
67199
+ const rendered = serialized === void 0 ? String(value) : serialized;
67200
+ return rendered.length <= 160 ? rendered : `${rendered.slice(0, 157)}...`;
67201
+ }
67202
+ function comparisonData(recordKind, value) {
67203
+ if (!isObjectRecord2(value)) return value;
67204
+ const result = { ...value };
67205
+ delete result.createdAt;
67206
+ delete result.updatedAt;
67207
+ if (recordKind === "member") {
67208
+ delete result.getter;
67209
+ delete result.setter;
67210
+ if (result.kind === 23) delete result.action;
67211
+ }
67212
+ if (recordKind === "migration") delete result.action;
67213
+ return result;
67214
+ }
67215
+ function compareSeeds(expected, received) {
67216
+ const normalize = (values) => values.map(normalizeSeedForComparison).sort((left, right) => {
67217
+ if (left.memberId < right.memberId) return -1;
67218
+ if (left.memberId > right.memberId) return 1;
67219
+ return 0;
67220
+ });
67221
+ if (canonicalStringify(normalize(expected)) !== canonicalStringify(normalize(received))) {
67222
+ throw new ProjectSourceCommitVerificationError(
67223
+ "Source commit static value seeds do not match trusted lowering."
67224
+ );
67225
+ }
67226
+ }
67227
+ function normalizeSeedForComparison(seed) {
67228
+ const normalized = { ...seed };
67229
+ if (seed.values !== void 0) {
67230
+ normalized.values = sortSeedRows(seed.values);
67231
+ }
67232
+ if (seed.bindingMembers !== void 0) {
67233
+ normalized.bindingMembers = sortSeedRows(seed.bindingMembers);
67234
+ }
67235
+ if (seed.localizedTexts !== void 0) {
67236
+ normalized.localizedTexts = sortSeedRows(seed.localizedTexts);
67237
+ }
67238
+ return normalized;
67239
+ }
67240
+ function sortSeedRows(values) {
67241
+ return [...values].sort((left, right) => {
67242
+ if (left.id < right.id) return -1;
67243
+ if (left.id > right.id) return 1;
67244
+ return 0;
67245
+ });
67246
+ }
67247
+ var ProjectSourceCommitVerificationError, ABSENT_DIFFERENCE_VALUE;
67248
+ var init_trusted_commit_verification = __esm({
67249
+ "src/project-source/trusted-commit-verification.ts"() {
67250
+ "use strict";
67251
+ init_workspace_status();
67252
+ init_source_diagnostics();
67253
+ init_projection();
67254
+ init_member_value_id();
67255
+ ProjectSourceCommitVerificationError = class extends Error {
67256
+ constructor(message) {
67257
+ super(message);
67258
+ this.name = "ProjectSourceCommitVerificationError";
67259
+ }
67260
+ };
67261
+ ABSENT_DIFFERENCE_VALUE = /* @__PURE__ */ Symbol("absent-difference-value");
66568
67262
  }
66569
67263
  });
66570
67264
 
@@ -66609,7 +67303,7 @@ __export(push_exports, {
66609
67303
  stripServerDerivedNeoScript: () => stripServerDerivedNeoScript,
66610
67304
  workspaceChangesRequireCompleteBodySweep: () => workspaceChangesRequireCompleteBodySweep
66611
67305
  });
66612
- import { randomUUID as randomUUID5 } from "node:crypto";
67306
+ import { randomUUID as randomUUID6 } from "node:crypto";
66613
67307
  import {
66614
67308
  mkdirSync as mkdirSync11,
66615
67309
  writeFileSync as writeFileSync11,
@@ -66617,7 +67311,7 @@ import {
66617
67311
  existsSync as existsSync12,
66618
67312
  readFileSync as readFileSync13
66619
67313
  } from "node:fs";
66620
- import { dirname as dirname7, join as join15, relative as relative5, sep as sep5 } from "node:path";
67314
+ import { dirname as dirname7, join as join16, relative as relative5, sep as sep5 } from "node:path";
66621
67315
  function assignPendingIds(changes, staticValueSeeds, reconstructed3) {
66622
67316
  const assigned = /* @__PURE__ */ new Map();
66623
67317
  const assign = (pendingId2) => {
@@ -66630,7 +67324,7 @@ function assignPendingIds(changes, staticValueSeeds, reconstructed3) {
66630
67324
  assigned.set(pendingId2, derived);
66631
67325
  return derived;
66632
67326
  }
66633
- const fresh = randomUUID5();
67327
+ const fresh = randomUUID6();
66634
67328
  assigned.set(pendingId2, fresh);
66635
67329
  return fresh;
66636
67330
  };
@@ -66941,6 +67635,10 @@ function createPushProgressReporter(json) {
66941
67635
  };
66942
67636
  }
66943
67637
  function projectTransactionProgressLabel(event) {
67638
+ if (event.type === "push-progress") {
67639
+ if (event.phase === "preparing") return "Preparing push\u2026";
67640
+ return `Uploading files\u2026 ${event.completedFiles.toLocaleString("en-US")}/${event.totalFiles.toLocaleString("en-US")} (${formatByteProgress(event.completedBytes)}/${formatByteProgress(event.totalBytes)})`;
67641
+ }
66944
67642
  if (event.phase === "submitting") return "Pushing\u2026";
66945
67643
  if (event.phase === "preparing") return "Preparing push\u2026";
66946
67644
  if (event.phase === "waiting") return "Waiting to apply project transaction\u2026";
@@ -66958,6 +67656,11 @@ function projectTransactionProgressLabel(event) {
66958
67656
  }
66959
67657
  return "Finalizing project transaction\u2026";
66960
67658
  }
67659
+ function formatByteProgress(bytes) {
67660
+ if (bytes < 1024) return `${bytes} B`;
67661
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
67662
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
67663
+ }
66961
67664
  function progressEventFromAccepted(accepted) {
66962
67665
  return {
66963
67666
  type: "project-transaction-progress",
@@ -67230,46 +67933,111 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
67230
67933
  console.log("Push cancelled.");
67231
67934
  return;
67232
67935
  }
67233
- const sourceBundle = await createPendingProjectSourceBundleV4(
67234
- workspace,
67235
- status,
67236
- status.staticValueSeeds
67237
- );
67238
- const pendingAssignment = assignPendingIds(
67239
- status.changes,
67240
- status.staticValueSeeds,
67241
- status.reconstructed
67242
- );
67243
- const operations = new Set(status.changes.map((change) => change.kind));
67244
- const transactionOperation = operations.size === 1 ? status.changes[0].kind : "update";
67245
- const client = NeoApiClient.forBaseUrl(workspace.config.apiBaseUrl);
67246
- let stagedFiles = await stageProjectFilePushesV4({
67247
- workspace,
67248
- changes: status.changes,
67249
- binaryChanges: status.binaryChanges ?? [],
67250
- assignedIds: pendingAssignment.assigned,
67251
- client
67936
+ let progress = createPushProgressReporter(options.json === true);
67937
+ progress.report({
67938
+ type: "push-progress",
67939
+ phase: "preparing",
67940
+ completedFiles: 0,
67941
+ totalFiles: status.binaryChanges?.length ?? 0,
67942
+ completedBytes: 0,
67943
+ totalBytes: 0
67252
67944
  });
67945
+ const prepareAndStage = async () => {
67946
+ const source2 = await createPendingProjectSourceBundleV4(
67947
+ workspace,
67948
+ status,
67949
+ status.staticValueSeeds
67950
+ );
67951
+ const pendingAssignment2 = assignPendingIds(
67952
+ status.changes,
67953
+ status.staticValueSeeds,
67954
+ status.reconstructed
67955
+ );
67956
+ const operations = new Set(status.changes.map((change) => change.kind));
67957
+ const transactionOperation2 = operations.size === 1 ? status.changes[0].kind : "update";
67958
+ const client2 = NeoApiClient.forBaseUrl(workspace.config.apiBaseUrl);
67959
+ const transportChanges2 = status.changes.map((change) => ({
67960
+ recordKind: change.recordKind,
67961
+ recordId: change.recordId,
67962
+ operation: change.kind,
67963
+ nextData: change.kind === "delete" ? void 0 : stripServerDerivedNeoScript(change.nextData),
67964
+ deleted: change.kind === "delete" ? true : void 0,
67965
+ intent: createNeoCliPushIntent(change),
67966
+ expectedBaseContentHash: change.kind === "create" ? null : change.casBaseHash ?? null
67967
+ }));
67968
+ const transportSeeds2 = [...pendingAssignment2.staticValueSeeds].map(
67969
+ ([memberId, seed]) => ({ memberId, ...seed })
67970
+ );
67971
+ const files = prepareProjectFilePushesV4({
67972
+ workspace,
67973
+ changes: status.changes,
67974
+ binaryChanges: status.binaryChanges ?? [],
67975
+ assignedIds: pendingAssignment2.assigned
67976
+ });
67977
+ verifyProjectSourceCommitAgainstStateV4({
67978
+ projectId: workspace.config.projectId,
67979
+ versionId: workspace.config.versionId,
67980
+ stateRecords: workspace.state.records,
67981
+ files: source2.files,
67982
+ changes: transportChanges2,
67983
+ staticValueSeeds: transportSeeds2,
67984
+ pendingIdAssignments: Object.fromEntries(pendingAssignment2.assigned),
67985
+ stagedFiles: files
67986
+ });
67987
+ const stagedFiles2 = await stageProjectFilePushesV4({
67988
+ workspace,
67989
+ changes: status.changes,
67990
+ binaryChanges: status.binaryChanges ?? [],
67991
+ assignedIds: pendingAssignment2.assigned,
67992
+ client: client2,
67993
+ prepared: files,
67994
+ onProgress: (upload) => progress.report({
67995
+ type: "push-progress",
67996
+ phase: "uploading",
67997
+ ...upload
67998
+ })
67999
+ });
68000
+ return {
68001
+ source: source2,
68002
+ pendingAssignment: pendingAssignment2,
68003
+ transactionOperation: transactionOperation2,
68004
+ client: client2,
68005
+ transportChanges: transportChanges2,
68006
+ transportSeeds: transportSeeds2,
68007
+ preparedFiles: files,
68008
+ stagedFiles: stagedFiles2
68009
+ };
68010
+ };
68011
+ let preparedPush;
68012
+ try {
68013
+ preparedPush = await prepareAndStage();
68014
+ } catch (error) {
68015
+ progress.stop();
68016
+ if (error instanceof ProjectFilePushCancelledError) {
68017
+ if (options.json !== true) console.error("Push cancelled.");
68018
+ process.exitCode = 130;
68019
+ return;
68020
+ }
68021
+ throw error;
68022
+ }
68023
+ const {
68024
+ source,
68025
+ pendingAssignment,
68026
+ transactionOperation,
68027
+ client,
68028
+ transportChanges,
68029
+ transportSeeds,
68030
+ preparedFiles
68031
+ } = preparedPush;
68032
+ let { stagedFiles } = preparedPush;
67253
68033
  const commit = async (force) => await client.post(
67254
68034
  `/api/projects/${workspace.config.projectId}/versions/${workspace.config.versionId}/schema/commit`,
67255
68035
  {
67256
68036
  operation: transactionOperation,
67257
- changes: status.changes.map((change) => {
67258
- return {
67259
- recordKind: change.recordKind,
67260
- recordId: change.recordId,
67261
- operation: change.kind,
67262
- nextData: change.kind === "delete" ? void 0 : stripServerDerivedNeoScript(change.nextData),
67263
- deleted: change.kind === "delete" ? true : void 0,
67264
- intent: createNeoCliPushIntent(change),
67265
- expectedBaseContentHash: change.kind === "create" ? null : change.casBaseHash ?? null
67266
- };
67267
- }),
67268
- staticValueSeeds: [...pendingAssignment.staticValueSeeds].map(
67269
- ([memberId, seed]) => ({ memberId, ...seed })
67270
- ),
68037
+ changes: transportChanges,
68038
+ staticValueSeeds: transportSeeds,
67271
68039
  stagedFiles,
67272
- sourceBundle,
68040
+ sourceBundle: source.bundle,
67273
68041
  pendingIdAssignments: Object.fromEntries(pendingAssignment.assigned),
67274
68042
  summary: options.summary ?? "neo push"
67275
68043
  },
@@ -67383,7 +68151,6 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
67383
68151
  if (options.json !== true) success("Push complete.");
67384
68152
  return true;
67385
68153
  };
67386
- let progress = createPushProgressReporter(options.json === true);
67387
68154
  progress.report({
67388
68155
  type: "project-transaction-progress",
67389
68156
  transactionId: null,
@@ -67416,28 +68183,39 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
67416
68183
  fallback: false
67417
68184
  })) {
67418
68185
  progress = createPushProgressReporter(options.json === true);
67419
- progress.report({
67420
- type: "project-transaction-progress",
67421
- transactionId: null,
67422
- phase: "submitting",
67423
- totalChangeCount: status.changes.length,
67424
- appliedChangeCount: 0,
67425
- totalChunkCount: null,
67426
- appliedChunkCount: 0,
67427
- errorCode: null,
67428
- errorMessage: null
67429
- });
67430
68186
  try {
67431
68187
  stagedFiles = await stageProjectFilePushesV4({
67432
68188
  workspace,
67433
68189
  changes: status.changes,
67434
68190
  binaryChanges: status.binaryChanges ?? [],
67435
68191
  assignedIds: pendingAssignment.assigned,
67436
- client
68192
+ client,
68193
+ prepared: preparedFiles,
68194
+ onProgress: (upload) => progress.report({
68195
+ type: "push-progress",
68196
+ phase: "uploading",
68197
+ ...upload
68198
+ })
68199
+ });
68200
+ progress.report({
68201
+ type: "project-transaction-progress",
68202
+ transactionId: null,
68203
+ phase: "submitting",
68204
+ totalChangeCount: status.changes.length,
68205
+ appliedChangeCount: 0,
68206
+ totalChunkCount: null,
68207
+ appliedChunkCount: 0,
68208
+ errorCode: null,
68209
+ errorMessage: null
67437
68210
  });
67438
68211
  result = await commit(true);
67439
68212
  } catch (retryError) {
67440
68213
  progress.stop();
68214
+ if (retryError instanceof ProjectFilePushCancelledError) {
68215
+ if (options.json !== true) console.error("Push cancelled.");
68216
+ process.exitCode = 130;
68217
+ return;
68218
+ }
67441
68219
  if (retryError instanceof NeoApiError) {
67442
68220
  reportPushRejection(retryError.status, retryError.body);
67443
68221
  process.exitCode = 1;
@@ -67566,17 +68344,19 @@ async function createPendingProjectSourceBundleV4(workspace, status, staticValue
67566
68344
  }
67567
68345
  }
67568
68346
  const emission = emitProjectDocumentFilesV4(records2);
67569
- return createProjectSourceBundle(
67570
- emission.files.map((file) => {
67571
- const kind = neoProjectSourceKind(file.path);
67572
- if (kind === null) {
67573
- throw new Error(
67574
- `Emitted project source bundle file ${JSON.stringify(file.path)} is not a recognized Neo source path.`
67575
- );
67576
- }
67577
- return { path: file.path, kind, content: file.content };
67578
- })
67579
- );
68347
+ const files = emission.files.map((file) => {
68348
+ const kind = neoProjectSourceKind(file.path);
68349
+ if (kind === null) {
68350
+ throw new Error(
68351
+ `Emitted project source bundle file ${JSON.stringify(file.path)} is not a recognized Neo source path.`
68352
+ );
68353
+ }
68354
+ return { path: file.path, kind, content: file.content };
68355
+ });
68356
+ return {
68357
+ files,
68358
+ bundle: await createProjectSourceBundle(files)
68359
+ };
67580
68360
  }
67581
68361
  function staticSeedBindingMemberRecord(projectId, bindingMember, timestamp) {
67582
68362
  return {
@@ -67967,11 +68747,11 @@ ${finalErrors.map(
67967
68747
  for (const recordState of Object.values(workspace.state.records)) {
67968
68748
  const previousPath = recordState.file;
67969
68749
  if (previousPath === void 0 || emittedPaths.has(previousPath)) continue;
67970
- const absolute = join15(workspace.root, previousPath);
68750
+ const absolute = join16(workspace.root, previousPath);
67971
68751
  if (existsSync12(absolute)) rmSync6(absolute);
67972
68752
  }
67973
68753
  for (const file of files) {
67974
- const absolute = join15(workspace.root, file.path);
68754
+ const absolute = join16(workspace.root, file.path);
67975
68755
  mkdirSync11(dirname7(absolute), { recursive: true });
67976
68756
  const existing = existsSync12(absolute) ? readFileSync13(absolute, "utf8") : null;
67977
68757
  if (existing !== file.content)
@@ -68651,6 +69431,7 @@ var init_push = __esm({
68651
69431
  init_source_diagnostics();
68652
69432
  init_projection();
68653
69433
  init_project_file_push();
69434
+ init_trusted_commit_verification();
68654
69435
  init_world_system_classes();
68655
69436
  init_project_manifest();
68656
69437
  init_merge();
@@ -68693,7 +69474,7 @@ __export(dev_exports, {
68693
69474
  runDev: () => runDev
68694
69475
  });
68695
69476
  import { watch } from "node:fs";
68696
- import { join as join16 } from "node:path";
69477
+ import { join as join17 } from "node:path";
68697
69478
  import { emitKeypressEvents } from "node:readline";
68698
69479
  import { ConvexClient } from "convex/browser";
68699
69480
  function isSchemaSignal(value) {
@@ -68800,7 +69581,7 @@ async function runDev(workspace, options) {
68800
69581
  };
68801
69582
  for (const dir of ["Classes", "Enums"]) {
68802
69583
  try {
68803
- watch(join16(workspace.root, dir), { persistent: true }, onFileChange);
69584
+ watch(join17(workspace.root, dir), { persistent: true }, onFileChange);
68804
69585
  } catch {
68805
69586
  }
68806
69587
  }
@@ -68856,7 +69637,7 @@ __export(resolve_exports, {
68856
69637
  workspaceFilePath: () => workspaceFilePath
68857
69638
  });
68858
69639
  import { readFileSync as readFileSync14, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "node:fs";
68859
- import { join as join17 } from "node:path";
69640
+ import { join as join18 } from "node:path";
68860
69641
  function runResolve(workspace, side) {
68861
69642
  let resolvedFiles = 0;
68862
69643
  for (const filePath of listProjectSourceFilesV4(workspace.root)) {
@@ -68871,12 +69652,12 @@ function runResolve(workspace, side) {
68871
69652
  const binary = state.projectBinary;
68872
69653
  const conflict2 = binary?.conflict;
68873
69654
  if (binary === void 0 || conflict2 === void 0) continue;
68874
- const destination = join17(workspace.root, binary.path);
69655
+ const destination = join18(workspace.root, binary.path);
68875
69656
  if (side === "theirs") {
68876
69657
  if (conflict2.remoteSha256 !== null && conflict2.artifactPath !== void 0) {
68877
69658
  writeVerifiedBinaryDownloadV4(
68878
69659
  destination,
68879
- readFileSync14(join17(workspace.root, conflict2.artifactPath)),
69660
+ readFileSync14(join18(workspace.root, conflict2.artifactPath)),
68880
69661
  conflict2.remoteSha256
68881
69662
  );
68882
69663
  binary.sha256 = conflict2.remoteSha256;
@@ -68886,7 +69667,7 @@ function runResolve(workspace, side) {
68886
69667
  }
68887
69668
  }
68888
69669
  if (conflict2.artifactPath !== void 0) {
68889
- rmSync7(join17(workspace.root, conflict2.artifactPath), { force: true });
69670
+ rmSync7(join18(workspace.root, conflict2.artifactPath), { force: true });
68890
69671
  }
68891
69672
  delete binary.conflict;
68892
69673
  resolvedBinaries += 1;
@@ -68939,7 +69720,7 @@ function resolveMarkers(source, side) {
68939
69720
  return output.join("\n");
68940
69721
  }
68941
69722
  function workspaceFilePath(workspace, file) {
68942
- return join17(workspace.root, file);
69723
+ return join18(workspace.root, file);
68943
69724
  }
68944
69725
  var init_resolve = __esm({
68945
69726
  "src/commands/resolve.ts"() {