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