@neocompose/cli 0.6.1 → 0.6.3

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/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"),
@@ -38036,8 +38039,9 @@ function regenerateDialogueSourceNamesV4(records2) {
38036
38039
  const nodeNames = regeneratedNodeNames(nodes, triggerId, classMembers);
38037
38040
  const dialogueLinks = regenerateBindingSourceNames(
38038
38041
  dialogue.linkedValues,
38039
- new DialogueSourceNameAllocator(),
38040
- placements
38042
+ classMembers,
38043
+ placements,
38044
+ "pascal"
38041
38045
  );
38042
38046
  const dialogueBindingNames = new Map(
38043
38047
  dialogueLinks.map((link) => [
@@ -38045,9 +38049,6 @@ function regenerateDialogueSourceNamesV4(records2) {
38045
38049
  stringField(link, "sourceName")
38046
38050
  ])
38047
38051
  );
38048
- for (const link of dialogueLinks) {
38049
- classMembers.reserve(stringField(link, "sourceName"));
38050
- }
38051
38052
  const functionNames = regeneratedSourceFunctionNames(nodes, classMembers);
38052
38053
  const nextDialogue = rewriteFunctionSourceNames(
38053
38054
  { ...dialogue, sourceName: className, linkedValues: dialogueLinks },
@@ -38120,7 +38121,7 @@ function regeneratedNodeNames(nodes, triggerId, allocator) {
38120
38121
  }
38121
38122
  return result;
38122
38123
  }
38123
- function regenerateBindingSourceNames(rawLinks, allocator, placements) {
38124
+ function regenerateBindingSourceNames(rawLinks, allocator, placements, casing) {
38124
38125
  if (!Array.isArray(rawLinks)) return [];
38125
38126
  return rawLinks.map((rawLink, index) => {
38126
38127
  if (!isObjectRecord2(rawLink) || typeof rawLink.valueId !== "string") {
@@ -38128,7 +38129,8 @@ function regenerateBindingSourceNames(rawLinks, allocator, placements) {
38128
38129
  }
38129
38130
  const preferred = placements.preferredBindingName(
38130
38131
  rawLink.valueId,
38131
- optionalString2(rawLink.declaredTypeName) ?? `value${index + 1}`
38132
+ optionalString2(rawLink.declaredTypeName) ?? `value${index + 1}`,
38133
+ casing
38132
38134
  );
38133
38135
  return { ...rawLink, sourceName: allocator.claim(preferred) };
38134
38136
  });
@@ -38141,25 +38143,47 @@ function regenerateNodeBindingSourceNames(rawLinks, placements, dialogueNamesByV
38141
38143
  }
38142
38144
  return rawLink;
38143
38145
  });
38146
+ const inheritedValueIds = new Set(
38147
+ links.flatMap((link) => {
38148
+ const valueId = stringField(link, "valueId");
38149
+ return dialogueNamesByValueId.has(valueId) ? [valueId] : [];
38150
+ })
38151
+ );
38144
38152
  const allocator = new DialogueSourceNameAllocator(
38145
- dialogueNamesByValueId.values()
38153
+ [...dialogueNamesByValueId].filter(([valueId]) => !inheritedValueIds.has(valueId)).map(([, name]) => name)
38146
38154
  );
38147
- return links.map((link, index) => {
38155
+ const namesByValueId = /* @__PURE__ */ new Map();
38156
+ const allocate = (link, index) => {
38148
38157
  const valueId = stringField(link, "valueId");
38149
- const inherited = dialogueNamesByValueId.get(valueId);
38150
- if (inherited !== void 0) {
38151
- return { ...link, sourceName: inherited };
38152
- }
38153
- return {
38154
- ...link,
38155
- sourceName: allocator.claim(
38158
+ namesByValueId.set(
38159
+ valueId,
38160
+ allocator.claim(
38156
38161
  placements.preferredBindingName(
38157
38162
  valueId,
38158
- optionalString2(link.declaredTypeName) ?? `value${index + 1}`
38163
+ optionalString2(link.declaredTypeName) ?? `value${index + 1}`,
38164
+ "camel"
38159
38165
  )
38160
38166
  )
38161
- };
38167
+ );
38168
+ };
38169
+ links.forEach((link, index) => {
38170
+ if (dialogueNamesByValueId.has(stringField(link, "valueId"))) {
38171
+ allocate(link, index);
38172
+ }
38162
38173
  });
38174
+ links.forEach((link, index) => {
38175
+ if (!dialogueNamesByValueId.has(stringField(link, "valueId"))) {
38176
+ allocate(link, index);
38177
+ }
38178
+ });
38179
+ return links.map((link) => ({
38180
+ ...link,
38181
+ sourceName: requiredMap(
38182
+ namesByValueId,
38183
+ stringField(link, "valueId"),
38184
+ "dialogue-node linked value"
38185
+ )
38186
+ }));
38163
38187
  }
38164
38188
  function regenerateTextChildren(node) {
38165
38189
  if (!isObjectRecord2(node.optionSettings)) return node;
@@ -39190,6 +39214,12 @@ function lowerIdentifier(value) {
39190
39214
  const result = pascalIdentifier2(value);
39191
39215
  return `${result[0]?.toLowerCase() ?? "v"}${result.slice(1)}`;
39192
39216
  }
39217
+ function bindingIdentifier(value, casing) {
39218
+ const cased = casing === "camel" ? lowerIdentifier(value) : value;
39219
+ if (isDialogueSourceIdentifier(cased)) return cased;
39220
+ const escaped = `_${cased}`;
39221
+ return isDialogueSourceIdentifier(escaped) ? escaped : "_Value";
39222
+ }
39193
39223
  function ensureStatement(value) {
39194
39224
  const trimmed = value.trim();
39195
39225
  return trimmed.endsWith(";") ? trimmed : `${trimmed};`;
@@ -39271,8 +39301,10 @@ var init_dialogue_sources = __esm({
39271
39301
  classes = /* @__PURE__ */ new Map();
39272
39302
  classesByName = /* @__PURE__ */ new Map();
39273
39303
  values = /* @__PURE__ */ new Map();
39304
+ localizedTexts = /* @__PURE__ */ new Map();
39274
39305
  ownerClassIdsByMemberId = /* @__PURE__ */ new Map();
39275
39306
  parentValueIdsByChildId = /* @__PURE__ */ new Map();
39307
+ mainLocale;
39276
39308
  constructor(records2) {
39277
39309
  for (const record3 of records2) {
39278
39310
  if (record3.recordKind === "member")
@@ -39281,7 +39313,13 @@ var init_dialogue_sources = __esm({
39281
39313
  this.classes.set(record3.recordId, recordData2(record3));
39282
39314
  else if (record3.recordKind === "value")
39283
39315
  this.values.set(record3.recordId, recordData2(record3));
39316
+ else if (record3.recordKind === "localized-text")
39317
+ this.localizedTexts.set(record3.recordId, recordData2(record3));
39284
39318
  }
39319
+ const localization = records2.find(
39320
+ (record3) => record3.recordKind === "localization-config"
39321
+ );
39322
+ this.mainLocale = localization ? optionalString2(recordData2(localization).mainLocale) ?? "en-US" : "en-US";
39285
39323
  for (const schemaClass2 of this.classes.values()) {
39286
39324
  const name = optionalString2(schemaClass2.name);
39287
39325
  if (name && !this.classesByName.has(name)) {
@@ -39336,34 +39374,48 @@ var init_dialogue_sources = __esm({
39336
39374
  get(valueId) {
39337
39375
  return this.symbols.get(valueId) ?? this.inferClassStamped(valueId) ?? this.inferDetached(valueId);
39338
39376
  }
39339
- preferredBindingName(valueId, fallback) {
39340
- const displayName = this.valueDisplayName(valueId);
39377
+ preferredBindingName(valueId, fallback, casing) {
39378
+ const symbol = this.get(valueId);
39379
+ const displayName = this.valueDisplayName(
39380
+ valueId,
39381
+ symbol?.type ?? fallback
39382
+ );
39341
39383
  if (displayName !== null) {
39342
- return lowerIdentifier(
39343
- dialogueSourceNameFromDisplayName(displayName, fallback)
39384
+ return bindingIdentifier(
39385
+ dialogueSourceNameFromDisplayName(displayName, fallback),
39386
+ casing
39344
39387
  );
39345
39388
  }
39346
- const symbol = this.get(valueId);
39347
39389
  const placedName = symbol?.expression.includes(".") === true ? symbol.expression.split(".").at(-1) : null;
39348
- return lowerIdentifier(
39349
- dialogueSourceNameFromDisplayName(placedName, symbol?.type ?? fallback)
39390
+ return bindingIdentifier(
39391
+ dialogueSourceNameFromDisplayName(placedName, symbol?.type ?? fallback),
39392
+ casing
39350
39393
  );
39351
39394
  }
39352
- valueDisplayName(valueId) {
39395
+ valueDisplayName(valueId, typeName) {
39353
39396
  const value = this.values.get(valueId);
39354
39397
  if (!value || !isObjectRecord2(value.value)) return null;
39355
39398
  const classId = optionalString2(value.classId);
39356
- const schemaClass2 = classId ? this.classes.get(classId) : void 0;
39399
+ const schemaClass2 = classId ? this.classes.get(classId) : this.classesByName.get(typeName);
39357
39400
  if (!schemaClass2 || !isObjectRecord2(schemaClass2.schema)) return null;
39358
39401
  const nameEntry = Object.entries(schemaClass2.schema).find(
39359
39402
  ([memberName2]) => memberName2.toLocaleLowerCase("en-US") === "name"
39360
39403
  );
39361
39404
  if (nameEntry === void 0) return null;
39362
- const [memberName] = nameEntry;
39405
+ const [memberName, memberId] = nameEntry;
39406
+ if (typeof memberId !== "string") return null;
39407
+ const nameMember = this.members.get(memberId);
39408
+ if (nameMember?.kind !== 3) return null;
39363
39409
  const childValueId = value.value[memberName];
39364
39410
  if (typeof childValueId !== "string") return null;
39365
39411
  const child = this.values.get(childValueId);
39366
- return typeof child?.value === "string" && child.value.trim().length > 0 ? child.value : null;
39412
+ if (typeof child?.value !== "string") return null;
39413
+ const localizedText = this.localizedTexts.get(child.value);
39414
+ const localeValues = isObjectRecord2(localizedText?.localeValues) ? localizedText.localeValues : null;
39415
+ const mainLocaleEntry = localeValues?.[this.mainLocale];
39416
+ const mainLocaleValue = isObjectRecord2(mainLocaleEntry) ? mainLocaleEntry.value : null;
39417
+ const displayName = typeof mainLocaleValue === "string" ? mainLocaleValue : child.value;
39418
+ return displayName.trim().length > 0 ? displayName : null;
39367
39419
  }
39368
39420
  inferClassStamped(valueId) {
39369
39421
  const value = this.values.get(valueId);
@@ -45473,6 +45525,7 @@ __export(project_document_read_exports, {
45473
45525
  readOptionalArrayField: () => readOptionalArrayField,
45474
45526
  readProjectDocument: () => readProjectDocument,
45475
45527
  readProjectDocumentContentHashHeads: () => readProjectDocumentContentHashHeads,
45528
+ readProjectDocumentManifestPageRecords: () => readProjectDocumentManifestPageRecords,
45476
45529
  readProjectDocumentManifestRecords: () => readProjectDocumentManifestRecords,
45477
45530
  readProjectDocumentRevisionMarker: () => readProjectDocumentRevisionMarker
45478
45531
  });
@@ -45678,6 +45731,12 @@ function readProjectDocumentManifestRecords(value) {
45678
45731
  };
45679
45732
  });
45680
45733
  }
45734
+ function readProjectDocumentManifestPageRecords(value) {
45735
+ if (!isObject3(value)) {
45736
+ throw new Error("Convex project document manifest page must be an object.");
45737
+ }
45738
+ return readProjectDocumentManifestRecords(value.records);
45739
+ }
45681
45740
  async function fetchProjectDocumentSnapshots(fetchBatch, snapshotIds) {
45682
45741
  const chunks = [];
45683
45742
  for (let offset = 0; offset < snapshotIds.length; offset += DOCUMENT_SNAPSHOT_FETCH_CHUNK_SIZE) {
@@ -46639,6 +46698,32 @@ var init_http = __esm({
46639
46698
  }
46640
46699
  return parsed;
46641
46700
  }
46701
+ async get(path, options = {}) {
46702
+ const url = new URL(path, this.apiBaseUrl).toString();
46703
+ const response = await fetch(url, {
46704
+ method: "GET",
46705
+ headers: { Authorization: `Bearer ${this.token}` },
46706
+ signal: options.signal
46707
+ });
46708
+ const text = await response.text();
46709
+ let parsed = null;
46710
+ if (text.length > 0) {
46711
+ try {
46712
+ parsed = JSON.parse(text);
46713
+ } catch {
46714
+ parsed = text;
46715
+ }
46716
+ }
46717
+ if (!response.ok) {
46718
+ const detail = typeof parsed === "object" && parsed !== null && "error" in parsed && typeof parsed.error === "string" ? parsed.error : text.slice(0, 300);
46719
+ throw new NeoApiError(
46720
+ `GET ${path} failed with status ${response.status}: ${detail}`,
46721
+ response.status,
46722
+ parsed
46723
+ );
46724
+ }
46725
+ return parsed;
46726
+ }
46642
46727
  async getBytes(path) {
46643
46728
  const url = new URL(path, this.apiBaseUrl).toString();
46644
46729
  const response = await fetch(url, {
@@ -59063,11 +59148,32 @@ async function runMerge(workspace, sourceRef, dryRun, runMigrations = false) {
59063
59148
  }
59064
59149
  }
59065
59150
  try {
59066
- const result = await client.post(mergePath, {
59151
+ let result = await client.post(mergePath, {
59067
59152
  sourceVersionId: source.id,
59068
59153
  dryRun,
59069
59154
  resolutions: resolutions.length > 0 ? resolutions : void 0
59070
59155
  });
59156
+ const accepted = readAcceptedMergeTransaction(result);
59157
+ if (accepted !== null) {
59158
+ const progress = spinner("Preparing merge\u2026");
59159
+ try {
59160
+ await waitForMergeTransaction({
59161
+ client,
59162
+ projectId: workspace.config.projectId,
59163
+ versionId: workspace.config.versionId,
59164
+ accepted,
59165
+ update: (label) => progress.update(label)
59166
+ });
59167
+ progress.succeed("Merge transaction committed.");
59168
+ } catch (error) {
59169
+ progress.fail("Merge transaction failed.");
59170
+ throw error;
59171
+ }
59172
+ result = { ...result, commitStatus: "committed" };
59173
+ if (result.sourceArchivePending === true) {
59174
+ console.log("Source branch archival is finishing in the background.");
59175
+ }
59176
+ }
59071
59177
  const merged = typeof result.merged === "number" ? result.merged : 0;
59072
59178
  if (dryRun) {
59073
59179
  console.log(`Merge dry-run: ${merged} record(s) would merge cleanly.`);
@@ -59135,6 +59241,104 @@ async function runMerge(workspace, sourceRef, dryRun, runMigrations = false) {
59135
59241
  throw error;
59136
59242
  }
59137
59243
  }
59244
+ function readAcceptedMergeTransaction(value) {
59245
+ if (!isObjectRecord2(value)) {
59246
+ throw new Error("Merge response must be an object.");
59247
+ }
59248
+ if (value.kind !== "accepted") return null;
59249
+ if (typeof value.transactionId !== "string") {
59250
+ throw new Error("Accepted merge is missing transactionId.");
59251
+ }
59252
+ if (!isMergeTransactionStatus(value.commitStatus)) {
59253
+ throw new Error("Accepted merge has an invalid commitStatus.");
59254
+ }
59255
+ const totalChangeCount = value.totalChangeCount === null ? null : readMergeProgressCount(value.totalChangeCount, "totalChangeCount");
59256
+ const appliedChangeCount = readMergeProgressCount(
59257
+ value.appliedChangeCount,
59258
+ "appliedChangeCount"
59259
+ );
59260
+ return {
59261
+ transactionId: value.transactionId,
59262
+ commitStatus: value.commitStatus,
59263
+ totalChangeCount,
59264
+ appliedChangeCount
59265
+ };
59266
+ }
59267
+ async function waitForMergeTransaction(args) {
59268
+ const transactionPath = `/api/projects/${encodeURIComponent(args.projectId)}/versions/${encodeURIComponent(args.versionId)}/schema/transactions/${encodeURIComponent(args.accepted.transactionId)}`;
59269
+ let status = args.accepted;
59270
+ let delayMs = 250;
59271
+ for (; ; ) {
59272
+ args.update(mergeTransactionProgressLabel(status));
59273
+ if (status.commitStatus === "committed") return;
59274
+ if (status.commitStatus === "failed") {
59275
+ throw new Error(
59276
+ `Merge transaction "${status.transactionId}" failed after rollback.`
59277
+ );
59278
+ }
59279
+ const response = await args.client.get(transactionPath);
59280
+ status = readMergeTransactionStatus(response, status.transactionId);
59281
+ if (status.commitStatus === "committed") continue;
59282
+ if (status.commitStatus === "failed") continue;
59283
+ await new Promise((resolve4) => setTimeout(resolve4, delayMs));
59284
+ delayMs = Math.min(delayMs * 2, 5e3);
59285
+ }
59286
+ }
59287
+ function readMergeTransactionStatus(value, transactionId) {
59288
+ if (!isObjectRecord2(value)) {
59289
+ throw new Error(
59290
+ `Merge transaction "${transactionId}" status must be an object.`
59291
+ );
59292
+ }
59293
+ if (value.transactionId !== transactionId) {
59294
+ throw new Error(
59295
+ `Merge transaction status expected "${transactionId}", received ${JSON.stringify(value.transactionId)}.`
59296
+ );
59297
+ }
59298
+ if (!isMergeTransactionStatus(value.commitStatus)) {
59299
+ throw new Error(
59300
+ `Merge transaction "${transactionId}" has an invalid commitStatus.`
59301
+ );
59302
+ }
59303
+ const totalChangeCount = value.totalChangeCount === null ? null : readMergeProgressCount(value.totalChangeCount, "totalChangeCount");
59304
+ const appliedChangeCount = readMergeProgressCount(
59305
+ value.appliedChangeCount,
59306
+ "appliedChangeCount"
59307
+ );
59308
+ return {
59309
+ transactionId,
59310
+ commitStatus: value.commitStatus,
59311
+ totalChangeCount,
59312
+ appliedChangeCount
59313
+ };
59314
+ }
59315
+ function readMergeProgressCount(value, field) {
59316
+ if (typeof value !== "number") {
59317
+ throw new Error(`Merge transaction ${field} must be a number.`);
59318
+ }
59319
+ if (!Number.isSafeInteger(value)) {
59320
+ throw new Error(`Merge transaction ${field} must be a safe integer.`);
59321
+ }
59322
+ if (value < 0) {
59323
+ throw new Error(`Merge transaction ${field} must not be negative.`);
59324
+ }
59325
+ return value;
59326
+ }
59327
+ function isMergeTransactionStatus(value) {
59328
+ return value === "preparing" || value === "waiting" || value === "queued" || value === "applying" || value === "reverting" || value === "committed" || value === "failed";
59329
+ }
59330
+ function mergeTransactionProgressLabel(status) {
59331
+ if (status.commitStatus === "preparing") return "Preparing merge\u2026";
59332
+ if (status.commitStatus === "waiting") return "Waiting to apply merge\u2026";
59333
+ if (status.commitStatus === "queued") return "Queueing merge\u2026";
59334
+ if (status.commitStatus === "reverting") return "Reverting merge\u2026";
59335
+ if (status.commitStatus === "committed") return "Merge committed.";
59336
+ if (status.commitStatus === "failed") return "Merge failed.";
59337
+ if (status.totalChangeCount !== null) {
59338
+ return `Applying merge\u2026 ${status.appliedChangeCount.toLocaleString("en-US")}/${status.totalChangeCount.toLocaleString("en-US")} changes`;
59339
+ }
59340
+ return "Applying merge\u2026";
59341
+ }
59138
59342
  async function runRefresh(workspace, dryRun) {
59139
59343
  const { versions } = await loadVersionMetadata(workspace);
59140
59344
  const current = versions.find(
@@ -61726,6 +61930,345 @@ function assignPendingIds(changes, staticValueSeeds, reconstructed3) {
61726
61930
  }
61727
61931
  return { assigned, staticValueSeeds: rewrittenSeeds };
61728
61932
  }
61933
+ function readAcceptedProjectVersionCommitResponse(value) {
61934
+ if (!isObjectRecord2(value) || value.kind !== "accepted") return null;
61935
+ if (typeof value.transactionId !== "string") {
61936
+ throw new Error("Accepted project transaction is missing transactionId.");
61937
+ }
61938
+ const rawStatus = value.commitStatus ?? value.status;
61939
+ if (rawStatus !== "preparing" && rawStatus !== "waiting" && rawStatus !== "queued" && rawStatus !== "applying" && rawStatus !== "reverting" && rawStatus !== "committed" && rawStatus !== "failed") {
61940
+ throw new Error(
61941
+ `Accepted project transaction "${value.transactionId}" has invalid commitStatus ${JSON.stringify(rawStatus)}.`
61942
+ );
61943
+ }
61944
+ const totalChangeCount = readNullableCount(
61945
+ value.totalChangeCount,
61946
+ "accepted totalChangeCount"
61947
+ );
61948
+ const appliedChangeCount = readCount(
61949
+ value.appliedChangeCount,
61950
+ "accepted appliedChangeCount"
61951
+ );
61952
+ const totalChunkCount = readNullableCount(
61953
+ value.totalChunkCount,
61954
+ "accepted totalChunkCount"
61955
+ );
61956
+ const appliedChunkCount = readCount(
61957
+ value.appliedChunkCount,
61958
+ "accepted appliedChunkCount"
61959
+ );
61960
+ return {
61961
+ kind: "accepted",
61962
+ transactionId: value.transactionId,
61963
+ commitStatus: rawStatus,
61964
+ totalChangeCount,
61965
+ appliedChangeCount,
61966
+ totalChunkCount,
61967
+ appliedChunkCount
61968
+ };
61969
+ }
61970
+ function readProjectVersionTransactionStatus(value, transactionId) {
61971
+ if (!isObjectRecord2(value)) {
61972
+ throw new Error(
61973
+ `Project transaction "${transactionId}" status response must be an object.`
61974
+ );
61975
+ }
61976
+ if (value.transactionId !== transactionId) {
61977
+ throw new Error(
61978
+ `Project transaction status response expected transactionId "${transactionId}"; received ${JSON.stringify(value.transactionId)}.`
61979
+ );
61980
+ }
61981
+ if (!isProjectVersionTransactionCommitStatus(value.commitStatus)) {
61982
+ throw new Error(
61983
+ `Project transaction "${transactionId}" has invalid commitStatus ${JSON.stringify(value.commitStatus)}.`
61984
+ );
61985
+ }
61986
+ const errorCode = readNullableString(
61987
+ value.errorCode,
61988
+ `Project transaction "${transactionId}" errorCode`
61989
+ );
61990
+ const errorMessage3 = readNullableString(
61991
+ value.errorMessage,
61992
+ `Project transaction "${transactionId}" errorMessage`
61993
+ );
61994
+ const resultCursor = readNullableString(
61995
+ value.resultCursor,
61996
+ `Project transaction "${transactionId}" resultCursor`
61997
+ );
61998
+ return {
61999
+ transactionId,
62000
+ commitStatus: value.commitStatus,
62001
+ totalChangeCount: readNullableCount(
62002
+ value.totalChangeCount,
62003
+ `Project transaction "${transactionId}" totalChangeCount`
62004
+ ),
62005
+ appliedChangeCount: readCount(
62006
+ value.appliedChangeCount,
62007
+ `Project transaction "${transactionId}" appliedChangeCount`
62008
+ ),
62009
+ totalChunkCount: readNullableCount(
62010
+ value.totalChunkCount,
62011
+ `Project transaction "${transactionId}" totalChunkCount`
62012
+ ),
62013
+ appliedChunkCount: readCount(
62014
+ value.appliedChunkCount,
62015
+ `Project transaction "${transactionId}" appliedChunkCount`
62016
+ ),
62017
+ errorCode,
62018
+ errorMessage: errorMessage3,
62019
+ resultCursor
62020
+ };
62021
+ }
62022
+ function readProjectVersionTransactionResultPage(value, transactionId) {
62023
+ if (!isObjectRecord2(value)) {
62024
+ throw new Error(
62025
+ `Project transaction "${transactionId}" result page must be an object.`
62026
+ );
62027
+ }
62028
+ if (value.transactionId !== transactionId) {
62029
+ throw new Error(
62030
+ `Project transaction result page expected transactionId "${transactionId}"; received ${JSON.stringify(value.transactionId)}.`
62031
+ );
62032
+ }
62033
+ if (!isObjectRecord2(value.assignments)) {
62034
+ throw new Error(
62035
+ `Project transaction "${transactionId}" result page assignments must be an object.`
62036
+ );
62037
+ }
62038
+ const assignments = {};
62039
+ for (const [pendingId2, assignedId] of Object.entries(value.assignments)) {
62040
+ if (typeof assignedId !== "string") {
62041
+ throw new Error(
62042
+ `Project transaction "${transactionId}" result assignment ${JSON.stringify(pendingId2)} must be a string.`
62043
+ );
62044
+ }
62045
+ assignments[pendingId2] = assignedId;
62046
+ }
62047
+ if (!Array.isArray(value.changedRecords)) {
62048
+ throw new Error(
62049
+ `Project transaction "${transactionId}" result page changedRecords must be an array.`
62050
+ );
62051
+ }
62052
+ const continueCursor = readNullableString(
62053
+ value.continueCursor,
62054
+ `Project transaction "${transactionId}" result page continueCursor`
62055
+ );
62056
+ if (typeof value.isDone !== "boolean") {
62057
+ throw new Error(
62058
+ `Project transaction "${transactionId}" result page isDone must be a boolean.`
62059
+ );
62060
+ }
62061
+ return {
62062
+ transactionId,
62063
+ assignments,
62064
+ changedRecords: value.changedRecords,
62065
+ continueCursor,
62066
+ isDone: value.isDone
62067
+ };
62068
+ }
62069
+ function readCount(value, field) {
62070
+ if (typeof value !== "number") {
62071
+ throw new Error(`${field} must be a number.`);
62072
+ }
62073
+ if (!Number.isSafeInteger(value)) {
62074
+ throw new Error(`${field} must be a safe integer.`);
62075
+ }
62076
+ if (value < 0) {
62077
+ throw new Error(`${field} must not be negative.`);
62078
+ }
62079
+ return value;
62080
+ }
62081
+ function readNullableCount(value, field) {
62082
+ if (value === null) return null;
62083
+ return readCount(value, field);
62084
+ }
62085
+ function readNullableString(value, field) {
62086
+ if (value === null) return null;
62087
+ if (typeof value !== "string") {
62088
+ throw new Error(`${field} must be a string or null.`);
62089
+ }
62090
+ return value;
62091
+ }
62092
+ function isProjectVersionTransactionCommitStatus(value) {
62093
+ return value === "preparing" || value === "waiting" || value === "queued" || value === "applying" || value === "reverting" || value === "committed" || value === "failed";
62094
+ }
62095
+ function createPushProgressReporter(json) {
62096
+ const animated = process.stdout.isTTY === true && !json;
62097
+ const indicator = animated ? spinner("Pushing\u2026") : null;
62098
+ let lastStructuredEvent = "";
62099
+ return {
62100
+ report(event) {
62101
+ if (indicator !== null) {
62102
+ indicator.update(projectTransactionProgressLabel(event));
62103
+ return;
62104
+ }
62105
+ const encoded = JSON.stringify(event);
62106
+ if (encoded === lastStructuredEvent) return;
62107
+ lastStructuredEvent = encoded;
62108
+ console.log(encoded);
62109
+ },
62110
+ stop() {
62111
+ indicator?.stop();
62112
+ }
62113
+ };
62114
+ }
62115
+ function projectTransactionProgressLabel(event) {
62116
+ if (event.phase === "submitting") return "Pushing\u2026";
62117
+ if (event.phase === "preparing") return "Preparing push\u2026";
62118
+ if (event.phase === "waiting") return "Waiting to apply project transaction\u2026";
62119
+ if (event.phase === "reverting") return "Reverting project transaction\u2026";
62120
+ if (event.phase === "finalizing") return "Finalizing project transaction\u2026";
62121
+ if (event.phase === "committed") return "Push complete.";
62122
+ if (event.phase === "failed") return "Project transaction failed.";
62123
+ if (event.phase === "interrupted")
62124
+ return "Project transaction still running.";
62125
+ if (event.totalChangeCount !== null && event.appliedChangeCount < event.totalChangeCount) {
62126
+ return `Applying project transaction\u2026 ${event.appliedChangeCount.toLocaleString("en-US")}/${event.totalChangeCount.toLocaleString("en-US")} changes`;
62127
+ }
62128
+ if (event.totalChunkCount !== null && event.appliedChunkCount < event.totalChunkCount) {
62129
+ return `Applying project transaction\u2026 ${event.appliedChunkCount.toLocaleString("en-US")}/${event.totalChunkCount.toLocaleString("en-US")} chunks`;
62130
+ }
62131
+ return "Finalizing project transaction\u2026";
62132
+ }
62133
+ function progressEventFromAccepted(accepted) {
62134
+ return {
62135
+ type: "project-transaction-progress",
62136
+ transactionId: accepted.transactionId,
62137
+ phase: accepted.commitStatus,
62138
+ totalChangeCount: accepted.totalChangeCount,
62139
+ appliedChangeCount: accepted.appliedChangeCount,
62140
+ totalChunkCount: accepted.totalChunkCount,
62141
+ appliedChunkCount: accepted.appliedChunkCount,
62142
+ errorCode: null,
62143
+ errorMessage: null
62144
+ };
62145
+ }
62146
+ function progressEventFromStatus(status) {
62147
+ const phase = status.commitStatus === "committed" || status.commitStatus === "applying" && status.totalChunkCount !== null && status.appliedChunkCount === status.totalChunkCount ? "finalizing" : status.commitStatus;
62148
+ return {
62149
+ type: "project-transaction-progress",
62150
+ transactionId: status.transactionId,
62151
+ phase,
62152
+ totalChangeCount: status.totalChangeCount,
62153
+ appliedChangeCount: status.appliedChangeCount,
62154
+ totalChunkCount: status.totalChunkCount,
62155
+ appliedChunkCount: status.appliedChunkCount,
62156
+ errorCode: status.errorCode,
62157
+ errorMessage: status.errorMessage
62158
+ };
62159
+ }
62160
+ async function waitForAcceptedProjectVersionTransaction(args) {
62161
+ const { client, projectId, versionId, accepted, reporter } = args;
62162
+ const transactionPath = `/api/projects/${projectId}/versions/${versionId}/schema/transactions/${accepted.transactionId}`;
62163
+ const controller = new AbortController();
62164
+ let interrupted = false;
62165
+ const interrupt = () => {
62166
+ interrupted = true;
62167
+ controller.abort();
62168
+ };
62169
+ process.once("SIGINT", interrupt);
62170
+ reporter.report(progressEventFromAccepted(accepted));
62171
+ let pollIndex = 0;
62172
+ try {
62173
+ for (; ; ) {
62174
+ const rawStatus = await client.get(transactionPath, {
62175
+ signal: controller.signal
62176
+ });
62177
+ const status = readProjectVersionTransactionStatus(
62178
+ rawStatus,
62179
+ accepted.transactionId
62180
+ );
62181
+ reporter.report(progressEventFromStatus(status));
62182
+ if (status.commitStatus === "committed") {
62183
+ return await downloadProjectVersionTransactionResult({
62184
+ client,
62185
+ transactionPath,
62186
+ transactionId: accepted.transactionId,
62187
+ status,
62188
+ signal: controller.signal
62189
+ });
62190
+ }
62191
+ if (status.commitStatus === "failed") {
62192
+ throw new ProjectTransactionFailedError(status);
62193
+ }
62194
+ const delay = PROJECT_TRANSACTION_POLL_DELAYS_MS[Math.min(pollIndex, PROJECT_TRANSACTION_POLL_DELAYS_MS.length - 1)];
62195
+ pollIndex += 1;
62196
+ await waitForPollDelay(delay, controller.signal);
62197
+ }
62198
+ } catch (error) {
62199
+ if (interrupted) {
62200
+ throw new ProjectTransactionInterruptedError(accepted.transactionId);
62201
+ }
62202
+ throw error;
62203
+ } finally {
62204
+ process.removeListener("SIGINT", interrupt);
62205
+ }
62206
+ }
62207
+ async function waitForPollDelay(delayMs, signal) {
62208
+ if (signal.aborted) {
62209
+ throw new Error("Project transaction polling was interrupted.");
62210
+ }
62211
+ await new Promise((resolve4, reject) => {
62212
+ const aborted = () => {
62213
+ clearTimeout(timer);
62214
+ reject(new Error("Project transaction polling was interrupted."));
62215
+ };
62216
+ const timer = setTimeout(() => {
62217
+ signal.removeEventListener("abort", aborted);
62218
+ resolve4();
62219
+ }, delayMs);
62220
+ signal.addEventListener("abort", aborted, { once: true });
62221
+ });
62222
+ }
62223
+ async function downloadProjectVersionTransactionResult(args) {
62224
+ const assignments = {};
62225
+ const changedRecords = [];
62226
+ const seenCursors = /* @__PURE__ */ new Set();
62227
+ let cursor = args.status.resultCursor ?? "";
62228
+ for (; ; ) {
62229
+ if (seenCursors.has(cursor)) {
62230
+ throw new Error(
62231
+ `Project transaction "${args.transactionId}" result pagination repeated cursor ${JSON.stringify(cursor)}.`
62232
+ );
62233
+ }
62234
+ seenCursors.add(cursor);
62235
+ const query = new URLSearchParams({ cursor });
62236
+ const rawPage = await args.client.get(
62237
+ `${args.transactionPath}/results?${query.toString()}`,
62238
+ { signal: args.signal }
62239
+ );
62240
+ const page = readProjectVersionTransactionResultPage(
62241
+ rawPage,
62242
+ args.transactionId
62243
+ );
62244
+ for (const [pendingId2, assignedId] of Object.entries(page.assignments)) {
62245
+ const existing = assignments[pendingId2];
62246
+ if (existing !== void 0 && existing !== assignedId) {
62247
+ throw new Error(
62248
+ `Project transaction "${args.transactionId}" returned conflicting assignments for ${JSON.stringify(pendingId2)}.`
62249
+ );
62250
+ }
62251
+ assignments[pendingId2] = assignedId;
62252
+ }
62253
+ changedRecords.push(...page.changedRecords);
62254
+ if (page.isDone) {
62255
+ return {
62256
+ kind: "committed",
62257
+ transactionId: args.transactionId,
62258
+ totalChangeCount: args.status.totalChangeCount ?? changedRecords.length,
62259
+ totalChunkCount: args.status.totalChunkCount ?? 0,
62260
+ assignments,
62261
+ changedRecords
62262
+ };
62263
+ }
62264
+ if (page.continueCursor === null) {
62265
+ throw new Error(
62266
+ `Project transaction "${args.transactionId}" result page is not done but has no continueCursor.`
62267
+ );
62268
+ }
62269
+ cursor = page.continueCursor;
62270
+ }
62271
+ }
61729
62272
  async function runPush(workspace, options) {
61730
62273
  const status = computeWorkspaceStatus(workspace);
61731
62274
  if (status.conflictedFiles.length > 0) {
@@ -61756,7 +62299,19 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61756
62299
  binaryChanges: status.binaryChanges ?? []
61757
62300
  });
61758
62301
  if (status.changes.length === 0) {
61759
- console.log("Nothing to push \u2014 working copy is clean.");
62302
+ if (options.json === true) {
62303
+ console.log(
62304
+ JSON.stringify({
62305
+ type: "push-plan",
62306
+ dryRun: options.dryRun,
62307
+ changeCount: 0,
62308
+ staticValueSeedCount: 0,
62309
+ changes: []
62310
+ })
62311
+ );
62312
+ } else {
62313
+ console.log("Nothing to push \u2014 working copy is clean.");
62314
+ }
61760
62315
  return;
61761
62316
  }
61762
62317
  prepareNSPropertySetterChanges(workspace, status);
@@ -61802,23 +62357,42 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61802
62357
  change.nextData = { ...change.nextData, updatedAt: now };
61803
62358
  }
61804
62359
  }
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) {
62360
+ if (options.json === true) {
61809
62361
  console.log(
61810
- ` ${paintChangeKind(change.kind)} ${describeChange(change).replace(`${change.kind} `, "")}`
62362
+ JSON.stringify({
62363
+ type: "push-plan",
62364
+ dryRun: options.dryRun,
62365
+ changeCount: status.changes.length,
62366
+ staticValueSeedCount: status.staticValueSeeds.size,
62367
+ changes: status.changes.map((change) => ({
62368
+ kind: change.kind,
62369
+ recordKind: change.recordKind,
62370
+ recordId: change.recordId
62371
+ }))
62372
+ })
61811
62373
  );
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) {
62374
+ } else {
61819
62375
  console.log(
61820
- ` ${color.dim(`create ${status.staticValueSeeds.size} member value seed(s)`)}`
62376
+ `${options.dryRun ? "Would push" : "Pushing"} ${color.bold(String(status.changes.length))} change(s):`
61821
62377
  );
62378
+ for (const change of status.changes) {
62379
+ console.log(
62380
+ ` ${paintChangeKind(change.kind)} ${describeChange(change).replace(`${change.kind} `, "")}`
62381
+ );
62382
+ if (change.kind === "update") {
62383
+ for (const line of summarizeFieldDiff(
62384
+ change.baseData,
62385
+ change.nextData
62386
+ )) {
62387
+ console.log(color.dim(` ${line}`));
62388
+ }
62389
+ }
62390
+ }
62391
+ if (status.staticValueSeeds.size > 0) {
62392
+ console.log(
62393
+ ` ${color.dim(`create ${status.staticValueSeeds.size} member value seed(s)`)}`
62394
+ );
62395
+ }
61822
62396
  }
61823
62397
  if (options.dryRun) return;
61824
62398
  if (isInteractive() && !await promptConfirm({
@@ -61882,12 +62456,131 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61882
62456
  },
61883
62457
  force ? { "x-neo-force-project-version-write": "true" } : void 0
61884
62458
  );
61885
- const pushing = spinner("Pushing\u2026");
62459
+ const finishCommitResponse = async (response, reporter) => {
62460
+ let accepted;
62461
+ try {
62462
+ accepted = readAcceptedProjectVersionCommitResponse(response);
62463
+ } catch (error) {
62464
+ reporter.stop();
62465
+ throw error;
62466
+ }
62467
+ if (accepted === null) {
62468
+ try {
62469
+ applyPushResult(workspace, response);
62470
+ } catch (error) {
62471
+ reporter.stop();
62472
+ throw error;
62473
+ }
62474
+ reporter.report({
62475
+ type: "project-transaction-progress",
62476
+ transactionId: immediateTransactionId(response),
62477
+ phase: "committed",
62478
+ totalChangeCount: status.changes.length,
62479
+ appliedChangeCount: status.changes.length,
62480
+ totalChunkCount: null,
62481
+ appliedChunkCount: 0,
62482
+ errorCode: null,
62483
+ errorMessage: null
62484
+ });
62485
+ reporter.stop();
62486
+ if (options.json !== true) success("Push complete.");
62487
+ return true;
62488
+ }
62489
+ let completed;
62490
+ try {
62491
+ completed = await waitForAcceptedProjectVersionTransaction({
62492
+ client,
62493
+ projectId: workspace.config.projectId,
62494
+ versionId: workspace.config.versionId,
62495
+ accepted,
62496
+ reporter
62497
+ });
62498
+ } catch (error) {
62499
+ if (error instanceof ProjectTransactionInterruptedError) {
62500
+ reporter.report({
62501
+ type: "project-transaction-progress",
62502
+ transactionId: error.transactionId,
62503
+ phase: "interrupted",
62504
+ totalChangeCount: accepted.totalChangeCount,
62505
+ appliedChangeCount: accepted.appliedChangeCount,
62506
+ totalChunkCount: accepted.totalChunkCount,
62507
+ appliedChunkCount: accepted.appliedChunkCount,
62508
+ errorCode: null,
62509
+ errorMessage: error.message
62510
+ });
62511
+ reporter.stop();
62512
+ if (options.json !== true) console.error(error.message);
62513
+ process.exitCode = 130;
62514
+ return false;
62515
+ }
62516
+ if (error instanceof ProjectTransactionFailedError) {
62517
+ const failed = error.status;
62518
+ reporter.report({
62519
+ type: "project-transaction-progress",
62520
+ transactionId: failed.transactionId,
62521
+ phase: "failed",
62522
+ totalChangeCount: failed.totalChangeCount,
62523
+ appliedChangeCount: failed.appliedChangeCount,
62524
+ totalChunkCount: failed.totalChunkCount,
62525
+ appliedChunkCount: failed.appliedChunkCount,
62526
+ errorCode: failed.errorCode,
62527
+ errorMessage: error.message
62528
+ });
62529
+ reporter.stop();
62530
+ if (options.json !== true) {
62531
+ console.error(
62532
+ `Push failed (${failed.errorCode ?? "project-transaction-failed"}, transaction ${failed.transactionId}): ${error.message}`
62533
+ );
62534
+ }
62535
+ process.exitCode = 1;
62536
+ return false;
62537
+ }
62538
+ reporter.stop();
62539
+ throw error;
62540
+ }
62541
+ try {
62542
+ applyCommittedProjectTransactionResult({
62543
+ workspace,
62544
+ result: completed,
62545
+ submittedChanges: status.changes,
62546
+ candidateAssignments: pendingAssignment.assigned
62547
+ });
62548
+ } catch (error) {
62549
+ reporter.stop();
62550
+ throw error;
62551
+ }
62552
+ reporter.report({
62553
+ type: "project-transaction-progress",
62554
+ transactionId: completed.transactionId,
62555
+ phase: "committed",
62556
+ totalChangeCount: completed.totalChangeCount,
62557
+ appliedChangeCount: completed.totalChangeCount,
62558
+ totalChunkCount: completed.totalChunkCount,
62559
+ appliedChunkCount: completed.totalChunkCount,
62560
+ errorCode: null,
62561
+ errorMessage: null
62562
+ });
62563
+ reporter.stop();
62564
+ if (options.json !== true) success("Push complete.");
62565
+ return true;
62566
+ };
62567
+ let progress = createPushProgressReporter(options.json === true);
62568
+ progress.report({
62569
+ type: "project-transaction-progress",
62570
+ transactionId: null,
62571
+ phase: "submitting",
62572
+ totalChangeCount: status.changes.length,
62573
+ appliedChangeCount: 0,
62574
+ totalChunkCount: null,
62575
+ appliedChunkCount: 0,
62576
+ errorCode: null,
62577
+ errorMessage: null
62578
+ });
61886
62579
  let result;
61887
62580
  try {
61888
62581
  result = await commit(options.acceptBump);
61889
62582
  } catch (error) {
61890
- pushing.stop();
62583
+ progress.stop();
61891
62584
  if (!(error instanceof NeoApiError)) throw error;
61892
62585
  const rejection = error.body;
61893
62586
  if (isInteractive() && !options.acceptBump && isObjectRecord2(rejection) && rejection.error === "version-bump-required") {
@@ -61903,7 +62596,18 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61903
62596
  message: "Accept the bump and push?",
61904
62597
  fallback: false
61905
62598
  })) {
61906
- const retrying = spinner("Pushing (bump accepted)\u2026");
62599
+ progress = createPushProgressReporter(options.json === true);
62600
+ progress.report({
62601
+ type: "project-transaction-progress",
62602
+ transactionId: null,
62603
+ phase: "submitting",
62604
+ totalChangeCount: status.changes.length,
62605
+ appliedChangeCount: 0,
62606
+ totalChunkCount: null,
62607
+ appliedChunkCount: 0,
62608
+ errorCode: null,
62609
+ errorMessage: null
62610
+ });
61907
62611
  try {
61908
62612
  stagedFiles = await stageProjectFilePushesV4({
61909
62613
  workspace,
@@ -61914,7 +62618,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61914
62618
  });
61915
62619
  result = await commit(true);
61916
62620
  } catch (retryError) {
61917
- retrying.stop();
62621
+ progress.stop();
61918
62622
  if (retryError instanceof NeoApiError) {
61919
62623
  reportPushRejection(retryError.body);
61920
62624
  process.exitCode = 1;
@@ -61922,9 +62626,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61922
62626
  }
61923
62627
  throw retryError;
61924
62628
  }
61925
- retrying.stop();
61926
- applyPushResult(workspace, result);
61927
- success("Push complete.");
62629
+ await finishCommitResponse(result, progress);
61928
62630
  return;
61929
62631
  }
61930
62632
  console.log("Push cancelled.");
@@ -61935,9 +62637,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61935
62637
  process.exitCode = 1;
61936
62638
  return;
61937
62639
  }
61938
- pushing.stop();
61939
- applyPushResult(workspace, result);
61940
- success("Push complete.");
62640
+ await finishCommitResponse(result, progress);
61941
62641
  }
61942
62642
  async function createPendingProjectSourceBundleV4(workspace, status, staticValueSeeds) {
61943
62643
  const records2 = /* @__PURE__ */ new Map();
@@ -62084,52 +62784,298 @@ function reportPushRejection(body) {
62084
62784
  }
62085
62785
  console.error(`Push rejected (409): ${JSON.stringify(body)}`);
62086
62786
  }
62087
- function applyPushResult(workspace, result) {
62088
- if (!isObjectRecord2(result) || !Array.isArray(result.changedRecords)) {
62089
- throw new Error("Transaction response is missing changedRecords.");
62787
+ function immediateTransactionId(result) {
62788
+ if (!isObjectRecord2(result)) return null;
62789
+ if (typeof result.transactionId === "string") return result.transactionId;
62790
+ if (!isObjectRecord2(result.transaction)) return null;
62791
+ return typeof result.transaction.id === "string" ? result.transaction.id : null;
62792
+ }
62793
+ function applyCommittedProjectTransactionResult(args) {
62794
+ const { workspace, result, submittedChanges, candidateAssignments } = args;
62795
+ const localStatus = computeWorkspaceStatus(workspace);
62796
+ const blockingErrors = localStatus.parseErrors.filter(
62797
+ isBlockingSchemaSourceError
62798
+ );
62799
+ if (blockingErrors.length > 0) {
62800
+ throw new Error(
62801
+ `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:
62802
+ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
62803
+ );
62804
+ }
62805
+ if (localStatus.conflictedFiles.length > 0) {
62806
+ throw new Error(
62807
+ `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.`
62808
+ );
62090
62809
  }
62810
+ const replacements = projectTransactionResultReplacements(
62811
+ candidateAssignments,
62812
+ result.assignments
62813
+ );
62814
+ const localRecords = rewriteReconstructedRecords(
62815
+ localStatus.reconstructed,
62816
+ replacements
62817
+ );
62818
+ const submittedByKey = /* @__PURE__ */ new Map();
62819
+ for (const change of submittedChanges) {
62820
+ if (change.nextData === void 0) continue;
62821
+ const recordId = rewriteAssignedString(change.recordId, replacements);
62822
+ const nextData = rewriteAssignedValue(change.nextData, replacements);
62823
+ if (!isObjectRecord2(nextData)) {
62824
+ throw new Error(
62825
+ `Submitted ${change.recordKind} "${recordId}" did not rewrite to an object.`
62826
+ );
62827
+ }
62828
+ submittedByKey.set(recordStateKey(change.recordKind, recordId), nextData);
62829
+ }
62830
+ const previousRecords = workspace.state.records;
62831
+ const nextRecords = foldChangedRecords(
62832
+ previousRecords,
62833
+ result.changedRecords
62834
+ );
62835
+ const changedKeys = /* @__PURE__ */ new Set();
62091
62836
  for (const changed of result.changedRecords) {
62092
- if (!isObjectRecord2(changed)) {
62093
- throw new Error("Transaction changedRecords entries must be objects.");
62837
+ const identity2 = readChangedRecordIdentity(changed);
62838
+ changedKeys.add(recordStateKey(identity2.recordKind, identity2.recordId));
62839
+ }
62840
+ const emitRecords = /* @__PURE__ */ new Map();
62841
+ for (const [key, state] of Object.entries(nextRecords)) {
62842
+ const prior = previousRecords[key];
62843
+ const local = localRecords.get(key);
62844
+ if (local === void 0 && prior?.file !== void 0) continue;
62845
+ const submitted = submittedByKey.get(key);
62846
+ const emitData = local === void 0 ? state.data : changedKeys.has(key) && submitted !== void 0 ? mergeCommittedRecordWithLaterLocalEdits(
62847
+ state.recordKind,
62848
+ submitted,
62849
+ state.data,
62850
+ local.fullData
62851
+ ) : local.fullData;
62852
+ emitRecords.set(key, {
62853
+ recordKind: state.recordKind,
62854
+ recordId: state.recordId,
62855
+ contentHash: state.contentHash,
62856
+ deleted: false,
62857
+ data: emitData
62858
+ });
62859
+ }
62860
+ for (const [key, local] of localRecords) {
62861
+ if (emitRecords.has(key)) continue;
62862
+ if (changedKeys.has(key)) continue;
62863
+ emitRecords.set(key, {
62864
+ recordKind: local.recordKind,
62865
+ recordId: local.recordId,
62866
+ contentHash: "",
62867
+ deleted: false,
62868
+ data: local.fullData
62869
+ });
62870
+ }
62871
+ const previousState = workspace.state;
62872
+ workspace.state = { ...previousState, records: nextRecords };
62873
+ try {
62874
+ rewriteFilesFromState(workspace, emitRecords);
62875
+ writeWorkspaceState(workspace.root, workspace.state);
62876
+ } catch (error) {
62877
+ workspace.state = previousState;
62878
+ throw error;
62879
+ }
62880
+ }
62881
+ function projectTransactionResultReplacements(candidateAssignments, resultAssignments) {
62882
+ const replacements = /* @__PURE__ */ new Map();
62883
+ for (const [pendingId2, assignedId] of Object.entries(resultAssignments)) {
62884
+ replacements.set(pendingId2, assignedId);
62885
+ const candidateId = candidateAssignments.get(pendingId2);
62886
+ if (candidateId !== void 0) replacements.set(candidateId, assignedId);
62887
+ }
62888
+ return {
62889
+ exact: replacements,
62890
+ embedded: [...replacements].sort(
62891
+ ([left], [right]) => right.length - left.length
62892
+ )
62893
+ };
62894
+ }
62895
+ function rewriteReconstructedRecords(records2, replacements) {
62896
+ const rewritten = /* @__PURE__ */ new Map();
62897
+ for (const record3 of records2.values()) {
62898
+ const recordId = rewriteAssignedString(record3.recordId, replacements);
62899
+ const fileFields = rewriteAssignedValue(record3.fileFields, replacements);
62900
+ if (!isObjectRecord2(fileFields)) {
62901
+ throw new Error(
62902
+ `Local ${record3.recordKind} "${recordId}" file fields did not rewrite to an object.`
62903
+ );
62904
+ }
62905
+ const fullData = rewriteAssignedValue(record3.fullData, replacements);
62906
+ if (!isObjectRecord2(fullData)) {
62907
+ throw new Error(
62908
+ `Local ${record3.recordKind} "${recordId}" full data did not rewrite to an object.`
62909
+ );
62910
+ }
62911
+ const key = recordStateKey(record3.recordKind, recordId);
62912
+ if (rewritten.has(key)) {
62913
+ throw new Error(
62914
+ `Project transaction assignments collapse multiple local records onto "${key}".`
62915
+ );
62916
+ }
62917
+ rewritten.set(key, { ...record3, recordId, fileFields, fullData });
62918
+ }
62919
+ return rewritten;
62920
+ }
62921
+ function rewriteAssignedString(value, replacements) {
62922
+ const exact = replacements.exact.get(value);
62923
+ if (exact !== void 0) return exact;
62924
+ let result = value;
62925
+ for (const [source, target] of replacements.embedded) {
62926
+ if (result.includes(source)) result = result.replaceAll(source, target);
62927
+ }
62928
+ return result;
62929
+ }
62930
+ function rewriteAssignedValue(value, replacements) {
62931
+ if (typeof value === "string") {
62932
+ return rewriteAssignedString(value, replacements);
62933
+ }
62934
+ if (Array.isArray(value)) {
62935
+ return value.map((entry) => rewriteAssignedValue(entry, replacements));
62936
+ }
62937
+ if (!isObjectRecord2(value)) return value;
62938
+ const rewritten = {};
62939
+ for (const [key, entry] of Object.entries(value)) {
62940
+ rewritten[rewriteAssignedString(key, replacements)] = rewriteAssignedValue(
62941
+ entry,
62942
+ replacements
62943
+ );
62944
+ }
62945
+ return rewritten;
62946
+ }
62947
+ function mergeCommittedRecordWithLaterLocalEdits(recordKind, submitted, committed, local) {
62948
+ if (!isObjectRecord2(committed)) return local;
62949
+ if (isSchemaDocumentRecordKind(recordKind)) {
62950
+ if (schemaDocumentAuthoredSemanticallyEqual(recordKind, submitted, local)) {
62951
+ return committed;
62952
+ }
62953
+ const merged2 = mergeSchemaDocumentRecord(
62954
+ recordKind,
62955
+ submitted,
62956
+ committed,
62957
+ local
62958
+ );
62959
+ return merged2.conflictFields.length === 0 ? merged2.merged : local;
62960
+ }
62961
+ const comparison = {
62962
+ base: sourceComparableRecordForPush(recordKind, submitted),
62963
+ server: sourceComparableRecordForPush(recordKind, committed),
62964
+ local: sourceComparableRecordForPush(recordKind, local)
62965
+ };
62966
+ if (canonicallyEqual(comparison.base, comparison.local)) return committed;
62967
+ const merged = threeWayMergeRecord(submitted, committed, local, {
62968
+ comparison,
62969
+ serverWinsFields: /* @__PURE__ */ new Set([
62970
+ "projectId",
62971
+ "createdAt",
62972
+ "updatedAt",
62973
+ "getter",
62974
+ "setter",
62975
+ "action",
62976
+ "layout"
62977
+ ]),
62978
+ recursive: true
62979
+ });
62980
+ return merged.conflictFields.length === 0 ? merged.merged : local;
62981
+ }
62982
+ function sourceComparableRecordForPush(recordKind, value) {
62983
+ const result = {};
62984
+ const hasAuthoredCode = typeof value.code === "string" || typeof value.setterCode === "string";
62985
+ for (const [key, entry] of Object.entries(value)) {
62986
+ if (key === "projectId" || key === "createdAt" || key === "updatedAt") {
62987
+ continue;
62094
62988
  }
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.");
62989
+ if (recordKind === "dialogue-node" && key === "layout") continue;
62990
+ if (hasAuthoredCode && (key === "getter" || key === "setter" || key === "action")) {
62991
+ continue;
62099
62992
  }
62993
+ result[key] = sourceComparableValueForPush(recordKind, entry);
62994
+ }
62995
+ return result;
62996
+ }
62997
+ function sourceComparableValueForPush(recordKind, value) {
62998
+ if (Array.isArray(value)) {
62999
+ return value.map(
63000
+ (entry) => sourceComparableValueForPush(recordKind, entry)
63001
+ );
63002
+ }
63003
+ if (!isObjectRecord2(value)) return value;
63004
+ return sourceComparableRecordForPush(recordKind, value);
63005
+ }
63006
+ function readChangedRecordIdentity(changed) {
63007
+ if (!isObjectRecord2(changed)) {
63008
+ throw new Error("Transaction changedRecords entries must be objects.");
63009
+ }
63010
+ if (typeof changed.recordKind !== "string") {
63011
+ throw new Error(
63012
+ "Changed record is missing recordKind/recordId: recordKind is not a string."
63013
+ );
63014
+ }
63015
+ if (typeof changed.recordId !== "string") {
63016
+ throw new Error(
63017
+ "Changed record is missing recordKind/recordId: recordId is not a string."
63018
+ );
63019
+ }
63020
+ return { recordKind: changed.recordKind, recordId: changed.recordId };
63021
+ }
63022
+ function foldChangedRecords(previous, changedRecords) {
63023
+ const records2 = { ...previous };
63024
+ for (const changed of changedRecords) {
63025
+ const { recordKind, recordId } = readChangedRecordIdentity(changed);
62100
63026
  const key = recordStateKey(recordKind, recordId);
63027
+ if (!isObjectRecord2(changed)) {
63028
+ throw new Error(`Changed record "${key}" must be an object.`);
63029
+ }
62101
63030
  if (changed.deleted === true) {
62102
- delete workspace.state.records[key];
63031
+ delete records2[key];
62103
63032
  continue;
62104
63033
  }
62105
63034
  const snapshot = changed.snapshot;
62106
- if (!isObjectRecord2(snapshot) || typeof snapshot.contentHash !== "string") {
63035
+ if (!isObjectRecord2(snapshot)) {
63036
+ throw new Error(
63037
+ `Changed record "${key}" response is missing its snapshot.`
63038
+ );
63039
+ }
63040
+ if (typeof snapshot.contentHash !== "string") {
62107
63041
  throw new Error(
62108
63042
  `Changed record "${key}" response is missing its snapshot content hash.`
62109
63043
  );
62110
63044
  }
62111
- workspace.state.records[key] = {
63045
+ records2[key] = {
62112
63046
  recordKind,
62113
63047
  recordId,
62114
63048
  contentHash: snapshot.contentHash,
62115
63049
  data: changed.data,
62116
- file: workspace.state.records[key]?.file
63050
+ file: records2[key]?.file,
63051
+ projectBinary: records2[key]?.projectBinary
62117
63052
  };
62118
63053
  }
63054
+ return records2;
63055
+ }
63056
+ function applyPushResult(workspace, result) {
63057
+ if (!isObjectRecord2(result) || !Array.isArray(result.changedRecords)) {
63058
+ throw new Error("Transaction response is missing changedRecords.");
63059
+ }
63060
+ workspace.state.records = foldChangedRecords(
63061
+ workspace.state.records,
63062
+ result.changedRecords
63063
+ );
62119
63064
  rewriteFilesFromState(workspace);
62120
63065
  writeWorkspaceState(workspace.root, workspace.state);
62121
63066
  }
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, {
63067
+ function rewriteFilesFromState(workspace, records2 = new Map(
63068
+ Object.entries(workspace.state.records).map(([key, recordState]) => [
63069
+ key,
63070
+ {
62126
63071
  recordKind: recordState.recordKind,
62127
63072
  recordId: recordState.recordId,
62128
63073
  contentHash: recordState.contentHash,
62129
63074
  deleted: false,
62130
63075
  data: recordState.data
62131
- });
62132
- }
63076
+ }
63077
+ ])
63078
+ )) {
62133
63079
  const result = emitProjectDocumentFilesV4(records2);
62134
63080
  writeProjectSourceAnalysisCacheV4(workspace.root, result.analysis);
62135
63081
  const emittedPaths = new Set(result.files.map((file) => file.path));
@@ -62610,7 +63556,7 @@ function compileMigrationAction(schema, migrationData) {
62610
63556
  migrationContext: true
62611
63557
  });
62612
63558
  }
62613
- var compileNSAction2, compileNSFunction2, compileNSGetter2, compileNSSetter2, createProjectVersionIntent2, isKnownProjectVersionIntentType2;
63559
+ var compileNSAction2, compileNSFunction2, compileNSGetter2, compileNSSetter2, createProjectVersionIntent2, isKnownProjectVersionIntentType2, ProjectTransactionInterruptedError, ProjectTransactionFailedError, PROJECT_TRANSACTION_POLL_DELAYS_MS;
62614
63560
  var init_push = __esm({
62615
63561
  "src/commands/push.ts"() {
62616
63562
  "use strict";
@@ -62627,8 +63573,38 @@ var init_push = __esm({
62627
63573
  init_source_diagnostics();
62628
63574
  init_projection();
62629
63575
  init_project_file_push();
63576
+ init_project_manifest();
63577
+ init_merge();
62630
63578
  ({ compileNSAction: compileNSAction2, compileNSFunction: compileNSFunction2, compileNSGetter: compileNSGetter2, compileNSSetter: compileNSSetter2 } = compiler_adapter_exports);
62631
63579
  ({ createProjectVersionIntent: createProjectVersionIntent2, isKnownProjectVersionIntentType: isKnownProjectVersionIntentType2 } = project_version_intents_exports);
63580
+ ProjectTransactionInterruptedError = class extends Error {
63581
+ constructor(transactionId) {
63582
+ super(
63583
+ `Project transaction "${transactionId}" is still running. Run the same neo push again to reconnect.`
63584
+ );
63585
+ this.transactionId = transactionId;
63586
+ this.name = "ProjectTransactionInterruptedError";
63587
+ }
63588
+ transactionId;
63589
+ };
63590
+ ProjectTransactionFailedError = class extends Error {
63591
+ constructor(status) {
63592
+ super(
63593
+ status.errorMessage ?? `Project transaction "${status.transactionId}" failed after rollback.`
63594
+ );
63595
+ this.status = status;
63596
+ this.name = "ProjectTransactionFailedError";
63597
+ }
63598
+ status;
63599
+ };
63600
+ PROJECT_TRANSACTION_POLL_DELAYS_MS = [
63601
+ 250,
63602
+ 500,
63603
+ 1e3,
63604
+ 2e3,
63605
+ 4e3,
63606
+ 5e3
63607
+ ];
62632
63608
  }
62633
63609
  });
62634
63610
 
@@ -63368,7 +64344,7 @@ ${h("Start")}
63368
64344
  whoami ${d("[--api <url>]")}
63369
64345
 
63370
64346
  ${h("Working copy")}
63371
- pull ${d("[--force|--reset] [--regenerate-source-names]")} push ${d("[--dry-run] [--summary <text>] [--accept-bump]")}
64347
+ pull ${d("[--force|--reset] [--regenerate-source-names]")} push ${d("[--dry-run] [--summary <text>] [--accept-bump] [--json]")}
63372
64348
  status ${d("[--json]")} diff ${d("[--json]")}
63373
64349
  dev ${d("[--push]")} resolve ${d("[--mine|--theirs]")}
63374
64350
  doctor ${d("[--json]")} ${d("validate format/compiler/editor/source/file contracts")}
@@ -63418,16 +64394,18 @@ and tracked project binary from the authoritative server records.
63418
64394
  return `${h("neo push")} \u2014 commit the working copy to the server
63419
64395
 
63420
64396
  ${h("Usage")}
63421
- neo push ${d("[--dry-run] [--summary <text>] [--accept-bump]")}
64397
+ neo push ${d("[--dry-run] [--summary <text>] [--accept-bump] [--json]")}
63422
64398
 
63423
64399
  ${h("Flags")}
63424
64400
  --dry-run ${d("Preview the change set; commit nothing.")}
63425
64401
  --summary <text> ${d("Attach a summary message to the transaction.")}
63426
64402
  --accept-bump ${d("Accept a server-required version bump instead of aborting.")}
64403
+ --json ${d("Emit machine-readable plan and transaction progress events.")}
63427
64404
 
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.
64405
+ Push sends the ${h("entire")} working copy as one compare-and-swap logical
64406
+ transaction. Large pushes continue as a durable server job; rerun the identical
64407
+ push to reconnect if the terminal closes. Run ${h("neo status")} / ${h("neo diff")}
64408
+ first \u2014 local-ahead changes you did not make this session ride along too.
63431
64409
  `;
63432
64410
  }
63433
64411
  if (command === "script") {
@@ -63919,7 +64897,8 @@ async function main() {
63919
64897
  await runPush2(workspace, {
63920
64898
  dryRun: boolFlag(args, "dry-run"),
63921
64899
  summary: stringFlag(args, "summary"),
63922
- acceptBump: boolFlag(args, "accept-bump")
64900
+ acceptBump: boolFlag(args, "accept-bump"),
64901
+ json: boolFlag(args, "json")
63923
64902
  });
63924
64903
  return;
63925
64904
  }