@neocompose/cli 0.59.0 → 0.59.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/dist/neo.mjs CHANGED
@@ -31503,9 +31503,8 @@ var init_project_source_construction_quick_fixes = __esm({
31503
31503
  });
31504
31504
 
31505
31505
  // ../packages/neoscript-language/src/project-source-manifest.ts
31506
- function compileNeoProjectSources(inputs, options = {}) {
31507
- const analysis = analyzeNeoProjectSources(inputs, options.parsedDocuments);
31508
- options.onDocuments?.(analysis.documents);
31506
+ function compileNeoProjectSources(inputs) {
31507
+ const analysis = analyzeNeoProjectSources(inputs);
31509
31508
  const schemaClasses = [];
31510
31509
  const schemaInterfaces = [];
31511
31510
  const schemaEnums = [];
@@ -87808,6 +87807,15 @@ var init_project_document_read = __esm({
87808
87807
  });
87809
87808
 
87810
87809
  // ../src/models/search/universal-search-projection.ts
87810
+ function valueSearchClassName(name) {
87811
+ const trimmed = typeof name === "string" ? nonEmptyTrimmed(name) : void 0;
87812
+ return trimmed === void 0 ? null : truncateUtf8(trimmed, UNIVERSAL_SEARCH_MAX_SUBTITLE_BYTES);
87813
+ }
87814
+ function nonEmptyTrimmed(value) {
87815
+ if (value === void 0 || value === null) return void 0;
87816
+ const trimmed = value.trim();
87817
+ return trimmed.length === 0 ? void 0 : trimmed;
87818
+ }
87811
87819
  function truncateUtf8(value, maxBytes) {
87812
87820
  if (value === void 0) return "";
87813
87821
  if (!Number.isInteger(maxBytes) || maxBytes < 0) {
@@ -87833,11 +87841,12 @@ function isAscii(value) {
87833
87841
  }
87834
87842
  return true;
87835
87843
  }
87836
- var utf8Encoder;
87844
+ var UNIVERSAL_SEARCH_MAX_SUBTITLE_BYTES, utf8Encoder;
87837
87845
  var init_universal_search_projection = __esm({
87838
87846
  "../src/models/search/universal-search-projection.ts"() {
87839
87847
  "use strict";
87840
87848
  init_classes();
87849
+ UNIVERSAL_SEARCH_MAX_SUBTITLE_BYTES = 512;
87841
87850
  utf8Encoder = new TextEncoder();
87842
87851
  }
87843
87852
  });
@@ -87993,7 +88002,7 @@ function projectValueHeadPlacementGraph(args) {
87993
88002
  valueOwnerId: ownerId,
87994
88003
  valueRootMemberId: item.rootMemberId,
87995
88004
  valueClassId: classId,
87996
- valueSearchClassName: schemaClass2 !== void 0 && typeof schemaClass2.name === "string" ? truncateUtf8(schemaClass2.name, 512) : null,
88005
+ valueSearchClassName: valueSearchClassName(schemaClass2?.name),
87997
88006
  valueSearchPlacementPath: item.path,
87998
88007
  ...item.constructorParameterId === void 0 ? {} : { constructorParameterId: item.constructorParameterId },
87999
88008
  ...item.constructorSchemaKey === void 0 ? {} : { constructorSchemaKey: item.constructorSchemaKey }
@@ -88120,7 +88129,7 @@ function projectValueHeadPlacementGraph(args) {
88120
88129
  valueOwnerId: classId === null ? null : valueId,
88121
88130
  valueRootMemberId: null,
88122
88131
  valueClassId: classId,
88123
- valueSearchClassName: schemaClass2 !== void 0 && typeof schemaClass2.name === "string" ? truncateUtf8(schemaClass2.name, 512) : null,
88132
+ valueSearchClassName: valueSearchClassName(schemaClass2?.name),
88124
88133
  valueSearchPlacementPath: null
88125
88134
  });
88126
88135
  }
@@ -92743,7 +92752,7 @@ function planCommitPreparationLoadedClosureScope(args) {
92743
92752
  function animationValidationScope(memberIds) {
92744
92753
  return memberIds.size === 0 ? {} : { animationValidationMemberIds: [...memberIds].sort() };
92745
92754
  }
92746
- function withCommitPreparationInMemoryPlacement(records2) {
92755
+ function placeInMemoryRecords(records2, extractions) {
92747
92756
  const recordKeys = new Set(records2.map(commitPreparationRecordKey));
92748
92757
  const parentByChild = /* @__PURE__ */ new Map();
92749
92758
  const ambiguousParentsByChild = /* @__PURE__ */ new Map();
@@ -92781,7 +92790,7 @@ function withCommitPreparationInMemoryPlacement(records2) {
92781
92790
  }
92782
92791
  parentByChild.set(childKey, { ...parent, authority });
92783
92792
  };
92784
- for (const record6 of records2) {
92793
+ for (const [index, record6] of records2.entries()) {
92785
92794
  const sourceKey = commitPreparationRecordKey(record6);
92786
92795
  if (record6.parentRecordKind !== void 0 && record6.parentRecordKind !== null && record6.parentRecordId !== void 0 && record6.parentRecordId !== null) {
92787
92796
  rememberParent(
@@ -92806,7 +92815,7 @@ function withCommitPreparationInMemoryPlacement(records2) {
92806
92815
  );
92807
92816
  }
92808
92817
  }
92809
- for (const edge of extractProjectSnapshotDerivedEdges(record6).edges) {
92818
+ for (const edge of extractions[index].edges) {
92810
92819
  if (edge.edgeKind !== "containment-child" || edge.targetAddressing !== "record") {
92811
92820
  continue;
92812
92821
  }
@@ -92862,33 +92871,13 @@ function commitPreparationPackedRootIds(records2) {
92862
92871
  }
92863
92872
  return rootIdByChildId;
92864
92873
  }
92865
- function readCommitPreparationScopeInMemory(args) {
92866
- const records2 = withCommitPreparationInMemoryPlacement(args.records);
92867
- const recordsByKey = new Map(
92874
+ function createCommitPreparationInMemoryScopeReader(args) {
92875
+ const extractions = args.records.map(extractProjectSnapshotDerivedEdges);
92876
+ const records2 = placeInMemoryRecords(args.records, extractions);
92877
+ const recordByKey = new Map(
92868
92878
  records2.map((record6) => [commitPreparationRecordKey(record6), record6])
92869
92879
  );
92870
92880
  const packedRootIdByChildId = commitPreparationPackedRootIds(records2);
92871
- const exactStates = /* @__PURE__ */ new Map();
92872
- const packedRootKeyByExactKey = /* @__PURE__ */ new Map();
92873
- for (const record6 of args.request.exactRecords) {
92874
- const key = commitPreparationRecordKey(record6);
92875
- if (recordsByKey.has(key)) {
92876
- exactStates.set(key, "live");
92877
- continue;
92878
- }
92879
- const rootId = record6.recordKind === "value" ? packedRootIdByChildId.get(record6.recordId) : void 0;
92880
- if (rootId === void 0) {
92881
- exactStates.set(key, "absent");
92882
- continue;
92883
- }
92884
- const rootKey = commitPreparationRecordKey({
92885
- recordKind: "value",
92886
- recordId: rootId
92887
- });
92888
- exactStates.set(key, "live");
92889
- exactStates.set(rootKey, "live");
92890
- packedRootKeyByExactKey.set(key, rootKey);
92891
- }
92892
92881
  const keysByKind = /* @__PURE__ */ new Map();
92893
92882
  const sourceOnlyKeys = [];
92894
92883
  const valueKeysByMemberId = /* @__PURE__ */ new Map();
@@ -92959,63 +92948,113 @@ function readCommitPreparationScopeInMemory(args) {
92959
92948
  rememberChild(parentKey, sourceKey);
92960
92949
  }
92961
92950
  }
92962
- const rangeRecordKeys = /* @__PURE__ */ new Map();
92963
- for (const request of args.request.ranges) {
92964
- const matching = request.rangeKind === "record-kind" ? keysByKind.get(request.targetKey) ?? [] : request.rangeKind === "source-only" ? sourceOnlyKeys : request.rangeKind === "value-member" ? valueKeysByMemberId.get(request.targetKey) ?? [] : request.rangeKind === "value-class" ? valueKeysByClassId.get(request.targetKey) ?? [] : request.rangeKind === "container-members" ? valueKeysByContainerId.get(request.targetKey) ?? [] : request.rangeKind === "children" ? childKeysByParentKey.get(request.targetKey) ?? [] : request.rangeKind === "world-kind" ? valueKeysByWorldKind.get(request.targetKey) ?? [] : keysByKind.get("value") ?? [];
92965
- if (matching.length > request.limit) {
92966
- return {
92967
- kind: "fallback",
92968
- reason: {
92969
- code: "range-row-cap-exceeded",
92970
- requestKey: commitPreparationRangeRequestKey(request),
92971
- observed: matching.length,
92972
- limit: request.limit
92973
- }
92974
- };
92951
+ for (const index of [
92952
+ keysByKind,
92953
+ valueKeysByMemberId,
92954
+ valueKeysByClassId,
92955
+ valueKeysByContainerId,
92956
+ valueKeysByWorldKind,
92957
+ childKeysByParentKey
92958
+ ]) {
92959
+ for (const keys of index.values()) keys.sort();
92960
+ }
92961
+ sourceOnlyKeys.sort();
92962
+ const persistedByPostingKey = /* @__PURE__ */ new Map();
92963
+ let incompleteRecordKey;
92964
+ for (const [index, record6] of records2.entries()) {
92965
+ const extraction = extractions[index];
92966
+ if (!extraction.complete)
92967
+ incompleteRecordKey ??= commitPreparationRecordKey(record6);
92968
+ for (const edge of extraction.edges) {
92969
+ const postingKey = commitPreparationPostingRequestKey(edge);
92970
+ const edges = persistedByPostingKey.get(postingKey) ?? [];
92971
+ edges.push({
92972
+ recordKind: record6.recordKind,
92973
+ recordId: record6.recordId,
92974
+ ...edge
92975
+ });
92976
+ persistedByPostingKey.set(postingKey, edges);
92975
92977
  }
92976
- rangeRecordKeys.set(
92977
- commitPreparationRangeRequestKey(request),
92978
- [...matching].sort()
92979
- );
92980
92978
  }
92981
- const persisted = [];
92982
- for (const record6 of records2) {
92983
- const extraction = extractProjectSnapshotDerivedEdges(record6);
92984
- if (!extraction.complete && args.request.postings.some(
92985
- (request) => request.edgeKind === "constructor-aggregate"
92979
+ const read = (request, prepared) => {
92980
+ const exactStates = /* @__PURE__ */ new Map();
92981
+ const packedRootKeyByExactKey = /* @__PURE__ */ new Map();
92982
+ for (const record6 of request.exactRecords) {
92983
+ const key = commitPreparationRecordKey(record6);
92984
+ if (recordByKey.has(key)) {
92985
+ exactStates.set(key, "live");
92986
+ continue;
92987
+ }
92988
+ const rootId = record6.recordKind === "value" ? packedRootIdByChildId.get(record6.recordId) : void 0;
92989
+ if (rootId === void 0) {
92990
+ exactStates.set(key, "absent");
92991
+ continue;
92992
+ }
92993
+ const rootKey = commitPreparationRecordKey({
92994
+ recordKind: "value",
92995
+ recordId: rootId
92996
+ });
92997
+ exactStates.set(key, "live");
92998
+ exactStates.set(rootKey, "live");
92999
+ packedRootKeyByExactKey.set(key, rootKey);
93000
+ }
93001
+ const rangeRecordKeys = /* @__PURE__ */ new Map();
93002
+ for (const range2 of request.ranges) {
93003
+ const matching = range2.rangeKind === "record-kind" ? keysByKind.get(range2.targetKey) ?? [] : range2.rangeKind === "source-only" ? sourceOnlyKeys : range2.rangeKind === "value-member" ? valueKeysByMemberId.get(range2.targetKey) ?? [] : range2.rangeKind === "value-class" ? valueKeysByClassId.get(range2.targetKey) ?? [] : range2.rangeKind === "container-members" ? valueKeysByContainerId.get(range2.targetKey) ?? [] : range2.rangeKind === "children" ? childKeysByParentKey.get(range2.targetKey) ?? [] : range2.rangeKind === "world-kind" ? valueKeysByWorldKind.get(range2.targetKey) ?? [] : keysByKind.get("value") ?? [];
93004
+ if (matching.length > range2.limit) {
93005
+ return {
93006
+ kind: "fallback",
93007
+ reason: {
93008
+ code: "range-row-cap-exceeded",
93009
+ requestKey: commitPreparationRangeRequestKey(range2),
93010
+ observed: matching.length,
93011
+ limit: range2.limit
93012
+ }
93013
+ };
93014
+ }
93015
+ rangeRecordKeys.set(commitPreparationRangeRequestKey(range2), matching);
93016
+ }
93017
+ if (incompleteRecordKey !== void 0 && request.postings.some(
93018
+ (posting) => posting.edgeKind === "constructor-aggregate"
92986
93019
  )) {
92987
93020
  return {
92988
93021
  kind: "fallback",
92989
93022
  reason: {
92990
93023
  code: "derived-edge-extraction-incomplete",
92991
- recordKey: commitPreparationRecordKey(record6)
93024
+ recordKey: incompleteRecordKey
92992
93025
  }
92993
93026
  };
92994
93027
  }
92995
- for (const edge of extraction.edges) {
92996
- persisted.push({ ...record6, ...edge });
92997
- }
92998
- }
92999
- const overlaid = overlayCommitPreparationDerivedEdgePostings({
93000
- requests: args.request.postings,
93001
- persisted,
93002
- prepared: args.prepared
93003
- });
93004
- if (overlaid.kind === "fallback") return overlaid;
93005
- return {
93006
- kind: "complete",
93007
- value: {
93008
- exactStates,
93009
- packedRootKeyByExactKey,
93010
- rangeRecordKeys,
93011
- postingSourceKeys: new Map(
93012
- [...overlaid.value].map(([key, edges]) => [
93013
- key,
93014
- edges.map(commitPreparationRecordKey)
93015
- ])
93016
- )
93028
+ const persisted = [];
93029
+ for (const postingKey of new Set(
93030
+ request.postings.map(commitPreparationPostingRequestKey)
93031
+ )) {
93032
+ for (const edge of persistedByPostingKey.get(postingKey) ?? []) {
93033
+ persisted.push(edge);
93034
+ }
93017
93035
  }
93036
+ const overlaid = overlayCommitPreparationDerivedEdgePostings({
93037
+ requests: request.postings,
93038
+ persisted,
93039
+ prepared
93040
+ });
93041
+ if (overlaid.kind === "fallback") return overlaid;
93042
+ return {
93043
+ kind: "complete",
93044
+ value: {
93045
+ exactStates,
93046
+ packedRootKeyByExactKey,
93047
+ rangeRecordKeys,
93048
+ postingSourceKeys: new Map(
93049
+ [...overlaid.value].map(([key, edges]) => [
93050
+ key,
93051
+ edges.map(commitPreparationRecordKey)
93052
+ ])
93053
+ )
93054
+ }
93055
+ };
93018
93056
  };
93057
+ return { recordByKey, read };
93019
93058
  }
93020
93059
  function commitPreparationRecordKey(record6) {
93021
93060
  return `${record6.recordKind}:${record6.recordId}`;
@@ -117901,7 +117940,7 @@ function computeWorkspaceStatus(workspace, options) {
117901
117940
  "Format-4 status requires a pulled schema base; run `neo pull --reset`."
117902
117941
  );
117903
117942
  }
117904
- const analysis = options.readProjectAnalysisCache?.(workspace.root, compilationSources) ?? options.compileProjectAnalysis?.(workspace.root, compilationSources) ?? compileNeoProjectSources(compilationSources);
117943
+ const analysis = options.readProjectAnalysisCache?.(workspace.root, compilationSources) ?? compileNeoProjectSources(compilationSources);
117905
117944
  projectAnalysisV4 = analysis;
117906
117945
  for (const diagnostic of analysis.diagnostics) {
117907
117946
  const positioned = new SchemaSourceError(
@@ -120385,7 +120424,7 @@ function prepareAgainstPulledBase(args, base) {
120385
120424
  changes,
120386
120425
  timestamp: serverTimestamp
120387
120426
  });
120388
- const placedRecords = () => {
120427
+ const scopeReader = () => {
120389
120428
  const canonicalPlacementByValueId = projectValueHeadPlacementGraph({
120390
120429
  rootMembers: [
120391
120430
  { id: document.project.rootAssetsMemberId, label: "Assets" },
@@ -120400,8 +120439,9 @@ function prepareAgainstPulledBase(args, base) {
120400
120439
  classes: document.classes,
120401
120440
  values: document.values
120402
120441
  });
120403
- return withCommitPreparationInMemoryPlacement(
120404
- Object.values(args.workspace.state.records).map((record6) => {
120442
+ return createCommitPreparationInMemoryScopeReader({
120443
+ document,
120444
+ records: Object.values(args.workspace.state.records).map((record6) => {
120405
120445
  const recordKind = narrowRecordKind(
120406
120446
  record6.recordKind,
120407
120447
  `${record6.recordKind}:${record6.recordId}`
@@ -120417,7 +120457,7 @@ function prepareAgainstPulledBase(args, base) {
120417
120457
  }
120418
120458
  };
120419
120459
  })
120420
- );
120460
+ });
120421
120461
  };
120422
120462
  const preparationArgs = {
120423
120463
  document,
@@ -120457,7 +120497,7 @@ function prepareAgainstPulledBase(args, base) {
120457
120497
  }
120458
120498
  return prepareWithScopedSafetyValve({
120459
120499
  prepareScoped: () => {
120460
- const records2 = placedRecords();
120500
+ const reader = scopeReader();
120461
120501
  const initialScope = planCommitPreparationInitialScope({
120462
120502
  pass: COMMIT_PREPARATION_PASS_ORDER[0],
120463
120503
  document,
@@ -120467,12 +120507,7 @@ function prepareAgainstPulledBase(args, base) {
120467
120507
  postingLimit: COMMIT_PREPARATION_SCOPE_POSTING_ROW_LIMIT
120468
120508
  });
120469
120509
  if (initialScope.fullContentDimension) return null;
120470
- const scopeAnswer = readCommitPreparationScopeInMemory({
120471
- request: initialScope,
120472
- records: records2,
120473
- prepared: changes,
120474
- document
120475
- });
120510
+ const scopeAnswer = reader.read(initialScope, changes);
120476
120511
  if (scopeAnswer.kind === "fallback") return null;
120477
120512
  const selectedContentKeys = /* @__PURE__ */ new Set();
120478
120513
  const descendantRootKeys = /* @__PURE__ */ new Set();
@@ -120486,7 +120521,7 @@ function prepareAgainstPulledBase(args, base) {
120486
120521
  absentExactKeys
120487
120522
  );
120488
120523
  const initialClosed = closeInMemoryLoadedScope({
120489
- records: records2,
120524
+ reader,
120490
120525
  selected: selectedContentKeys,
120491
120526
  descendantRoots: descendantRootKeys,
120492
120527
  absentExactKeys,
@@ -120525,12 +120560,10 @@ function prepareAgainstPulledBase(args, base) {
120525
120560
  postingLimit: COMMIT_PREPARATION_SCOPE_POSTING_ROW_LIMIT
120526
120561
  });
120527
120562
  if (topUpRequest.fullContentDimension) return null;
120528
- const topUpAnswer = readCommitPreparationScopeInMemory({
120529
- request: topUpRequest,
120530
- records: records2,
120531
- prepared: step.value.preparedChanges,
120532
- document
120533
- });
120563
+ const topUpAnswer = reader.read(
120564
+ topUpRequest,
120565
+ step.value.preparedChanges
120566
+ );
120534
120567
  if (topUpAnswer.kind === "fallback") return null;
120535
120568
  absorbInMemoryScopeAnswer(
120536
120569
  selectedContentKeys,
@@ -120546,7 +120579,7 @@ function prepareAgainstPulledBase(args, base) {
120546
120579
  descendantRootKeys.add(`${change.recordKind}:${change.recordId}`);
120547
120580
  }
120548
120581
  if (!closeInMemoryLoadedScope({
120549
- records: records2,
120582
+ reader,
120550
120583
  selected: selectedContentKeys,
120551
120584
  descendantRoots: descendantRootKeys,
120552
120585
  absentExactKeys,
@@ -120608,14 +120641,14 @@ function absorbInMemoryScopeAnswer(selected, answer, descendantRoots, descendant
120608
120641
  function closeInMemoryLoadedScope(args) {
120609
120642
  const animationSchema = commitPreparationAnimationSchema(args.document);
120610
120643
  for (; ; ) {
120611
- const closureRecords = args.records.filter(
120612
- (record6) => args.selected.has(`${record6.recordKind}:${record6.recordId}`)
120613
- ).map((record6) => ({
120614
- ...record6,
120615
- includeDescendants: args.descendantRoots.has(
120616
- `${record6.recordKind}:${record6.recordId}`
120617
- )
120618
- }));
120644
+ const closureRecords = selectedRecords(args.reader, args.selected).map(
120645
+ (record6) => ({
120646
+ ...record6,
120647
+ includeDescendants: args.descendantRoots.has(
120648
+ `${record6.recordKind}:${record6.recordId}`
120649
+ )
120650
+ })
120651
+ );
120619
120652
  const request = planCommitPreparationLoadedClosureScope({
120620
120653
  pass: args.pass,
120621
120654
  records: closureRecords,
@@ -120624,12 +120657,7 @@ function closeInMemoryLoadedScope(args) {
120624
120657
  animationSchema,
120625
120658
  placementCreationScope: createdWorldPlacementIds(args.prepared) !== null
120626
120659
  });
120627
- const answer = readCommitPreparationScopeInMemory({
120628
- request,
120629
- records: args.records,
120630
- prepared: args.prepared,
120631
- document: args.document
120632
- });
120660
+ const answer = args.reader.read(request, args.prepared);
120633
120661
  if (answer.kind === "fallback") return false;
120634
120662
  const priorSelectedSize = args.selected.size;
120635
120663
  const priorDescendantSize = args.descendantRoots.size;
@@ -120643,20 +120671,26 @@ function closeInMemoryLoadedScope(args) {
120643
120671
  );
120644
120672
  if (args.selected.size === priorSelectedSize && args.descendantRoots.size === priorDescendantSize) {
120645
120673
  return selectedAncestorPathsIntact(
120646
- args.records,
120674
+ args.reader,
120647
120675
  args.selected,
120648
120676
  args.absentExactKeys
120649
120677
  );
120650
120678
  }
120651
120679
  }
120652
120680
  }
120653
- function selectedAncestorPathsIntact(records2, selected, absentExactKeys) {
120681
+ function selectedRecords(reader, selected) {
120682
+ const records2 = [];
120683
+ for (const key of selected) {
120684
+ const record6 = reader.recordByKey.get(key);
120685
+ if (record6 !== void 0) records2.push(record6);
120686
+ }
120687
+ return records2;
120688
+ }
120689
+ function selectedAncestorPathsIntact(reader, selected, absentExactKeys) {
120654
120690
  if (absentExactKeys.size === 0) return true;
120655
- const selectedRecords = records2.filter(
120656
- (record6) => selected.has(`${record6.recordKind}:${record6.recordId}`)
120657
- );
120658
- const packedRootIdByChildId = commitPreparationPackedRootIds(selectedRecords);
120659
- for (const record6 of selectedRecords) {
120691
+ const records2 = selectedRecords(reader, selected);
120692
+ const packedRootIdByChildId = commitPreparationPackedRootIds(records2);
120693
+ for (const record6 of records2) {
120660
120694
  if (record6.recordKind !== "value") continue;
120661
120695
  if (record6.parentRecordKind !== "value") continue;
120662
120696
  const parentId = record6.parentRecordId;
@@ -129001,6 +129035,34 @@ function retainAuthoredDefinitionGroupingV4(files, recordFiles) {
129001
129035
  };
129002
129036
  }
129003
129037
  function emitProjectDocumentFilesV4(records2, options = {}) {
129038
+ const emission = emitProjectDocumentSourcesV4(records2, options);
129039
+ const analysis = compileNeoProjectSources(
129040
+ emission.files.map((file) => {
129041
+ const kind = neoProjectSourceKind(file.path, file.content);
129042
+ if (kind === null || !isNeoProjectProductionSourceKind(kind)) {
129043
+ throw new Error(
129044
+ `Canonical projection emitted non-production source ${JSON.stringify(file.path)}.`
129045
+ );
129046
+ }
129047
+ return { uri: file.path, kind, text: file.content };
129048
+ })
129049
+ );
129050
+ const errors = analysis.diagnostics.filter(
129051
+ (diagnostic) => diagnostic.severity === "error" && // Pull must expose incomplete stored objects so source can repair them.
129052
+ // Status, tests, and push still reject these construction errors.
129053
+ diagnostic.code !== "unsettled-required-member" && !isPullRecoveryDiagnosticCode(diagnostic.code)
129054
+ );
129055
+ if (errors.length > 0) {
129056
+ throw new Error(
129057
+ `Canonical format-4 projection produced invalid source:
129058
+ ${errors.map(
129059
+ (diagnostic) => ` ${diagnostic.uri}:${diagnostic.range.start.line + 1}:${diagnostic.range.start.character + 1} ${diagnostic.message}`
129060
+ ).join("\n")}`
129061
+ );
129062
+ }
129063
+ return { ...emission, analysis };
129064
+ }
129065
+ function emitProjectDocumentSourcesV4(records2, options = {}) {
129004
129066
  const schemaRecords = [];
129005
129067
  for (const record6 of records2.values()) {
129006
129068
  if (record6.deleted || !isSchemaRecordKindV4(record6.recordKind)) continue;
@@ -129129,34 +129191,9 @@ function emitProjectDocumentFilesV4(records2, options = {}) {
129129
129191
  STATIC_MEMBER_OWNERSHIP_RECOVERY_SOURCE
129130
129192
  );
129131
129193
  }
129132
- const analysis = compileNeoProjectSources(
129133
- files.map((file) => {
129134
- const kind = neoProjectSourceKind(file.path, file.content);
129135
- if (kind === null || !isNeoProjectProductionSourceKind(kind)) {
129136
- throw new Error(
129137
- `Canonical projection emitted non-production source ${JSON.stringify(file.path)}.`
129138
- );
129139
- }
129140
- return { uri: file.path, kind, text: file.content };
129141
- })
129142
- );
129143
- const errors = analysis.diagnostics.filter(
129144
- (diagnostic) => diagnostic.severity === "error" && // Pull must expose incomplete stored objects so source can repair them.
129145
- // Status, tests, and push still reject these construction errors.
129146
- diagnostic.code !== "unsettled-required-member" && !isPullRecoveryDiagnosticCode(diagnostic.code)
129147
- );
129148
- if (errors.length > 0) {
129149
- throw new Error(
129150
- `Canonical format-4 projection produced invalid source:
129151
- ${errors.map(
129152
- (diagnostic) => ` ${diagnostic.uri}:${diagnostic.range.start.line + 1}:${diagnostic.range.start.character + 1} ${diagnostic.message}`
129153
- ).join("\n")}`
129154
- );
129155
- }
129156
129194
  return {
129157
129195
  files,
129158
129196
  recordFiles,
129159
- analysis,
129160
129197
  materializedConstructors,
129161
129198
  materializedConstructorOverrideKeys,
129162
129199
  formAlternates: source.formAlternates.map((alternate) => ({
@@ -129278,98 +129315,6 @@ function writeProjectSourceAnalysisCacheV4(root, analysis, sources) {
129278
129315
  );
129279
129316
  renameSync5(temporary, buildFile);
129280
129317
  }
129281
- function compileProjectSourceAnalysisWithBuildCacheV4(root, sources) {
129282
- const parsedDocuments = readProjectSourceDocumentBuildCacheV1(root, sources);
129283
- let documents;
129284
- try {
129285
- const analysis = compileNeoProjectSources(sources, {
129286
- parsedDocuments,
129287
- onDocuments: (result) => {
129288
- documents = result;
129289
- }
129290
- });
129291
- if (documents !== void 0) {
129292
- writeProjectSourceDocumentBuildCacheV1(root, sources, documents);
129293
- }
129294
- return analysis;
129295
- } catch (error) {
129296
- if (parsedDocuments.size === 0) throw error;
129297
- documents = void 0;
129298
- const analysis = compileNeoProjectSources(sources, {
129299
- onDocuments: (result) => {
129300
- documents = result;
129301
- }
129302
- });
129303
- if (documents !== void 0) {
129304
- writeProjectSourceDocumentBuildCacheV1(root, sources, documents);
129305
- }
129306
- return analysis;
129307
- }
129308
- }
129309
- function readProjectSourceDocumentBuildCacheV1(root, sources) {
129310
- try {
129311
- const parsed = JSON.parse(
129312
- readFileSync6(
129313
- join5(root, PROJECT_SOURCE_DOCUMENT_BUILD_CACHE_PATH),
129314
- "utf8"
129315
- )
129316
- );
129317
- if (parsed === null || typeof parsed !== "object") return /* @__PURE__ */ new Map();
129318
- const cache = parsed;
129319
- if (cache.revision !== PROJECT_SOURCE_DOCUMENT_BUILD_CACHE_REVISION || cache.compilerRevision !== NEOSCRIPT_COMPILER_REVISION || cache.files === null || typeof cache.files !== "object" || Array.isArray(cache.files)) {
129320
- return /* @__PURE__ */ new Map();
129321
- }
129322
- const files = cache.files;
129323
- const documents = /* @__PURE__ */ new Map();
129324
- for (const source of sources) {
129325
- const cached = files[source.uri];
129326
- if (cached?.fingerprint !== projectSourceFileFingerprint(source) || !isCachedProjectSourceDocument(cached.document, source)) {
129327
- continue;
129328
- }
129329
- documents.set(source.uri, cached.document);
129330
- }
129331
- return documents;
129332
- } catch {
129333
- return /* @__PURE__ */ new Map();
129334
- }
129335
- }
129336
- function writeProjectSourceDocumentBuildCacheV1(root, sources, documents) {
129337
- const file = join5(root, PROJECT_SOURCE_DOCUMENT_BUILD_CACHE_PATH);
129338
- mkdirSync6(dirname6(file), { recursive: true });
129339
- const temporary = `${file}.${process.pid}.tmp`;
129340
- writeFileSync6(
129341
- temporary,
129342
- `${JSON.stringify({
129343
- revision: PROJECT_SOURCE_DOCUMENT_BUILD_CACHE_REVISION,
129344
- compilerRevision: NEOSCRIPT_COMPILER_REVISION,
129345
- files: Object.fromEntries(
129346
- sources.flatMap((source) => {
129347
- const document = documents.get(source.uri);
129348
- return document === void 0 ? [] : [
129349
- [
129350
- source.uri,
129351
- {
129352
- fingerprint: projectSourceFileFingerprint(source),
129353
- document
129354
- }
129355
- ]
129356
- ];
129357
- })
129358
- )
129359
- })}
129360
- `,
129361
- "utf8"
129362
- );
129363
- renameSync5(temporary, file);
129364
- }
129365
- function projectSourceFileFingerprint(source) {
129366
- return createHash6("sha256").update(String(PROJECT_SOURCE_DOCUMENT_BUILD_CACHE_REVISION)).update("\0").update(String(NEOSCRIPT_COMPILER_REVISION)).update("\0").update(source.kind).update("\0").update(source.text).digest("hex");
129367
- }
129368
- function isCachedProjectSourceDocument(value, source) {
129369
- if (value === null || typeof value !== "object") return false;
129370
- const document = value;
129371
- return document.kind === source.kind && document.sourceText === source.text && Array.isArray(document.declarations) && Array.isArray(document.diagnostics);
129372
- }
129373
129318
  function projectSourceFingerprint(sources) {
129374
129319
  const hash = createHash6("sha256");
129375
129320
  hash.update(String(PROJECT_SOURCE_BUILD_CACHE_REVISION));
@@ -129385,7 +129330,7 @@ function projectSourceFingerprint(sources) {
129385
129330
  }
129386
129331
  return hash.digest("hex");
129387
129332
  }
129388
- var PROJECT_SOURCE_BUILD_CACHE_PATH, PROJECT_SOURCE_BUILD_CACHE_REVISION, PROJECT_SOURCE_DOCUMENT_BUILD_CACHE_PATH, PROJECT_SOURCE_DOCUMENT_BUILD_CACHE_REVISION;
129333
+ var PROJECT_SOURCE_BUILD_CACHE_PATH, PROJECT_SOURCE_BUILD_CACHE_REVISION;
129389
129334
  var init_project_document_cache = __esm({
129390
129335
  "src/project-source/project-document-cache.ts"() {
129391
129336
  "use strict";
@@ -129393,8 +129338,6 @@ var init_project_document_cache = __esm({
129393
129338
  init_project_documents();
129394
129339
  PROJECT_SOURCE_BUILD_CACHE_PATH = ".neo/build/project-source-analysis-v4.json";
129395
129340
  PROJECT_SOURCE_BUILD_CACHE_REVISION = 2;
129396
- PROJECT_SOURCE_DOCUMENT_BUILD_CACHE_PATH = ".neo/build/project-source-documents-v1.json";
129397
- PROJECT_SOURCE_DOCUMENT_BUILD_CACHE_REVISION = 1;
129398
129341
  }
129399
129342
  });
129400
129343
 
@@ -129984,7 +129927,6 @@ function computeWorkspaceStatus2(workspace, options = {}) {
129984
129927
  virtualSourceFiles,
129985
129928
  writeProjectAnalysisCache: options.writeProjectAnalysisCache ?? writeProjectSourceAnalysisCacheV4,
129986
129929
  readProjectAnalysisCache: options.readProjectAnalysisCache ?? readProjectSourceAnalysisBuildCacheV4,
129987
- compileProjectAnalysis: options.compileProjectAnalysis ?? compileProjectSourceAnalysisWithBuildCacheV4,
129988
129930
  inspectProjectBinaries: inspectProjectBinaryStatusV4,
129989
129931
  lowerSupplementalRecords: (state, analysis, trustedPendingFiles) => lowerSupplementalProjectSourcesV4(workspace.root, state, analysis, {
129990
129932
  ...trustedPendingFiles === void 0 ? {} : { trustedPendingFiles }
@@ -130292,6 +130234,60 @@ var init_schema_commit_transport = __esm({
130292
130234
  }
130293
130235
  });
130294
130236
 
130237
+ // src/off-thread-fetch.ts
130238
+ import { Worker as Worker2 } from "node:worker_threads";
130239
+ async function fetchOffThread(url, headers, signal) {
130240
+ signal?.throwIfAborted();
130241
+ const worker = new Worker2(REQUEST, {
130242
+ eval: true,
130243
+ execArgv: [],
130244
+ workerData: { url: url.href, headers }
130245
+ });
130246
+ let abort = () => void 0;
130247
+ try {
130248
+ const message = await new Promise((resolve5, reject) => {
130249
+ abort = () => reject(signal?.reason);
130250
+ signal?.addEventListener("abort", abort, { once: true });
130251
+ worker.once("message", resolve5);
130252
+ worker.once("error", reject);
130253
+ worker.once(
130254
+ "exit",
130255
+ (code) => reject(new Error(`The request worker exited with code ${code}.`))
130256
+ );
130257
+ });
130258
+ if (typeof message !== "object" || message === null)
130259
+ throw new Error("The request worker posted a non-object result.");
130260
+ if ("error" in message && typeof message.error === "string")
130261
+ throw new Error(
130262
+ message.error,
130263
+ "cause" in message && typeof message.cause === "string" ? { cause: message.cause } : void 0
130264
+ );
130265
+ if (!("ok" in message) || typeof message.ok !== "boolean")
130266
+ throw new Error('The request worker result is missing boolean "ok".');
130267
+ if (!("status" in message) || typeof message.status !== "number")
130268
+ throw new Error('The request worker result is missing numeric "status".');
130269
+ if (!("body" in message) || typeof message.body !== "string")
130270
+ throw new Error('The request worker result is missing string "body".');
130271
+ return { ok: message.ok, status: message.status, body: message.body };
130272
+ } finally {
130273
+ signal?.removeEventListener("abort", abort);
130274
+ void worker.terminate();
130275
+ }
130276
+ }
130277
+ var REQUEST;
130278
+ var init_off_thread_fetch = __esm({
130279
+ "src/off-thread-fetch.ts"() {
130280
+ "use strict";
130281
+ REQUEST = `
130282
+ const { workerData, parentPort } = require("node:worker_threads");
130283
+ fetch(workerData.url, { headers: workerData.headers, redirect: "error" }).then(
130284
+ async (response) => parentPort.postMessage({ ok: response.ok, status: response.status, body: await response.text() }),
130285
+ (error) => parentPort.postMessage({ error: String(error), cause: error?.cause === undefined ? null : String(error.cause) }),
130286
+ );
130287
+ `;
130288
+ }
130289
+ });
130290
+
130295
130291
  // ../convex/_generated/api.js
130296
130292
  var api_exports = {};
130297
130293
  __export(api_exports, {
@@ -130325,20 +130321,22 @@ async function mintConvexJwt(apiBaseUrl, signal, convexSiteUrl) {
130325
130321
  }
130326
130322
  const pinnedSite = loadTrustedConvexSite(apiBaseUrl);
130327
130323
  const direct = convexSiteUrl !== void 0 && pinnedSite === convexSiteUrl;
130328
- const response = await fetch(
130324
+ const response = await fetchOffThread(
130329
130325
  new URL("/api/auth/convex/token", direct ? convexSiteUrl : apiBaseUrl),
130330
- {
130331
- headers: { Authorization: `Bearer ${sessionToken}` },
130332
- redirect: "error",
130333
- signal
130334
- }
130326
+ { Authorization: `Bearer ${sessionToken}` },
130327
+ signal
130335
130328
  );
130336
- const body = await response.json();
130337
130329
  if (!response.ok) {
130338
130330
  throw new Error(
130339
- `Convex JWT mint failed with status ${response.status}: ${JSON.stringify(body).slice(0, 200)}`
130331
+ `Convex JWT mint failed with status ${response.status}: ${response.body.slice(0, 200)}`
130340
130332
  );
130341
130333
  }
130334
+ let body;
130335
+ try {
130336
+ body = JSON.parse(response.body);
130337
+ } catch {
130338
+ throw new Error("Convex token response is not JSON.");
130339
+ }
130342
130340
  if (typeof body !== "object" || body === null || !("token" in body) || typeof body.token !== "string")
130343
130341
  throw new Error('Convex token response is missing a string "token".');
130344
130342
  const payload = convexJwtPayload(body.token);
@@ -130520,11 +130518,45 @@ function convexHttpActionUrl(convexUrl) {
130520
130518
  async function createConvexPushConnection(workspace) {
130521
130519
  const convexUrl = await resolveConvexUrl(workspace);
130522
130520
  const siteUrl = convexHttpActionUrl(convexUrl);
130521
+ const lifetime = new AbortController();
130523
130522
  const tokenCache = new ConvexJwtCache(
130524
130523
  workspace.config.apiBaseUrl,
130525
- void 0,
130524
+ lifetime.signal,
130526
130525
  siteUrl
130527
130526
  );
130527
+ void tokenCache.getToken().catch(() => void 0);
130528
+ let subscriber;
130529
+ let authFailed = false;
130530
+ const fail2 = (error) => {
130531
+ if (lifetime.signal.aborted) return;
130532
+ if (subscriber === void 0) authFailed = true;
130533
+ else subscriber(error);
130534
+ };
130535
+ const authenticate = (client) => client.setAuth(
130536
+ async ({ forceRefreshToken }) => {
130537
+ try {
130538
+ return await tokenCache.getToken(forceRefreshToken);
130539
+ } catch (error) {
130540
+ fail2(error instanceof Error ? error : new Error(String(error)));
130541
+ return null;
130542
+ }
130543
+ },
130544
+ (authenticated) => {
130545
+ if (!authenticated)
130546
+ fail2(new Error("Convex rejected the session authentication."));
130547
+ }
130548
+ );
130549
+ let socket;
130550
+ const connect = () => {
130551
+ if (socket !== void 0) return socket;
130552
+ socket = new ConvexClient(convexUrl, {
130553
+ initialAuthTokenReuse: true,
130554
+ logger: false
130555
+ });
130556
+ authenticate(socket);
130557
+ return socket;
130558
+ };
130559
+ let closing;
130528
130560
  return {
130529
130561
  async submit(body, force) {
130530
130562
  const json = JSON.stringify(body);
@@ -130554,6 +130586,7 @@ async function createConvexPushConnection(workspace) {
130554
130586
  limitBytes: SCHEMA_COMMIT_HTTP_MAX_BYTES
130555
130587
  }
130556
130588
  );
130589
+ connect();
130557
130590
  const client = new NeoApiClient(siteUrl, await tokenCache.getToken());
130558
130591
  const path = `${SCHEMA_COMMIT_HTTP_PATH}?${new URLSearchParams({ projectId: workspace.config.projectId, versionId: workspace.config.versionId })}`;
130559
130592
  const finish = startPerformanceSpan("commit-transport", {
@@ -130573,28 +130606,26 @@ async function createConvexPushConnection(workspace) {
130573
130606
  finish();
130574
130607
  }
130575
130608
  },
130576
- async subscribe(signal, onError) {
130577
- await tokenCache.getToken(false, signal);
130609
+ /** Routes the socket's authentication failures to `onError` until `signal` aborts. */
130610
+ subscribe(signal, onError) {
130578
130611
  signal.throwIfAborted();
130579
- const client = new ConvexClient(convexUrl, {
130580
- initialAuthTokenReuse: true,
130581
- logger: false
130582
- });
130583
- client.setAuth(
130584
- async ({ forceRefreshToken }) => {
130585
- try {
130586
- return await tokenCache.getToken(forceRefreshToken, signal);
130587
- } catch (error) {
130588
- onError(error instanceof Error ? error : new Error(String(error)));
130589
- return null;
130590
- }
130591
- },
130592
- (authenticated) => {
130593
- if (!authenticated && !signal.aborted)
130594
- onError(new Error("Convex rejected the session authentication."));
130595
- }
130596
- );
130612
+ subscriber = (error) => {
130613
+ if (!signal.aborted) onError(error);
130614
+ };
130615
+ const client = connect();
130616
+ if (authFailed) {
130617
+ authFailed = false;
130618
+ authenticate(client);
130619
+ }
130597
130620
  return client;
130621
+ },
130622
+ /** Idempotent; cancels an in-flight mint and closes the socket. */
130623
+ close() {
130624
+ if (closing === void 0) {
130625
+ lifetime.abort();
130626
+ closing = socket?.close() ?? Promise.resolve();
130627
+ }
130628
+ return closing;
130598
130629
  }
130599
130630
  };
130600
130631
  }
@@ -130605,6 +130636,7 @@ var init_convex = __esm({
130605
130636
  init_http();
130606
130637
  init_schema_commit_transport();
130607
130638
  init_performance();
130639
+ init_off_thread_fetch();
130608
130640
  init_api();
130609
130641
  init_token_store();
130610
130642
  init_workspace();
@@ -131154,9 +131186,286 @@ var init_concurrency = __esm({
131154
131186
  }
131155
131187
  });
131156
131188
 
131189
+ // src/project-source/project-file-pull.ts
131190
+ import {
131191
+ existsSync as existsSync8,
131192
+ mkdtempSync,
131193
+ readFileSync as readFileSync11,
131194
+ rmSync as rmSync5,
131195
+ writeFileSync as writeFileSync8
131196
+ } from "node:fs";
131197
+ import { tmpdir } from "node:os";
131198
+ import { join as join9 } from "node:path";
131199
+ async function pullProjectBinariesV4(args) {
131200
+ const resolvePath = createWorkspaceSourcePathResolver(args.workspace.root);
131201
+ const plans = /* @__PURE__ */ new Map();
131202
+ const downloads = [];
131203
+ const localById = new Map(
131204
+ (args.localBinaries ?? []).map((binary) => [binary.fileId, binary])
131205
+ );
131206
+ const states = /* @__PURE__ */ new Map();
131207
+ const conflicts = [];
131208
+ let downloaded = 0;
131209
+ let conflicted = 0;
131210
+ for (const record6 of args.document.records.values()) {
131211
+ if (record6.deleted || record6.recordKind !== "project-file") continue;
131212
+ if (!isObjectRecord2(record6.data)) {
131213
+ throw new Error(`project-file:${record6.recordId} must be an object.`);
131214
+ }
131215
+ if (record6.data.status !== "uploaded") continue;
131216
+ const existing = plans.get(record6.recordId);
131217
+ if (existing !== void 0) {
131218
+ if (existing.contentHash !== record6.contentHash)
131219
+ throw new Error(
131220
+ `Project file ${record6.recordId} appears with conflicting content hashes in the pulled document.`
131221
+ );
131222
+ continue;
131223
+ }
131224
+ const key = `project-file:${record6.recordId}`;
131225
+ const previous = args.previousRecords[key];
131226
+ const localStatus = localById.get(record6.recordId);
131227
+ const declarationPresent = args.destructive === true || args.localBinaries === void 0 || previous === void 0 || localStatus !== void 0;
131228
+ const path = localStatus?.path ?? previous?.projectBinary?.path ?? canonicalProjectBinaryPathV42(record6.data);
131229
+ const absolute = resolvePath(path);
131230
+ const localDigest = existsSync8(absolute) ? sha256File(absolute) : null;
131231
+ const baseDigest = previous?.projectBinary?.sha256 ?? readSha256(previous?.data) ?? null;
131232
+ const remoteDigest = requiredSha256(
131233
+ record6.data,
131234
+ `project-file:${record6.recordId} metadata`
131235
+ );
131236
+ let action = planBinaryMergeV4({
131237
+ baseDigest,
131238
+ localDigest,
131239
+ remoteDigest,
131240
+ declarationPresent,
131241
+ hadLocalBytes: previous?.projectBinary !== void 0
131242
+ });
131243
+ if (args.destructive === true) {
131244
+ action = localDigest === remoteDigest ? "unchanged" : "download";
131245
+ }
131246
+ const plan = {
131247
+ key,
131248
+ fileId: record6.recordId,
131249
+ contentHash: record6.contentHash,
131250
+ data: record6.data,
131251
+ previous,
131252
+ path,
131253
+ baseDigest,
131254
+ remoteDigest,
131255
+ action
131256
+ };
131257
+ plans.set(record6.recordId, plan);
131258
+ if (action === "download" || action === "conflict") downloads.push(plan);
131259
+ }
131260
+ let stagingDirectory;
131261
+ try {
131262
+ const stagedPaths = /* @__PURE__ */ new Map();
131263
+ if (downloads.length > 0) {
131264
+ stagingDirectory = mkdtempSync(join9(tmpdir(), "neo-pull-files-"));
131265
+ const directory = stagingDirectory;
131266
+ const client = args.client ?? NeoApiClient.forBaseUrl(args.workspace.config.apiBaseUrl);
131267
+ await mapWithConcurrency(
131268
+ downloads,
131269
+ DOWNLOAD_CONCURRENCY,
131270
+ async (plan, index) => {
131271
+ const response = await download(
131272
+ plan.data,
131273
+ plan.fileId,
131274
+ client,
131275
+ args.workspace
131276
+ );
131277
+ if (response.sha256 !== plan.remoteDigest) {
131278
+ throw new Error(
131279
+ `Project file ${plan.fileId} content changed while pull was in progress (${plan.remoteDigest} -> ${response.sha256}). Pull again against a stable version head.`
131280
+ );
131281
+ }
131282
+ const stagedPath = join9(directory, String(index));
131283
+ writeFileSync8(stagedPath, response.bytes);
131284
+ stagedPaths.set(plan.fileId, stagedPath);
131285
+ }
131286
+ );
131287
+ }
131288
+ for (const plan of plans.values()) {
131289
+ const {
131290
+ key,
131291
+ fileId,
131292
+ data,
131293
+ previous,
131294
+ path,
131295
+ baseDigest,
131296
+ remoteDigest,
131297
+ action
131298
+ } = plan;
131299
+ const absolute = resolvePath(path);
131300
+ const stagedPath = stagedPaths.get(fileId);
131301
+ const remoteBytes = stagedPath === void 0 ? null : readFileSync11(stagedPath);
131302
+ if (action === "download") {
131303
+ if (remoteBytes === null) {
131304
+ throw new Error(
131305
+ `Project file ${fileId} download plan has no verified remote bytes.`
131306
+ );
131307
+ }
131308
+ writeVerifiedBinaryDownloadV4(absolute, remoteBytes, remoteDigest);
131309
+ downloaded += 1;
131310
+ states.set(key, binaryState(path, remoteDigest, data));
131311
+ continue;
131312
+ }
131313
+ if (action === "conflict") {
131314
+ if (remoteBytes === null) {
131315
+ throw new Error(
131316
+ `Project file ${fileId} conflict plan has no verified remote bytes.`
131317
+ );
131318
+ }
131319
+ const artifactPath = writeBinaryConflictArtifactV4(
131320
+ args.workspace.root,
131321
+ fileId,
131322
+ fileName(data),
131323
+ remoteBytes,
131324
+ remoteDigest
131325
+ );
131326
+ const state = binaryState(path, baseDigest, data);
131327
+ states.set(key, {
131328
+ ...state,
131329
+ conflict: {
131330
+ action: "conflict",
131331
+ remoteSha256: remoteDigest,
131332
+ artifactPath
131333
+ }
131334
+ });
131335
+ conflicts.push(path);
131336
+ conflicted += 1;
131337
+ continue;
131338
+ }
131339
+ if (action === "converged" || action === "unchanged") {
131340
+ states.set(key, binaryState(path, remoteDigest, data));
131341
+ removePreviousConflict(args.workspace.root, previous?.projectBinary);
131342
+ continue;
131343
+ }
131344
+ states.set(key, binaryState(path, baseDigest, data));
131345
+ }
131346
+ for (const [key, previous] of Object.entries(args.previousRecords)) {
131347
+ if (previous.recordKind !== "project-file") continue;
131348
+ if (args.document.records.has(key)) continue;
131349
+ const localStatus = localById.get(previous.recordId);
131350
+ const path = localStatus?.path ?? previous.projectBinary?.path ?? canonicalProjectBinaryPathV42(previous.data);
131351
+ const absolute = resolvePath(path);
131352
+ const localDigest = existsSync8(absolute) ? sha256File(absolute) : null;
131353
+ const baseDigest = previous.projectBinary?.sha256 ?? readSha256(previous.data) ?? null;
131354
+ const action = planBinaryMergeV4({
131355
+ baseDigest,
131356
+ localDigest,
131357
+ remoteDigest: null,
131358
+ declarationPresent: localStatus !== void 0,
131359
+ hadLocalBytes: previous.projectBinary !== void 0
131360
+ });
131361
+ if (action === "delete-modify") {
131362
+ const data = isObjectRecord2(previous.data) ? previous.data : {};
131363
+ states.set(key, {
131364
+ ...binaryState(path, baseDigest, data),
131365
+ conflict: {
131366
+ action: "delete-modify",
131367
+ remoteSha256: null
131368
+ }
131369
+ });
131370
+ conflicts.push(path);
131371
+ conflicted += 1;
131372
+ continue;
131373
+ }
131374
+ rmSync5(absolute, { force: true });
131375
+ removePreviousConflict(args.workspace.root, previous.projectBinary);
131376
+ }
131377
+ return { states, downloaded, conflicted, conflicts };
131378
+ } finally {
131379
+ if (stagingDirectory !== void 0)
131380
+ rmSync5(stagingDirectory, { recursive: true, force: true });
131381
+ }
131382
+ }
131383
+ async function download(data, fileId, client, workspace) {
131384
+ const version = typeof data.storageETag === "string" ? `?v=${encodeURIComponent(data.storageETag)}` : "";
131385
+ const path = `/api/projects/${workspace.config.projectId}/versions/${workspace.config.versionId}/files/${fileId}/content${version}`;
131386
+ const response = await client.getBytes(path);
131387
+ const computed = sha256Bytes(response.bytes);
131388
+ const metadataDigest = requiredSha256(
131389
+ data,
131390
+ `project-file:${fileId} metadata`
131391
+ );
131392
+ const headerDigest = normalizeSha256V4(
131393
+ response.contentSha256,
131394
+ `download response for project file ${fileId}`
131395
+ );
131396
+ if (headerDigest !== null && metadataDigest !== headerDigest) {
131397
+ throw new Error(
131398
+ `Project file ${fileId} metadata SHA-256 ${metadataDigest} did not match authenticated content response ${headerDigest}.`
131399
+ );
131400
+ }
131401
+ if (computed !== metadataDigest) {
131402
+ throw new Error(
131403
+ `Downloaded project file ${fileId} SHA-256 ${computed} did not match expected ${metadataDigest}.`
131404
+ );
131405
+ }
131406
+ if (typeof data.byteLength === "number" && response.bytes.byteLength !== data.byteLength) {
131407
+ throw new Error(
131408
+ `Downloaded project file ${fileId} has ${response.bytes.byteLength} bytes; metadata requires ${data.byteLength}.`
131409
+ );
131410
+ }
131411
+ if (typeof data.mimeType === "string" && response.contentType !== null && response.contentType !== data.mimeType) {
131412
+ throw new Error(
131413
+ `Downloaded project file ${fileId} content type ${response.contentType} did not match metadata ${data.mimeType}.`
131414
+ );
131415
+ }
131416
+ return { bytes: response.bytes, sha256: computed };
131417
+ }
131418
+ function committedProjectBinaryStateV4(previous, data) {
131419
+ return isObjectRecord2(data) ? binaryState(previous.path, readSha256(data), data) : previous;
131420
+ }
131421
+ function binaryState(path, sha256, data) {
131422
+ return {
131423
+ path,
131424
+ sha256,
131425
+ byteLength: typeof data.byteLength === "number" ? data.byteLength : 0,
131426
+ mimeType: typeof data.mimeType === "string" ? data.mimeType : "application/octet-stream"
131427
+ };
131428
+ }
131429
+ function readSha256(data) {
131430
+ if (!isObjectRecord2(data)) return null;
131431
+ return normalizeSha256V4(data.contentSha256, "project file metadata");
131432
+ }
131433
+ function requiredSha256(data, source) {
131434
+ if (!isObjectRecord2(data)) {
131435
+ throw new Error(`${source} must be an object.`);
131436
+ }
131437
+ const digest = normalizeSha256V4(data.contentSha256, source);
131438
+ if (digest === null) {
131439
+ throw new Error(`${source} is missing required contentSha256.`);
131440
+ }
131441
+ return digest;
131442
+ }
131443
+ function fileName(data) {
131444
+ if (typeof data.name !== "string") {
131445
+ throw new Error("Project file metadata has no filename.");
131446
+ }
131447
+ return data.name;
131448
+ }
131449
+ function removePreviousConflict(root, state) {
131450
+ if (state?.conflict?.artifactPath === void 0) return;
131451
+ rmSync5(join9(root, state.conflict.artifactPath), { force: true });
131452
+ }
131453
+ var DOWNLOAD_CONCURRENCY;
131454
+ var init_project_file_pull = __esm({
131455
+ "src/project-source/project-file-pull.ts"() {
131456
+ "use strict";
131457
+ init_workspace_source_path();
131458
+ init_concurrency();
131459
+ init_http();
131460
+ init_projection();
131461
+ init_project_files();
131462
+ DOWNLOAD_CONCURRENCY = 8;
131463
+ }
131464
+ });
131465
+
131157
131466
  // src/project-source/project-file-push.ts
131158
- import { basename as basename2, join as join9 } from "node:path";
131159
- import { readFileSync as readFileSync11 } from "node:fs";
131467
+ import { basename as basename2, join as join10 } from "node:path";
131468
+ import { readFileSync as readFileSync12 } from "node:fs";
131160
131469
  import { randomUUID as randomUUID4 } from "node:crypto";
131161
131470
  async function cleanupRejectedProjectFilePushesV4(args) {
131162
131471
  if (args.stagedUploadSessionId === null) return;
@@ -131212,8 +131521,8 @@ function prepareProjectFilePushesV4(args) {
131212
131521
  `Project file ${recordId} has upload bytes but its source change has no record data.`
131213
131522
  );
131214
131523
  }
131215
- const absolute = join9(args.workspace.root, binary.path);
131216
- const bytes = new Uint8Array(readFileSync11(absolute));
131524
+ const absolute = join10(args.workspace.root, binary.path);
131525
+ const bytes = new Uint8Array(readFileSync12(absolute));
131217
131526
  const digest = sha256Bytes(bytes);
131218
131527
  if (binary.localSha256 !== null && digest !== binary.localSha256) {
131219
131528
  throw new Error(
@@ -131829,8 +132138,8 @@ var init_project_version_validation = __esm({
131829
132138
  });
131830
132139
 
131831
132140
  // src/commands/push-body-diagnostics.ts
131832
- import { existsSync as existsSync8, readFileSync as readFileSync12 } from "node:fs";
131833
- import { join as join10 } from "node:path";
132141
+ import { existsSync as existsSync9, readFileSync as readFileSync13 } from "node:fs";
132142
+ import { join as join11 } from "node:path";
131834
132143
  function createNeoScriptBodySourceLocator(workspace, status) {
131835
132144
  const textByFile = /* @__PURE__ */ new Map();
131836
132145
  return {
@@ -131852,8 +132161,8 @@ function createNeoScriptBodySourceLocator(workspace, status) {
131852
132161
  textOf(file) {
131853
132162
  const cached = textByFile.get(file);
131854
132163
  if (cached !== void 0) return cached;
131855
- const path = join10(workspace.root, file);
131856
- const text = existsSync8(path) ? readFileSync12(path, "utf8") : null;
132164
+ const path = join11(workspace.root, file);
132165
+ const text = existsSync9(path) ? readFileSync13(path, "utf8") : null;
131857
132166
  textByFile.set(file, text);
131858
132167
  return text;
131859
132168
  }
@@ -132011,13 +132320,13 @@ __export(push_exports, {
132011
132320
  import { createHash as createHash10, randomUUID as randomUUID5 } from "node:crypto";
132012
132321
  import {
132013
132322
  mkdirSync as mkdirSync8,
132014
- writeFileSync as writeFileSync8,
132015
- rmSync as rmSync5,
132016
- existsSync as existsSync9,
132017
- readFileSync as readFileSync13,
132323
+ writeFileSync as writeFileSync9,
132324
+ rmSync as rmSync6,
132325
+ existsSync as existsSync10,
132326
+ readFileSync as readFileSync14,
132018
132327
  statSync
132019
132328
  } from "node:fs";
132020
- import { join as join11, relative as relative6, sep as sep6 } from "node:path";
132329
+ import { join as join12, relative as relative6, sep as sep6 } from "node:path";
132021
132330
  function assignPendingIds(changes, authoredValueSeeds, reconstructed3) {
132022
132331
  const assigned = /* @__PURE__ */ new Map();
132023
132332
  const assign = (pendingId2) => {
@@ -132250,9 +132559,9 @@ async function commitProjectVersionChanges(args) {
132250
132559
  errorCode: null,
132251
132560
  errorMessage: null
132252
132561
  });
132562
+ const connection = createConvexPushConnection(workspace);
132253
132563
  try {
132254
- const connection = await createConvexPushConnection(workspace);
132255
- const response = await connection.submit(body, false);
132564
+ const response = await (await connection).submit(body, false);
132256
132565
  const accepted = readAcceptedProjectVersionCommitResponse(response);
132257
132566
  if (accepted === null) {
132258
132567
  if (!isObjectRecord2(response) || typeof response.kind !== "string") {
@@ -132269,7 +132578,7 @@ async function commitProjectVersionChanges(args) {
132269
132578
  workspace,
132270
132579
  accepted,
132271
132580
  reporter,
132272
- connection
132581
+ connection: await connection
132273
132582
  });
132274
132583
  return {
132275
132584
  kind: completed.totalChangeCount === 0 ? "noop" : "committed",
@@ -132277,8 +132586,15 @@ async function commitProjectVersionChanges(args) {
132277
132586
  };
132278
132587
  } finally {
132279
132588
  reporter.stop();
132589
+ await closePushConnection(connection);
132280
132590
  }
132281
132591
  }
132592
+ async function closePushConnection(connection) {
132593
+ await connection.then(
132594
+ (opened) => opened.close(),
132595
+ () => void 0
132596
+ );
132597
+ }
132282
132598
  function clientPreparedCommitBody(args) {
132283
132599
  return {
132284
132600
  operation: args.operation,
@@ -132302,7 +132618,7 @@ function readAcceptedProjectVersionCommitResponse(value) {
132302
132618
  throw new Error("Accepted project transaction is missing transactionId.");
132303
132619
  }
132304
132620
  const rawStatus = value.commitStatus ?? value.status;
132305
- if (rawStatus !== "preparing" && rawStatus !== "waiting" && rawStatus !== "queued" && rawStatus !== "applying" && rawStatus !== "reverting" && rawStatus !== "committed" && rawStatus !== "failed") {
132621
+ if (!isProjectVersionTransactionCommitStatus(rawStatus)) {
132306
132622
  throw new Error(
132307
132623
  `Accepted project transaction "${value.transactionId}" has invalid commitStatus ${JSON.stringify(rawStatus)}.`
132308
132624
  );
@@ -132330,7 +132646,70 @@ function readAcceptedProjectVersionCommitResponse(value) {
132330
132646
  totalChangeCount,
132331
132647
  appliedChangeCount,
132332
132648
  totalChunkCount,
132333
- appliedChunkCount
132649
+ appliedChunkCount,
132650
+ ...value.completion === void 0 ? {} : {
132651
+ completion: readAcceptedCompletion(
132652
+ value.completion,
132653
+ value.transactionId
132654
+ )
132655
+ }
132656
+ };
132657
+ }
132658
+ function readAcceptedCompletion(value, transactionId) {
132659
+ const status = readProjectVersionTransactionStatus(value, transactionId);
132660
+ if (status.commitStatus !== "committed" && status.commitStatus !== "noop" && status.commitStatus !== "failed") {
132661
+ throw new Error(
132662
+ `Accepted project transaction "${transactionId}" reported a completion with commitStatus "${status.commitStatus}".`
132663
+ );
132664
+ }
132665
+ const resultPage = isObjectRecord2(value) ? value.resultPage : void 0;
132666
+ return resultPage === void 0 ? { status } : {
132667
+ status,
132668
+ resultPage: readStoredResultPage(resultPage, transactionId)
132669
+ };
132670
+ }
132671
+ function readStoredResultPage(value, transactionId) {
132672
+ const label = `Project transaction "${transactionId}" completion result page`;
132673
+ if (!isObjectRecord2(value)) {
132674
+ throw new Error(`${label} must be an object.`);
132675
+ }
132676
+ if (value.transactionId !== transactionId) {
132677
+ throw new Error(
132678
+ `${label} expected transactionId "${transactionId}"; received ${JSON.stringify(value.transactionId)}.`
132679
+ );
132680
+ }
132681
+ if (!Array.isArray(value.chunks)) {
132682
+ throw new Error(`${label} chunks must be an array.`);
132683
+ }
132684
+ const chunks = [];
132685
+ for (const [index, chunk] of value.chunks.entries()) {
132686
+ const chunkLabel = `${label} chunk ${index}`;
132687
+ if (!isObjectRecord2(chunk)) {
132688
+ throw new Error(`${chunkLabel} must be an object.`);
132689
+ }
132690
+ if (typeof chunk.assignmentsJson !== "string") {
132691
+ throw new Error(`${chunkLabel} assignmentsJson must be a string.`);
132692
+ }
132693
+ if (typeof chunk.changedRecordsJson !== "string") {
132694
+ throw new Error(`${chunkLabel} changedRecordsJson must be a string.`);
132695
+ }
132696
+ chunks.push({
132697
+ chunkIndex: readCount(chunk.chunkIndex, `${chunkLabel} chunkIndex`),
132698
+ assignmentsJson: chunk.assignmentsJson,
132699
+ changedRecordsJson: chunk.changedRecordsJson
132700
+ });
132701
+ }
132702
+ if (typeof value.isDone !== "boolean") {
132703
+ throw new Error(`${label} isDone must be a boolean.`);
132704
+ }
132705
+ return {
132706
+ transactionId,
132707
+ chunks,
132708
+ continueCursor: readNullableString(
132709
+ value.continueCursor,
132710
+ `${label} continueCursor`
132711
+ ),
132712
+ isDone: value.isDone
132334
132713
  };
132335
132714
  }
132336
132715
  function readProjectVersionTransactionStatus(value, transactionId) {
@@ -132599,24 +132978,20 @@ function progressEventFromStatus(status) {
132599
132978
  };
132600
132979
  }
132601
132980
  async function waitForAcceptedProjectVersionTransaction(args) {
132602
- const { workspace, accepted, reporter } = args;
132981
+ const { workspace, accepted, reporter, connection } = args;
132603
132982
  const { projectId, versionId } = workspace.config;
132604
132983
  const controller = new AbortController();
132605
132984
  const interrupt = () => controller.abort(
132606
132985
  new ProjectTransactionInterruptedError(accepted.transactionId)
132607
132986
  );
132608
132987
  process.once("SIGINT", interrupt);
132609
- reporter.report(progressEventFromAccepted(accepted));
132988
+ if (accepted.completion === void 0)
132989
+ reporter.report(progressEventFromAccepted(accepted));
132610
132990
  const finishWait = startPerformanceSpan("transaction-wait");
132611
- let client;
132612
132991
  let unsubscribe;
132613
132992
  let progressTimer;
132614
132993
  let settled = false;
132615
132994
  try {
132616
- const connection = args.connection ?? await withTransactionAbort(
132617
- createConvexPushConnection(workspace),
132618
- controller.signal
132619
- );
132620
132995
  let failed = () => void 0;
132621
132996
  let updated = () => void 0;
132622
132997
  let firstResultPage;
@@ -132651,24 +133026,19 @@ async function waitForAcceptedProjectVersionTransaction(args) {
132651
133026
  };
132652
133027
  }
132653
133028
  );
132654
- void terminal2.catch(() => void 0);
132655
- client = await connection.subscribe(controller.signal, (error) => {
133029
+ const client = connection.subscribe(controller.signal, (error) => {
132656
133030
  controller.abort(error);
132657
133031
  failed(error);
132658
133032
  });
132659
- const progressClient = client;
132660
133033
  const refreshProgress = async () => {
132661
133034
  if (settled || controller.signal.aborted) return;
132662
133035
  try {
132663
133036
  const response = await withTransactionAbort(
132664
- progressClient.query(
132665
- api2.projectVersionTransactionJobs.getTransactionStatus,
132666
- {
132667
- projectId,
132668
- versionId,
132669
- transactionId: accepted.transactionId
132670
- }
132671
- ),
133037
+ client.query(api2.projectVersionTransactionJobs.getTransactionStatus, {
133038
+ projectId,
133039
+ versionId,
133040
+ transactionId: accepted.transactionId
133041
+ }),
132672
133042
  controller.signal
132673
133043
  );
132674
133044
  if (settled || controller.signal.aborted) return;
@@ -132688,29 +133058,36 @@ async function waitForAcceptedProjectVersionTransaction(args) {
132688
133058
  const finishFirstStatus = startPerformanceSpan("transaction-first-status");
132689
133059
  let firstStatus = true;
132690
133060
  try {
132691
- unsubscribe = client.onUpdate(
132692
- api2.projectVersionTransactionJobs.getTransactionCompletion,
132693
- {
132694
- projectId,
132695
- versionId,
132696
- transactionId: accepted.transactionId,
132697
- includeResultPage: true
132698
- },
132699
- (response) => {
132700
- if (firstStatus) {
132701
- firstStatus = false;
132702
- finishFirstStatus();
132703
- }
132704
- if (settled) return;
132705
- firstResultPage = response?.resultPage;
132706
- updated(response);
132707
- },
132708
- failed
132709
- );
133061
+ if (accepted.completion === void 0) {
133062
+ unsubscribe = client.onUpdate(
133063
+ api2.projectVersionTransactionJobs.getTransactionCompletion,
133064
+ {
133065
+ projectId,
133066
+ versionId,
133067
+ transactionId: accepted.transactionId,
133068
+ includeResultPage: true
133069
+ },
133070
+ (response) => {
133071
+ if (firstStatus) {
133072
+ firstStatus = false;
133073
+ finishFirstStatus();
133074
+ }
133075
+ if (settled) return;
133076
+ firstResultPage = response?.resultPage;
133077
+ updated(response);
133078
+ },
133079
+ failed
133080
+ );
133081
+ } else {
133082
+ firstStatus = false;
133083
+ finishFirstStatus();
133084
+ firstResultPage = accepted.completion.resultPage;
133085
+ updated(accepted.completion.status);
133086
+ }
132710
133087
  if (!settled)
132711
133088
  progressTimer = setTimeout(() => void refreshProgress(), 5e3);
132712
133089
  const status = await withTransactionAbort(terminal2, controller.signal);
132713
- unsubscribe();
133090
+ unsubscribe?.();
132714
133091
  unsubscribe = void 0;
132715
133092
  const finishDownload = startPerformanceSpan("result-download");
132716
133093
  try {
@@ -132734,7 +133111,6 @@ async function waitForAcceptedProjectVersionTransaction(args) {
132734
133111
  clearTimeout(progressTimer);
132735
133112
  unsubscribe?.();
132736
133113
  controller.abort();
132737
- if (client !== void 0) await client.close();
132738
133114
  finishWait();
132739
133115
  process.removeListener("SIGINT", interrupt);
132740
133116
  }
@@ -132925,6 +133301,19 @@ async function runPush(workspace, options, preparationOverride) {
132925
133301
  `neo push cannot run from configured hook ${JSON.stringify(process.env.NEO_HOOK)}. Nested pushes are always blocked; run the intended validation command directly.`
132926
133302
  );
132927
133303
  }
133304
+ if (options.dryRun) {
133305
+ await pushWorkspace(workspace, options, preparationOverride, void 0);
133306
+ return;
133307
+ }
133308
+ const connection = createConvexPushConnection(workspace);
133309
+ await connection.catch(() => void 0);
133310
+ try {
133311
+ await pushWorkspace(workspace, options, preparationOverride, connection);
133312
+ } finally {
133313
+ await closePushConnection(connection);
133314
+ }
133315
+ }
133316
+ async function pushWorkspace(workspace, options, preparationOverride, connection) {
132928
133317
  const configuredHook = options.dryRun ? workspace.config.prePushDryRunHook : workspace.config.prePushHook;
132929
133318
  const verifiesConfiguredHook = options.noVerify !== true && configuredHook !== void 0;
132930
133319
  const preparedInputFingerprint = verifiesConfiguredHook ? fingerprintPushInputs(workspace) : void 0;
@@ -132984,7 +133373,7 @@ async function runPush(workspace, options, preparationOverride) {
132984
133373
  );
132985
133374
  }
132986
133375
  if (options.noVerify !== true) {
132987
- const preparedBuildDir = configuredHook === void 0 ? void 0 : join11(
133376
+ const preparedBuildDir = configuredHook === void 0 ? void 0 : join12(
132988
133377
  workspace.root,
132989
133378
  ".neo",
132990
133379
  "test-build",
@@ -133000,8 +133389,8 @@ async function runPush(workspace, options, preparationOverride) {
133000
133389
  }
133001
133390
  const documentJson = JSON.stringify(candidate.document);
133002
133391
  mkdirSync8(preparedBuildDir, { recursive: true });
133003
- writeFileSync8(
133004
- join11(preparedBuildDir, "candidate.json"),
133392
+ writeFileSync9(
133393
+ join12(preparedBuildDir, "candidate.json"),
133005
133394
  `${JSON.stringify({
133006
133395
  version: 1,
133007
133396
  projectFingerprint: `sha256:${preparedLocal.source.sourceHash}`,
@@ -133023,7 +133412,7 @@ async function runPush(workspace, options, preparationOverride) {
133023
133412
  });
133024
133413
  } finally {
133025
133414
  if (preparedBuildDir !== void 0) {
133026
- rmSync5(preparedBuildDir, { recursive: true, force: true });
133415
+ rmSync6(preparedBuildDir, { recursive: true, force: true });
133027
133416
  }
133028
133417
  }
133029
133418
  } else {
@@ -133077,6 +133466,8 @@ async function runPush(workspace, options, preparationOverride) {
133077
133466
  }
133078
133467
  }
133079
133468
  if (options.dryRun) return;
133469
+ if (connection === void 0)
133470
+ throw new Error("A committing push has no connection to commit through.");
133080
133471
  if (isInteractive() && !await promptConfirm({
133081
133472
  message: `Push ${status.changes.length} change(s)?`,
133082
133473
  fallback: true
@@ -133158,7 +133549,6 @@ async function runPush(workspace, options, preparationOverride) {
133158
133549
  stagedUploadSessionId
133159
133550
  } = preparedPush;
133160
133551
  const { stagedFiles } = preparedPush;
133161
- let pushConnection;
133162
133552
  const commit = async (force) => {
133163
133553
  const body = clientPreparedCommitBody({
133164
133554
  workspace,
@@ -133170,8 +133560,7 @@ async function runPush(workspace, options, preparationOverride) {
133170
133560
  pendingIdAssignments: Object.fromEntries(pendingAssignment.assigned),
133171
133561
  summary: options.summary ?? "neo push"
133172
133562
  });
133173
- pushConnection ??= await createConvexPushConnection(workspace);
133174
- return await pushConnection.submit(body, force);
133563
+ return await (await connection).submit(body, force);
133175
133564
  };
133176
133565
  const finishCommitResponse = async (response, reporter) => {
133177
133566
  let accepted;
@@ -133217,13 +133606,14 @@ async function runPush(workspace, options, preparationOverride) {
133217
133606
  if (options.json !== true) success("Push complete.");
133218
133607
  return true;
133219
133608
  }
133609
+ const opened = await connection;
133220
133610
  let completed;
133221
133611
  try {
133222
133612
  completed = await waitForAcceptedProjectVersionTransaction({
133223
133613
  workspace,
133224
133614
  accepted,
133225
133615
  reporter,
133226
- connection: pushConnection
133616
+ connection: opened
133227
133617
  });
133228
133618
  } catch (error) {
133229
133619
  if (error instanceof ProjectTransactionInterruptedError) {
@@ -133268,6 +133658,7 @@ async function runPush(workspace, options, preparationOverride) {
133268
133658
  reporter.stop();
133269
133659
  throw error;
133270
133660
  }
133661
+ void opened.close();
133271
133662
  try {
133272
133663
  const finishApply = startPerformanceSpan("apply-result");
133273
133664
  try {
@@ -134223,7 +134614,8 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
134223
134614
  valueIds: binding.valueIds.map(
134224
134615
  (id2) => rewriteAssignedString(id2, replacements)
134225
134616
  )
134226
- }))
134617
+ })),
134618
+ localStatus.sourceSnapshot
134227
134619
  );
134228
134620
  } catch (error) {
134229
134621
  workspace.state = {
@@ -134253,7 +134645,7 @@ function statusForPushResult(workspace, submitted) {
134253
134645
  const input = snapshot.inputs[index];
134254
134646
  if (input?.uri !== relative6(workspace.root, path).split(sep6).join("/"))
134255
134647
  return computeWorkspaceStatus2(workspace);
134256
- if (input.text !== readFileSync13(path, "utf8"))
134648
+ if (input.text !== readFileSync14(path, "utf8"))
134257
134649
  return computeWorkspaceStatus2(workspace);
134258
134650
  }
134259
134651
  const binaries = inspectProjectBinaryStatusV4(
@@ -134422,13 +134814,14 @@ function foldChangedRecords(previous, changedRecords) {
134422
134814
  `Changed record "${key}" response is missing its snapshot content hash.`
134423
134815
  );
134424
134816
  }
134817
+ const projectBinary = records2[key]?.projectBinary;
134425
134818
  records2[key] = {
134426
134819
  recordKind,
134427
134820
  recordId,
134428
134821
  contentHash: snapshot.contentHash,
134429
134822
  data: changed.data,
134430
134823
  file: records2[key]?.file,
134431
- projectBinary: records2[key]?.projectBinary
134824
+ projectBinary: projectBinary && committedProjectBinaryStateV4(projectBinary, changed.data)
134432
134825
  };
134433
134826
  }
134434
134827
  return records2;
@@ -134459,8 +134852,8 @@ function authoredMemberFormsByFile(root, alternates) {
134459
134852
  for (const alternate of alternates) {
134460
134853
  let existing = contents.get(alternate.path);
134461
134854
  if (existing === void 0) {
134462
- const absolute = join11(root, alternate.path);
134463
- existing = existsSync9(absolute) ? readFileSync13(absolute, "utf8") : null;
134855
+ const absolute = join12(root, alternate.path);
134856
+ existing = existsSync10(absolute) ? readFileSync14(absolute, "utf8") : null;
134464
134857
  contents.set(alternate.path, existing);
134465
134858
  }
134466
134859
  if (existing === null || !existing.includes(alternate.block)) continue;
@@ -134509,8 +134902,8 @@ function rewriteFilesFromState(workspace, records2 = new Map(
134509
134902
  data: recordState.data
134510
134903
  }
134511
134904
  ])
134512
- ), preservedSourceFiles = /* @__PURE__ */ new Map(), recordFiles = /* @__PURE__ */ new Map(), localValues = workspace.state.localValues) {
134513
- const result = emitProjectDocumentFilesV4(
134905
+ ), preservedSourceFiles = /* @__PURE__ */ new Map(), recordFiles = /* @__PURE__ */ new Map(), localValues = workspace.state.localValues, sourceSnapshot) {
134906
+ const result = emitProjectDocumentSourcesV4(
134514
134907
  candidateValueRecordsForEmissionV4(
134515
134908
  records2,
134516
134909
  workspace.state.records,
@@ -134549,11 +134942,15 @@ function rewriteFilesFromState(workspace, records2 = new Map(
134549
134942
  }
134550
134943
  return { uri: file.path, kind, text: file.content };
134551
134944
  });
134552
- const finalAnalysis = files.every(
134553
- (file, index) => file.content === result.files[index]?.content
134554
- ) ? result.analysis : compileNeoProjectSources(finalInputs);
134945
+ const unchanged = sourceSnapshot !== void 0 && sameCompilationInputs(finalInputs, sourceSnapshot.inputs);
134946
+ const finalAnalysis = unchanged ? sourceSnapshot.analysis : compileNeoProjectSources(finalInputs);
134555
134947
  assertAssignedIdSourceRewriteValid(finalAnalysis);
134556
- writeProjectSourceAnalysisCacheV4(workspace.root, finalAnalysis, finalInputs);
134948
+ if (!unchanged)
134949
+ writeProjectSourceAnalysisCacheV4(
134950
+ workspace.root,
134951
+ finalAnalysis,
134952
+ finalInputs
134953
+ );
134557
134954
  const resolveSourcePath = createWorkspaceSourcePathResolver(workspace.root);
134558
134955
  const emittedEntries = new Set(
134559
134956
  files.map((file) => fileSystemEntryKey(resolveSourcePath(file.path)))
@@ -134561,15 +134958,13 @@ function rewriteFilesFromState(workspace, records2 = new Map(
134561
134958
  const output = new Map(
134562
134959
  files.map((file) => [file.path, file.content])
134563
134960
  );
134564
- const previousPaths = [
134565
- ...Object.values(workspace.state.records).flatMap(
134566
- (record6) => record6.file === void 0 ? [] : [record6.file]
134567
- ),
134568
- ...preservedSourceFiles.keys()
134569
- ];
134961
+ const previousPaths = new Set(preservedSourceFiles.keys());
134962
+ for (const record6 of Object.values(workspace.state.records)) {
134963
+ if (record6.file !== void 0) previousPaths.add(record6.file);
134964
+ }
134570
134965
  for (const path of previousPaths) {
134571
134966
  const absolute = resolveSourcePath(path);
134572
- if (!existsSync9(absolute) || emittedEntries.has(fileSystemEntryKey(absolute)))
134967
+ if (!existsSync10(absolute) || emittedEntries.has(fileSystemEntryKey(absolute)))
134573
134968
  continue;
134574
134969
  output.set(normalizeWorkspaceSourcePath(path), null);
134575
134970
  }
@@ -134577,7 +134972,7 @@ function rewriteFilesFromState(workspace, records2 = new Map(
134577
134972
  const absolute = resolveSourcePath(path);
134578
134973
  return {
134579
134974
  path,
134580
- before: existsSync9(absolute) ? readFileSync13(absolute, "utf8") : null,
134975
+ before: existsSync10(absolute) ? readFileSync14(absolute, "utf8") : null,
134581
134976
  after
134582
134977
  };
134583
134978
  });
@@ -134603,6 +134998,13 @@ function rewriteFilesFromState(workspace, records2 = new Map(
134603
134998
  writeWorkspaceState(workspace.root, workspace.state);
134604
134999
  finishCommittedPushProjection(workspace);
134605
135000
  }
135001
+ function sameCompilationInputs(left, right) {
135002
+ if (left.length !== right.length) return false;
135003
+ return left.every((input, index) => {
135004
+ const other = right[index];
135005
+ return input.uri === other?.uri && input.kind === other.kind && input.text === other.text;
135006
+ });
135007
+ }
134606
135008
  function assertAssignedIdSourceRewriteValid(analysis) {
134607
135009
  const finalErrors = analysis.diagnostics.filter(
134608
135010
  (diagnostic) => diagnostic.severity === "error" && !isPullRecoveryDiagnosticCode(diagnostic.code)
@@ -134629,7 +135031,7 @@ function materializeAssignedSchemaIdsInAuthoredSource(workspace, replacements, p
134629
135031
  if (pendingIdsByUri.size === 0) return /* @__PURE__ */ new Map();
134630
135032
  const inputs = options.sourceSnapshot?.inputs ?? listProjectSourceFilesV4(workspace.root).map((absolutePath) => {
134631
135033
  const uri = relative6(workspace.root, absolutePath).split(sep6).join("/");
134632
- const text = readFileSync13(absolutePath, "utf8");
135034
+ const text = readFileSync14(absolutePath, "utf8");
134633
135035
  const kind = neoProjectSourceKind(uri, text);
134634
135036
  if (kind === null || !isNeoProjectProductionSourceKind(kind)) {
134635
135037
  throw new Error(
@@ -135433,6 +135835,7 @@ var init_push = __esm({
135433
135835
  init_project_documents();
135434
135836
  init_project_document_cache();
135435
135837
  init_project_files();
135838
+ init_project_file_pull();
135436
135839
  init_workspace_status();
135437
135840
  init_lower_support();
135438
135841
  init_init_conversion();
@@ -136190,13 +136593,13 @@ var init_source_record_comparison = __esm({
136190
136593
 
136191
136594
  // src/project-source/reset.ts
136192
136595
  import {
136193
- existsSync as existsSync10,
136596
+ existsSync as existsSync11,
136194
136597
  mkdirSync as mkdirSync9,
136195
- readFileSync as readFileSync14,
136196
- rmSync as rmSync6,
136197
- writeFileSync as writeFileSync9
136598
+ readFileSync as readFileSync15,
136599
+ rmSync as rmSync7,
136600
+ writeFileSync as writeFileSync10
136198
136601
  } from "node:fs";
136199
- import { dirname as dirname8, join as join12, relative as relative7, sep as sep7 } from "node:path";
136602
+ import { dirname as dirname8, join as join13, relative as relative7, sep as sep7 } from "node:path";
136200
136603
  function prepareWorkspaceResetV4(workspace, document, options = {}) {
136201
136604
  const emissionRecords = options.regenerateSourceNames ? regenerateDialogueSourceNamesV4(document.records) : document.records;
136202
136605
  const layoutWorkspace = workspaceForSourceLayoutV4(workspace, document);
@@ -136226,21 +136629,21 @@ function resetWorkspaceToProjectSourcesV4(workspace, document, options = {}) {
136226
136629
  }
136227
136630
  for (const file of emitted.files) obsolete.delete(file.path);
136228
136631
  for (const path of obsolete) {
136229
- rmSync6(resolveSourcePath(path), { force: true });
136632
+ rmSync7(resolveSourcePath(path), { force: true });
136230
136633
  }
136231
136634
  for (const file of LEGACY_ROOT_FILES) {
136232
- rmSync6(join12(workspace.root, file), { force: true });
136635
+ rmSync7(join13(workspace.root, file), { force: true });
136233
136636
  }
136234
136637
  for (const privatePath of LEGACY_PRIVATE_PATHS) {
136235
- rmSync6(join12(workspace.root, privatePath), { recursive: true, force: true });
136638
+ rmSync7(join13(workspace.root, privatePath), { recursive: true, force: true });
136236
136639
  }
136237
136640
  for (const file of emitted.files) {
136238
136641
  const absolute = resolveSourcePath(file.path);
136239
136642
  mkdirSync9(dirname8(absolute), { recursive: true });
136240
- if (existsSync10(absolute) && readFileSync14(absolute, "utf8") === file.content) {
136643
+ if (existsSync11(absolute) && readFileSync15(absolute, "utf8") === file.content) {
136241
136644
  continue;
136242
136645
  }
136243
- writeFileSync9(absolute, file.content, "utf8");
136646
+ writeFileSync10(absolute, file.content, "utf8");
136244
136647
  }
136245
136648
  writeProjectSourceAnalysisCacheV4(workspace.root, emitted.analysis);
136246
136649
  const records2 = {};
@@ -136281,7 +136684,7 @@ function trackedProductionSourcesBeforeReset(workspace, resolveSourcePath) {
136281
136684
  continue;
136282
136685
  }
136283
136686
  const absolute = resolveSourcePath(path);
136284
- if (existsSync10(absolute)) paths.add(path);
136687
+ if (existsSync11(absolute)) paths.add(path);
136285
136688
  }
136286
136689
  return paths;
136287
136690
  }
@@ -136325,280 +136728,6 @@ var init_reset = __esm({
136325
136728
  }
136326
136729
  });
136327
136730
 
136328
- // src/project-source/project-file-pull.ts
136329
- import {
136330
- existsSync as existsSync11,
136331
- mkdtempSync,
136332
- readFileSync as readFileSync15,
136333
- rmSync as rmSync7,
136334
- writeFileSync as writeFileSync10
136335
- } from "node:fs";
136336
- import { tmpdir } from "node:os";
136337
- import { join as join13 } from "node:path";
136338
- async function pullProjectBinariesV4(args) {
136339
- const resolvePath = createWorkspaceSourcePathResolver(args.workspace.root);
136340
- const plans = /* @__PURE__ */ new Map();
136341
- const downloads = [];
136342
- const localById = new Map(
136343
- (args.localBinaries ?? []).map((binary) => [binary.fileId, binary])
136344
- );
136345
- const states = /* @__PURE__ */ new Map();
136346
- const conflicts = [];
136347
- let downloaded = 0;
136348
- let conflicted = 0;
136349
- for (const record6 of args.document.records.values()) {
136350
- if (record6.deleted || record6.recordKind !== "project-file") continue;
136351
- if (!isObjectRecord2(record6.data)) {
136352
- throw new Error(`project-file:${record6.recordId} must be an object.`);
136353
- }
136354
- if (record6.data.status !== "uploaded") continue;
136355
- const existing = plans.get(record6.recordId);
136356
- if (existing !== void 0) {
136357
- if (existing.contentHash !== record6.contentHash)
136358
- throw new Error(
136359
- `Project file ${record6.recordId} appears with conflicting content hashes in the pulled document.`
136360
- );
136361
- continue;
136362
- }
136363
- const key = `project-file:${record6.recordId}`;
136364
- const previous = args.previousRecords[key];
136365
- const localStatus = localById.get(record6.recordId);
136366
- const declarationPresent = args.destructive === true || args.localBinaries === void 0 || previous === void 0 || localStatus !== void 0;
136367
- const path = localStatus?.path ?? previous?.projectBinary?.path ?? canonicalProjectBinaryPathV42(record6.data);
136368
- const absolute = resolvePath(path);
136369
- const localDigest = existsSync11(absolute) ? sha256File(absolute) : null;
136370
- const baseDigest = previous?.projectBinary?.sha256 ?? readSha256(previous?.data) ?? null;
136371
- const remoteDigest = requiredSha256(
136372
- record6.data,
136373
- `project-file:${record6.recordId} metadata`
136374
- );
136375
- let action = planBinaryMergeV4({
136376
- baseDigest,
136377
- localDigest,
136378
- remoteDigest,
136379
- declarationPresent,
136380
- hadLocalBytes: previous?.projectBinary !== void 0
136381
- });
136382
- if (args.destructive === true) {
136383
- action = localDigest === remoteDigest ? "unchanged" : "download";
136384
- }
136385
- const plan = {
136386
- key,
136387
- fileId: record6.recordId,
136388
- contentHash: record6.contentHash,
136389
- data: record6.data,
136390
- previous,
136391
- path,
136392
- baseDigest,
136393
- remoteDigest,
136394
- action
136395
- };
136396
- plans.set(record6.recordId, plan);
136397
- if (action === "download" || action === "conflict") downloads.push(plan);
136398
- }
136399
- let stagingDirectory;
136400
- try {
136401
- const stagedPaths = /* @__PURE__ */ new Map();
136402
- if (downloads.length > 0) {
136403
- stagingDirectory = mkdtempSync(join13(tmpdir(), "neo-pull-files-"));
136404
- const directory = stagingDirectory;
136405
- const client = args.client ?? NeoApiClient.forBaseUrl(args.workspace.config.apiBaseUrl);
136406
- await mapWithConcurrency(
136407
- downloads,
136408
- DOWNLOAD_CONCURRENCY,
136409
- async (plan, index) => {
136410
- const response = await download(
136411
- plan.data,
136412
- plan.fileId,
136413
- client,
136414
- args.workspace
136415
- );
136416
- if (response.sha256 !== plan.remoteDigest) {
136417
- throw new Error(
136418
- `Project file ${plan.fileId} content changed while pull was in progress (${plan.remoteDigest} -> ${response.sha256}). Pull again against a stable version head.`
136419
- );
136420
- }
136421
- const stagedPath = join13(directory, String(index));
136422
- writeFileSync10(stagedPath, response.bytes);
136423
- stagedPaths.set(plan.fileId, stagedPath);
136424
- }
136425
- );
136426
- }
136427
- for (const plan of plans.values()) {
136428
- const {
136429
- key,
136430
- fileId,
136431
- data,
136432
- previous,
136433
- path,
136434
- baseDigest,
136435
- remoteDigest,
136436
- action
136437
- } = plan;
136438
- const absolute = resolvePath(path);
136439
- const stagedPath = stagedPaths.get(fileId);
136440
- const remoteBytes = stagedPath === void 0 ? null : readFileSync15(stagedPath);
136441
- if (action === "download") {
136442
- if (remoteBytes === null) {
136443
- throw new Error(
136444
- `Project file ${fileId} download plan has no verified remote bytes.`
136445
- );
136446
- }
136447
- writeVerifiedBinaryDownloadV4(absolute, remoteBytes, remoteDigest);
136448
- downloaded += 1;
136449
- states.set(key, binaryState(path, remoteDigest, data));
136450
- continue;
136451
- }
136452
- if (action === "conflict") {
136453
- if (remoteBytes === null) {
136454
- throw new Error(
136455
- `Project file ${fileId} conflict plan has no verified remote bytes.`
136456
- );
136457
- }
136458
- const artifactPath = writeBinaryConflictArtifactV4(
136459
- args.workspace.root,
136460
- fileId,
136461
- fileName(data),
136462
- remoteBytes,
136463
- remoteDigest
136464
- );
136465
- const state = binaryState(path, baseDigest, data);
136466
- states.set(key, {
136467
- ...state,
136468
- conflict: {
136469
- action: "conflict",
136470
- remoteSha256: remoteDigest,
136471
- artifactPath
136472
- }
136473
- });
136474
- conflicts.push(path);
136475
- conflicted += 1;
136476
- continue;
136477
- }
136478
- if (action === "converged" || action === "unchanged") {
136479
- states.set(key, binaryState(path, remoteDigest, data));
136480
- removePreviousConflict(args.workspace.root, previous?.projectBinary);
136481
- continue;
136482
- }
136483
- states.set(key, binaryState(path, baseDigest, data));
136484
- }
136485
- for (const [key, previous] of Object.entries(args.previousRecords)) {
136486
- if (previous.recordKind !== "project-file") continue;
136487
- if (args.document.records.has(key)) continue;
136488
- const localStatus = localById.get(previous.recordId);
136489
- const path = localStatus?.path ?? previous.projectBinary?.path ?? canonicalProjectBinaryPathV42(previous.data);
136490
- const absolute = resolvePath(path);
136491
- const localDigest = existsSync11(absolute) ? sha256File(absolute) : null;
136492
- const baseDigest = previous.projectBinary?.sha256 ?? readSha256(previous.data) ?? null;
136493
- const action = planBinaryMergeV4({
136494
- baseDigest,
136495
- localDigest,
136496
- remoteDigest: null,
136497
- declarationPresent: localStatus !== void 0,
136498
- hadLocalBytes: previous.projectBinary !== void 0
136499
- });
136500
- if (action === "delete-modify") {
136501
- const data = isObjectRecord2(previous.data) ? previous.data : {};
136502
- states.set(key, {
136503
- ...binaryState(path, baseDigest, data),
136504
- conflict: {
136505
- action: "delete-modify",
136506
- remoteSha256: null
136507
- }
136508
- });
136509
- conflicts.push(path);
136510
- conflicted += 1;
136511
- continue;
136512
- }
136513
- rmSync7(absolute, { force: true });
136514
- removePreviousConflict(args.workspace.root, previous.projectBinary);
136515
- }
136516
- return { states, downloaded, conflicted, conflicts };
136517
- } finally {
136518
- if (stagingDirectory !== void 0)
136519
- rmSync7(stagingDirectory, { recursive: true, force: true });
136520
- }
136521
- }
136522
- async function download(data, fileId, client, workspace) {
136523
- const version = typeof data.storageETag === "string" ? `?v=${encodeURIComponent(data.storageETag)}` : "";
136524
- const path = `/api/projects/${workspace.config.projectId}/versions/${workspace.config.versionId}/files/${fileId}/content${version}`;
136525
- const response = await client.getBytes(path);
136526
- const computed = sha256Bytes(response.bytes);
136527
- const metadataDigest = requiredSha256(
136528
- data,
136529
- `project-file:${fileId} metadata`
136530
- );
136531
- const headerDigest = normalizeSha256V4(
136532
- response.contentSha256,
136533
- `download response for project file ${fileId}`
136534
- );
136535
- if (headerDigest !== null && metadataDigest !== headerDigest) {
136536
- throw new Error(
136537
- `Project file ${fileId} metadata SHA-256 ${metadataDigest} did not match authenticated content response ${headerDigest}.`
136538
- );
136539
- }
136540
- if (computed !== metadataDigest) {
136541
- throw new Error(
136542
- `Downloaded project file ${fileId} SHA-256 ${computed} did not match expected ${metadataDigest}.`
136543
- );
136544
- }
136545
- if (typeof data.byteLength === "number" && response.bytes.byteLength !== data.byteLength) {
136546
- throw new Error(
136547
- `Downloaded project file ${fileId} has ${response.bytes.byteLength} bytes; metadata requires ${data.byteLength}.`
136548
- );
136549
- }
136550
- if (typeof data.mimeType === "string" && response.contentType !== null && response.contentType !== data.mimeType) {
136551
- throw new Error(
136552
- `Downloaded project file ${fileId} content type ${response.contentType} did not match metadata ${data.mimeType}.`
136553
- );
136554
- }
136555
- return { bytes: response.bytes, sha256: computed };
136556
- }
136557
- function binaryState(path, sha256, data) {
136558
- return {
136559
- path,
136560
- sha256,
136561
- byteLength: typeof data.byteLength === "number" ? data.byteLength : 0,
136562
- mimeType: typeof data.mimeType === "string" ? data.mimeType : "application/octet-stream"
136563
- };
136564
- }
136565
- function readSha256(data) {
136566
- if (!isObjectRecord2(data)) return null;
136567
- return normalizeSha256V4(data.contentSha256, "project file metadata");
136568
- }
136569
- function requiredSha256(data, source) {
136570
- if (!isObjectRecord2(data)) {
136571
- throw new Error(`${source} must be an object.`);
136572
- }
136573
- const digest = normalizeSha256V4(data.contentSha256, source);
136574
- if (digest === null) {
136575
- throw new Error(`${source} is missing required contentSha256.`);
136576
- }
136577
- return digest;
136578
- }
136579
- function fileName(data) {
136580
- if (typeof data.name !== "string") {
136581
- throw new Error("Project file metadata has no filename.");
136582
- }
136583
- return data.name;
136584
- }
136585
- function removePreviousConflict(root, state) {
136586
- if (state?.conflict?.artifactPath === void 0) return;
136587
- rmSync7(join13(root, state.conflict.artifactPath), { force: true });
136588
- }
136589
- var DOWNLOAD_CONCURRENCY;
136590
- var init_project_file_pull = __esm({
136591
- "src/project-source/project-file-pull.ts"() {
136592
- "use strict";
136593
- init_workspace_source_path();
136594
- init_concurrency();
136595
- init_http();
136596
- init_projection();
136597
- init_project_files();
136598
- DOWNLOAD_CONCURRENCY = 8;
136599
- }
136600
- });
136601
-
136602
136731
  // src/project-source/static-value-seed-records.ts
136603
136732
  function staticValueSeedEmitRecords(status, present, projectId, mainLocale) {
136604
136733
  const records2 = /* @__PURE__ */ new Map();
@@ -140752,7 +140881,7 @@ var init_registry2 = __esm({
140752
140881
  "schema-contract/registry.mjs"() {
140753
140882
  "use strict";
140754
140883
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
140755
- cliVersion: "0.59.0",
140884
+ cliVersion: "0.59.2",
140756
140885
  projectFileUploadBatchSize: 32,
140757
140886
  documentRecords: {
140758
140887
  member: {
@@ -148573,7 +148702,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
148573
148702
  async function main() {
148574
148703
  const args = parseArgs(process.argv.slice(2));
148575
148704
  if (args.command === "--version") {
148576
- console.log("0.59.0");
148705
+ console.log("0.59.2");
148577
148706
  return;
148578
148707
  }
148579
148708
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {