@neocompose/cli 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.2] - 2026-07-19
4
+
5
+ ### Fixed
6
+
7
+ - Follow large `neo push` operations as one durable server transaction,
8
+ reporting bounded preparation/apply progress until commit and recovering the
9
+ same assigned IDs and result records after reconnect. Ctrl-C now leaves the
10
+ server job running without mutating the local working copy.
11
+
3
12
  ## [0.6.0] - 2026-07-19
4
13
 
5
14
  ### Added
package/dist/neo.mjs CHANGED
@@ -26173,6 +26173,9 @@ var init_merge = __esm({
26173
26173
  });
26174
26174
 
26175
26175
  // src/project-manifest/merge.ts
26176
+ function isSchemaDocumentRecordKind(recordKind) {
26177
+ return Object.hasOwn(SCHEMA_DOCUMENT_FIELD_CONTRACTS, recordKind);
26178
+ }
26176
26179
  function schemaDocumentSemanticallyEqual(recordKind, left, right) {
26177
26180
  return canonicallyEqual(
26178
26181
  normalizeDocumentForComparison(recordKind, left, "left"),
@@ -45473,6 +45476,7 @@ __export(project_document_read_exports, {
45473
45476
  readOptionalArrayField: () => readOptionalArrayField,
45474
45477
  readProjectDocument: () => readProjectDocument,
45475
45478
  readProjectDocumentContentHashHeads: () => readProjectDocumentContentHashHeads,
45479
+ readProjectDocumentManifestPageRecords: () => readProjectDocumentManifestPageRecords,
45476
45480
  readProjectDocumentManifestRecords: () => readProjectDocumentManifestRecords,
45477
45481
  readProjectDocumentRevisionMarker: () => readProjectDocumentRevisionMarker
45478
45482
  });
@@ -45678,6 +45682,12 @@ function readProjectDocumentManifestRecords(value) {
45678
45682
  };
45679
45683
  });
45680
45684
  }
45685
+ function readProjectDocumentManifestPageRecords(value) {
45686
+ if (!isObject3(value)) {
45687
+ throw new Error("Convex project document manifest page must be an object.");
45688
+ }
45689
+ return readProjectDocumentManifestRecords(value.records);
45690
+ }
45681
45691
  async function fetchProjectDocumentSnapshots(fetchBatch, snapshotIds) {
45682
45692
  const chunks = [];
45683
45693
  for (let offset = 0; offset < snapshotIds.length; offset += DOCUMENT_SNAPSHOT_FETCH_CHUNK_SIZE) {
@@ -46639,6 +46649,32 @@ var init_http = __esm({
46639
46649
  }
46640
46650
  return parsed;
46641
46651
  }
46652
+ async get(path, options = {}) {
46653
+ const url = new URL(path, this.apiBaseUrl).toString();
46654
+ const response = await fetch(url, {
46655
+ method: "GET",
46656
+ headers: { Authorization: `Bearer ${this.token}` },
46657
+ signal: options.signal
46658
+ });
46659
+ const text = await response.text();
46660
+ let parsed = null;
46661
+ if (text.length > 0) {
46662
+ try {
46663
+ parsed = JSON.parse(text);
46664
+ } catch {
46665
+ parsed = text;
46666
+ }
46667
+ }
46668
+ if (!response.ok) {
46669
+ const detail = typeof parsed === "object" && parsed !== null && "error" in parsed && typeof parsed.error === "string" ? parsed.error : text.slice(0, 300);
46670
+ throw new NeoApiError(
46671
+ `GET ${path} failed with status ${response.status}: ${detail}`,
46672
+ response.status,
46673
+ parsed
46674
+ );
46675
+ }
46676
+ return parsed;
46677
+ }
46642
46678
  async getBytes(path) {
46643
46679
  const url = new URL(path, this.apiBaseUrl).toString();
46644
46680
  const response = await fetch(url, {
@@ -59063,11 +59099,32 @@ async function runMerge(workspace, sourceRef, dryRun, runMigrations = false) {
59063
59099
  }
59064
59100
  }
59065
59101
  try {
59066
- const result = await client.post(mergePath, {
59102
+ let result = await client.post(mergePath, {
59067
59103
  sourceVersionId: source.id,
59068
59104
  dryRun,
59069
59105
  resolutions: resolutions.length > 0 ? resolutions : void 0
59070
59106
  });
59107
+ const accepted = readAcceptedMergeTransaction(result);
59108
+ if (accepted !== null) {
59109
+ const progress = spinner("Preparing merge\u2026");
59110
+ try {
59111
+ await waitForMergeTransaction({
59112
+ client,
59113
+ projectId: workspace.config.projectId,
59114
+ versionId: workspace.config.versionId,
59115
+ accepted,
59116
+ update: (label) => progress.update(label)
59117
+ });
59118
+ progress.succeed("Merge transaction committed.");
59119
+ } catch (error) {
59120
+ progress.fail("Merge transaction failed.");
59121
+ throw error;
59122
+ }
59123
+ result = { ...result, commitStatus: "committed" };
59124
+ if (result.sourceArchivePending === true) {
59125
+ console.log("Source branch archival is finishing in the background.");
59126
+ }
59127
+ }
59071
59128
  const merged = typeof result.merged === "number" ? result.merged : 0;
59072
59129
  if (dryRun) {
59073
59130
  console.log(`Merge dry-run: ${merged} record(s) would merge cleanly.`);
@@ -59135,6 +59192,104 @@ async function runMerge(workspace, sourceRef, dryRun, runMigrations = false) {
59135
59192
  throw error;
59136
59193
  }
59137
59194
  }
59195
+ function readAcceptedMergeTransaction(value) {
59196
+ if (!isObjectRecord2(value)) {
59197
+ throw new Error("Merge response must be an object.");
59198
+ }
59199
+ if (value.kind !== "accepted") return null;
59200
+ if (typeof value.transactionId !== "string") {
59201
+ throw new Error("Accepted merge is missing transactionId.");
59202
+ }
59203
+ if (!isMergeTransactionStatus(value.commitStatus)) {
59204
+ throw new Error("Accepted merge has an invalid commitStatus.");
59205
+ }
59206
+ const totalChangeCount = value.totalChangeCount === null ? null : readMergeProgressCount(value.totalChangeCount, "totalChangeCount");
59207
+ const appliedChangeCount = readMergeProgressCount(
59208
+ value.appliedChangeCount,
59209
+ "appliedChangeCount"
59210
+ );
59211
+ return {
59212
+ transactionId: value.transactionId,
59213
+ commitStatus: value.commitStatus,
59214
+ totalChangeCount,
59215
+ appliedChangeCount
59216
+ };
59217
+ }
59218
+ async function waitForMergeTransaction(args) {
59219
+ const transactionPath = `/api/projects/${encodeURIComponent(args.projectId)}/versions/${encodeURIComponent(args.versionId)}/schema/transactions/${encodeURIComponent(args.accepted.transactionId)}`;
59220
+ let status = args.accepted;
59221
+ let delayMs = 250;
59222
+ for (; ; ) {
59223
+ args.update(mergeTransactionProgressLabel(status));
59224
+ if (status.commitStatus === "committed") return;
59225
+ if (status.commitStatus === "failed") {
59226
+ throw new Error(
59227
+ `Merge transaction "${status.transactionId}" failed after rollback.`
59228
+ );
59229
+ }
59230
+ const response = await args.client.get(transactionPath);
59231
+ status = readMergeTransactionStatus(response, status.transactionId);
59232
+ if (status.commitStatus === "committed") continue;
59233
+ if (status.commitStatus === "failed") continue;
59234
+ await new Promise((resolve4) => setTimeout(resolve4, delayMs));
59235
+ delayMs = Math.min(delayMs * 2, 5e3);
59236
+ }
59237
+ }
59238
+ function readMergeTransactionStatus(value, transactionId) {
59239
+ if (!isObjectRecord2(value)) {
59240
+ throw new Error(
59241
+ `Merge transaction "${transactionId}" status must be an object.`
59242
+ );
59243
+ }
59244
+ if (value.transactionId !== transactionId) {
59245
+ throw new Error(
59246
+ `Merge transaction status expected "${transactionId}", received ${JSON.stringify(value.transactionId)}.`
59247
+ );
59248
+ }
59249
+ if (!isMergeTransactionStatus(value.commitStatus)) {
59250
+ throw new Error(
59251
+ `Merge transaction "${transactionId}" has an invalid commitStatus.`
59252
+ );
59253
+ }
59254
+ const totalChangeCount = value.totalChangeCount === null ? null : readMergeProgressCount(value.totalChangeCount, "totalChangeCount");
59255
+ const appliedChangeCount = readMergeProgressCount(
59256
+ value.appliedChangeCount,
59257
+ "appliedChangeCount"
59258
+ );
59259
+ return {
59260
+ transactionId,
59261
+ commitStatus: value.commitStatus,
59262
+ totalChangeCount,
59263
+ appliedChangeCount
59264
+ };
59265
+ }
59266
+ function readMergeProgressCount(value, field) {
59267
+ if (typeof value !== "number") {
59268
+ throw new Error(`Merge transaction ${field} must be a number.`);
59269
+ }
59270
+ if (!Number.isSafeInteger(value)) {
59271
+ throw new Error(`Merge transaction ${field} must be a safe integer.`);
59272
+ }
59273
+ if (value < 0) {
59274
+ throw new Error(`Merge transaction ${field} must not be negative.`);
59275
+ }
59276
+ return value;
59277
+ }
59278
+ function isMergeTransactionStatus(value) {
59279
+ return value === "preparing" || value === "waiting" || value === "queued" || value === "applying" || value === "reverting" || value === "committed" || value === "failed";
59280
+ }
59281
+ function mergeTransactionProgressLabel(status) {
59282
+ if (status.commitStatus === "preparing") return "Preparing merge\u2026";
59283
+ if (status.commitStatus === "waiting") return "Waiting to apply merge\u2026";
59284
+ if (status.commitStatus === "queued") return "Queueing merge\u2026";
59285
+ if (status.commitStatus === "reverting") return "Reverting merge\u2026";
59286
+ if (status.commitStatus === "committed") return "Merge committed.";
59287
+ if (status.commitStatus === "failed") return "Merge failed.";
59288
+ if (status.totalChangeCount !== null) {
59289
+ return `Applying merge\u2026 ${status.appliedChangeCount.toLocaleString("en-US")}/${status.totalChangeCount.toLocaleString("en-US")} changes`;
59290
+ }
59291
+ return "Applying merge\u2026";
59292
+ }
59138
59293
  async function runRefresh(workspace, dryRun) {
59139
59294
  const { versions } = await loadVersionMetadata(workspace);
59140
59295
  const current = versions.find(
@@ -61726,6 +61881,345 @@ function assignPendingIds(changes, staticValueSeeds, reconstructed3) {
61726
61881
  }
61727
61882
  return { assigned, staticValueSeeds: rewrittenSeeds };
61728
61883
  }
61884
+ function readAcceptedProjectVersionCommitResponse(value) {
61885
+ if (!isObjectRecord2(value) || value.kind !== "accepted") return null;
61886
+ if (typeof value.transactionId !== "string") {
61887
+ throw new Error("Accepted project transaction is missing transactionId.");
61888
+ }
61889
+ const rawStatus = value.commitStatus ?? value.status;
61890
+ if (rawStatus !== "preparing" && rawStatus !== "waiting" && rawStatus !== "queued" && rawStatus !== "applying" && rawStatus !== "reverting" && rawStatus !== "committed" && rawStatus !== "failed") {
61891
+ throw new Error(
61892
+ `Accepted project transaction "${value.transactionId}" has invalid commitStatus ${JSON.stringify(rawStatus)}.`
61893
+ );
61894
+ }
61895
+ const totalChangeCount = readNullableCount(
61896
+ value.totalChangeCount,
61897
+ "accepted totalChangeCount"
61898
+ );
61899
+ const appliedChangeCount = readCount(
61900
+ value.appliedChangeCount,
61901
+ "accepted appliedChangeCount"
61902
+ );
61903
+ const totalChunkCount = readNullableCount(
61904
+ value.totalChunkCount,
61905
+ "accepted totalChunkCount"
61906
+ );
61907
+ const appliedChunkCount = readCount(
61908
+ value.appliedChunkCount,
61909
+ "accepted appliedChunkCount"
61910
+ );
61911
+ return {
61912
+ kind: "accepted",
61913
+ transactionId: value.transactionId,
61914
+ commitStatus: rawStatus,
61915
+ totalChangeCount,
61916
+ appliedChangeCount,
61917
+ totalChunkCount,
61918
+ appliedChunkCount
61919
+ };
61920
+ }
61921
+ function readProjectVersionTransactionStatus(value, transactionId) {
61922
+ if (!isObjectRecord2(value)) {
61923
+ throw new Error(
61924
+ `Project transaction "${transactionId}" status response must be an object.`
61925
+ );
61926
+ }
61927
+ if (value.transactionId !== transactionId) {
61928
+ throw new Error(
61929
+ `Project transaction status response expected transactionId "${transactionId}"; received ${JSON.stringify(value.transactionId)}.`
61930
+ );
61931
+ }
61932
+ if (!isProjectVersionTransactionCommitStatus(value.commitStatus)) {
61933
+ throw new Error(
61934
+ `Project transaction "${transactionId}" has invalid commitStatus ${JSON.stringify(value.commitStatus)}.`
61935
+ );
61936
+ }
61937
+ const errorCode = readNullableString(
61938
+ value.errorCode,
61939
+ `Project transaction "${transactionId}" errorCode`
61940
+ );
61941
+ const errorMessage3 = readNullableString(
61942
+ value.errorMessage,
61943
+ `Project transaction "${transactionId}" errorMessage`
61944
+ );
61945
+ const resultCursor = readNullableString(
61946
+ value.resultCursor,
61947
+ `Project transaction "${transactionId}" resultCursor`
61948
+ );
61949
+ return {
61950
+ transactionId,
61951
+ commitStatus: value.commitStatus,
61952
+ totalChangeCount: readNullableCount(
61953
+ value.totalChangeCount,
61954
+ `Project transaction "${transactionId}" totalChangeCount`
61955
+ ),
61956
+ appliedChangeCount: readCount(
61957
+ value.appliedChangeCount,
61958
+ `Project transaction "${transactionId}" appliedChangeCount`
61959
+ ),
61960
+ totalChunkCount: readNullableCount(
61961
+ value.totalChunkCount,
61962
+ `Project transaction "${transactionId}" totalChunkCount`
61963
+ ),
61964
+ appliedChunkCount: readCount(
61965
+ value.appliedChunkCount,
61966
+ `Project transaction "${transactionId}" appliedChunkCount`
61967
+ ),
61968
+ errorCode,
61969
+ errorMessage: errorMessage3,
61970
+ resultCursor
61971
+ };
61972
+ }
61973
+ function readProjectVersionTransactionResultPage(value, transactionId) {
61974
+ if (!isObjectRecord2(value)) {
61975
+ throw new Error(
61976
+ `Project transaction "${transactionId}" result page must be an object.`
61977
+ );
61978
+ }
61979
+ if (value.transactionId !== transactionId) {
61980
+ throw new Error(
61981
+ `Project transaction result page expected transactionId "${transactionId}"; received ${JSON.stringify(value.transactionId)}.`
61982
+ );
61983
+ }
61984
+ if (!isObjectRecord2(value.assignments)) {
61985
+ throw new Error(
61986
+ `Project transaction "${transactionId}" result page assignments must be an object.`
61987
+ );
61988
+ }
61989
+ const assignments = {};
61990
+ for (const [pendingId2, assignedId] of Object.entries(value.assignments)) {
61991
+ if (typeof assignedId !== "string") {
61992
+ throw new Error(
61993
+ `Project transaction "${transactionId}" result assignment ${JSON.stringify(pendingId2)} must be a string.`
61994
+ );
61995
+ }
61996
+ assignments[pendingId2] = assignedId;
61997
+ }
61998
+ if (!Array.isArray(value.changedRecords)) {
61999
+ throw new Error(
62000
+ `Project transaction "${transactionId}" result page changedRecords must be an array.`
62001
+ );
62002
+ }
62003
+ const continueCursor = readNullableString(
62004
+ value.continueCursor,
62005
+ `Project transaction "${transactionId}" result page continueCursor`
62006
+ );
62007
+ if (typeof value.isDone !== "boolean") {
62008
+ throw new Error(
62009
+ `Project transaction "${transactionId}" result page isDone must be a boolean.`
62010
+ );
62011
+ }
62012
+ return {
62013
+ transactionId,
62014
+ assignments,
62015
+ changedRecords: value.changedRecords,
62016
+ continueCursor,
62017
+ isDone: value.isDone
62018
+ };
62019
+ }
62020
+ function readCount(value, field) {
62021
+ if (typeof value !== "number") {
62022
+ throw new Error(`${field} must be a number.`);
62023
+ }
62024
+ if (!Number.isSafeInteger(value)) {
62025
+ throw new Error(`${field} must be a safe integer.`);
62026
+ }
62027
+ if (value < 0) {
62028
+ throw new Error(`${field} must not be negative.`);
62029
+ }
62030
+ return value;
62031
+ }
62032
+ function readNullableCount(value, field) {
62033
+ if (value === null) return null;
62034
+ return readCount(value, field);
62035
+ }
62036
+ function readNullableString(value, field) {
62037
+ if (value === null) return null;
62038
+ if (typeof value !== "string") {
62039
+ throw new Error(`${field} must be a string or null.`);
62040
+ }
62041
+ return value;
62042
+ }
62043
+ function isProjectVersionTransactionCommitStatus(value) {
62044
+ return value === "preparing" || value === "waiting" || value === "queued" || value === "applying" || value === "reverting" || value === "committed" || value === "failed";
62045
+ }
62046
+ function createPushProgressReporter(json) {
62047
+ const animated = process.stdout.isTTY === true && !json;
62048
+ const indicator = animated ? spinner("Pushing\u2026") : null;
62049
+ let lastStructuredEvent = "";
62050
+ return {
62051
+ report(event) {
62052
+ if (indicator !== null) {
62053
+ indicator.update(projectTransactionProgressLabel(event));
62054
+ return;
62055
+ }
62056
+ const encoded = JSON.stringify(event);
62057
+ if (encoded === lastStructuredEvent) return;
62058
+ lastStructuredEvent = encoded;
62059
+ console.log(encoded);
62060
+ },
62061
+ stop() {
62062
+ indicator?.stop();
62063
+ }
62064
+ };
62065
+ }
62066
+ function projectTransactionProgressLabel(event) {
62067
+ if (event.phase === "submitting") return "Pushing\u2026";
62068
+ if (event.phase === "preparing") return "Preparing push\u2026";
62069
+ if (event.phase === "waiting") return "Waiting to apply project transaction\u2026";
62070
+ if (event.phase === "reverting") return "Reverting project transaction\u2026";
62071
+ if (event.phase === "finalizing") return "Finalizing project transaction\u2026";
62072
+ if (event.phase === "committed") return "Push complete.";
62073
+ if (event.phase === "failed") return "Project transaction failed.";
62074
+ if (event.phase === "interrupted")
62075
+ return "Project transaction still running.";
62076
+ if (event.totalChangeCount !== null && event.appliedChangeCount < event.totalChangeCount) {
62077
+ return `Applying project transaction\u2026 ${event.appliedChangeCount.toLocaleString("en-US")}/${event.totalChangeCount.toLocaleString("en-US")} changes`;
62078
+ }
62079
+ if (event.totalChunkCount !== null && event.appliedChunkCount < event.totalChunkCount) {
62080
+ return `Applying project transaction\u2026 ${event.appliedChunkCount.toLocaleString("en-US")}/${event.totalChunkCount.toLocaleString("en-US")} chunks`;
62081
+ }
62082
+ return "Finalizing project transaction\u2026";
62083
+ }
62084
+ function progressEventFromAccepted(accepted) {
62085
+ return {
62086
+ type: "project-transaction-progress",
62087
+ transactionId: accepted.transactionId,
62088
+ phase: accepted.commitStatus,
62089
+ totalChangeCount: accepted.totalChangeCount,
62090
+ appliedChangeCount: accepted.appliedChangeCount,
62091
+ totalChunkCount: accepted.totalChunkCount,
62092
+ appliedChunkCount: accepted.appliedChunkCount,
62093
+ errorCode: null,
62094
+ errorMessage: null
62095
+ };
62096
+ }
62097
+ function progressEventFromStatus(status) {
62098
+ const phase = status.commitStatus === "committed" || status.commitStatus === "applying" && status.totalChunkCount !== null && status.appliedChunkCount === status.totalChunkCount ? "finalizing" : status.commitStatus;
62099
+ return {
62100
+ type: "project-transaction-progress",
62101
+ transactionId: status.transactionId,
62102
+ phase,
62103
+ totalChangeCount: status.totalChangeCount,
62104
+ appliedChangeCount: status.appliedChangeCount,
62105
+ totalChunkCount: status.totalChunkCount,
62106
+ appliedChunkCount: status.appliedChunkCount,
62107
+ errorCode: status.errorCode,
62108
+ errorMessage: status.errorMessage
62109
+ };
62110
+ }
62111
+ async function waitForAcceptedProjectVersionTransaction(args) {
62112
+ const { client, projectId, versionId, accepted, reporter } = args;
62113
+ const transactionPath = `/api/projects/${projectId}/versions/${versionId}/schema/transactions/${accepted.transactionId}`;
62114
+ const controller = new AbortController();
62115
+ let interrupted = false;
62116
+ const interrupt = () => {
62117
+ interrupted = true;
62118
+ controller.abort();
62119
+ };
62120
+ process.once("SIGINT", interrupt);
62121
+ reporter.report(progressEventFromAccepted(accepted));
62122
+ let pollIndex = 0;
62123
+ try {
62124
+ for (; ; ) {
62125
+ const rawStatus = await client.get(transactionPath, {
62126
+ signal: controller.signal
62127
+ });
62128
+ const status = readProjectVersionTransactionStatus(
62129
+ rawStatus,
62130
+ accepted.transactionId
62131
+ );
62132
+ reporter.report(progressEventFromStatus(status));
62133
+ if (status.commitStatus === "committed") {
62134
+ return await downloadProjectVersionTransactionResult({
62135
+ client,
62136
+ transactionPath,
62137
+ transactionId: accepted.transactionId,
62138
+ status,
62139
+ signal: controller.signal
62140
+ });
62141
+ }
62142
+ if (status.commitStatus === "failed") {
62143
+ throw new ProjectTransactionFailedError(status);
62144
+ }
62145
+ const delay = PROJECT_TRANSACTION_POLL_DELAYS_MS[Math.min(pollIndex, PROJECT_TRANSACTION_POLL_DELAYS_MS.length - 1)];
62146
+ pollIndex += 1;
62147
+ await waitForPollDelay(delay, controller.signal);
62148
+ }
62149
+ } catch (error) {
62150
+ if (interrupted) {
62151
+ throw new ProjectTransactionInterruptedError(accepted.transactionId);
62152
+ }
62153
+ throw error;
62154
+ } finally {
62155
+ process.removeListener("SIGINT", interrupt);
62156
+ }
62157
+ }
62158
+ async function waitForPollDelay(delayMs, signal) {
62159
+ if (signal.aborted) {
62160
+ throw new Error("Project transaction polling was interrupted.");
62161
+ }
62162
+ await new Promise((resolve4, reject) => {
62163
+ const aborted = () => {
62164
+ clearTimeout(timer);
62165
+ reject(new Error("Project transaction polling was interrupted."));
62166
+ };
62167
+ const timer = setTimeout(() => {
62168
+ signal.removeEventListener("abort", aborted);
62169
+ resolve4();
62170
+ }, delayMs);
62171
+ signal.addEventListener("abort", aborted, { once: true });
62172
+ });
62173
+ }
62174
+ async function downloadProjectVersionTransactionResult(args) {
62175
+ const assignments = {};
62176
+ const changedRecords = [];
62177
+ const seenCursors = /* @__PURE__ */ new Set();
62178
+ let cursor = args.status.resultCursor ?? "";
62179
+ for (; ; ) {
62180
+ if (seenCursors.has(cursor)) {
62181
+ throw new Error(
62182
+ `Project transaction "${args.transactionId}" result pagination repeated cursor ${JSON.stringify(cursor)}.`
62183
+ );
62184
+ }
62185
+ seenCursors.add(cursor);
62186
+ const query = new URLSearchParams({ cursor });
62187
+ const rawPage = await args.client.get(
62188
+ `${args.transactionPath}/results?${query.toString()}`,
62189
+ { signal: args.signal }
62190
+ );
62191
+ const page = readProjectVersionTransactionResultPage(
62192
+ rawPage,
62193
+ args.transactionId
62194
+ );
62195
+ for (const [pendingId2, assignedId] of Object.entries(page.assignments)) {
62196
+ const existing = assignments[pendingId2];
62197
+ if (existing !== void 0 && existing !== assignedId) {
62198
+ throw new Error(
62199
+ `Project transaction "${args.transactionId}" returned conflicting assignments for ${JSON.stringify(pendingId2)}.`
62200
+ );
62201
+ }
62202
+ assignments[pendingId2] = assignedId;
62203
+ }
62204
+ changedRecords.push(...page.changedRecords);
62205
+ if (page.isDone) {
62206
+ return {
62207
+ kind: "committed",
62208
+ transactionId: args.transactionId,
62209
+ totalChangeCount: args.status.totalChangeCount ?? changedRecords.length,
62210
+ totalChunkCount: args.status.totalChunkCount ?? 0,
62211
+ assignments,
62212
+ changedRecords
62213
+ };
62214
+ }
62215
+ if (page.continueCursor === null) {
62216
+ throw new Error(
62217
+ `Project transaction "${args.transactionId}" result page is not done but has no continueCursor.`
62218
+ );
62219
+ }
62220
+ cursor = page.continueCursor;
62221
+ }
62222
+ }
61729
62223
  async function runPush(workspace, options) {
61730
62224
  const status = computeWorkspaceStatus(workspace);
61731
62225
  if (status.conflictedFiles.length > 0) {
@@ -61756,7 +62250,19 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61756
62250
  binaryChanges: status.binaryChanges ?? []
61757
62251
  });
61758
62252
  if (status.changes.length === 0) {
61759
- console.log("Nothing to push \u2014 working copy is clean.");
62253
+ if (options.json === true) {
62254
+ console.log(
62255
+ JSON.stringify({
62256
+ type: "push-plan",
62257
+ dryRun: options.dryRun,
62258
+ changeCount: 0,
62259
+ staticValueSeedCount: 0,
62260
+ changes: []
62261
+ })
62262
+ );
62263
+ } else {
62264
+ console.log("Nothing to push \u2014 working copy is clean.");
62265
+ }
61760
62266
  return;
61761
62267
  }
61762
62268
  prepareNSPropertySetterChanges(workspace, status);
@@ -61802,23 +62308,42 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61802
62308
  change.nextData = { ...change.nextData, updatedAt: now };
61803
62309
  }
61804
62310
  }
61805
- console.log(
61806
- `${options.dryRun ? "Would push" : "Pushing"} ${color.bold(String(status.changes.length))} change(s):`
61807
- );
61808
- for (const change of status.changes) {
62311
+ if (options.json === true) {
61809
62312
  console.log(
61810
- ` ${paintChangeKind(change.kind)} ${describeChange(change).replace(`${change.kind} `, "")}`
62313
+ JSON.stringify({
62314
+ type: "push-plan",
62315
+ dryRun: options.dryRun,
62316
+ changeCount: status.changes.length,
62317
+ staticValueSeedCount: status.staticValueSeeds.size,
62318
+ changes: status.changes.map((change) => ({
62319
+ kind: change.kind,
62320
+ recordKind: change.recordKind,
62321
+ recordId: change.recordId
62322
+ }))
62323
+ })
61811
62324
  );
61812
- if (change.kind === "update") {
61813
- for (const line of summarizeFieldDiff(change.baseData, change.nextData)) {
61814
- console.log(color.dim(` ${line}`));
61815
- }
61816
- }
61817
- }
61818
- if (status.staticValueSeeds.size > 0) {
62325
+ } else {
61819
62326
  console.log(
61820
- ` ${color.dim(`create ${status.staticValueSeeds.size} member value seed(s)`)}`
62327
+ `${options.dryRun ? "Would push" : "Pushing"} ${color.bold(String(status.changes.length))} change(s):`
61821
62328
  );
62329
+ for (const change of status.changes) {
62330
+ console.log(
62331
+ ` ${paintChangeKind(change.kind)} ${describeChange(change).replace(`${change.kind} `, "")}`
62332
+ );
62333
+ if (change.kind === "update") {
62334
+ for (const line of summarizeFieldDiff(
62335
+ change.baseData,
62336
+ change.nextData
62337
+ )) {
62338
+ console.log(color.dim(` ${line}`));
62339
+ }
62340
+ }
62341
+ }
62342
+ if (status.staticValueSeeds.size > 0) {
62343
+ console.log(
62344
+ ` ${color.dim(`create ${status.staticValueSeeds.size} member value seed(s)`)}`
62345
+ );
62346
+ }
61822
62347
  }
61823
62348
  if (options.dryRun) return;
61824
62349
  if (isInteractive() && !await promptConfirm({
@@ -61882,12 +62407,131 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61882
62407
  },
61883
62408
  force ? { "x-neo-force-project-version-write": "true" } : void 0
61884
62409
  );
61885
- const pushing = spinner("Pushing\u2026");
62410
+ const finishCommitResponse = async (response, reporter) => {
62411
+ let accepted;
62412
+ try {
62413
+ accepted = readAcceptedProjectVersionCommitResponse(response);
62414
+ } catch (error) {
62415
+ reporter.stop();
62416
+ throw error;
62417
+ }
62418
+ if (accepted === null) {
62419
+ try {
62420
+ applyPushResult(workspace, response);
62421
+ } catch (error) {
62422
+ reporter.stop();
62423
+ throw error;
62424
+ }
62425
+ reporter.report({
62426
+ type: "project-transaction-progress",
62427
+ transactionId: immediateTransactionId(response),
62428
+ phase: "committed",
62429
+ totalChangeCount: status.changes.length,
62430
+ appliedChangeCount: status.changes.length,
62431
+ totalChunkCount: null,
62432
+ appliedChunkCount: 0,
62433
+ errorCode: null,
62434
+ errorMessage: null
62435
+ });
62436
+ reporter.stop();
62437
+ if (options.json !== true) success("Push complete.");
62438
+ return true;
62439
+ }
62440
+ let completed;
62441
+ try {
62442
+ completed = await waitForAcceptedProjectVersionTransaction({
62443
+ client,
62444
+ projectId: workspace.config.projectId,
62445
+ versionId: workspace.config.versionId,
62446
+ accepted,
62447
+ reporter
62448
+ });
62449
+ } catch (error) {
62450
+ if (error instanceof ProjectTransactionInterruptedError) {
62451
+ reporter.report({
62452
+ type: "project-transaction-progress",
62453
+ transactionId: error.transactionId,
62454
+ phase: "interrupted",
62455
+ totalChangeCount: accepted.totalChangeCount,
62456
+ appliedChangeCount: accepted.appliedChangeCount,
62457
+ totalChunkCount: accepted.totalChunkCount,
62458
+ appliedChunkCount: accepted.appliedChunkCount,
62459
+ errorCode: null,
62460
+ errorMessage: error.message
62461
+ });
62462
+ reporter.stop();
62463
+ if (options.json !== true) console.error(error.message);
62464
+ process.exitCode = 130;
62465
+ return false;
62466
+ }
62467
+ if (error instanceof ProjectTransactionFailedError) {
62468
+ const failed = error.status;
62469
+ reporter.report({
62470
+ type: "project-transaction-progress",
62471
+ transactionId: failed.transactionId,
62472
+ phase: "failed",
62473
+ totalChangeCount: failed.totalChangeCount,
62474
+ appliedChangeCount: failed.appliedChangeCount,
62475
+ totalChunkCount: failed.totalChunkCount,
62476
+ appliedChunkCount: failed.appliedChunkCount,
62477
+ errorCode: failed.errorCode,
62478
+ errorMessage: error.message
62479
+ });
62480
+ reporter.stop();
62481
+ if (options.json !== true) {
62482
+ console.error(
62483
+ `Push failed (${failed.errorCode ?? "project-transaction-failed"}, transaction ${failed.transactionId}): ${error.message}`
62484
+ );
62485
+ }
62486
+ process.exitCode = 1;
62487
+ return false;
62488
+ }
62489
+ reporter.stop();
62490
+ throw error;
62491
+ }
62492
+ try {
62493
+ applyCommittedProjectTransactionResult({
62494
+ workspace,
62495
+ result: completed,
62496
+ submittedChanges: status.changes,
62497
+ candidateAssignments: pendingAssignment.assigned
62498
+ });
62499
+ } catch (error) {
62500
+ reporter.stop();
62501
+ throw error;
62502
+ }
62503
+ reporter.report({
62504
+ type: "project-transaction-progress",
62505
+ transactionId: completed.transactionId,
62506
+ phase: "committed",
62507
+ totalChangeCount: completed.totalChangeCount,
62508
+ appliedChangeCount: completed.totalChangeCount,
62509
+ totalChunkCount: completed.totalChunkCount,
62510
+ appliedChunkCount: completed.totalChunkCount,
62511
+ errorCode: null,
62512
+ errorMessage: null
62513
+ });
62514
+ reporter.stop();
62515
+ if (options.json !== true) success("Push complete.");
62516
+ return true;
62517
+ };
62518
+ let progress = createPushProgressReporter(options.json === true);
62519
+ progress.report({
62520
+ type: "project-transaction-progress",
62521
+ transactionId: null,
62522
+ phase: "submitting",
62523
+ totalChangeCount: status.changes.length,
62524
+ appliedChangeCount: 0,
62525
+ totalChunkCount: null,
62526
+ appliedChunkCount: 0,
62527
+ errorCode: null,
62528
+ errorMessage: null
62529
+ });
61886
62530
  let result;
61887
62531
  try {
61888
62532
  result = await commit(options.acceptBump);
61889
62533
  } catch (error) {
61890
- pushing.stop();
62534
+ progress.stop();
61891
62535
  if (!(error instanceof NeoApiError)) throw error;
61892
62536
  const rejection = error.body;
61893
62537
  if (isInteractive() && !options.acceptBump && isObjectRecord2(rejection) && rejection.error === "version-bump-required") {
@@ -61903,7 +62547,18 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61903
62547
  message: "Accept the bump and push?",
61904
62548
  fallback: false
61905
62549
  })) {
61906
- const retrying = spinner("Pushing (bump accepted)\u2026");
62550
+ progress = createPushProgressReporter(options.json === true);
62551
+ progress.report({
62552
+ type: "project-transaction-progress",
62553
+ transactionId: null,
62554
+ phase: "submitting",
62555
+ totalChangeCount: status.changes.length,
62556
+ appliedChangeCount: 0,
62557
+ totalChunkCount: null,
62558
+ appliedChunkCount: 0,
62559
+ errorCode: null,
62560
+ errorMessage: null
62561
+ });
61907
62562
  try {
61908
62563
  stagedFiles = await stageProjectFilePushesV4({
61909
62564
  workspace,
@@ -61914,7 +62569,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61914
62569
  });
61915
62570
  result = await commit(true);
61916
62571
  } catch (retryError) {
61917
- retrying.stop();
62572
+ progress.stop();
61918
62573
  if (retryError instanceof NeoApiError) {
61919
62574
  reportPushRejection(retryError.body);
61920
62575
  process.exitCode = 1;
@@ -61922,9 +62577,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61922
62577
  }
61923
62578
  throw retryError;
61924
62579
  }
61925
- retrying.stop();
61926
- applyPushResult(workspace, result);
61927
- success("Push complete.");
62580
+ await finishCommitResponse(result, progress);
61928
62581
  return;
61929
62582
  }
61930
62583
  console.log("Push cancelled.");
@@ -61935,9 +62588,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61935
62588
  process.exitCode = 1;
61936
62589
  return;
61937
62590
  }
61938
- pushing.stop();
61939
- applyPushResult(workspace, result);
61940
- success("Push complete.");
62591
+ await finishCommitResponse(result, progress);
61941
62592
  }
61942
62593
  async function createPendingProjectSourceBundleV4(workspace, status, staticValueSeeds) {
61943
62594
  const records2 = /* @__PURE__ */ new Map();
@@ -62084,52 +62735,298 @@ function reportPushRejection(body) {
62084
62735
  }
62085
62736
  console.error(`Push rejected (409): ${JSON.stringify(body)}`);
62086
62737
  }
62087
- function applyPushResult(workspace, result) {
62088
- if (!isObjectRecord2(result) || !Array.isArray(result.changedRecords)) {
62089
- throw new Error("Transaction response is missing changedRecords.");
62738
+ function immediateTransactionId(result) {
62739
+ if (!isObjectRecord2(result)) return null;
62740
+ if (typeof result.transactionId === "string") return result.transactionId;
62741
+ if (!isObjectRecord2(result.transaction)) return null;
62742
+ return typeof result.transaction.id === "string" ? result.transaction.id : null;
62743
+ }
62744
+ function applyCommittedProjectTransactionResult(args) {
62745
+ const { workspace, result, submittedChanges, candidateAssignments } = args;
62746
+ const localStatus = computeWorkspaceStatus(workspace);
62747
+ const blockingErrors = localStatus.parseErrors.filter(
62748
+ isBlockingSchemaSourceError
62749
+ );
62750
+ if (blockingErrors.length > 0) {
62751
+ throw new Error(
62752
+ `Project transaction "${result.transactionId}" committed, but its result cannot be applied while the working copy has parse errors. Fix the source and run the same neo push again:
62753
+ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
62754
+ );
62755
+ }
62756
+ if (localStatus.conflictedFiles.length > 0) {
62757
+ throw new Error(
62758
+ `Project transaction "${result.transactionId}" committed, but its result cannot be applied while conflict markers remain in ${localStatus.conflictedFiles.join(", ")}. Resolve them and run the same neo push again.`
62759
+ );
62090
62760
  }
62761
+ const replacements = projectTransactionResultReplacements(
62762
+ candidateAssignments,
62763
+ result.assignments
62764
+ );
62765
+ const localRecords = rewriteReconstructedRecords(
62766
+ localStatus.reconstructed,
62767
+ replacements
62768
+ );
62769
+ const submittedByKey = /* @__PURE__ */ new Map();
62770
+ for (const change of submittedChanges) {
62771
+ if (change.nextData === void 0) continue;
62772
+ const recordId = rewriteAssignedString(change.recordId, replacements);
62773
+ const nextData = rewriteAssignedValue(change.nextData, replacements);
62774
+ if (!isObjectRecord2(nextData)) {
62775
+ throw new Error(
62776
+ `Submitted ${change.recordKind} "${recordId}" did not rewrite to an object.`
62777
+ );
62778
+ }
62779
+ submittedByKey.set(recordStateKey(change.recordKind, recordId), nextData);
62780
+ }
62781
+ const previousRecords = workspace.state.records;
62782
+ const nextRecords = foldChangedRecords(
62783
+ previousRecords,
62784
+ result.changedRecords
62785
+ );
62786
+ const changedKeys = /* @__PURE__ */ new Set();
62091
62787
  for (const changed of result.changedRecords) {
62092
- if (!isObjectRecord2(changed)) {
62093
- throw new Error("Transaction changedRecords entries must be objects.");
62788
+ const identity2 = readChangedRecordIdentity(changed);
62789
+ changedKeys.add(recordStateKey(identity2.recordKind, identity2.recordId));
62790
+ }
62791
+ const emitRecords = /* @__PURE__ */ new Map();
62792
+ for (const [key, state] of Object.entries(nextRecords)) {
62793
+ const prior = previousRecords[key];
62794
+ const local = localRecords.get(key);
62795
+ if (local === void 0 && prior?.file !== void 0) continue;
62796
+ const submitted = submittedByKey.get(key);
62797
+ const emitData = local === void 0 ? state.data : changedKeys.has(key) && submitted !== void 0 ? mergeCommittedRecordWithLaterLocalEdits(
62798
+ state.recordKind,
62799
+ submitted,
62800
+ state.data,
62801
+ local.fullData
62802
+ ) : local.fullData;
62803
+ emitRecords.set(key, {
62804
+ recordKind: state.recordKind,
62805
+ recordId: state.recordId,
62806
+ contentHash: state.contentHash,
62807
+ deleted: false,
62808
+ data: emitData
62809
+ });
62810
+ }
62811
+ for (const [key, local] of localRecords) {
62812
+ if (emitRecords.has(key)) continue;
62813
+ if (changedKeys.has(key)) continue;
62814
+ emitRecords.set(key, {
62815
+ recordKind: local.recordKind,
62816
+ recordId: local.recordId,
62817
+ contentHash: "",
62818
+ deleted: false,
62819
+ data: local.fullData
62820
+ });
62821
+ }
62822
+ const previousState = workspace.state;
62823
+ workspace.state = { ...previousState, records: nextRecords };
62824
+ try {
62825
+ rewriteFilesFromState(workspace, emitRecords);
62826
+ writeWorkspaceState(workspace.root, workspace.state);
62827
+ } catch (error) {
62828
+ workspace.state = previousState;
62829
+ throw error;
62830
+ }
62831
+ }
62832
+ function projectTransactionResultReplacements(candidateAssignments, resultAssignments) {
62833
+ const replacements = /* @__PURE__ */ new Map();
62834
+ for (const [pendingId2, assignedId] of Object.entries(resultAssignments)) {
62835
+ replacements.set(pendingId2, assignedId);
62836
+ const candidateId = candidateAssignments.get(pendingId2);
62837
+ if (candidateId !== void 0) replacements.set(candidateId, assignedId);
62838
+ }
62839
+ return {
62840
+ exact: replacements,
62841
+ embedded: [...replacements].sort(
62842
+ ([left], [right]) => right.length - left.length
62843
+ )
62844
+ };
62845
+ }
62846
+ function rewriteReconstructedRecords(records2, replacements) {
62847
+ const rewritten = /* @__PURE__ */ new Map();
62848
+ for (const record3 of records2.values()) {
62849
+ const recordId = rewriteAssignedString(record3.recordId, replacements);
62850
+ const fileFields = rewriteAssignedValue(record3.fileFields, replacements);
62851
+ if (!isObjectRecord2(fileFields)) {
62852
+ throw new Error(
62853
+ `Local ${record3.recordKind} "${recordId}" file fields did not rewrite to an object.`
62854
+ );
62855
+ }
62856
+ const fullData = rewriteAssignedValue(record3.fullData, replacements);
62857
+ if (!isObjectRecord2(fullData)) {
62858
+ throw new Error(
62859
+ `Local ${record3.recordKind} "${recordId}" full data did not rewrite to an object.`
62860
+ );
62094
62861
  }
62095
- const recordKind = changed.recordKind;
62096
- const recordId = changed.recordId;
62097
- if (typeof recordKind !== "string" || typeof recordId !== "string") {
62098
- throw new Error("Changed record is missing recordKind/recordId.");
62862
+ const key = recordStateKey(record3.recordKind, recordId);
62863
+ if (rewritten.has(key)) {
62864
+ throw new Error(
62865
+ `Project transaction assignments collapse multiple local records onto "${key}".`
62866
+ );
62867
+ }
62868
+ rewritten.set(key, { ...record3, recordId, fileFields, fullData });
62869
+ }
62870
+ return rewritten;
62871
+ }
62872
+ function rewriteAssignedString(value, replacements) {
62873
+ const exact = replacements.exact.get(value);
62874
+ if (exact !== void 0) return exact;
62875
+ let result = value;
62876
+ for (const [source, target] of replacements.embedded) {
62877
+ if (result.includes(source)) result = result.replaceAll(source, target);
62878
+ }
62879
+ return result;
62880
+ }
62881
+ function rewriteAssignedValue(value, replacements) {
62882
+ if (typeof value === "string") {
62883
+ return rewriteAssignedString(value, replacements);
62884
+ }
62885
+ if (Array.isArray(value)) {
62886
+ return value.map((entry) => rewriteAssignedValue(entry, replacements));
62887
+ }
62888
+ if (!isObjectRecord2(value)) return value;
62889
+ const rewritten = {};
62890
+ for (const [key, entry] of Object.entries(value)) {
62891
+ rewritten[rewriteAssignedString(key, replacements)] = rewriteAssignedValue(
62892
+ entry,
62893
+ replacements
62894
+ );
62895
+ }
62896
+ return rewritten;
62897
+ }
62898
+ function mergeCommittedRecordWithLaterLocalEdits(recordKind, submitted, committed, local) {
62899
+ if (!isObjectRecord2(committed)) return local;
62900
+ if (isSchemaDocumentRecordKind(recordKind)) {
62901
+ if (schemaDocumentAuthoredSemanticallyEqual(recordKind, submitted, local)) {
62902
+ return committed;
62099
62903
  }
62904
+ const merged2 = mergeSchemaDocumentRecord(
62905
+ recordKind,
62906
+ submitted,
62907
+ committed,
62908
+ local
62909
+ );
62910
+ return merged2.conflictFields.length === 0 ? merged2.merged : local;
62911
+ }
62912
+ const comparison = {
62913
+ base: sourceComparableRecordForPush(recordKind, submitted),
62914
+ server: sourceComparableRecordForPush(recordKind, committed),
62915
+ local: sourceComparableRecordForPush(recordKind, local)
62916
+ };
62917
+ if (canonicallyEqual(comparison.base, comparison.local)) return committed;
62918
+ const merged = threeWayMergeRecord(submitted, committed, local, {
62919
+ comparison,
62920
+ serverWinsFields: /* @__PURE__ */ new Set([
62921
+ "projectId",
62922
+ "createdAt",
62923
+ "updatedAt",
62924
+ "getter",
62925
+ "setter",
62926
+ "action",
62927
+ "layout"
62928
+ ]),
62929
+ recursive: true
62930
+ });
62931
+ return merged.conflictFields.length === 0 ? merged.merged : local;
62932
+ }
62933
+ function sourceComparableRecordForPush(recordKind, value) {
62934
+ const result = {};
62935
+ const hasAuthoredCode = typeof value.code === "string" || typeof value.setterCode === "string";
62936
+ for (const [key, entry] of Object.entries(value)) {
62937
+ if (key === "projectId" || key === "createdAt" || key === "updatedAt") {
62938
+ continue;
62939
+ }
62940
+ if (recordKind === "dialogue-node" && key === "layout") continue;
62941
+ if (hasAuthoredCode && (key === "getter" || key === "setter" || key === "action")) {
62942
+ continue;
62943
+ }
62944
+ result[key] = sourceComparableValueForPush(recordKind, entry);
62945
+ }
62946
+ return result;
62947
+ }
62948
+ function sourceComparableValueForPush(recordKind, value) {
62949
+ if (Array.isArray(value)) {
62950
+ return value.map(
62951
+ (entry) => sourceComparableValueForPush(recordKind, entry)
62952
+ );
62953
+ }
62954
+ if (!isObjectRecord2(value)) return value;
62955
+ return sourceComparableRecordForPush(recordKind, value);
62956
+ }
62957
+ function readChangedRecordIdentity(changed) {
62958
+ if (!isObjectRecord2(changed)) {
62959
+ throw new Error("Transaction changedRecords entries must be objects.");
62960
+ }
62961
+ if (typeof changed.recordKind !== "string") {
62962
+ throw new Error(
62963
+ "Changed record is missing recordKind/recordId: recordKind is not a string."
62964
+ );
62965
+ }
62966
+ if (typeof changed.recordId !== "string") {
62967
+ throw new Error(
62968
+ "Changed record is missing recordKind/recordId: recordId is not a string."
62969
+ );
62970
+ }
62971
+ return { recordKind: changed.recordKind, recordId: changed.recordId };
62972
+ }
62973
+ function foldChangedRecords(previous, changedRecords) {
62974
+ const records2 = { ...previous };
62975
+ for (const changed of changedRecords) {
62976
+ const { recordKind, recordId } = readChangedRecordIdentity(changed);
62100
62977
  const key = recordStateKey(recordKind, recordId);
62978
+ if (!isObjectRecord2(changed)) {
62979
+ throw new Error(`Changed record "${key}" must be an object.`);
62980
+ }
62101
62981
  if (changed.deleted === true) {
62102
- delete workspace.state.records[key];
62982
+ delete records2[key];
62103
62983
  continue;
62104
62984
  }
62105
62985
  const snapshot = changed.snapshot;
62106
- if (!isObjectRecord2(snapshot) || typeof snapshot.contentHash !== "string") {
62986
+ if (!isObjectRecord2(snapshot)) {
62987
+ throw new Error(
62988
+ `Changed record "${key}" response is missing its snapshot.`
62989
+ );
62990
+ }
62991
+ if (typeof snapshot.contentHash !== "string") {
62107
62992
  throw new Error(
62108
62993
  `Changed record "${key}" response is missing its snapshot content hash.`
62109
62994
  );
62110
62995
  }
62111
- workspace.state.records[key] = {
62996
+ records2[key] = {
62112
62997
  recordKind,
62113
62998
  recordId,
62114
62999
  contentHash: snapshot.contentHash,
62115
63000
  data: changed.data,
62116
- file: workspace.state.records[key]?.file
63001
+ file: records2[key]?.file,
63002
+ projectBinary: records2[key]?.projectBinary
62117
63003
  };
62118
63004
  }
63005
+ return records2;
63006
+ }
63007
+ function applyPushResult(workspace, result) {
63008
+ if (!isObjectRecord2(result) || !Array.isArray(result.changedRecords)) {
63009
+ throw new Error("Transaction response is missing changedRecords.");
63010
+ }
63011
+ workspace.state.records = foldChangedRecords(
63012
+ workspace.state.records,
63013
+ result.changedRecords
63014
+ );
62119
63015
  rewriteFilesFromState(workspace);
62120
63016
  writeWorkspaceState(workspace.root, workspace.state);
62121
63017
  }
62122
- function rewriteFilesFromState(workspace) {
62123
- const records2 = /* @__PURE__ */ new Map();
62124
- for (const [key, recordState] of Object.entries(workspace.state.records)) {
62125
- records2.set(key, {
63018
+ function rewriteFilesFromState(workspace, records2 = new Map(
63019
+ Object.entries(workspace.state.records).map(([key, recordState]) => [
63020
+ key,
63021
+ {
62126
63022
  recordKind: recordState.recordKind,
62127
63023
  recordId: recordState.recordId,
62128
63024
  contentHash: recordState.contentHash,
62129
63025
  deleted: false,
62130
63026
  data: recordState.data
62131
- });
62132
- }
63027
+ }
63028
+ ])
63029
+ )) {
62133
63030
  const result = emitProjectDocumentFilesV4(records2);
62134
63031
  writeProjectSourceAnalysisCacheV4(workspace.root, result.analysis);
62135
63032
  const emittedPaths = new Set(result.files.map((file) => file.path));
@@ -62610,7 +63507,7 @@ function compileMigrationAction(schema, migrationData) {
62610
63507
  migrationContext: true
62611
63508
  });
62612
63509
  }
62613
- var compileNSAction2, compileNSFunction2, compileNSGetter2, compileNSSetter2, createProjectVersionIntent2, isKnownProjectVersionIntentType2;
63510
+ var compileNSAction2, compileNSFunction2, compileNSGetter2, compileNSSetter2, createProjectVersionIntent2, isKnownProjectVersionIntentType2, ProjectTransactionInterruptedError, ProjectTransactionFailedError, PROJECT_TRANSACTION_POLL_DELAYS_MS;
62614
63511
  var init_push = __esm({
62615
63512
  "src/commands/push.ts"() {
62616
63513
  "use strict";
@@ -62627,8 +63524,38 @@ var init_push = __esm({
62627
63524
  init_source_diagnostics();
62628
63525
  init_projection();
62629
63526
  init_project_file_push();
63527
+ init_project_manifest();
63528
+ init_merge();
62630
63529
  ({ compileNSAction: compileNSAction2, compileNSFunction: compileNSFunction2, compileNSGetter: compileNSGetter2, compileNSSetter: compileNSSetter2 } = compiler_adapter_exports);
62631
63530
  ({ createProjectVersionIntent: createProjectVersionIntent2, isKnownProjectVersionIntentType: isKnownProjectVersionIntentType2 } = project_version_intents_exports);
63531
+ ProjectTransactionInterruptedError = class extends Error {
63532
+ constructor(transactionId) {
63533
+ super(
63534
+ `Project transaction "${transactionId}" is still running. Run the same neo push again to reconnect.`
63535
+ );
63536
+ this.transactionId = transactionId;
63537
+ this.name = "ProjectTransactionInterruptedError";
63538
+ }
63539
+ transactionId;
63540
+ };
63541
+ ProjectTransactionFailedError = class extends Error {
63542
+ constructor(status) {
63543
+ super(
63544
+ status.errorMessage ?? `Project transaction "${status.transactionId}" failed after rollback.`
63545
+ );
63546
+ this.status = status;
63547
+ this.name = "ProjectTransactionFailedError";
63548
+ }
63549
+ status;
63550
+ };
63551
+ PROJECT_TRANSACTION_POLL_DELAYS_MS = [
63552
+ 250,
63553
+ 500,
63554
+ 1e3,
63555
+ 2e3,
63556
+ 4e3,
63557
+ 5e3
63558
+ ];
62632
63559
  }
62633
63560
  });
62634
63561
 
@@ -63368,7 +64295,7 @@ ${h("Start")}
63368
64295
  whoami ${d("[--api <url>]")}
63369
64296
 
63370
64297
  ${h("Working copy")}
63371
- pull ${d("[--force|--reset] [--regenerate-source-names]")} push ${d("[--dry-run] [--summary <text>] [--accept-bump]")}
64298
+ pull ${d("[--force|--reset] [--regenerate-source-names]")} push ${d("[--dry-run] [--summary <text>] [--accept-bump] [--json]")}
63372
64299
  status ${d("[--json]")} diff ${d("[--json]")}
63373
64300
  dev ${d("[--push]")} resolve ${d("[--mine|--theirs]")}
63374
64301
  doctor ${d("[--json]")} ${d("validate format/compiler/editor/source/file contracts")}
@@ -63418,16 +64345,18 @@ and tracked project binary from the authoritative server records.
63418
64345
  return `${h("neo push")} \u2014 commit the working copy to the server
63419
64346
 
63420
64347
  ${h("Usage")}
63421
- neo push ${d("[--dry-run] [--summary <text>] [--accept-bump]")}
64348
+ neo push ${d("[--dry-run] [--summary <text>] [--accept-bump] [--json]")}
63422
64349
 
63423
64350
  ${h("Flags")}
63424
64351
  --dry-run ${d("Preview the change set; commit nothing.")}
63425
64352
  --summary <text> ${d("Attach a summary message to the transaction.")}
63426
64353
  --accept-bump ${d("Accept a server-required version bump instead of aborting.")}
64354
+ --json ${d("Emit machine-readable plan and transaction progress events.")}
63427
64355
 
63428
- Push sends the ${h("entire")} working copy as one atomic compare-and-swap
63429
- transaction. Run ${h("neo status")} / ${h("neo diff")} first \u2014 local-ahead changes
63430
- you did not make this session ride along too.
64356
+ Push sends the ${h("entire")} working copy as one compare-and-swap logical
64357
+ transaction. Large pushes continue as a durable server job; rerun the identical
64358
+ push to reconnect if the terminal closes. Run ${h("neo status")} / ${h("neo diff")}
64359
+ first \u2014 local-ahead changes you did not make this session ride along too.
63431
64360
  `;
63432
64361
  }
63433
64362
  if (command === "script") {
@@ -63919,7 +64848,8 @@ async function main() {
63919
64848
  await runPush2(workspace, {
63920
64849
  dryRun: boolFlag(args, "dry-run"),
63921
64850
  summary: stringFlag(args, "summary"),
63922
- acceptBump: boolFlag(args, "accept-bump")
64851
+ acceptBump: boolFlag(args, "accept-bump"),
64852
+ json: boolFlag(args, "json")
63923
64853
  });
63924
64854
  return;
63925
64855
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",