@neocompose/cli 0.11.0 → 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 +12 -0
  2. package/dist/neo.mjs +874 -144
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.1] - 2026-07-27
4
+
5
+ ### Fixed
6
+
7
+ - Show push preparation and file-upload progress immediately after confirmation,
8
+ validate trusted lowering before uploading bytes, and batch presigning with
9
+ bounded concurrent transfers, request deadlines, retries, and cancellation.
10
+ - Preserve inherited numeric settings when lowering newly authored member
11
+ overrides, preventing trusted-lowering rejection after file staging.
12
+ - Report the first divergent trusted-lowering field when a source commit is
13
+ rejected instead of only identifying the affected record.
14
+
3
15
  ## [0.11.0] - 2026-07-27
4
16
 
5
17
  ### Added
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": [
@@ -19507,8 +19508,8 @@ function memberSymbol(member, schemaKey, environment, field) {
19507
19508
  `${field}.source`
19508
19509
  );
19509
19510
  const overrideOf = nullableString(member.overrideOf, `${field}.overrideOf`);
19510
- const inheritedMember = overrideOf ? environment.members.get(overrideOf) : void 0;
19511
- 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;
19512
19513
  const inheritedTypeId = inheritedOwner?.kind === "classMember" && typeof inheritedOwner.classId === "string" ? inheritedOwner.classId : void 0;
19513
19514
  const inheritedType = inheritedTypeId ? environment.classes.get(inheritedTypeId) : void 0;
19514
19515
  const common = {
@@ -29260,6 +29261,7 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29260
29261
  const fieldType = partial ? requiredTypeArgument(declaration.type, 0) : declaration.type;
29261
29262
  const storedStatic = declaration.modifiers.includes("static") && !declaration.modifiers.includes("readonly");
29262
29263
  const settings = annotation(declaration.annotations, "settings");
29264
+ const inherited = inheritedMember(context, commonInput.overrideOf);
29263
29265
  const storesSelectionArray = context.enumIdsByName.has(fieldType.name) || fieldType.name === "Dialogue" || Boolean(settings && argument(settings, "collection"));
29264
29266
  const common = {
29265
29267
  ...commonInput,
@@ -29291,11 +29293,12 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29291
29293
  return { ...common, kind: name };
29292
29294
  }
29293
29295
  if (name === "int") {
29296
+ const inheritedInt = inherited?.kind === "int" ? inherited : void 0;
29294
29297
  return {
29295
29298
  ...common,
29296
29299
  kind: "int",
29297
- min: numberArgument(settings, "min"),
29298
- max: numberArgument(settings, "max")
29300
+ min: numberArgument(settings, "min") ?? inheritedInt?.min ?? null,
29301
+ max: numberArgument(settings, "max") ?? inheritedInt?.max ?? null
29299
29302
  };
29300
29303
  }
29301
29304
  if (name === "string") {
@@ -29310,20 +29313,24 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29310
29313
  const min = numberTextArgument(settings, "min");
29311
29314
  const max = numberTextArgument(settings, "max");
29312
29315
  if (name === "decimal") {
29316
+ const inheritedDecimal = inherited?.kind === "decimal" ? inherited : void 0;
29313
29317
  return {
29314
29318
  ...common,
29315
29319
  kind: "decimal",
29316
- min,
29317
- max,
29318
- decimalPoints: numberArgument(settings, "decimalPoints")
29320
+ min: min ?? inheritedDecimal?.min ?? null,
29321
+ max: max ?? inheritedDecimal?.max ?? null,
29322
+ decimalPoints: numberArgument(settings, "decimalPoints") ?? inheritedDecimal?.decimalPoints ?? null
29319
29323
  };
29320
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);
29321
29328
  return {
29322
29329
  ...common,
29323
29330
  kind: "float",
29324
- min: min === null ? null : Number(min),
29325
- max: max === null ? null : Number(max),
29326
- decimalPoints: numberArgument(settings, "decimalPoints")
29331
+ min: numericMin ?? inheritedFloat?.min ?? null,
29332
+ max: numericMax ?? inheritedFloat?.max ?? null,
29333
+ decimalPoints: numberArgument(settings, "decimalPoints") ?? inheritedFloat?.decimalPoints ?? null
29327
29334
  };
29328
29335
  }
29329
29336
  const primitive3 = primitiveMemberKind(name);
@@ -29443,6 +29450,10 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29443
29450
  `Unsupported field type ${JSON.stringify(name)} on ${ownerClass.name}.${declaration.name}.`
29444
29451
  );
29445
29452
  }
29453
+ function inheritedMember(context, memberId) {
29454
+ if (memberId === null) return void 0;
29455
+ return context.loweredMembers.get(memberId) ?? context.baseMembers.get(memberId);
29456
+ }
29446
29457
  function lowerListIndexes(ownerClass, declaration) {
29447
29458
  const declared = /* @__PURE__ */ new Set();
29448
29459
  return declaration.annotations.filter((entry) => entry.name === "index").map((entry) => {
@@ -51477,7 +51488,7 @@ var init_http = __esm({
51477
51488
  }
51478
51489
  return new _NeoApiClient(apiBaseUrl, token);
51479
51490
  }
51480
- async post(path, body, headers) {
51491
+ async post(path, body, headers, options = {}) {
51481
51492
  const url = new URL(path, this.apiBaseUrl).toString();
51482
51493
  const response = await fetch(url, {
51483
51494
  method: "POST",
@@ -51486,7 +51497,8 @@ var init_http = __esm({
51486
51497
  "Content-Type": "application/json",
51487
51498
  ...headers
51488
51499
  },
51489
- body: body === void 0 ? void 0 : JSON.stringify(body)
51500
+ body: body === void 0 ? void 0 : JSON.stringify(body),
51501
+ signal: options.signal
51490
51502
  });
51491
51503
  const text = await response.text();
51492
51504
  let parsed = null;
@@ -59572,13 +59584,13 @@ function ownedObjectChildMember(row, sourceMember, key, ctx) {
59572
59584
  return memberForCustomSchemaValue(classId, key, ctx);
59573
59585
  }
59574
59586
  function freshCloneValueId() {
59575
- const randomUUID6 = globalThis.crypto?.randomUUID;
59576
- if (randomUUID6 === void 0) {
59587
+ const randomUUID7 = globalThis.crypto?.randomUUID;
59588
+ if (randomUUID7 === void 0) {
59577
59589
  throw new NSGetterRuntimeError(
59578
59590
  "Class.Clone cannot mint a value id because crypto.randomUUID is unavailable."
59579
59591
  );
59580
59592
  }
59581
- return randomUUID6.call(globalThis.crypto);
59593
+ return randomUUID7.call(globalThis.crypto);
59582
59594
  }
59583
59595
  function parseDialogueMemoryPointer(pointer) {
59584
59596
  if (typeof pointer !== "string") return null;
@@ -66504,80 +66516,154 @@ function ensureProjectFileBinaryChangesV4(args) {
66504
66516
  });
66505
66517
  }
66506
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
+ }
66507
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
+ });
66508
66571
  const staged = [];
66509
66572
  const cleanupKeys = [];
66510
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();
66511
66588
  try {
66512
- for (const binary of args.binaryChanges) {
66513
- if (binary.action !== "create" && binary.action !== "upload") continue;
66514
- const recordId = args.assignedIds.get(binary.fileId) ?? binary.fileId;
66515
- const change = args.changes.find(
66516
- (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
66517
66593
  );
66518
- if (change === void 0 || !isObjectRecord2(change.nextData)) {
66519
- throw new Error(
66520
- `Project file ${recordId} has upload bytes but no source change.`
66521
- );
66522
- }
66523
- const absolute = join14(args.workspace.root, binary.path);
66524
- const bytes = new Uint8Array(readFileSync12(absolute));
66525
- const digest = sha256Bytes(bytes);
66526
- if (binary.localSha256 !== null && digest !== binary.localSha256) {
66527
- throw new Error(
66528
- `Project file ${binary.path} changed after status was computed; run push again.`
66529
- );
66530
- }
66531
- const mimeType = binary.mimeType;
66532
- if (mimeType === null) {
66533
- throw new Error(`Project file ${binary.path} has no MIME type.`);
66534
- }
66535
- const name = basename2(binary.path);
66536
- const replaceFileId = binary.action === "upload" ? recordId : null;
66537
- const presign = await args.client.post(
66594
+ const presign = await postWithTimeout(
66595
+ args.client,
66538
66596
  versionPath2(args.workspace, "upload"),
66539
66597
  {
66540
66598
  route: "projectFile",
66541
66599
  metadata: {
66542
- projectFileId: recordId,
66543
- replaceFileId,
66544
- contentSha256: digest,
66545
- 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
+ }))
66546
66608
  },
66547
- files: [{ name, size: bytes.byteLength, type: mimeType }]
66548
- }
66609
+ files: batch.map((file) => ({
66610
+ name: file.uploadToken,
66611
+ size: file.byteLength,
66612
+ type: file.mimeType
66613
+ }))
66614
+ },
66615
+ signal
66549
66616
  );
66550
- const entry = Array.isArray(presign.files) ? presign.files.find(isObjectRecord2) : void 0;
66551
- const fileInfo = isObjectRecord2(entry?.file) ? entry.file : {};
66552
- const objectInfo = isObjectRecord2(fileInfo.objectInfo) ? fileInfo.objectInfo : {};
66553
- if (typeof entry?.signedUrl !== "string" || typeof objectInfo.key !== "string") {
66554
- 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;
66555
66653
  }
66556
- cleanupKeys.push(objectInfo.key);
66557
- const headers = uploadHeaders(entry, objectInfo, mimeType);
66558
- const response = await put(entry.signedUrl, {
66559
- method: "PUT",
66560
- headers,
66561
- body: bytes
66562
- });
66563
- 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) {
66564
66661
  throw new Error(
66565
- `Storage PUT failed (${response.status}): ${await response.text()}`
66662
+ `Uploaded project file ${file.name} (${file.uploadToken}) is missing from the staged result.`
66566
66663
  );
66567
66664
  }
66568
- staged.push({
66569
- recordId,
66570
- replaceFileId,
66571
- name,
66572
- fileType: binary.kind,
66573
- mimeType,
66574
- byteLength: bytes.byteLength,
66575
- storageKey: objectInfo.key,
66576
- contentSha256: digest,
66577
- audioDurationSeconds: binary.kind === "audio" && typeof change.nextData.audioDurationSeconds === "number" ? change.nextData.audioDurationSeconds : null
66578
- });
66579
- }
66580
- return staged;
66665
+ return result;
66666
+ });
66581
66667
  } catch (error) {
66582
66668
  if (cleanupKeys.length > 0) {
66583
66669
  try {
@@ -66588,9 +66674,146 @@ async function stageProjectFilePushesV4(args) {
66588
66674
  } catch {
66589
66675
  }
66590
66676
  }
66677
+ if (interruptController.signal.aborted || args.signal?.aborted === true) {
66678
+ throw new ProjectFilePushCancelledError();
66679
+ }
66591
66680
  throw error;
66681
+ } finally {
66682
+ process.removeListener("SIGINT", interrupt);
66592
66683
  }
66593
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;
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);
66816
+ }
66594
66817
  function uploadHeaders(entry, objectInfo, mimeType) {
66595
66818
  const headers = { "Content-Type": mimeType };
66596
66819
  if (typeof objectInfo.cacheControl === "string") {
@@ -66611,11 +66834,431 @@ function uploadHeaders(entry, objectInfo, mimeType) {
66611
66834
  function versionPath2(workspace, suffix) {
66612
66835
  return `/api/projects/${workspace.config.projectId}/versions/${workspace.config.versionId}/${suffix}`;
66613
66836
  }
66837
+ var UPLOAD_CONCURRENCY, PRESIGN_TIMEOUT_MS, PRESIGN_ATTEMPTS, STORAGE_PUT_TIMEOUT_MS, STORAGE_PUT_ATTEMPTS, ProjectFilePushCancelledError, RETRYABLE_NETWORK_ERROR_CODES;
66614
66838
  var init_project_file_push = __esm({
66615
66839
  "src/project-source/project-file-push.ts"() {
66616
66840
  "use strict";
66617
66841
  init_project_files();
66618
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");
66619
67262
  }
66620
67263
  });
66621
67264
 
@@ -66660,7 +67303,7 @@ __export(push_exports, {
66660
67303
  stripServerDerivedNeoScript: () => stripServerDerivedNeoScript,
66661
67304
  workspaceChangesRequireCompleteBodySweep: () => workspaceChangesRequireCompleteBodySweep
66662
67305
  });
66663
- import { randomUUID as randomUUID5 } from "node:crypto";
67306
+ import { randomUUID as randomUUID6 } from "node:crypto";
66664
67307
  import {
66665
67308
  mkdirSync as mkdirSync11,
66666
67309
  writeFileSync as writeFileSync11,
@@ -66668,7 +67311,7 @@ import {
66668
67311
  existsSync as existsSync12,
66669
67312
  readFileSync as readFileSync13
66670
67313
  } from "node:fs";
66671
- 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";
66672
67315
  function assignPendingIds(changes, staticValueSeeds, reconstructed3) {
66673
67316
  const assigned = /* @__PURE__ */ new Map();
66674
67317
  const assign = (pendingId2) => {
@@ -66681,7 +67324,7 @@ function assignPendingIds(changes, staticValueSeeds, reconstructed3) {
66681
67324
  assigned.set(pendingId2, derived);
66682
67325
  return derived;
66683
67326
  }
66684
- const fresh = randomUUID5();
67327
+ const fresh = randomUUID6();
66685
67328
  assigned.set(pendingId2, fresh);
66686
67329
  return fresh;
66687
67330
  };
@@ -66992,6 +67635,10 @@ function createPushProgressReporter(json) {
66992
67635
  };
66993
67636
  }
66994
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
+ }
66995
67642
  if (event.phase === "submitting") return "Pushing\u2026";
66996
67643
  if (event.phase === "preparing") return "Preparing push\u2026";
66997
67644
  if (event.phase === "waiting") return "Waiting to apply project transaction\u2026";
@@ -67009,6 +67656,11 @@ function projectTransactionProgressLabel(event) {
67009
67656
  }
67010
67657
  return "Finalizing project transaction\u2026";
67011
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
+ }
67012
67664
  function progressEventFromAccepted(accepted) {
67013
67665
  return {
67014
67666
  type: "project-transaction-progress",
@@ -67281,46 +67933,111 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
67281
67933
  console.log("Push cancelled.");
67282
67934
  return;
67283
67935
  }
67284
- const sourceBundle = await createPendingProjectSourceBundleV4(
67285
- workspace,
67286
- status,
67287
- status.staticValueSeeds
67288
- );
67289
- const pendingAssignment = assignPendingIds(
67290
- status.changes,
67291
- status.staticValueSeeds,
67292
- status.reconstructed
67293
- );
67294
- const operations = new Set(status.changes.map((change) => change.kind));
67295
- const transactionOperation = operations.size === 1 ? status.changes[0].kind : "update";
67296
- const client = NeoApiClient.forBaseUrl(workspace.config.apiBaseUrl);
67297
- let stagedFiles = await stageProjectFilePushesV4({
67298
- workspace,
67299
- changes: status.changes,
67300
- binaryChanges: status.binaryChanges ?? [],
67301
- assignedIds: pendingAssignment.assigned,
67302
- 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
67303
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;
67304
68033
  const commit = async (force) => await client.post(
67305
68034
  `/api/projects/${workspace.config.projectId}/versions/${workspace.config.versionId}/schema/commit`,
67306
68035
  {
67307
68036
  operation: transactionOperation,
67308
- changes: status.changes.map((change) => {
67309
- return {
67310
- recordKind: change.recordKind,
67311
- recordId: change.recordId,
67312
- operation: change.kind,
67313
- nextData: change.kind === "delete" ? void 0 : stripServerDerivedNeoScript(change.nextData),
67314
- deleted: change.kind === "delete" ? true : void 0,
67315
- intent: createNeoCliPushIntent(change),
67316
- expectedBaseContentHash: change.kind === "create" ? null : change.casBaseHash ?? null
67317
- };
67318
- }),
67319
- staticValueSeeds: [...pendingAssignment.staticValueSeeds].map(
67320
- ([memberId, seed]) => ({ memberId, ...seed })
67321
- ),
68037
+ changes: transportChanges,
68038
+ staticValueSeeds: transportSeeds,
67322
68039
  stagedFiles,
67323
- sourceBundle,
68040
+ sourceBundle: source.bundle,
67324
68041
  pendingIdAssignments: Object.fromEntries(pendingAssignment.assigned),
67325
68042
  summary: options.summary ?? "neo push"
67326
68043
  },
@@ -67434,7 +68151,6 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
67434
68151
  if (options.json !== true) success("Push complete.");
67435
68152
  return true;
67436
68153
  };
67437
- let progress = createPushProgressReporter(options.json === true);
67438
68154
  progress.report({
67439
68155
  type: "project-transaction-progress",
67440
68156
  transactionId: null,
@@ -67467,28 +68183,39 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
67467
68183
  fallback: false
67468
68184
  })) {
67469
68185
  progress = createPushProgressReporter(options.json === true);
67470
- progress.report({
67471
- type: "project-transaction-progress",
67472
- transactionId: null,
67473
- phase: "submitting",
67474
- totalChangeCount: status.changes.length,
67475
- appliedChangeCount: 0,
67476
- totalChunkCount: null,
67477
- appliedChunkCount: 0,
67478
- errorCode: null,
67479
- errorMessage: null
67480
- });
67481
68186
  try {
67482
68187
  stagedFiles = await stageProjectFilePushesV4({
67483
68188
  workspace,
67484
68189
  changes: status.changes,
67485
68190
  binaryChanges: status.binaryChanges ?? [],
67486
68191
  assignedIds: pendingAssignment.assigned,
67487
- 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
67488
68210
  });
67489
68211
  result = await commit(true);
67490
68212
  } catch (retryError) {
67491
68213
  progress.stop();
68214
+ if (retryError instanceof ProjectFilePushCancelledError) {
68215
+ if (options.json !== true) console.error("Push cancelled.");
68216
+ process.exitCode = 130;
68217
+ return;
68218
+ }
67492
68219
  if (retryError instanceof NeoApiError) {
67493
68220
  reportPushRejection(retryError.status, retryError.body);
67494
68221
  process.exitCode = 1;
@@ -67617,17 +68344,19 @@ async function createPendingProjectSourceBundleV4(workspace, status, staticValue
67617
68344
  }
67618
68345
  }
67619
68346
  const emission = emitProjectDocumentFilesV4(records2);
67620
- return createProjectSourceBundle(
67621
- emission.files.map((file) => {
67622
- const kind = neoProjectSourceKind(file.path);
67623
- if (kind === null) {
67624
- throw new Error(
67625
- `Emitted project source bundle file ${JSON.stringify(file.path)} is not a recognized Neo source path.`
67626
- );
67627
- }
67628
- return { path: file.path, kind, content: file.content };
67629
- })
67630
- );
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
+ };
67631
68360
  }
67632
68361
  function staticSeedBindingMemberRecord(projectId, bindingMember, timestamp) {
67633
68362
  return {
@@ -68018,11 +68747,11 @@ ${finalErrors.map(
68018
68747
  for (const recordState of Object.values(workspace.state.records)) {
68019
68748
  const previousPath = recordState.file;
68020
68749
  if (previousPath === void 0 || emittedPaths.has(previousPath)) continue;
68021
- const absolute = join15(workspace.root, previousPath);
68750
+ const absolute = join16(workspace.root, previousPath);
68022
68751
  if (existsSync12(absolute)) rmSync6(absolute);
68023
68752
  }
68024
68753
  for (const file of files) {
68025
- const absolute = join15(workspace.root, file.path);
68754
+ const absolute = join16(workspace.root, file.path);
68026
68755
  mkdirSync11(dirname7(absolute), { recursive: true });
68027
68756
  const existing = existsSync12(absolute) ? readFileSync13(absolute, "utf8") : null;
68028
68757
  if (existing !== file.content)
@@ -68702,6 +69431,7 @@ var init_push = __esm({
68702
69431
  init_source_diagnostics();
68703
69432
  init_projection();
68704
69433
  init_project_file_push();
69434
+ init_trusted_commit_verification();
68705
69435
  init_world_system_classes();
68706
69436
  init_project_manifest();
68707
69437
  init_merge();
@@ -68744,7 +69474,7 @@ __export(dev_exports, {
68744
69474
  runDev: () => runDev
68745
69475
  });
68746
69476
  import { watch } from "node:fs";
68747
- import { join as join16 } from "node:path";
69477
+ import { join as join17 } from "node:path";
68748
69478
  import { emitKeypressEvents } from "node:readline";
68749
69479
  import { ConvexClient } from "convex/browser";
68750
69480
  function isSchemaSignal(value) {
@@ -68851,7 +69581,7 @@ async function runDev(workspace, options) {
68851
69581
  };
68852
69582
  for (const dir of ["Classes", "Enums"]) {
68853
69583
  try {
68854
- watch(join16(workspace.root, dir), { persistent: true }, onFileChange);
69584
+ watch(join17(workspace.root, dir), { persistent: true }, onFileChange);
68855
69585
  } catch {
68856
69586
  }
68857
69587
  }
@@ -68907,7 +69637,7 @@ __export(resolve_exports, {
68907
69637
  workspaceFilePath: () => workspaceFilePath
68908
69638
  });
68909
69639
  import { readFileSync as readFileSync14, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "node:fs";
68910
- import { join as join17 } from "node:path";
69640
+ import { join as join18 } from "node:path";
68911
69641
  function runResolve(workspace, side) {
68912
69642
  let resolvedFiles = 0;
68913
69643
  for (const filePath of listProjectSourceFilesV4(workspace.root)) {
@@ -68922,12 +69652,12 @@ function runResolve(workspace, side) {
68922
69652
  const binary = state.projectBinary;
68923
69653
  const conflict2 = binary?.conflict;
68924
69654
  if (binary === void 0 || conflict2 === void 0) continue;
68925
- const destination = join17(workspace.root, binary.path);
69655
+ const destination = join18(workspace.root, binary.path);
68926
69656
  if (side === "theirs") {
68927
69657
  if (conflict2.remoteSha256 !== null && conflict2.artifactPath !== void 0) {
68928
69658
  writeVerifiedBinaryDownloadV4(
68929
69659
  destination,
68930
- readFileSync14(join17(workspace.root, conflict2.artifactPath)),
69660
+ readFileSync14(join18(workspace.root, conflict2.artifactPath)),
68931
69661
  conflict2.remoteSha256
68932
69662
  );
68933
69663
  binary.sha256 = conflict2.remoteSha256;
@@ -68937,7 +69667,7 @@ function runResolve(workspace, side) {
68937
69667
  }
68938
69668
  }
68939
69669
  if (conflict2.artifactPath !== void 0) {
68940
- rmSync7(join17(workspace.root, conflict2.artifactPath), { force: true });
69670
+ rmSync7(join18(workspace.root, conflict2.artifactPath), { force: true });
68941
69671
  }
68942
69672
  delete binary.conflict;
68943
69673
  resolvedBinaries += 1;
@@ -68990,7 +69720,7 @@ function resolveMarkers(source, side) {
68990
69720
  return output.join("\n");
68991
69721
  }
68992
69722
  function workspaceFilePath(workspace, file) {
68993
- return join17(workspace.root, file);
69723
+ return join18(workspace.root, file);
68994
69724
  }
68995
69725
  var init_resolve = __esm({
68996
69726
  "src/commands/resolve.ts"() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",