@neocompose/cli 0.6.0 → 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.
Files changed (3) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/neo.mjs +1054 -85
  3. package/package.json +1 -1
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"),
@@ -28256,29 +28259,38 @@ function genericConstraint(context, type) {
28256
28259
  };
28257
28260
  }
28258
28261
  function inheritedMemberId(context, ownerClass, name) {
28259
- const baseType = ownerClass.baseTypes.find(
28262
+ const directBase = ownerClass.baseTypes.find(
28260
28263
  (entry) => context.classIdsByName.has(entry.name)
28261
28264
  );
28262
- if (!baseType) return null;
28263
- const baseClass = context.baseClasses.get(
28264
- requiredName(context.classIdsByName, baseType.name, "class")
28265
- );
28266
- if (baseClass !== void 0) return baseClass.schema[name] ?? null;
28267
- const baseClassId = requiredName(
28268
- context.classIdsByName,
28269
- baseType.name,
28270
- "class"
28271
- );
28272
- const sourceBase = context.sourceClasses.get(baseClassId);
28273
- if (sourceBase === void 0) return null;
28274
- const sourceMember = sourceBase.members.find(
28275
- (member) => member.name === name
28276
- );
28277
- return sourceMember === void 0 ? null : materializedId(
28278
- sourceMember,
28279
- "member",
28280
- `${sourceBase.name}.${sourceMember.name}`
28281
- );
28265
+ let classId = directBase ? requiredName(context.classIdsByName, directBase.name, "class") : null;
28266
+ const visited = /* @__PURE__ */ new Set();
28267
+ while (classId !== null && !visited.has(classId)) {
28268
+ visited.add(classId);
28269
+ const sourceClass = context.sourceClasses.get(classId);
28270
+ if (sourceClass !== void 0) {
28271
+ const sourceMember = sourceClass.members.find(
28272
+ (member) => member.name === name
28273
+ );
28274
+ if (sourceMember !== void 0) {
28275
+ return materializedId(
28276
+ sourceMember,
28277
+ "member",
28278
+ `${sourceClass.name}.${sourceMember.name}`
28279
+ );
28280
+ }
28281
+ const sourceBase = sourceClass.baseTypes.find(
28282
+ (entry) => context.classIdsByName.has(entry.name)
28283
+ );
28284
+ classId = sourceBase ? requiredName(context.classIdsByName, sourceBase.name, "class") : null;
28285
+ continue;
28286
+ }
28287
+ const pulledClass = context.baseClasses.get(classId);
28288
+ if (pulledClass === void 0) return null;
28289
+ const memberId = pulledClass.schema[name];
28290
+ if (memberId !== void 0) return memberId;
28291
+ classId = pulledClass.extendsClassId;
28292
+ }
28293
+ return null;
28282
28294
  }
28283
28295
  function resolveQualifiedMemberId(context, expression) {
28284
28296
  const match = /^([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(
@@ -39728,6 +39740,7 @@ function lowerMemberDefaultSourcesV4(state, analysis, manifest) {
39728
39740
  if (record3.recordKind === "value") pulledValueIds.add(record3.recordId);
39729
39741
  }
39730
39742
  const memberDefaultValues = /* @__PURE__ */ new Map();
39743
+ const seeds = /* @__PURE__ */ new Map();
39731
39744
  for (const sourceClass of analysis.schema.classes) {
39732
39745
  for (const declaration of sourceClass.members) {
39733
39746
  if (declaration.kind !== "field" || declaration.initializer === null) {
@@ -39736,13 +39749,6 @@ function lowerMemberDefaultSourcesV4(state, analysis, manifest) {
39736
39749
  if (declaration.modifiers.includes("static")) continue;
39737
39750
  const label = `${sourceClass.name}.${declaration.name}`;
39738
39751
  const memberId = sourceIdentityId(declaration, "member", label);
39739
- const baseData3 = stateData(context, "member", memberId);
39740
- if (baseData3 === null) continue;
39741
- const backed = rowBackedDefaultBody(
39742
- baseData3,
39743
- (id2) => pulledValueIds.has(id2)
39744
- );
39745
- if (backed === null) continue;
39746
39752
  const member = context.members.get(memberId);
39747
39753
  if (member === void 0) continue;
39748
39754
  const binding = {
@@ -39751,17 +39757,52 @@ function lowerMemberDefaultSourcesV4(state, analysis, manifest) {
39751
39757
  source: declaration.source,
39752
39758
  label
39753
39759
  };
39754
- memberDefaultValues.set(
39755
- memberId,
39756
- lowerRowBackedDefault(context, member, backed, baseData3, binding)
39760
+ const baseData3 = stateData(context, "member", memberId);
39761
+ const backed = baseData3 === null ? null : rowBackedDefaultBody(baseData3, (id2) => pulledValueIds.has(id2));
39762
+ if (backed !== null && baseData3 !== null) {
39763
+ memberDefaultValues.set(
39764
+ memberId,
39765
+ lowerRowBackedDefault(context, member, backed, baseData3, binding)
39766
+ );
39767
+ continue;
39768
+ }
39769
+ const expression = parseCachedInitializer(
39770
+ context.parsedInitializers,
39771
+ declaration.initializer
39772
+ );
39773
+ if (!defaultRequiresOwnedRows(member, expression)) continue;
39774
+ const rootValueId = pendingValueId(binding, `${label}.default`);
39775
+ const seed = lowerStaticSeed(
39776
+ context,
39777
+ member,
39778
+ expression,
39779
+ binding,
39780
+ rootValueId
39757
39781
  );
39782
+ seeds.set(memberId, { ...seed, valueId: rootValueId });
39783
+ memberDefaultValues.set(memberId, {
39784
+ value: seed.value,
39785
+ classId: seed.classId
39786
+ });
39758
39787
  }
39759
39788
  }
39760
39789
  return {
39761
39790
  records: [...context.reconstructed.values()],
39762
- memberDefaultValues
39791
+ memberDefaultValues,
39792
+ seeds
39763
39793
  };
39764
39794
  }
39795
+ function defaultRequiresOwnedRows(member, sourceExpression) {
39796
+ const expression = annotatedValue(sourceExpression).expression;
39797
+ if (member.kind === "class") return expression.kind === "new";
39798
+ if (member.kind === "list") {
39799
+ return expression.kind === "litList" && expression.elements.length > 0;
39800
+ }
39801
+ if (member.kind === "dictionary") {
39802
+ return expression.kind === "litDict" && expression.entries.length > 0;
39803
+ }
39804
+ return false;
39805
+ }
39765
39806
  function lowerRowBackedDefault(context, member, backed, baseData3, binding) {
39766
39807
  const baseDefault = isObjectRecord2(baseData3.defaultValue) ? baseData3.defaultValue : {};
39767
39808
  const storedClassId = stringOrNull(baseDefault.classId);
@@ -43737,7 +43778,8 @@ function computeWorkspaceStatus(workspace, options = {}) {
43737
43778
  let staticMemberValueIds = /* @__PURE__ */ new Map();
43738
43779
  let memberDefaults = {
43739
43780
  records: [],
43740
- memberDefaultValues: /* @__PURE__ */ new Map()
43781
+ memberDefaultValues: /* @__PURE__ */ new Map(),
43782
+ seeds: /* @__PURE__ */ new Map()
43741
43783
  };
43742
43784
  let projectAnalysisV4 = null;
43743
43785
  try {
@@ -43887,7 +43929,7 @@ function computeWorkspaceStatus(workspace, options = {}) {
43887
43929
  projectAnalysisV4,
43888
43930
  manifest
43889
43931
  );
43890
- staticValueSeeds = staticValues.seeds;
43932
+ staticValueSeeds = new Map([...staticValues.seeds, ...memberDefaults.seeds]);
43891
43933
  staticMemberValueIds = staticValues.memberValueIds;
43892
43934
  const rootRecords = lowerProjectRootSourceV4(
43893
43935
  workspace.state.records,
@@ -45434,6 +45476,7 @@ __export(project_document_read_exports, {
45434
45476
  readOptionalArrayField: () => readOptionalArrayField,
45435
45477
  readProjectDocument: () => readProjectDocument,
45436
45478
  readProjectDocumentContentHashHeads: () => readProjectDocumentContentHashHeads,
45479
+ readProjectDocumentManifestPageRecords: () => readProjectDocumentManifestPageRecords,
45437
45480
  readProjectDocumentManifestRecords: () => readProjectDocumentManifestRecords,
45438
45481
  readProjectDocumentRevisionMarker: () => readProjectDocumentRevisionMarker
45439
45482
  });
@@ -45639,6 +45682,12 @@ function readProjectDocumentManifestRecords(value) {
45639
45682
  };
45640
45683
  });
45641
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
+ }
45642
45691
  async function fetchProjectDocumentSnapshots(fetchBatch, snapshotIds) {
45643
45692
  const chunks = [];
45644
45693
  for (let offset = 0; offset < snapshotIds.length; offset += DOCUMENT_SNAPSHOT_FETCH_CHUNK_SIZE) {
@@ -46600,6 +46649,32 @@ var init_http = __esm({
46600
46649
  }
46601
46650
  return parsed;
46602
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
+ }
46603
46678
  async getBytes(path) {
46604
46679
  const url = new URL(path, this.apiBaseUrl).toString();
46605
46680
  const response = await fetch(url, {
@@ -59024,11 +59099,32 @@ async function runMerge(workspace, sourceRef, dryRun, runMigrations = false) {
59024
59099
  }
59025
59100
  }
59026
59101
  try {
59027
- const result = await client.post(mergePath, {
59102
+ let result = await client.post(mergePath, {
59028
59103
  sourceVersionId: source.id,
59029
59104
  dryRun,
59030
59105
  resolutions: resolutions.length > 0 ? resolutions : void 0
59031
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
+ }
59032
59128
  const merged = typeof result.merged === "number" ? result.merged : 0;
59033
59129
  if (dryRun) {
59034
59130
  console.log(`Merge dry-run: ${merged} record(s) would merge cleanly.`);
@@ -59096,6 +59192,104 @@ async function runMerge(workspace, sourceRef, dryRun, runMigrations = false) {
59096
59192
  throw error;
59097
59193
  }
59098
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
+ }
59099
59293
  async function runRefresh(workspace, dryRun) {
59100
59294
  const { versions } = await loadVersionMetadata(workspace);
59101
59295
  const current = versions.find(
@@ -61687,6 +61881,345 @@ function assignPendingIds(changes, staticValueSeeds, reconstructed3) {
61687
61881
  }
61688
61882
  return { assigned, staticValueSeeds: rewrittenSeeds };
61689
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
+ }
61690
62223
  async function runPush(workspace, options) {
61691
62224
  const status = computeWorkspaceStatus(workspace);
61692
62225
  if (status.conflictedFiles.length > 0) {
@@ -61717,7 +62250,19 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61717
62250
  binaryChanges: status.binaryChanges ?? []
61718
62251
  });
61719
62252
  if (status.changes.length === 0) {
61720
- 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
+ }
61721
62266
  return;
61722
62267
  }
61723
62268
  prepareNSPropertySetterChanges(workspace, status);
@@ -61763,23 +62308,42 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61763
62308
  change.nextData = { ...change.nextData, updatedAt: now };
61764
62309
  }
61765
62310
  }
61766
- console.log(
61767
- `${options.dryRun ? "Would push" : "Pushing"} ${color.bold(String(status.changes.length))} change(s):`
61768
- );
61769
- for (const change of status.changes) {
62311
+ if (options.json === true) {
61770
62312
  console.log(
61771
- ` ${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
+ })
61772
62324
  );
61773
- if (change.kind === "update") {
61774
- for (const line of summarizeFieldDiff(change.baseData, change.nextData)) {
61775
- console.log(color.dim(` ${line}`));
61776
- }
61777
- }
61778
- }
61779
- if (status.staticValueSeeds.size > 0) {
62325
+ } else {
61780
62326
  console.log(
61781
- ` ${color.dim(`create ${status.staticValueSeeds.size} static value seed(s)`)}`
62327
+ `${options.dryRun ? "Would push" : "Pushing"} ${color.bold(String(status.changes.length))} change(s):`
61782
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
+ }
61783
62347
  }
61784
62348
  if (options.dryRun) return;
61785
62349
  if (isInteractive() && !await promptConfirm({
@@ -61843,12 +62407,131 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61843
62407
  },
61844
62408
  force ? { "x-neo-force-project-version-write": "true" } : void 0
61845
62409
  );
61846
- 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
+ });
61847
62530
  let result;
61848
62531
  try {
61849
62532
  result = await commit(options.acceptBump);
61850
62533
  } catch (error) {
61851
- pushing.stop();
62534
+ progress.stop();
61852
62535
  if (!(error instanceof NeoApiError)) throw error;
61853
62536
  const rejection = error.body;
61854
62537
  if (isInteractive() && !options.acceptBump && isObjectRecord2(rejection) && rejection.error === "version-bump-required") {
@@ -61864,7 +62547,18 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61864
62547
  message: "Accept the bump and push?",
61865
62548
  fallback: false
61866
62549
  })) {
61867
- 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
+ });
61868
62562
  try {
61869
62563
  stagedFiles = await stageProjectFilePushesV4({
61870
62564
  workspace,
@@ -61875,7 +62569,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61875
62569
  });
61876
62570
  result = await commit(true);
61877
62571
  } catch (retryError) {
61878
- retrying.stop();
62572
+ progress.stop();
61879
62573
  if (retryError instanceof NeoApiError) {
61880
62574
  reportPushRejection(retryError.body);
61881
62575
  process.exitCode = 1;
@@ -61883,9 +62577,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61883
62577
  }
61884
62578
  throw retryError;
61885
62579
  }
61886
- retrying.stop();
61887
- applyPushResult(workspace, result);
61888
- success("Push complete.");
62580
+ await finishCommitResponse(result, progress);
61889
62581
  return;
61890
62582
  }
61891
62583
  console.log("Push cancelled.");
@@ -61896,9 +62588,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
61896
62588
  process.exitCode = 1;
61897
62589
  return;
61898
62590
  }
61899
- pushing.stop();
61900
- applyPushResult(workspace, result);
61901
- success("Push complete.");
62591
+ await finishCommitResponse(result, progress);
61902
62592
  }
61903
62593
  async function createPendingProjectSourceBundleV4(workspace, status, staticValueSeeds) {
61904
62594
  const records2 = /* @__PURE__ */ new Map();
@@ -62045,52 +62735,298 @@ function reportPushRejection(body) {
62045
62735
  }
62046
62736
  console.error(`Push rejected (409): ${JSON.stringify(body)}`);
62047
62737
  }
62048
- function applyPushResult(workspace, result) {
62049
- if (!isObjectRecord2(result) || !Array.isArray(result.changedRecords)) {
62050
- 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
+ );
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);
62051
62780
  }
62781
+ const previousRecords = workspace.state.records;
62782
+ const nextRecords = foldChangedRecords(
62783
+ previousRecords,
62784
+ result.changedRecords
62785
+ );
62786
+ const changedKeys = /* @__PURE__ */ new Set();
62052
62787
  for (const changed of result.changedRecords) {
62053
- if (!isObjectRecord2(changed)) {
62054
- 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
+ );
62861
+ }
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;
62055
62903
  }
62056
- const recordKind = changed.recordKind;
62057
- const recordId = changed.recordId;
62058
- if (typeof recordKind !== "string" || typeof recordId !== "string") {
62059
- throw new Error("Changed record is missing recordKind/recordId.");
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;
62060
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);
62061
62977
  const key = recordStateKey(recordKind, recordId);
62978
+ if (!isObjectRecord2(changed)) {
62979
+ throw new Error(`Changed record "${key}" must be an object.`);
62980
+ }
62062
62981
  if (changed.deleted === true) {
62063
- delete workspace.state.records[key];
62982
+ delete records2[key];
62064
62983
  continue;
62065
62984
  }
62066
62985
  const snapshot = changed.snapshot;
62067
- 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") {
62068
62992
  throw new Error(
62069
62993
  `Changed record "${key}" response is missing its snapshot content hash.`
62070
62994
  );
62071
62995
  }
62072
- workspace.state.records[key] = {
62996
+ records2[key] = {
62073
62997
  recordKind,
62074
62998
  recordId,
62075
62999
  contentHash: snapshot.contentHash,
62076
63000
  data: changed.data,
62077
- file: workspace.state.records[key]?.file
63001
+ file: records2[key]?.file,
63002
+ projectBinary: records2[key]?.projectBinary
62078
63003
  };
62079
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
+ );
62080
63015
  rewriteFilesFromState(workspace);
62081
63016
  writeWorkspaceState(workspace.root, workspace.state);
62082
63017
  }
62083
- function rewriteFilesFromState(workspace) {
62084
- const records2 = /* @__PURE__ */ new Map();
62085
- for (const [key, recordState] of Object.entries(workspace.state.records)) {
62086
- records2.set(key, {
63018
+ function rewriteFilesFromState(workspace, records2 = new Map(
63019
+ Object.entries(workspace.state.records).map(([key, recordState]) => [
63020
+ key,
63021
+ {
62087
63022
  recordKind: recordState.recordKind,
62088
63023
  recordId: recordState.recordId,
62089
63024
  contentHash: recordState.contentHash,
62090
63025
  deleted: false,
62091
63026
  data: recordState.data
62092
- });
62093
- }
63027
+ }
63028
+ ])
63029
+ )) {
62094
63030
  const result = emitProjectDocumentFilesV4(records2);
62095
63031
  writeProjectSourceAnalysisCacheV4(workspace.root, result.analysis);
62096
63032
  const emittedPaths = new Set(result.files.map((file) => file.path));
@@ -62571,7 +63507,7 @@ function compileMigrationAction(schema, migrationData) {
62571
63507
  migrationContext: true
62572
63508
  });
62573
63509
  }
62574
- var compileNSAction2, compileNSFunction2, compileNSGetter2, compileNSSetter2, createProjectVersionIntent2, isKnownProjectVersionIntentType2;
63510
+ var compileNSAction2, compileNSFunction2, compileNSGetter2, compileNSSetter2, createProjectVersionIntent2, isKnownProjectVersionIntentType2, ProjectTransactionInterruptedError, ProjectTransactionFailedError, PROJECT_TRANSACTION_POLL_DELAYS_MS;
62575
63511
  var init_push = __esm({
62576
63512
  "src/commands/push.ts"() {
62577
63513
  "use strict";
@@ -62588,8 +63524,38 @@ var init_push = __esm({
62588
63524
  init_source_diagnostics();
62589
63525
  init_projection();
62590
63526
  init_project_file_push();
63527
+ init_project_manifest();
63528
+ init_merge();
62591
63529
  ({ compileNSAction: compileNSAction2, compileNSFunction: compileNSFunction2, compileNSGetter: compileNSGetter2, compileNSSetter: compileNSSetter2 } = compiler_adapter_exports);
62592
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
+ ];
62593
63559
  }
62594
63560
  });
62595
63561
 
@@ -63329,7 +64295,7 @@ ${h("Start")}
63329
64295
  whoami ${d("[--api <url>]")}
63330
64296
 
63331
64297
  ${h("Working copy")}
63332
- 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]")}
63333
64299
  status ${d("[--json]")} diff ${d("[--json]")}
63334
64300
  dev ${d("[--push]")} resolve ${d("[--mine|--theirs]")}
63335
64301
  doctor ${d("[--json]")} ${d("validate format/compiler/editor/source/file contracts")}
@@ -63379,16 +64345,18 @@ and tracked project binary from the authoritative server records.
63379
64345
  return `${h("neo push")} \u2014 commit the working copy to the server
63380
64346
 
63381
64347
  ${h("Usage")}
63382
- neo push ${d("[--dry-run] [--summary <text>] [--accept-bump]")}
64348
+ neo push ${d("[--dry-run] [--summary <text>] [--accept-bump] [--json]")}
63383
64349
 
63384
64350
  ${h("Flags")}
63385
64351
  --dry-run ${d("Preview the change set; commit nothing.")}
63386
64352
  --summary <text> ${d("Attach a summary message to the transaction.")}
63387
64353
  --accept-bump ${d("Accept a server-required version bump instead of aborting.")}
64354
+ --json ${d("Emit machine-readable plan and transaction progress events.")}
63388
64355
 
63389
- Push sends the ${h("entire")} working copy as one atomic compare-and-swap
63390
- transaction. Run ${h("neo status")} / ${h("neo diff")} first \u2014 local-ahead changes
63391
- 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.
63392
64360
  `;
63393
64361
  }
63394
64362
  if (command === "script") {
@@ -63880,7 +64848,8 @@ async function main() {
63880
64848
  await runPush2(workspace, {
63881
64849
  dryRun: boolFlag(args, "dry-run"),
63882
64850
  summary: stringFlag(args, "summary"),
63883
- acceptBump: boolFlag(args, "accept-bump")
64851
+ acceptBump: boolFlag(args, "accept-bump"),
64852
+ json: boolFlag(args, "json")
63884
64853
  });
63885
64854
  return;
63886
64855
  }