@neocompose/cli 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/neo.mjs +1184 -193
  3. package/package.json +1 -1
package/dist/neo.mjs CHANGED
@@ -11837,12 +11837,13 @@ var init_project_source_parser = __esm({
11837
11837
  });
11838
11838
 
11839
11839
  // ../packages/neoscript-language/src/project-schema-contract.generated.ts
11840
- var PROJECT_SCHEMA_MANIFEST_FORMAT_VERSION, PROJECT_SCHEMA_MANIFEST_CONTRACT_VERSION, NEO_PROJECT_SOURCE_RECORD_CONTRACT;
11840
+ var PROJECT_SCHEMA_MANIFEST_FORMAT_VERSION, PROJECT_SCHEMA_MANIFEST_CONTRACT_VERSION, PROJECT_FILE_UPLOAD_BATCH_SIZE, NEO_PROJECT_SOURCE_RECORD_CONTRACT;
11841
11841
  var init_project_schema_contract_generated = __esm({
11842
11842
  "../packages/neoscript-language/src/project-schema-contract.generated.ts"() {
11843
11843
  "use strict";
11844
11844
  PROJECT_SCHEMA_MANIFEST_FORMAT_VERSION = 3;
11845
11845
  PROJECT_SCHEMA_MANIFEST_CONTRACT_VERSION = "3.5";
11846
+ PROJECT_FILE_UPLOAD_BATCH_SIZE = 32;
11846
11847
  NEO_PROJECT_SOURCE_RECORD_CONTRACT = {
11847
11848
  "recordFields": {
11848
11849
  "member": [
@@ -19507,8 +19508,8 @@ function memberSymbol(member, schemaKey, environment, field) {
19507
19508
  `${field}.source`
19508
19509
  );
19509
19510
  const overrideOf = nullableString(member.overrideOf, `${field}.overrideOf`);
19510
- const inheritedMember = overrideOf ? environment.members.get(overrideOf) : void 0;
19511
- const inheritedOwner = inheritedMember ? optionalRecord(inheritedMember.owner) : void 0;
19511
+ const inheritedMember2 = overrideOf ? environment.members.get(overrideOf) : void 0;
19512
+ const inheritedOwner = inheritedMember2 ? optionalRecord(inheritedMember2.owner) : void 0;
19512
19513
  const inheritedTypeId = inheritedOwner?.kind === "classMember" && typeof inheritedOwner.classId === "string" ? inheritedOwner.classId : void 0;
19513
19514
  const inheritedType = inheritedTypeId ? environment.classes.get(inheritedTypeId) : void 0;
19514
19515
  const common = {
@@ -29260,6 +29261,7 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29260
29261
  const fieldType = partial ? requiredTypeArgument(declaration.type, 0) : declaration.type;
29261
29262
  const storedStatic = declaration.modifiers.includes("static") && !declaration.modifiers.includes("readonly");
29262
29263
  const settings = annotation(declaration.annotations, "settings");
29264
+ const inherited = inheritedMember(context, commonInput.overrideOf);
29263
29265
  const storesSelectionArray = context.enumIdsByName.has(fieldType.name) || fieldType.name === "Dialogue" || Boolean(settings && argument(settings, "collection"));
29264
29266
  const common = {
29265
29267
  ...commonInput,
@@ -29291,11 +29293,12 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29291
29293
  return { ...common, kind: name };
29292
29294
  }
29293
29295
  if (name === "int") {
29296
+ const inheritedInt = inherited?.kind === "int" ? inherited : void 0;
29294
29297
  return {
29295
29298
  ...common,
29296
29299
  kind: "int",
29297
- min: numberArgument(settings, "min"),
29298
- max: numberArgument(settings, "max")
29300
+ min: numberArgument(settings, "min") ?? inheritedInt?.min ?? null,
29301
+ max: numberArgument(settings, "max") ?? inheritedInt?.max ?? null
29299
29302
  };
29300
29303
  }
29301
29304
  if (name === "string") {
@@ -29310,20 +29313,24 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29310
29313
  const min = numberTextArgument(settings, "min");
29311
29314
  const max = numberTextArgument(settings, "max");
29312
29315
  if (name === "decimal") {
29316
+ const inheritedDecimal = inherited?.kind === "decimal" ? inherited : void 0;
29313
29317
  return {
29314
29318
  ...common,
29315
29319
  kind: "decimal",
29316
- min,
29317
- max,
29318
- decimalPoints: numberArgument(settings, "decimalPoints")
29320
+ min: min ?? inheritedDecimal?.min ?? null,
29321
+ max: max ?? inheritedDecimal?.max ?? null,
29322
+ decimalPoints: numberArgument(settings, "decimalPoints") ?? inheritedDecimal?.decimalPoints ?? null
29319
29323
  };
29320
29324
  }
29325
+ const inheritedFloat = inherited?.kind === "float" ? inherited : void 0;
29326
+ const numericMin = min === null ? null : Number(min);
29327
+ const numericMax = max === null ? null : Number(max);
29321
29328
  return {
29322
29329
  ...common,
29323
29330
  kind: "float",
29324
- min: min === null ? null : Number(min),
29325
- max: max === null ? null : Number(max),
29326
- decimalPoints: numberArgument(settings, "decimalPoints")
29331
+ min: numericMin ?? inheritedFloat?.min ?? null,
29332
+ max: numericMax ?? inheritedFloat?.max ?? null,
29333
+ decimalPoints: numberArgument(settings, "decimalPoints") ?? inheritedFloat?.decimalPoints ?? null
29327
29334
  };
29328
29335
  }
29329
29336
  const primitive3 = primitiveMemberKind(name);
@@ -29443,6 +29450,10 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
29443
29450
  `Unsupported field type ${JSON.stringify(name)} on ${ownerClass.name}.${declaration.name}.`
29444
29451
  );
29445
29452
  }
29453
+ function inheritedMember(context, memberId) {
29454
+ if (memberId === null) return void 0;
29455
+ return context.loweredMembers.get(memberId) ?? context.baseMembers.get(memberId);
29456
+ }
29446
29457
  function lowerListIndexes(ownerClass, declaration) {
29447
29458
  const declared = /* @__PURE__ */ new Set();
29448
29459
  return declaration.annotations.filter((entry) => entry.name === "index").map((entry) => {
@@ -36051,6 +36062,14 @@ var init_world_system_classes_generated = __esm({
36051
36062
  defaultValue: { x: 1, y: 1, z: 0 },
36052
36063
  required: true,
36053
36064
  schemaKey: "Size"
36065
+ },
36066
+ {
36067
+ memberId: "system_4858148e-1c42-449d-8a03-c1601da529bd",
36068
+ memberKind: "bool",
36069
+ defaultValue: true,
36070
+ docsText: "When false, this object and its children are neither rendered nor collided with. The value stays live: member writes still apply and a running animation clip keeps playing. Disabling an object hides its whole subtree regardless of each child's own value, and re-enabling it restores exactly what was there.",
36071
+ required: true,
36072
+ schemaKey: "Enabled"
36054
36073
  }
36055
36074
  ],
36056
36075
  worldKind: "objectBase"
@@ -47688,6 +47707,24 @@ function assertAnimationClipDocumentValid(document) {
47688
47707
  context.validateClipMember(member);
47689
47708
  }
47690
47709
  }
47710
+ function collectAnimationClipNestedOwnerWarnings(document) {
47711
+ try {
47712
+ return new AnimationValidationContext(
47713
+ document
47714
+ ).collectNestedOwnerWarnings();
47715
+ } catch {
47716
+ return [];
47717
+ }
47718
+ }
47719
+ function compareNestedChildrenRowUseSites(left, right) {
47720
+ if (left.ownerClassName !== right.ownerClassName) {
47721
+ return left.ownerClassName < right.ownerClassName ? -1 : 1;
47722
+ }
47723
+ if (left.rowValueId !== right.rowValueId) {
47724
+ return left.rowValueId < right.rowValueId ? -1 : 1;
47725
+ }
47726
+ return 0;
47727
+ }
47691
47728
  function isRecord3(value) {
47692
47729
  return typeof value === "object" && value !== null && !Array.isArray(value);
47693
47730
  }
@@ -48257,13 +48294,19 @@ var init_animation_clips = __esm({
48257
48294
  return typeof member.valueId === "string" ? this.valueById.get(member.valueId) ?? null : null;
48258
48295
  }
48259
48296
  memberRootNode(member) {
48260
- const value = member.defaultValue?.value;
48297
+ const node = this.optionalMemberRootNode(member);
48298
+ if (node === null) {
48299
+ throw new Error(
48300
+ `Animation clip "${member.name}" must have a composite default-value graph.`
48301
+ );
48302
+ }
48303
+ return node;
48304
+ }
48305
+ /** {@link memberRootNode} without the throw, for diagnostics-only readers. */
48306
+ optionalMemberRootNode(member) {
48261
48307
  if (member.defaultValue !== void 0) {
48262
- if (!isRecord3(value)) {
48263
- throw new Error(
48264
- `Animation clip "${member.name}" must have a composite default-value graph.`
48265
- );
48266
- }
48308
+ const value = member.defaultValue.value;
48309
+ if (!isRecord3(value)) return null;
48267
48310
  return {
48268
48311
  id: `member-default:${member.id}`,
48269
48312
  projectId: member.projectId,
@@ -48274,13 +48317,8 @@ var init_animation_clips = __esm({
48274
48317
  };
48275
48318
  }
48276
48319
  const valueId = member.valueId;
48277
- if (typeof valueId === "string") {
48278
- const stored = this.valueById.get(valueId);
48279
- if (stored !== void 0) return stored;
48280
- }
48281
- throw new Error(
48282
- `Animation clip "${member.name}" must have a composite default-value graph.`
48283
- );
48320
+ if (typeof valueId !== "string") return null;
48321
+ return this.valueById.get(valueId) ?? null;
48284
48322
  }
48285
48323
  optionalFieldNode(parent, classId, memberId) {
48286
48324
  const field = this.field(classId, memberId);
@@ -48304,35 +48342,45 @@ var init_animation_clips = __esm({
48304
48342
  }
48305
48343
  requireListField(parent, classId, memberId, label) {
48306
48344
  const field = this.requireField(classId, memberId, label);
48345
+ const ids = this.listFieldIds(parent, field);
48346
+ if (!Array.isArray(ids)) {
48347
+ throw new Error(`${label} must be an ordered list.`);
48348
+ }
48349
+ const inferredClassId = this.listEntryClassId(field.member);
48350
+ return ids.map((id2) => {
48351
+ if (typeof id2 !== "string" || !this.valueById.has(id2)) {
48352
+ throw new Error(`${label} references missing entry "${String(id2)}".`);
48353
+ }
48354
+ const entry = this.valueById.get(id2);
48355
+ return typeof entry.classId === "string" || inferredClassId === void 0 ? entry : { ...entry, classId: inferredClassId };
48356
+ });
48357
+ }
48358
+ /**
48359
+ * The raw list payload behind a field: an explicit instance edge wins even
48360
+ * when it is dangling, then the member default, then the legacy value row.
48361
+ */
48362
+ listFieldIds(parent, field) {
48307
48363
  const mapped = isRecord3(parent.value) ? parent.value[field.schemaKey] : null;
48308
- let ids;
48309
48364
  if (typeof mapped === "string") {
48310
- ids = this.valueById.get(mapped)?.value;
48311
- } else if (field.member.defaultValue !== void 0) {
48312
- ids = field.member.defaultValue.value;
48313
- } else {
48314
- ids = typeof field.member.valueId === "string" ? this.valueById.get(field.member.valueId)?.value : void 0;
48365
+ return this.valueById.get(mapped)?.value;
48315
48366
  }
48316
- if (!Array.isArray(ids)) {
48317
- throw new Error(`${label} must be an ordered list.`);
48367
+ if (field.member.defaultValue !== void 0) {
48368
+ return field.member.defaultValue.value;
48318
48369
  }
48370
+ return typeof field.member.valueId === "string" ? this.valueById.get(field.member.valueId)?.value : void 0;
48371
+ }
48372
+ /** Class a list's rows carry when a row omits its own `classId`. */
48373
+ listEntryClassId(member) {
48319
48374
  const listMember = {
48320
- ...field.member,
48321
- ...resolveMember2(field.member, this.document.members)
48375
+ ...member,
48376
+ ...resolveMember2(member, this.document.members)
48322
48377
  };
48323
48378
  const entryMember = isMemberList(listMember) ? this.memberById.get(listMember.entryMemberId) : void 0;
48324
48379
  const resolvedEntryMember = entryMember === void 0 ? void 0 : {
48325
48380
  ...entryMember,
48326
48381
  ...resolveMember2(entryMember, this.document.members)
48327
48382
  };
48328
- const inferredClassId = isMemberClass(resolvedEntryMember) ? resolvedEntryMember.classId : void 0;
48329
- return ids.map((id2) => {
48330
- if (typeof id2 !== "string" || !this.valueById.has(id2)) {
48331
- throw new Error(`${label} references missing entry "${String(id2)}".`);
48332
- }
48333
- const entry = this.valueById.get(id2);
48334
- return typeof entry.classId === "string" || inferredClassId === void 0 ? entry : { ...entry, classId: inferredClassId };
48335
- });
48383
+ return isMemberClass(resolvedEntryMember) ? resolvedEntryMember.classId : void 0;
48336
48384
  }
48337
48385
  requireSingleLookupField(parent, classId, memberId, label) {
48338
48386
  const value = this.scalarField(parent, classId, memberId);
@@ -48377,18 +48425,23 @@ var init_animation_clips = __esm({
48377
48425
  return node.classId;
48378
48426
  }
48379
48427
  requireClassBinding(member, paramId, label) {
48428
+ const classId = this.optionalClassBinding(member, paramId);
48429
+ if (classId === null) {
48430
+ throw new Error(
48431
+ `${label} must bind its target generic to a Class member.`
48432
+ );
48433
+ }
48434
+ return classId;
48435
+ }
48436
+ /** {@link requireClassBinding} without the throw, for diagnostics-only readers. */
48437
+ optionalClassBinding(member, paramId) {
48380
48438
  const binding = resolveInstanceEnv(
48381
48439
  member.classId,
48382
48440
  member.classArguments,
48383
48441
  this.document.classes
48384
48442
  ).get(paramId);
48385
48443
  const bindingMember = binding?.kind === "member" ? this.memberById.get(binding.memberId) : void 0;
48386
- if (!isMemberClass(bindingMember)) {
48387
- throw new Error(
48388
- `${label} must bind its target generic to a Class member.`
48389
- );
48390
- }
48391
- return bindingMember.classId;
48444
+ return isMemberClass(bindingMember) ? bindingMember.classId : null;
48392
48445
  }
48393
48446
  requireClassMember(value) {
48394
48447
  if (!isMemberClass(value)) throw new Error("Expected Class member.");
@@ -48425,6 +48478,188 @@ var init_animation_clips = __esm({
48425
48478
  }
48426
48479
  return false;
48427
48480
  }
48481
+ // ---------------------------------------------------------------------------
48482
+ // P41 §2.3 SCAFFOLDING — DELETE EVERYTHING BELOW THIS BANNER WHEN P44 LANDS,
48483
+ // together with `collectAnimationClipNestedOwnerWarnings` and its tests. See
48484
+ // that function's doc block for why the check exists and why P44 retires it.
48485
+ // ---------------------------------------------------------------------------
48486
+ collectNestedOwnerWarnings() {
48487
+ const useSiteByClassId = this.nestedChildrenRowUseSites();
48488
+ if (useSiteByClassId.size === 0) return [];
48489
+ const warnings = [];
48490
+ for (const member of this.document.members) {
48491
+ if (!isMemberClass(member)) continue;
48492
+ if (!this.classHasWorldKind(member.classId, "animationClip")) continue;
48493
+ const ownerClassId2 = this.optionalClassBinding(
48494
+ member,
48495
+ WORLD_ANIMATION_CLIP_TARGET_PARAM_ID
48496
+ );
48497
+ if (ownerClassId2 === null) continue;
48498
+ const useSite = useSiteByClassId.get(ownerClassId2);
48499
+ if (useSite === void 0) continue;
48500
+ if (!this.clipDeclaresChildReferences(member)) continue;
48501
+ const ownerClassName = this.classById.get(ownerClassId2)?.name ?? ownerClassId2;
48502
+ const nestedRowClassName = this.classById.get(useSite.rowClassId)?.name ?? useSite.rowClassId;
48503
+ const nesting = useSite.rowClassId === ownerClassId2 ? "which is also used as a nested Children row" : `whose subclass "${nestedRowClassName}" is used as a nested Children row`;
48504
+ warnings.push({
48505
+ clipName: member.name,
48506
+ ownerClassName,
48507
+ nestedOwnerClassName: useSite.ownerClassName,
48508
+ nestedRowClassName,
48509
+ nestedRowValueId: useSite.rowValueId,
48510
+ message: `Animation clip "${member.name}" overrides or tracks children of "${ownerClassName}", ${nesting} (value "${useSite.rowValueId}" in class "${useSite.ownerClassName}"). A nested instance carries re-lowered copies of those rows, so the clip resolves none of them there. Give nested Children rows authored provenance (P44), or declare the child rows through a constructor (P43).`
48511
+ });
48512
+ }
48513
+ return warnings;
48514
+ }
48515
+ /**
48516
+ * Every class an authored `Children` list instantiates as a row, keyed by
48517
+ * that class and each of its ancestors — a nested row of a subclass inherits
48518
+ * the base class's clips, so the base is affected too.
48519
+ *
48520
+ * Both authoring shapes count. A class-default `Children` is one; a
48521
+ * `Children` list authored on a row inside one is the other, and it is the
48522
+ * shape P41 §3 itself writes — `CharacterBody.Children` holds a `HeadPart`
48523
+ * row whose own `Children` holds the hat. Scanning only class defaults would
48524
+ * miss every slot below the first level.
48525
+ *
48526
+ * A layer's directly placed objects hang off `NeoObjectLayerLink.Objects`, a
48527
+ * different member, so a directly placed class is never collected here.
48528
+ */
48529
+ nestedChildrenRowUseSites() {
48530
+ const declaringClasses = /* @__PURE__ */ new Map();
48531
+ for (const schemaClass2 of this.document.classes) {
48532
+ for (const [schemaKey, memberId] of Object.entries(schemaClass2.schema)) {
48533
+ if (typeof memberId !== "string") continue;
48534
+ if (declaringClasses.has(memberId)) continue;
48535
+ declaringClasses.set(memberId, { schemaClass: schemaClass2, schemaKey });
48536
+ }
48537
+ }
48538
+ const sites = [];
48539
+ const pending = [];
48540
+ for (const member of this.document.members) {
48541
+ if (!this.memberDescendsFrom(member.id, WORLD_OBJECT_CHILDREN_MEMBER_ID)) {
48542
+ continue;
48543
+ }
48544
+ const declaration = declaringClasses.get(member.id);
48545
+ if (declaration === void 0) continue;
48546
+ const node = this.resolveDefinitionChild(
48547
+ null,
48548
+ declaration.schemaKey,
48549
+ member
48550
+ );
48551
+ pending.push(
48552
+ ...this.childrenRowUseSites(node, member, declaration.schemaClass.name)
48553
+ );
48554
+ }
48555
+ const visited = /* @__PURE__ */ new Set();
48556
+ for (let index = 0; index < pending.length; index += 1) {
48557
+ const site = pending[index];
48558
+ if (site === void 0) continue;
48559
+ if (visited.has(site.rowValueId)) continue;
48560
+ visited.add(site.rowValueId);
48561
+ sites.push(site);
48562
+ const row = this.valueById.get(site.rowValueId);
48563
+ if (row === void 0) continue;
48564
+ const field = this.field(
48565
+ site.rowClassId,
48566
+ WORLD_OBJECT_CHILDREN_MEMBER_ID
48567
+ );
48568
+ if (field === null) continue;
48569
+ const rowClassName = this.classById.get(site.rowClassId)?.name ?? site.rowClassId;
48570
+ pending.push(
48571
+ ...this.childrenRowUseSites(
48572
+ { ...row, value: this.listFieldIds(row, field) },
48573
+ field.member,
48574
+ rowClassName
48575
+ )
48576
+ );
48577
+ }
48578
+ sites.sort(compareNestedChildrenRowUseSites);
48579
+ const useSiteByClassId = /* @__PURE__ */ new Map();
48580
+ for (const site of sites) {
48581
+ if (useSiteByClassId.has(site.rowClassId)) continue;
48582
+ useSiteByClassId.set(site.rowClassId, site);
48583
+ }
48584
+ for (const site of sites) {
48585
+ for (const classId of this.classAndAncestorIds(site.rowClassId)) {
48586
+ if (useSiteByClassId.has(classId)) continue;
48587
+ useSiteByClassId.set(classId, site);
48588
+ }
48589
+ }
48590
+ return useSiteByClassId;
48591
+ }
48592
+ /** The rows an authored `Children` list holds, as use sites of `ownerClassName`. */
48593
+ childrenRowUseSites(node, listMember, ownerClassName) {
48594
+ if (!Array.isArray(node?.value)) return [];
48595
+ const entryClassId = this.listEntryClassId(listMember);
48596
+ const sites = [];
48597
+ for (const rowValueId of node.value) {
48598
+ if (typeof rowValueId !== "string") continue;
48599
+ const row = this.valueById.get(rowValueId);
48600
+ const rowClassId = typeof row?.classId === "string" ? row.classId : entryClassId;
48601
+ if (rowClassId === void 0) continue;
48602
+ sites.push({ ownerClassName, rowValueId, rowClassId });
48603
+ }
48604
+ return sites;
48605
+ }
48606
+ /** True when the clip names at least one child through a track or override. */
48607
+ clipDeclaresChildReferences(clipMember) {
48608
+ const clipNode = this.optionalMemberRootNode(clipMember);
48609
+ if (clipNode === null) return false;
48610
+ const tracks = this.optionalListRows(
48611
+ clipNode,
48612
+ clipMember.classId,
48613
+ WORLD_ANIMATION_CLIP_TRACKS_MEMBER_ID
48614
+ );
48615
+ if (tracks !== null && tracks.length > 0) return true;
48616
+ const frames = this.optionalListRows(
48617
+ clipNode,
48618
+ clipMember.classId,
48619
+ WORLD_ANIMATION_CLIP_FRAMES_MEMBER_ID
48620
+ ) ?? [];
48621
+ for (const frame of frames) {
48622
+ if (typeof frame.classId !== "string") continue;
48623
+ const childOverrides = this.optionalListRows(
48624
+ frame,
48625
+ frame.classId,
48626
+ WORLD_ANIMATION_FRAME_CHILD_OVERRIDES_MEMBER_ID
48627
+ );
48628
+ if (childOverrides !== null && childOverrides.length > 0) return true;
48629
+ }
48630
+ return false;
48631
+ }
48632
+ /** {@link requireListField} without the throws, for diagnostics-only readers. */
48633
+ optionalListRows(parent, classId, memberId) {
48634
+ const field = this.field(classId, memberId);
48635
+ if (field === null) return null;
48636
+ const ids = this.listFieldIds(parent, field);
48637
+ if (!Array.isArray(ids)) return null;
48638
+ const inferredClassId = this.listEntryClassId(field.member);
48639
+ const rows = [];
48640
+ for (const id2 of ids) {
48641
+ if (typeof id2 !== "string") return null;
48642
+ const entry = this.valueById.get(id2);
48643
+ if (entry === void 0) return null;
48644
+ rows.push(
48645
+ typeof entry.classId === "string" || inferredClassId === void 0 ? entry : { ...entry, classId: inferredClassId }
48646
+ );
48647
+ }
48648
+ return rows;
48649
+ }
48650
+ /** The class itself followed by every ancestor it extends. */
48651
+ classAndAncestorIds(classId) {
48652
+ const ids = [];
48653
+ const visited = /* @__PURE__ */ new Set();
48654
+ let current = this.classById.get(classId);
48655
+ if (current === void 0) return [classId];
48656
+ while (current !== void 0 && !visited.has(current.id)) {
48657
+ visited.add(current.id);
48658
+ ids.push(current.id);
48659
+ current = current.extendsClassId ? this.classById.get(current.extendsClassId) : void 0;
48660
+ }
48661
+ return ids;
48662
+ }
48428
48663
  };
48429
48664
  }
48430
48665
  });
@@ -48703,6 +48938,7 @@ function compareWorkspacePaths(left, right) {
48703
48938
  function computeWorkspaceStatus(workspace, options = {}) {
48704
48939
  const conflictedFiles = [];
48705
48940
  const parseErrors = [];
48941
+ const warnings = [];
48706
48942
  const migrationSources = [];
48707
48943
  const projectSources = [];
48708
48944
  const sourceEntries = options.virtualSourceFiles === void 0 ? listProjectSourceFilesV4(workspace.root).map((filePath) => ({
@@ -48732,6 +48968,7 @@ function computeWorkspaceStatus(workspace, options = {}) {
48732
48968
  changes: [],
48733
48969
  conflictedFiles,
48734
48970
  parseErrors,
48971
+ warnings,
48735
48972
  reconstructed: /* @__PURE__ */ new Map(),
48736
48973
  staticValueSeeds: /* @__PURE__ */ new Map(),
48737
48974
  binaryChanges: [],
@@ -48780,6 +49017,7 @@ function computeWorkspaceStatus(workspace, options = {}) {
48780
49017
  changes: [],
48781
49018
  conflictedFiles,
48782
49019
  parseErrors,
49020
+ warnings,
48783
49021
  reconstructed: /* @__PURE__ */ new Map(),
48784
49022
  staticValueSeeds,
48785
49023
  binaryChanges: [],
@@ -48804,6 +49042,7 @@ function computeWorkspaceStatus(workspace, options = {}) {
48804
49042
  changes: [],
48805
49043
  conflictedFiles,
48806
49044
  parseErrors,
49045
+ warnings,
48807
49046
  reconstructed: /* @__PURE__ */ new Map(),
48808
49047
  staticValueSeeds,
48809
49048
  binaryChanges: [],
@@ -48851,6 +49090,7 @@ function computeWorkspaceStatus(workspace, options = {}) {
48851
49090
  changes: [],
48852
49091
  conflictedFiles,
48853
49092
  parseErrors,
49093
+ warnings,
48854
49094
  reconstructed: /* @__PURE__ */ new Map(),
48855
49095
  staticValueSeeds: /* @__PURE__ */ new Map(),
48856
49096
  binaryChanges: [],
@@ -49067,15 +49307,16 @@ function computeWorkspaceStatus(workspace, options = {}) {
49067
49307
  )) {
49068
49308
  parseErrors.push(new SchemaSourceError(message, "<project-files>", 1, 1));
49069
49309
  }
49310
+ let validatedAnimationRecords = null;
49070
49311
  try {
49071
- validateProspectiveAnimationRecordsV4(
49072
- prospectiveAnimationRecords(
49073
- workspace.state.records,
49074
- reconstructed3,
49075
- changes,
49076
- staticValueSeeds
49077
- )
49312
+ const animationRecords = prospectiveAnimationRecords(
49313
+ workspace.state.records,
49314
+ reconstructed3,
49315
+ changes,
49316
+ staticValueSeeds
49078
49317
  );
49318
+ validateProspectiveAnimationRecordsV4(animationRecords);
49319
+ validatedAnimationRecords = animationRecords;
49079
49320
  } catch (error) {
49080
49321
  parseErrors.push(
49081
49322
  new SchemaSourceError(
@@ -49086,6 +49327,11 @@ function computeWorkspaceStatus(workspace, options = {}) {
49086
49327
  )
49087
49328
  );
49088
49329
  }
49330
+ if (validatedAnimationRecords !== null) {
49331
+ warnings.push(
49332
+ ...collectProspectiveAnimationWarningsV4(validatedAnimationRecords)
49333
+ );
49334
+ }
49089
49335
  for (const binary of binaryChanges) {
49090
49336
  if (binary.action !== "missing-local") continue;
49091
49337
  parseErrors.push(
@@ -49101,6 +49347,7 @@ function computeWorkspaceStatus(workspace, options = {}) {
49101
49347
  changes,
49102
49348
  conflictedFiles,
49103
49349
  parseErrors,
49350
+ warnings,
49104
49351
  reconstructed: reconstructed3,
49105
49352
  staticValueSeeds,
49106
49353
  binaryChanges,
@@ -49108,11 +49355,27 @@ function computeWorkspaceStatus(workspace, options = {}) {
49108
49355
  };
49109
49356
  }
49110
49357
  function validateProspectiveAnimationRecordsV4(records2) {
49358
+ const document = prospectiveAnimationDocumentV4(records2);
49359
+ if (document === null) return;
49360
+ assertAnimationClipDocumentValid(document);
49361
+ }
49362
+ function collectProspectiveAnimationWarningsV4(records2) {
49363
+ try {
49364
+ const document = prospectiveAnimationDocumentV4(records2);
49365
+ if (document === null) return [];
49366
+ return collectAnimationClipNestedOwnerWarnings(document).map(
49367
+ (warning) => warning.message
49368
+ );
49369
+ } catch {
49370
+ return [];
49371
+ }
49372
+ }
49373
+ function prospectiveAnimationDocumentV4(records2) {
49111
49374
  const candidates = [...records2];
49112
49375
  const hasAnimationSchema = candidates.some(
49113
49376
  (record3) => record3.recordKind === "class" && (Array.isArray(record3.data.constructorProjections) || isObjectRecord2(record3.data.system) && typeof record3.data.system.worldKind === "string" && record3.data.system.worldKind.startsWith("animation"))
49114
49377
  );
49115
- if (!hasAnimationSchema) return;
49378
+ if (!hasAnimationSchema) return null;
49116
49379
  let project;
49117
49380
  const classes = [];
49118
49381
  const members = [];
@@ -49148,8 +49411,8 @@ function validateProspectiveAnimationRecordsV4(records2) {
49148
49411
  values.push(record3.data);
49149
49412
  }
49150
49413
  }
49151
- if (project === void 0) return;
49152
- assertAnimationClipDocumentValid({ project, classes, members, values });
49414
+ if (project === void 0) return null;
49415
+ return { project, classes, members, values };
49153
49416
  }
49154
49417
  function serverEnvelope(stored) {
49155
49418
  if (!isObjectRecord2(stored)) return {};
@@ -51477,7 +51740,7 @@ var init_http = __esm({
51477
51740
  }
51478
51741
  return new _NeoApiClient(apiBaseUrl, token);
51479
51742
  }
51480
- async post(path, body, headers) {
51743
+ async post(path, body, headers, options = {}) {
51481
51744
  const url = new URL(path, this.apiBaseUrl).toString();
51482
51745
  const response = await fetch(url, {
51483
51746
  method: "POST",
@@ -51486,7 +51749,8 @@ var init_http = __esm({
51486
51749
  "Content-Type": "application/json",
51487
51750
  ...headers
51488
51751
  },
51489
- body: body === void 0 ? void 0 : JSON.stringify(body)
51752
+ body: body === void 0 ? void 0 : JSON.stringify(body),
51753
+ signal: options.signal
51490
51754
  });
51491
51755
  const text = await response.text();
51492
51756
  let parsed = null;
@@ -59572,13 +59836,13 @@ function ownedObjectChildMember(row, sourceMember, key, ctx) {
59572
59836
  return memberForCustomSchemaValue(classId, key, ctx);
59573
59837
  }
59574
59838
  function freshCloneValueId() {
59575
- const randomUUID6 = globalThis.crypto?.randomUUID;
59576
- if (randomUUID6 === void 0) {
59839
+ const randomUUID7 = globalThis.crypto?.randomUUID;
59840
+ if (randomUUID7 === void 0) {
59577
59841
  throw new NSGetterRuntimeError(
59578
59842
  "Class.Clone cannot mint a value id because crypto.randomUUID is unavailable."
59579
59843
  );
59580
59844
  }
59581
- return randomUUID6.call(globalThis.crypto);
59845
+ return randomUUID7.call(globalThis.crypto);
59582
59846
  }
59583
59847
  function parseDialogueMemoryPointer(pointer) {
59584
59848
  if (typeof pointer !== "string") return null;
@@ -66504,80 +66768,154 @@ function ensureProjectFileBinaryChangesV4(args) {
66504
66768
  });
66505
66769
  }
66506
66770
  }
66771
+ function prepareProjectFilePushesV4(args) {
66772
+ const prepared = [];
66773
+ for (const binary of args.binaryChanges) {
66774
+ if (binary.action !== "create" && binary.action !== "upload") continue;
66775
+ const recordId = args.assignedIds.get(binary.fileId) ?? binary.fileId;
66776
+ const change = args.changes.find(
66777
+ (candidate) => candidate.recordKind === "project-file" && candidate.recordId === recordId
66778
+ );
66779
+ if (change === void 0) {
66780
+ throw new Error(
66781
+ `Project file ${recordId} has upload bytes but no source change.`
66782
+ );
66783
+ }
66784
+ if (!isObjectRecord2(change.nextData)) {
66785
+ throw new Error(
66786
+ `Project file ${recordId} has upload bytes but its source change has no record data.`
66787
+ );
66788
+ }
66789
+ const absolute = join14(args.workspace.root, binary.path);
66790
+ const bytes = new Uint8Array(readFileSync12(absolute));
66791
+ const digest = sha256Bytes(bytes);
66792
+ if (binary.localSha256 !== null && digest !== binary.localSha256) {
66793
+ throw new Error(
66794
+ `Project file ${binary.path} changed after status was computed; run push again.`
66795
+ );
66796
+ }
66797
+ const mimeType = binary.mimeType;
66798
+ if (mimeType === null) {
66799
+ throw new Error(`Project file ${binary.path} has no MIME type.`);
66800
+ }
66801
+ prepared.push({
66802
+ uploadToken: `${recordId}-${prepared.length}`,
66803
+ recordId,
66804
+ replaceFileId: binary.action === "upload" ? recordId : null,
66805
+ name: basename2(binary.path),
66806
+ fileType: binary.kind,
66807
+ mimeType,
66808
+ byteLength: bytes.byteLength,
66809
+ contentSha256: digest,
66810
+ audioDurationSeconds: binary.kind === "audio" && typeof change.nextData.audioDurationSeconds === "number" ? change.nextData.audioDurationSeconds : null,
66811
+ bytes
66812
+ });
66813
+ }
66814
+ return prepared;
66815
+ }
66507
66816
  async function stageProjectFilePushesV4(args) {
66817
+ const prepared = args.prepared ?? prepareProjectFilePushesV4({
66818
+ workspace: args.workspace,
66819
+ changes: args.changes,
66820
+ binaryChanges: args.binaryChanges,
66821
+ assignedIds: args.assignedIds
66822
+ });
66508
66823
  const staged = [];
66509
66824
  const cleanupKeys = [];
66510
66825
  const put = args.put ?? fetch;
66826
+ const interruptController = new AbortController();
66827
+ const interrupt = () => interruptController.abort(new ProjectFilePushCancelledError());
66828
+ process.once("SIGINT", interrupt);
66829
+ const signal = combineSignals(args.signal, interruptController.signal);
66830
+ const totalBytes = prepared.reduce((sum, file) => sum + file.byteLength, 0);
66831
+ let completedBytes = 0;
66832
+ let completedFiles = 0;
66833
+ const report = () => args.onProgress?.({
66834
+ completedFiles,
66835
+ totalFiles: prepared.length,
66836
+ completedBytes,
66837
+ totalBytes
66838
+ });
66839
+ report();
66511
66840
  try {
66512
- for (const binary of args.binaryChanges) {
66513
- if (binary.action !== "create" && binary.action !== "upload") continue;
66514
- const recordId = args.assignedIds.get(binary.fileId) ?? binary.fileId;
66515
- const change = args.changes.find(
66516
- (candidate) => candidate.recordKind === "project-file" && candidate.recordId === recordId
66841
+ for (let offset = 0; offset < prepared.length; offset += PROJECT_FILE_UPLOAD_BATCH_SIZE) {
66842
+ const batch = prepared.slice(
66843
+ offset,
66844
+ offset + PROJECT_FILE_UPLOAD_BATCH_SIZE
66517
66845
  );
66518
- if (change === void 0 || !isObjectRecord2(change.nextData)) {
66519
- throw new Error(
66520
- `Project file ${recordId} has upload bytes but no source change.`
66521
- );
66522
- }
66523
- const absolute = join14(args.workspace.root, binary.path);
66524
- const bytes = new Uint8Array(readFileSync12(absolute));
66525
- const digest = sha256Bytes(bytes);
66526
- if (binary.localSha256 !== null && digest !== binary.localSha256) {
66527
- throw new Error(
66528
- `Project file ${binary.path} changed after status was computed; run push again.`
66529
- );
66530
- }
66531
- const mimeType = binary.mimeType;
66532
- if (mimeType === null) {
66533
- throw new Error(`Project file ${binary.path} has no MIME type.`);
66534
- }
66535
- const name = basename2(binary.path);
66536
- const replaceFileId = binary.action === "upload" ? recordId : null;
66537
- const presign = await args.client.post(
66846
+ const presign = await postWithTimeout(
66847
+ args.client,
66538
66848
  versionPath2(args.workspace, "upload"),
66539
66849
  {
66540
66850
  route: "projectFile",
66541
66851
  metadata: {
66542
- projectFileId: recordId,
66543
- replaceFileId,
66544
- contentSha256: digest,
66545
- deferredSourceCommit: true
66852
+ deferredSourceCommit: true,
66853
+ uploads: batch.map((file) => ({
66854
+ uploadToken: file.uploadToken,
66855
+ projectFileId: file.recordId,
66856
+ replaceFileId: file.replaceFileId,
66857
+ name: file.name,
66858
+ contentSha256: file.contentSha256
66859
+ }))
66546
66860
  },
66547
- files: [{ name, size: bytes.byteLength, type: mimeType }]
66548
- }
66861
+ files: batch.map((file) => ({
66862
+ name: file.uploadToken,
66863
+ size: file.byteLength,
66864
+ type: file.mimeType
66865
+ }))
66866
+ },
66867
+ signal
66549
66868
  );
66550
- const entry = Array.isArray(presign.files) ? presign.files.find(isObjectRecord2) : void 0;
66551
- const fileInfo = isObjectRecord2(entry?.file) ? entry.file : {};
66552
- const objectInfo = isObjectRecord2(fileInfo.objectInfo) ? fileInfo.objectInfo : {};
66553
- if (typeof entry?.signedUrl !== "string" || typeof objectInfo.key !== "string") {
66554
- throw new Error("Upload presign response is missing signedUrl/key.");
66869
+ const entries = readPresignEntries(presign, batch);
66870
+ for (const entry of entries) cleanupKeys.push(entry.storageKey);
66871
+ const batchController = new AbortController();
66872
+ try {
66873
+ await mapWithConcurrency(entries, UPLOAD_CONCURRENCY, async (entry) => {
66874
+ try {
66875
+ await putWithRetry(
66876
+ put,
66877
+ entry,
66878
+ combineSignals(signal, batchController.signal)
66879
+ );
66880
+ } catch (error) {
66881
+ batchController.abort(error);
66882
+ throw error;
66883
+ }
66884
+ staged.push({
66885
+ uploadToken: entry.file.uploadToken,
66886
+ file: {
66887
+ recordId: entry.file.recordId,
66888
+ replaceFileId: entry.file.replaceFileId,
66889
+ name: entry.file.name,
66890
+ fileType: entry.file.fileType,
66891
+ mimeType: entry.file.mimeType,
66892
+ byteLength: entry.file.byteLength,
66893
+ storageKey: entry.storageKey,
66894
+ contentSha256: entry.file.contentSha256,
66895
+ audioDurationSeconds: entry.file.audioDurationSeconds
66896
+ }
66897
+ });
66898
+ completedFiles += 1;
66899
+ completedBytes += entry.file.byteLength;
66900
+ report();
66901
+ });
66902
+ } catch (error) {
66903
+ batchController.abort(error);
66904
+ throw error;
66555
66905
  }
66556
- cleanupKeys.push(objectInfo.key);
66557
- const headers = uploadHeaders(entry, objectInfo, mimeType);
66558
- const response = await put(entry.signedUrl, {
66559
- method: "PUT",
66560
- headers,
66561
- body: bytes
66562
- });
66563
- if (!response.ok) {
66906
+ }
66907
+ const byUploadToken = new Map(
66908
+ staged.map((entry) => [entry.uploadToken, entry.file])
66909
+ );
66910
+ return prepared.map((file) => {
66911
+ const result = byUploadToken.get(file.uploadToken);
66912
+ if (result === void 0) {
66564
66913
  throw new Error(
66565
- `Storage PUT failed (${response.status}): ${await response.text()}`
66914
+ `Uploaded project file ${file.name} (${file.uploadToken}) is missing from the staged result.`
66566
66915
  );
66567
66916
  }
66568
- staged.push({
66569
- recordId,
66570
- replaceFileId,
66571
- name,
66572
- fileType: binary.kind,
66573
- mimeType,
66574
- byteLength: bytes.byteLength,
66575
- storageKey: objectInfo.key,
66576
- contentSha256: digest,
66577
- audioDurationSeconds: binary.kind === "audio" && typeof change.nextData.audioDurationSeconds === "number" ? change.nextData.audioDurationSeconds : null
66578
- });
66579
- }
66580
- return staged;
66917
+ return result;
66918
+ });
66581
66919
  } catch (error) {
66582
66920
  if (cleanupKeys.length > 0) {
66583
66921
  try {
@@ -66588,8 +66926,145 @@ async function stageProjectFilePushesV4(args) {
66588
66926
  } catch {
66589
66927
  }
66590
66928
  }
66929
+ if (interruptController.signal.aborted || args.signal?.aborted === true) {
66930
+ throw new ProjectFilePushCancelledError();
66931
+ }
66591
66932
  throw error;
66933
+ } finally {
66934
+ process.removeListener("SIGINT", interrupt);
66935
+ }
66936
+ }
66937
+ function readPresignEntries(presign, batch) {
66938
+ const entries = Array.isArray(presign.files) ? presign.files.filter(isObjectRecord2) : [];
66939
+ return batch.map((file) => {
66940
+ const entry = entries.find((candidate) => {
66941
+ const info = isObjectRecord2(candidate.file) ? candidate.file : {};
66942
+ return info.name === file.uploadToken;
66943
+ });
66944
+ const fileInfo = isObjectRecord2(entry?.file) ? entry.file : {};
66945
+ const objectInfo = isObjectRecord2(fileInfo.objectInfo) ? fileInfo.objectInfo : {};
66946
+ if (typeof entry?.signedUrl !== "string") {
66947
+ throw new Error(
66948
+ `Upload presign response is missing signedUrl for ${file.name}.`
66949
+ );
66950
+ }
66951
+ if (typeof objectInfo.key !== "string") {
66952
+ throw new Error(
66953
+ `Upload presign response is missing a storage key for ${file.name}.`
66954
+ );
66955
+ }
66956
+ return {
66957
+ file,
66958
+ signedUrl: entry.signedUrl,
66959
+ storageKey: objectInfo.key,
66960
+ headers: uploadHeaders(entry, objectInfo, file.mimeType)
66961
+ };
66962
+ });
66963
+ }
66964
+ async function postWithTimeout(client, path, body, signal) {
66965
+ for (let attempt = 1; attempt <= PRESIGN_ATTEMPTS; attempt += 1) {
66966
+ try {
66967
+ return await client.post(path, body, void 0, {
66968
+ signal: combineSignals(signal, AbortSignal.timeout(PRESIGN_TIMEOUT_MS))
66969
+ });
66970
+ } catch (error) {
66971
+ if (signal.aborted || !isRetryableRequestError(error) || attempt === PRESIGN_ATTEMPTS) {
66972
+ throw error;
66973
+ }
66974
+ await waitForRetry(attempt, signal);
66975
+ }
66976
+ }
66977
+ throw new Error("Upload presign failed after all retry attempts.");
66978
+ }
66979
+ async function putWithRetry(put, entry, signal) {
66980
+ let lastError;
66981
+ for (let attempt = 1; attempt <= STORAGE_PUT_ATTEMPTS; attempt += 1) {
66982
+ let response;
66983
+ try {
66984
+ response = await put(entry.signedUrl, {
66985
+ method: "PUT",
66986
+ headers: entry.headers,
66987
+ body: Buffer.from(entry.file.bytes),
66988
+ signal: combineSignals(
66989
+ signal,
66990
+ AbortSignal.timeout(STORAGE_PUT_TIMEOUT_MS)
66991
+ )
66992
+ });
66993
+ } catch (error) {
66994
+ if (signal.aborted || attempt === STORAGE_PUT_ATTEMPTS) throw error;
66995
+ lastError = error;
66996
+ await waitForRetry(attempt, signal);
66997
+ continue;
66998
+ }
66999
+ if (response.ok) return;
67000
+ if (isRetryableStatus(response.status) && attempt < STORAGE_PUT_ATTEMPTS) {
67001
+ lastError = new Error(`Storage PUT failed (${response.status}).`);
67002
+ await waitForRetry(attempt, signal);
67003
+ continue;
67004
+ }
67005
+ throw new Error(
67006
+ `Storage PUT failed (${response.status}): ${await response.text()}`
67007
+ );
67008
+ }
67009
+ throw lastError instanceof Error ? lastError : new Error(`Storage PUT failed for ${entry.file.name}.`);
67010
+ }
67011
+ function isRetryableStatus(status) {
67012
+ return status === 408 || status === 429 || status >= 500;
67013
+ }
67014
+ function isRetryableRequestError(error) {
67015
+ if (isObjectRecord2(error) && typeof error.status === "number") {
67016
+ return isRetryableStatus(error.status);
67017
+ }
67018
+ if (isObjectRecord2(error) && error.name === "TimeoutError") return true;
67019
+ if (!(error instanceof TypeError) || error.message !== "fetch failed") {
67020
+ return false;
67021
+ }
67022
+ if (!isObjectRecord2(error.cause) || typeof error.cause.code !== "string") {
67023
+ return false;
66592
67024
  }
67025
+ return RETRYABLE_NETWORK_ERROR_CODES.has(error.cause.code);
67026
+ }
67027
+ async function waitForRetry(attempt, signal) {
67028
+ await new Promise((resolve4, reject) => {
67029
+ const abort = () => {
67030
+ clearTimeout(timeout);
67031
+ reject(signal.reason);
67032
+ };
67033
+ const timeout = setTimeout(
67034
+ () => {
67035
+ signal.removeEventListener("abort", abort);
67036
+ resolve4();
67037
+ },
67038
+ 200 * 2 ** (attempt - 1)
67039
+ );
67040
+ signal.addEventListener("abort", abort, { once: true });
67041
+ });
67042
+ }
67043
+ async function mapWithConcurrency(values, concurrency, run) {
67044
+ let nextIndex = 0;
67045
+ let firstError;
67046
+ const worker = async () => {
67047
+ while (firstError === void 0) {
67048
+ const index = nextIndex;
67049
+ nextIndex += 1;
67050
+ if (index >= values.length) return;
67051
+ try {
67052
+ await run(values[index]);
67053
+ } catch (error) {
67054
+ firstError ??= error;
67055
+ }
67056
+ }
67057
+ };
67058
+ await Promise.all(
67059
+ Array.from({ length: Math.min(concurrency, values.length) }, worker)
67060
+ );
67061
+ if (firstError !== void 0) throw firstError;
67062
+ }
67063
+ function combineSignals(...signals) {
67064
+ const defined = signals.filter(
67065
+ (signal) => signal !== void 0
67066
+ );
67067
+ return defined.length === 1 ? defined[0] : AbortSignal.any(defined);
66593
67068
  }
66594
67069
  function uploadHeaders(entry, objectInfo, mimeType) {
66595
67070
  const headers = { "Content-Type": mimeType };
@@ -66611,11 +67086,431 @@ function uploadHeaders(entry, objectInfo, mimeType) {
66611
67086
  function versionPath2(workspace, suffix) {
66612
67087
  return `/api/projects/${workspace.config.projectId}/versions/${workspace.config.versionId}/${suffix}`;
66613
67088
  }
67089
+ var UPLOAD_CONCURRENCY, PRESIGN_TIMEOUT_MS, PRESIGN_ATTEMPTS, STORAGE_PUT_TIMEOUT_MS, STORAGE_PUT_ATTEMPTS, ProjectFilePushCancelledError, RETRYABLE_NETWORK_ERROR_CODES;
66614
67090
  var init_project_file_push = __esm({
66615
67091
  "src/project-source/project-file-push.ts"() {
66616
67092
  "use strict";
66617
67093
  init_project_files();
66618
67094
  init_projection();
67095
+ init_src();
67096
+ UPLOAD_CONCURRENCY = 6;
67097
+ PRESIGN_TIMEOUT_MS = 3e4;
67098
+ PRESIGN_ATTEMPTS = 3;
67099
+ STORAGE_PUT_TIMEOUT_MS = 12e4;
67100
+ STORAGE_PUT_ATTEMPTS = 3;
67101
+ ProjectFilePushCancelledError = class extends Error {
67102
+ constructor() {
67103
+ super("Project file upload was cancelled.");
67104
+ this.name = "ProjectFilePushCancelledError";
67105
+ }
67106
+ };
67107
+ RETRYABLE_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
67108
+ "ECONNRESET",
67109
+ "ECONNREFUSED",
67110
+ "EHOSTUNREACH",
67111
+ "ENETUNREACH",
67112
+ "ENOTFOUND",
67113
+ "EPIPE",
67114
+ "ETIMEDOUT"
67115
+ ]);
67116
+ }
67117
+ });
67118
+
67119
+ // src/project-source/trusted-commit-verification.ts
67120
+ import { randomUUID as randomUUID5 } from "node:crypto";
67121
+ import { tmpdir } from "node:os";
67122
+ import { join as join15 } from "node:path";
67123
+ function verifyProjectSourceCommitAgainstStateV4(args) {
67124
+ const root = join15(tmpdir(), `neo-source-verify-virtual-${randomUUID5()}`);
67125
+ const workspace = {
67126
+ root,
67127
+ config: {
67128
+ formatVersion: 4,
67129
+ apiBaseUrl: "https://trusted-server.invalid",
67130
+ projectId: args.projectId,
67131
+ versionId: args.versionId,
67132
+ profile: "editor"
67133
+ },
67134
+ state: { records: { ...args.stateRecords } }
67135
+ };
67136
+ const assignments = validateAssignments(args.pendingIdAssignments);
67137
+ const pendingIdByAssignedId = new Map(
67138
+ [...assignments].map(([pendingId2, assignedId]) => [assignedId, pendingId2])
67139
+ );
67140
+ const trustedPendingProjectFiles = /* @__PURE__ */ new Map();
67141
+ const stagedRecordIds = /* @__PURE__ */ new Set();
67142
+ for (const file of args.stagedFiles) {
67143
+ if (stagedRecordIds.has(file.recordId)) {
67144
+ throw new ProjectSourceCommitVerificationError(
67145
+ "Source commit contains duplicate verified staged project files."
67146
+ );
67147
+ }
67148
+ stagedRecordIds.add(file.recordId);
67149
+ const metadata = {
67150
+ mimeType: file.mimeType,
67151
+ byteLength: file.byteLength,
67152
+ sha256: file.contentSha256
67153
+ };
67154
+ trustedPendingProjectFiles.set(file.recordId, metadata);
67155
+ const pendingId2 = pendingIdByAssignedId.get(file.recordId);
67156
+ if (pendingId2 !== void 0) {
67157
+ trustedPendingProjectFiles.set(pendingId2, metadata);
67158
+ }
67159
+ }
67160
+ const status = computeWorkspaceStatus(workspace, {
67161
+ skipProjectBinaryInspection: true,
67162
+ writeProjectAnalysisCache: () => void 0,
67163
+ trustedPendingProjectFiles,
67164
+ virtualSourceFiles: args.files
67165
+ });
67166
+ const blockingErrors = status.parseErrors.filter(isBlockingSchemaSourceError);
67167
+ if (status.conflictedFiles.length > 0 || blockingErrors.length > 0) {
67168
+ const first = blockingErrors[0];
67169
+ throw new ProjectSourceCommitVerificationError(
67170
+ first ? `Trusted source lowering failed: ${first.file}:${first.line}:${first.column} ${first.message}` : `Trusted source lowering found conflict markers in ${status.conflictedFiles[0]}.`
67171
+ );
67172
+ }
67173
+ const usedAssignments = /* @__PURE__ */ new Set();
67174
+ const expectedChanges = status.changes.map((change) => {
67175
+ const rewrittenData = change.nextData === void 0 ? void 0 : rewritePending(change.nextData, assignments, usedAssignments);
67176
+ return {
67177
+ recordKind: change.recordKind,
67178
+ recordId: rewritePending(change.recordId, assignments, usedAssignments),
67179
+ operation: change.kind,
67180
+ // Source syntax does not author project ownership. The CLI stamps the
67181
+ // authenticated project id on creates immediately before transport;
67182
+ // reproduce that server-known field instead of comparing the
67183
+ // lowerer's empty construction placeholder.
67184
+ nextData: change.kind === "create" && isObjectRecord2(rewrittenData) ? { ...rewrittenData, projectId: args.projectId } : rewrittenData,
67185
+ expectedBaseContentHash: change.kind === "create" ? null : change.casBaseHash ?? null
67186
+ };
67187
+ });
67188
+ const expectedSeeds = [...status.staticValueSeeds].map(
67189
+ ([memberId, seed]) => ({
67190
+ memberId: rewritePending(memberId, assignments, usedAssignments),
67191
+ value: rewritePending(seed.value, assignments, usedAssignments),
67192
+ classId: seed.classId === null ? null : rewritePending(seed.classId, assignments, usedAssignments),
67193
+ ...seed.valueId === void 0 ? {} : {
67194
+ valueId: rewritePending(seed.valueId, assignments, usedAssignments)
67195
+ },
67196
+ ...seed.values === void 0 ? {} : {
67197
+ values: rewritePending(seed.values, assignments, usedAssignments)
67198
+ },
67199
+ ...seed.bindingMembers === void 0 ? {} : {
67200
+ bindingMembers: rewritePending(
67201
+ seed.bindingMembers,
67202
+ assignments,
67203
+ usedAssignments
67204
+ )
67205
+ },
67206
+ ...seed.localizedTexts === void 0 ? {} : {
67207
+ localizedTexts: rewritePending(
67208
+ seed.localizedTexts,
67209
+ assignments,
67210
+ usedAssignments
67211
+ )
67212
+ }
67213
+ })
67214
+ );
67215
+ for (const pendingId2 of assignments.keys()) {
67216
+ if (!usedAssignments.has(pendingId2)) {
67217
+ throw new ProjectSourceCommitVerificationError(
67218
+ `Pending id assignment ${pendingId2} is not present in the verified source manifest.`
67219
+ );
67220
+ }
67221
+ }
67222
+ compareChanges(expectedChanges, args.changes);
67223
+ compareSeeds(expectedSeeds, args.staticValueSeeds);
67224
+ }
67225
+ function validateAssignments(value) {
67226
+ const assignments = /* @__PURE__ */ new Map();
67227
+ const assignedIds = /* @__PURE__ */ new Set();
67228
+ for (const [pendingId2, assignedId] of Object.entries(value)) {
67229
+ if (!pendingId2.startsWith("__pending__:")) {
67230
+ throw new ProjectSourceCommitVerificationError(
67231
+ `Pending id assignment key ${pendingId2} is not a pending identity.`
67232
+ );
67233
+ }
67234
+ if (pendingMemberValueMemberId(pendingId2) === null) {
67235
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(
67236
+ assignedId
67237
+ )) {
67238
+ throw new ProjectSourceCommitVerificationError(
67239
+ `Pending id assignment for ${pendingId2} is not a UUID v4.`
67240
+ );
67241
+ }
67242
+ }
67243
+ if (assignedIds.has(assignedId)) {
67244
+ throw new ProjectSourceCommitVerificationError(
67245
+ `Pending id assignments reuse durable id ${assignedId}.`
67246
+ );
67247
+ }
67248
+ assignments.set(pendingId2, assignedId);
67249
+ assignedIds.add(assignedId);
67250
+ }
67251
+ assertMemberValueAssignmentsAreDerived(assignments);
67252
+ return assignments;
67253
+ }
67254
+ function assertMemberValueAssignmentsAreDerived(assignments) {
67255
+ for (const [pendingId2, assignedId] of assignments) {
67256
+ const memberLocator = pendingMemberValueMemberId(pendingId2);
67257
+ if (memberLocator === null) continue;
67258
+ const memberId = memberLocator.startsWith("__pending__:") ? assignments.get(memberLocator) : memberLocator;
67259
+ if (memberId === void 0) {
67260
+ throw new ProjectSourceCommitVerificationError(
67261
+ `Member value assignment ${pendingId2} names member ${memberLocator}, which has no pending id assignment.`
67262
+ );
67263
+ }
67264
+ const derived = derivedMemberValueId(memberId);
67265
+ if (assignedId !== derived) {
67266
+ throw new ProjectSourceCommitVerificationError(
67267
+ `Member value assignment ${pendingId2} is ${assignedId}, but member ${memberId} owns value ${derived}.`
67268
+ );
67269
+ }
67270
+ }
67271
+ }
67272
+ function positionIndependentPendingKey(pendingId2) {
67273
+ const parts = pendingId2.split(":");
67274
+ if (parts.length !== 6) return null;
67275
+ const [prefix, kind, uri, line, character, label] = parts;
67276
+ if (prefix !== "__pending__") return null;
67277
+ if (!/^\d+$/u.test(line) || !/^\d+$/u.test(character)) return null;
67278
+ return `${prefix}:${kind}:${uri}:${label}`;
67279
+ }
67280
+ function positionIndependentAssignments(assignments) {
67281
+ const candidates = /* @__PURE__ */ new Map();
67282
+ for (const pendingId2 of assignments.keys()) {
67283
+ const key = positionIndependentPendingKey(pendingId2);
67284
+ if (key === null) continue;
67285
+ candidates.set(key, [...candidates.get(key) ?? [], pendingId2]);
67286
+ }
67287
+ const resolved = /* @__PURE__ */ new Map();
67288
+ for (const [key, pendingIds] of candidates) {
67289
+ if (pendingIds.length !== 1) continue;
67290
+ resolved.set(key, pendingIds[0]);
67291
+ }
67292
+ return resolved;
67293
+ }
67294
+ function rewritePending(value, assignments, used, byPosition) {
67295
+ const positionless = byPosition ?? positionIndependentAssignments(assignments);
67296
+ if (typeof value === "string") {
67297
+ const exact = assignments.get(value);
67298
+ if (exact !== void 0) {
67299
+ used.add(value);
67300
+ return exact;
67301
+ }
67302
+ if (value.startsWith("__pending__:")) {
67303
+ const key = positionIndependentPendingKey(value);
67304
+ const relocated = key === null ? void 0 : positionless.get(key);
67305
+ if (relocated !== void 0) {
67306
+ used.add(relocated);
67307
+ return assignments.get(relocated);
67308
+ }
67309
+ throw new ProjectSourceCommitVerificationError(
67310
+ `Verified source identity ${value} has no pending id assignment.`
67311
+ );
67312
+ }
67313
+ let rewritten = value;
67314
+ for (const [pendingId2, assignedId] of assignments) {
67315
+ if (!rewritten.includes(pendingId2)) continue;
67316
+ rewritten = rewritten.replaceAll(pendingId2, assignedId);
67317
+ used.add(pendingId2);
67318
+ }
67319
+ return rewritten;
67320
+ }
67321
+ if (Array.isArray(value)) {
67322
+ return value.map(
67323
+ (entry) => rewritePending(entry, assignments, used, positionless)
67324
+ );
67325
+ }
67326
+ if (isObjectRecord2(value)) {
67327
+ const result = {};
67328
+ for (const [key, entry] of Object.entries(value)) {
67329
+ const rewrittenKey = key.startsWith("__pending__:") ? rewritePending(key, assignments, used, positionless) : key;
67330
+ result[rewrittenKey] = rewritePending(
67331
+ entry,
67332
+ assignments,
67333
+ used,
67334
+ positionless
67335
+ );
67336
+ }
67337
+ return result;
67338
+ }
67339
+ return value;
67340
+ }
67341
+ function compareChanges(expected, received) {
67342
+ const expectedByKey = new Map(
67343
+ expected.map((change) => [
67344
+ `${change.recordKind}:${change.recordId}`,
67345
+ change
67346
+ ])
67347
+ );
67348
+ const receivedByKey = new Map(
67349
+ received.map((change) => [
67350
+ `${change.recordKind}:${change.recordId}`,
67351
+ change
67352
+ ])
67353
+ );
67354
+ if (expectedByKey.size !== expected.length || receivedByKey.size !== received.length) {
67355
+ throw new ProjectSourceCommitVerificationError(
67356
+ "Source commit contains duplicate semantic record changes."
67357
+ );
67358
+ }
67359
+ for (const [key, expectedChange] of expectedByKey) {
67360
+ const actual = receivedByKey.get(key);
67361
+ if (actual === void 0) {
67362
+ throw new ProjectSourceCommitVerificationError(
67363
+ `Source commit omitted semantic change ${key}.`
67364
+ );
67365
+ }
67366
+ if (actual.operation !== expectedChange.operation || (actual.expectedBaseContentHash ?? null) !== expectedChange.expectedBaseContentHash) {
67367
+ throw new ProjectSourceCommitVerificationError(
67368
+ `Source commit changed operation or CAS base for ${key}.`
67369
+ );
67370
+ }
67371
+ const expectedData = comparisonData(
67372
+ expectedChange.recordKind,
67373
+ expectedChange.nextData
67374
+ );
67375
+ const actualData = comparisonData(actual.recordKind, actual.nextData);
67376
+ if (canonicalStringify(expectedData) !== canonicalStringify(actualData)) {
67377
+ const difference = firstDifference(expectedData, actualData);
67378
+ throw new ProjectSourceCommitVerificationError(
67379
+ `Source commit nextData for ${key} does not match trusted lowering${difference === null ? "." : ` at ${difference.path}: trusted ${formatDifferenceValue(difference.trusted)}, submitted ${formatDifferenceValue(difference.submitted)}.`}`
67380
+ );
67381
+ }
67382
+ if (!isObjectRecord2(actual.intent) || actual.intent.type !== `${actual.recordKind}.${actual.operation}`) {
67383
+ throw new ProjectSourceCommitVerificationError(
67384
+ `Source commit intent for ${key} does not match its semantic operation.`
67385
+ );
67386
+ }
67387
+ receivedByKey.delete(key);
67388
+ }
67389
+ const extra = receivedByKey.keys().next().value;
67390
+ if (extra !== void 0) {
67391
+ throw new ProjectSourceCommitVerificationError(
67392
+ `Source commit added semantic change ${extra} absent from trusted lowering.`
67393
+ );
67394
+ }
67395
+ }
67396
+ function firstDifference(trusted, submitted, path = "$") {
67397
+ if (Object.is(trusted, submitted)) return null;
67398
+ if (Array.isArray(trusted) && Array.isArray(submitted)) {
67399
+ const count = Math.max(trusted.length, submitted.length);
67400
+ for (let index = 0; index < count; index += 1) {
67401
+ if (index >= trusted.length) {
67402
+ return {
67403
+ path: `${path}[${index}]`,
67404
+ trusted: ABSENT_DIFFERENCE_VALUE,
67405
+ submitted: submitted[index]
67406
+ };
67407
+ }
67408
+ if (index >= submitted.length) {
67409
+ return {
67410
+ path: `${path}[${index}]`,
67411
+ trusted: trusted[index],
67412
+ submitted: ABSENT_DIFFERENCE_VALUE
67413
+ };
67414
+ }
67415
+ const difference = firstDifference(
67416
+ trusted[index],
67417
+ submitted[index],
67418
+ `${path}[${index}]`
67419
+ );
67420
+ if (difference !== null) return difference;
67421
+ }
67422
+ }
67423
+ if (isObjectRecord2(trusted) && isObjectRecord2(submitted)) {
67424
+ const keys = [
67425
+ .../* @__PURE__ */ new Set([...Object.keys(trusted), ...Object.keys(submitted)])
67426
+ ].sort();
67427
+ for (const key of keys) {
67428
+ const trustedHasKey = Object.hasOwn(trusted, key);
67429
+ const submittedHasKey = Object.hasOwn(submitted, key);
67430
+ const childPath = `${path}.${key}`;
67431
+ if (!trustedHasKey || !submittedHasKey) {
67432
+ return {
67433
+ path: childPath,
67434
+ trusted: trustedHasKey ? trusted[key] : ABSENT_DIFFERENCE_VALUE,
67435
+ submitted: submittedHasKey ? submitted[key] : ABSENT_DIFFERENCE_VALUE
67436
+ };
67437
+ }
67438
+ const difference = firstDifference(
67439
+ trusted[key],
67440
+ submitted[key],
67441
+ childPath
67442
+ );
67443
+ if (difference !== null) return difference;
67444
+ }
67445
+ }
67446
+ return { path, trusted, submitted };
67447
+ }
67448
+ function formatDifferenceValue(value) {
67449
+ if (value === ABSENT_DIFFERENCE_VALUE) return "<absent>";
67450
+ const serialized = JSON.stringify(value);
67451
+ const rendered = serialized === void 0 ? String(value) : serialized;
67452
+ return rendered.length <= 160 ? rendered : `${rendered.slice(0, 157)}...`;
67453
+ }
67454
+ function comparisonData(recordKind, value) {
67455
+ if (!isObjectRecord2(value)) return value;
67456
+ const result = { ...value };
67457
+ delete result.createdAt;
67458
+ delete result.updatedAt;
67459
+ if (recordKind === "member") {
67460
+ delete result.getter;
67461
+ delete result.setter;
67462
+ if (result.kind === 23) delete result.action;
67463
+ }
67464
+ if (recordKind === "migration") delete result.action;
67465
+ return result;
67466
+ }
67467
+ function compareSeeds(expected, received) {
67468
+ const normalize = (values) => values.map(normalizeSeedForComparison).sort((left, right) => {
67469
+ if (left.memberId < right.memberId) return -1;
67470
+ if (left.memberId > right.memberId) return 1;
67471
+ return 0;
67472
+ });
67473
+ if (canonicalStringify(normalize(expected)) !== canonicalStringify(normalize(received))) {
67474
+ throw new ProjectSourceCommitVerificationError(
67475
+ "Source commit static value seeds do not match trusted lowering."
67476
+ );
67477
+ }
67478
+ }
67479
+ function normalizeSeedForComparison(seed) {
67480
+ const normalized = { ...seed };
67481
+ if (seed.values !== void 0) {
67482
+ normalized.values = sortSeedRows(seed.values);
67483
+ }
67484
+ if (seed.bindingMembers !== void 0) {
67485
+ normalized.bindingMembers = sortSeedRows(seed.bindingMembers);
67486
+ }
67487
+ if (seed.localizedTexts !== void 0) {
67488
+ normalized.localizedTexts = sortSeedRows(seed.localizedTexts);
67489
+ }
67490
+ return normalized;
67491
+ }
67492
+ function sortSeedRows(values) {
67493
+ return [...values].sort((left, right) => {
67494
+ if (left.id < right.id) return -1;
67495
+ if (left.id > right.id) return 1;
67496
+ return 0;
67497
+ });
67498
+ }
67499
+ var ProjectSourceCommitVerificationError, ABSENT_DIFFERENCE_VALUE;
67500
+ var init_trusted_commit_verification = __esm({
67501
+ "src/project-source/trusted-commit-verification.ts"() {
67502
+ "use strict";
67503
+ init_workspace_status();
67504
+ init_source_diagnostics();
67505
+ init_projection();
67506
+ init_member_value_id();
67507
+ ProjectSourceCommitVerificationError = class extends Error {
67508
+ constructor(message) {
67509
+ super(message);
67510
+ this.name = "ProjectSourceCommitVerificationError";
67511
+ }
67512
+ };
67513
+ ABSENT_DIFFERENCE_VALUE = /* @__PURE__ */ Symbol("absent-difference-value");
66619
67514
  }
66620
67515
  });
66621
67516
 
@@ -66660,7 +67555,7 @@ __export(push_exports, {
66660
67555
  stripServerDerivedNeoScript: () => stripServerDerivedNeoScript,
66661
67556
  workspaceChangesRequireCompleteBodySweep: () => workspaceChangesRequireCompleteBodySweep
66662
67557
  });
66663
- import { randomUUID as randomUUID5 } from "node:crypto";
67558
+ import { randomUUID as randomUUID6 } from "node:crypto";
66664
67559
  import {
66665
67560
  mkdirSync as mkdirSync11,
66666
67561
  writeFileSync as writeFileSync11,
@@ -66668,7 +67563,7 @@ import {
66668
67563
  existsSync as existsSync12,
66669
67564
  readFileSync as readFileSync13
66670
67565
  } from "node:fs";
66671
- import { dirname as dirname7, join as join15, relative as relative5, sep as sep5 } from "node:path";
67566
+ import { dirname as dirname7, join as join16, relative as relative5, sep as sep5 } from "node:path";
66672
67567
  function assignPendingIds(changes, staticValueSeeds, reconstructed3) {
66673
67568
  const assigned = /* @__PURE__ */ new Map();
66674
67569
  const assign = (pendingId2) => {
@@ -66681,7 +67576,7 @@ function assignPendingIds(changes, staticValueSeeds, reconstructed3) {
66681
67576
  assigned.set(pendingId2, derived);
66682
67577
  return derived;
66683
67578
  }
66684
- const fresh = randomUUID5();
67579
+ const fresh = randomUUID6();
66685
67580
  assigned.set(pendingId2, fresh);
66686
67581
  return fresh;
66687
67582
  };
@@ -66992,6 +67887,10 @@ function createPushProgressReporter(json) {
66992
67887
  };
66993
67888
  }
66994
67889
  function projectTransactionProgressLabel(event) {
67890
+ if (event.type === "push-progress") {
67891
+ if (event.phase === "preparing") return "Preparing push\u2026";
67892
+ return `Uploading files\u2026 ${event.completedFiles.toLocaleString("en-US")}/${event.totalFiles.toLocaleString("en-US")} (${formatByteProgress(event.completedBytes)}/${formatByteProgress(event.totalBytes)})`;
67893
+ }
66995
67894
  if (event.phase === "submitting") return "Pushing\u2026";
66996
67895
  if (event.phase === "preparing") return "Preparing push\u2026";
66997
67896
  if (event.phase === "waiting") return "Waiting to apply project transaction\u2026";
@@ -67009,6 +67908,11 @@ function projectTransactionProgressLabel(event) {
67009
67908
  }
67010
67909
  return "Finalizing project transaction\u2026";
67011
67910
  }
67911
+ function formatByteProgress(bytes) {
67912
+ if (bytes < 1024) return `${bytes} B`;
67913
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
67914
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
67915
+ }
67012
67916
  function progressEventFromAccepted(accepted) {
67013
67917
  return {
67014
67918
  type: "project-transaction-progress",
@@ -67177,6 +68081,9 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
67177
68081
  changes: status.changes,
67178
68082
  binaryChanges: status.binaryChanges ?? []
67179
68083
  });
68084
+ if (options.json !== true) {
68085
+ for (const message of status.warnings) warn(message);
68086
+ }
67180
68087
  if (status.changes.length === 0) {
67181
68088
  if (options.json === true) {
67182
68089
  console.log(
@@ -67185,7 +68092,8 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
67185
68092
  dryRun: options.dryRun,
67186
68093
  changeCount: 0,
67187
68094
  staticValueSeedCount: 0,
67188
- changes: []
68095
+ changes: [],
68096
+ warnings: status.warnings
67189
68097
  })
67190
68098
  );
67191
68099
  } else {
@@ -67247,7 +68155,8 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
67247
68155
  kind: change.kind,
67248
68156
  recordKind: change.recordKind,
67249
68157
  recordId: change.recordId
67250
- }))
68158
+ })),
68159
+ warnings: status.warnings
67251
68160
  })
67252
68161
  );
67253
68162
  } else {
@@ -67281,46 +68190,111 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
67281
68190
  console.log("Push cancelled.");
67282
68191
  return;
67283
68192
  }
67284
- const sourceBundle = await createPendingProjectSourceBundleV4(
67285
- workspace,
67286
- status,
67287
- status.staticValueSeeds
67288
- );
67289
- const pendingAssignment = assignPendingIds(
67290
- status.changes,
67291
- status.staticValueSeeds,
67292
- status.reconstructed
67293
- );
67294
- const operations = new Set(status.changes.map((change) => change.kind));
67295
- const transactionOperation = operations.size === 1 ? status.changes[0].kind : "update";
67296
- const client = NeoApiClient.forBaseUrl(workspace.config.apiBaseUrl);
67297
- let stagedFiles = await stageProjectFilePushesV4({
67298
- workspace,
67299
- changes: status.changes,
67300
- binaryChanges: status.binaryChanges ?? [],
67301
- assignedIds: pendingAssignment.assigned,
67302
- client
68193
+ let progress = createPushProgressReporter(options.json === true);
68194
+ progress.report({
68195
+ type: "push-progress",
68196
+ phase: "preparing",
68197
+ completedFiles: 0,
68198
+ totalFiles: status.binaryChanges?.length ?? 0,
68199
+ completedBytes: 0,
68200
+ totalBytes: 0
67303
68201
  });
68202
+ const prepareAndStage = async () => {
68203
+ const source2 = await createPendingProjectSourceBundleV4(
68204
+ workspace,
68205
+ status,
68206
+ status.staticValueSeeds
68207
+ );
68208
+ const pendingAssignment2 = assignPendingIds(
68209
+ status.changes,
68210
+ status.staticValueSeeds,
68211
+ status.reconstructed
68212
+ );
68213
+ const operations = new Set(status.changes.map((change) => change.kind));
68214
+ const transactionOperation2 = operations.size === 1 ? status.changes[0].kind : "update";
68215
+ const client2 = NeoApiClient.forBaseUrl(workspace.config.apiBaseUrl);
68216
+ const transportChanges2 = status.changes.map((change) => ({
68217
+ recordKind: change.recordKind,
68218
+ recordId: change.recordId,
68219
+ operation: change.kind,
68220
+ nextData: change.kind === "delete" ? void 0 : stripServerDerivedNeoScript(change.nextData),
68221
+ deleted: change.kind === "delete" ? true : void 0,
68222
+ intent: createNeoCliPushIntent(change),
68223
+ expectedBaseContentHash: change.kind === "create" ? null : change.casBaseHash ?? null
68224
+ }));
68225
+ const transportSeeds2 = [...pendingAssignment2.staticValueSeeds].map(
68226
+ ([memberId, seed]) => ({ memberId, ...seed })
68227
+ );
68228
+ const files = prepareProjectFilePushesV4({
68229
+ workspace,
68230
+ changes: status.changes,
68231
+ binaryChanges: status.binaryChanges ?? [],
68232
+ assignedIds: pendingAssignment2.assigned
68233
+ });
68234
+ verifyProjectSourceCommitAgainstStateV4({
68235
+ projectId: workspace.config.projectId,
68236
+ versionId: workspace.config.versionId,
68237
+ stateRecords: workspace.state.records,
68238
+ files: source2.files,
68239
+ changes: transportChanges2,
68240
+ staticValueSeeds: transportSeeds2,
68241
+ pendingIdAssignments: Object.fromEntries(pendingAssignment2.assigned),
68242
+ stagedFiles: files
68243
+ });
68244
+ const stagedFiles2 = await stageProjectFilePushesV4({
68245
+ workspace,
68246
+ changes: status.changes,
68247
+ binaryChanges: status.binaryChanges ?? [],
68248
+ assignedIds: pendingAssignment2.assigned,
68249
+ client: client2,
68250
+ prepared: files,
68251
+ onProgress: (upload) => progress.report({
68252
+ type: "push-progress",
68253
+ phase: "uploading",
68254
+ ...upload
68255
+ })
68256
+ });
68257
+ return {
68258
+ source: source2,
68259
+ pendingAssignment: pendingAssignment2,
68260
+ transactionOperation: transactionOperation2,
68261
+ client: client2,
68262
+ transportChanges: transportChanges2,
68263
+ transportSeeds: transportSeeds2,
68264
+ preparedFiles: files,
68265
+ stagedFiles: stagedFiles2
68266
+ };
68267
+ };
68268
+ let preparedPush;
68269
+ try {
68270
+ preparedPush = await prepareAndStage();
68271
+ } catch (error) {
68272
+ progress.stop();
68273
+ if (error instanceof ProjectFilePushCancelledError) {
68274
+ if (options.json !== true) console.error("Push cancelled.");
68275
+ process.exitCode = 130;
68276
+ return;
68277
+ }
68278
+ throw error;
68279
+ }
68280
+ const {
68281
+ source,
68282
+ pendingAssignment,
68283
+ transactionOperation,
68284
+ client,
68285
+ transportChanges,
68286
+ transportSeeds,
68287
+ preparedFiles
68288
+ } = preparedPush;
68289
+ let { stagedFiles } = preparedPush;
67304
68290
  const commit = async (force) => await client.post(
67305
68291
  `/api/projects/${workspace.config.projectId}/versions/${workspace.config.versionId}/schema/commit`,
67306
68292
  {
67307
68293
  operation: transactionOperation,
67308
- changes: status.changes.map((change) => {
67309
- return {
67310
- recordKind: change.recordKind,
67311
- recordId: change.recordId,
67312
- operation: change.kind,
67313
- nextData: change.kind === "delete" ? void 0 : stripServerDerivedNeoScript(change.nextData),
67314
- deleted: change.kind === "delete" ? true : void 0,
67315
- intent: createNeoCliPushIntent(change),
67316
- expectedBaseContentHash: change.kind === "create" ? null : change.casBaseHash ?? null
67317
- };
67318
- }),
67319
- staticValueSeeds: [...pendingAssignment.staticValueSeeds].map(
67320
- ([memberId, seed]) => ({ memberId, ...seed })
67321
- ),
68294
+ changes: transportChanges,
68295
+ staticValueSeeds: transportSeeds,
67322
68296
  stagedFiles,
67323
- sourceBundle,
68297
+ sourceBundle: source.bundle,
67324
68298
  pendingIdAssignments: Object.fromEntries(pendingAssignment.assigned),
67325
68299
  summary: options.summary ?? "neo push"
67326
68300
  },
@@ -67434,7 +68408,6 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
67434
68408
  if (options.json !== true) success("Push complete.");
67435
68409
  return true;
67436
68410
  };
67437
- let progress = createPushProgressReporter(options.json === true);
67438
68411
  progress.report({
67439
68412
  type: "project-transaction-progress",
67440
68413
  transactionId: null,
@@ -67467,28 +68440,39 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
67467
68440
  fallback: false
67468
68441
  })) {
67469
68442
  progress = createPushProgressReporter(options.json === true);
67470
- progress.report({
67471
- type: "project-transaction-progress",
67472
- transactionId: null,
67473
- phase: "submitting",
67474
- totalChangeCount: status.changes.length,
67475
- appliedChangeCount: 0,
67476
- totalChunkCount: null,
67477
- appliedChunkCount: 0,
67478
- errorCode: null,
67479
- errorMessage: null
67480
- });
67481
68443
  try {
67482
68444
  stagedFiles = await stageProjectFilePushesV4({
67483
68445
  workspace,
67484
68446
  changes: status.changes,
67485
68447
  binaryChanges: status.binaryChanges ?? [],
67486
68448
  assignedIds: pendingAssignment.assigned,
67487
- client
68449
+ client,
68450
+ prepared: preparedFiles,
68451
+ onProgress: (upload) => progress.report({
68452
+ type: "push-progress",
68453
+ phase: "uploading",
68454
+ ...upload
68455
+ })
68456
+ });
68457
+ progress.report({
68458
+ type: "project-transaction-progress",
68459
+ transactionId: null,
68460
+ phase: "submitting",
68461
+ totalChangeCount: status.changes.length,
68462
+ appliedChangeCount: 0,
68463
+ totalChunkCount: null,
68464
+ appliedChunkCount: 0,
68465
+ errorCode: null,
68466
+ errorMessage: null
67488
68467
  });
67489
68468
  result = await commit(true);
67490
68469
  } catch (retryError) {
67491
68470
  progress.stop();
68471
+ if (retryError instanceof ProjectFilePushCancelledError) {
68472
+ if (options.json !== true) console.error("Push cancelled.");
68473
+ process.exitCode = 130;
68474
+ return;
68475
+ }
67492
68476
  if (retryError instanceof NeoApiError) {
67493
68477
  reportPushRejection(retryError.status, retryError.body);
67494
68478
  process.exitCode = 1;
@@ -67617,17 +68601,19 @@ async function createPendingProjectSourceBundleV4(workspace, status, staticValue
67617
68601
  }
67618
68602
  }
67619
68603
  const emission = emitProjectDocumentFilesV4(records2);
67620
- return createProjectSourceBundle(
67621
- emission.files.map((file) => {
67622
- const kind = neoProjectSourceKind(file.path);
67623
- if (kind === null) {
67624
- throw new Error(
67625
- `Emitted project source bundle file ${JSON.stringify(file.path)} is not a recognized Neo source path.`
67626
- );
67627
- }
67628
- return { path: file.path, kind, content: file.content };
67629
- })
67630
- );
68604
+ const files = emission.files.map((file) => {
68605
+ const kind = neoProjectSourceKind(file.path);
68606
+ if (kind === null) {
68607
+ throw new Error(
68608
+ `Emitted project source bundle file ${JSON.stringify(file.path)} is not a recognized Neo source path.`
68609
+ );
68610
+ }
68611
+ return { path: file.path, kind, content: file.content };
68612
+ });
68613
+ return {
68614
+ files,
68615
+ bundle: await createProjectSourceBundle(files)
68616
+ };
67631
68617
  }
67632
68618
  function staticSeedBindingMemberRecord(projectId, bindingMember, timestamp) {
67633
68619
  return {
@@ -68018,11 +69004,11 @@ ${finalErrors.map(
68018
69004
  for (const recordState of Object.values(workspace.state.records)) {
68019
69005
  const previousPath = recordState.file;
68020
69006
  if (previousPath === void 0 || emittedPaths.has(previousPath)) continue;
68021
- const absolute = join15(workspace.root, previousPath);
69007
+ const absolute = join16(workspace.root, previousPath);
68022
69008
  if (existsSync12(absolute)) rmSync6(absolute);
68023
69009
  }
68024
69010
  for (const file of files) {
68025
- const absolute = join15(workspace.root, file.path);
69011
+ const absolute = join16(workspace.root, file.path);
68026
69012
  mkdirSync11(dirname7(absolute), { recursive: true });
68027
69013
  const existing = existsSync12(absolute) ? readFileSync13(absolute, "utf8") : null;
68028
69014
  if (existing !== file.content)
@@ -68702,6 +69688,7 @@ var init_push = __esm({
68702
69688
  init_source_diagnostics();
68703
69689
  init_projection();
68704
69690
  init_project_file_push();
69691
+ init_trusted_commit_verification();
68705
69692
  init_world_system_classes();
68706
69693
  init_project_manifest();
68707
69694
  init_merge();
@@ -68744,7 +69731,7 @@ __export(dev_exports, {
68744
69731
  runDev: () => runDev
68745
69732
  });
68746
69733
  import { watch } from "node:fs";
68747
- import { join as join16 } from "node:path";
69734
+ import { join as join17 } from "node:path";
68748
69735
  import { emitKeypressEvents } from "node:readline";
68749
69736
  import { ConvexClient } from "convex/browser";
68750
69737
  function isSchemaSignal(value) {
@@ -68851,7 +69838,7 @@ async function runDev(workspace, options) {
68851
69838
  };
68852
69839
  for (const dir of ["Classes", "Enums"]) {
68853
69840
  try {
68854
- watch(join16(workspace.root, dir), { persistent: true }, onFileChange);
69841
+ watch(join17(workspace.root, dir), { persistent: true }, onFileChange);
68855
69842
  } catch {
68856
69843
  }
68857
69844
  }
@@ -68907,7 +69894,7 @@ __export(resolve_exports, {
68907
69894
  workspaceFilePath: () => workspaceFilePath
68908
69895
  });
68909
69896
  import { readFileSync as readFileSync14, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "node:fs";
68910
- import { join as join17 } from "node:path";
69897
+ import { join as join18 } from "node:path";
68911
69898
  function runResolve(workspace, side) {
68912
69899
  let resolvedFiles = 0;
68913
69900
  for (const filePath of listProjectSourceFilesV4(workspace.root)) {
@@ -68922,12 +69909,12 @@ function runResolve(workspace, side) {
68922
69909
  const binary = state.projectBinary;
68923
69910
  const conflict2 = binary?.conflict;
68924
69911
  if (binary === void 0 || conflict2 === void 0) continue;
68925
- const destination = join17(workspace.root, binary.path);
69912
+ const destination = join18(workspace.root, binary.path);
68926
69913
  if (side === "theirs") {
68927
69914
  if (conflict2.remoteSha256 !== null && conflict2.artifactPath !== void 0) {
68928
69915
  writeVerifiedBinaryDownloadV4(
68929
69916
  destination,
68930
- readFileSync14(join17(workspace.root, conflict2.artifactPath)),
69917
+ readFileSync14(join18(workspace.root, conflict2.artifactPath)),
68931
69918
  conflict2.remoteSha256
68932
69919
  );
68933
69920
  binary.sha256 = conflict2.remoteSha256;
@@ -68937,7 +69924,7 @@ function runResolve(workspace, side) {
68937
69924
  }
68938
69925
  }
68939
69926
  if (conflict2.artifactPath !== void 0) {
68940
- rmSync7(join17(workspace.root, conflict2.artifactPath), { force: true });
69927
+ rmSync7(join18(workspace.root, conflict2.artifactPath), { force: true });
68941
69928
  }
68942
69929
  delete binary.conflict;
68943
69930
  resolvedBinaries += 1;
@@ -68990,7 +69977,7 @@ function resolveMarkers(source, side) {
68990
69977
  return output.join("\n");
68991
69978
  }
68992
69979
  function workspaceFilePath(workspace, file) {
68993
- return join17(workspace.root, file);
69980
+ return join18(workspace.root, file);
68994
69981
  }
68995
69982
  var init_resolve = __esm({
68996
69983
  "src/commands/resolve.ts"() {
@@ -69373,6 +70360,7 @@ function projectStatusJsonV4(status, options) {
69373
70360
  blocking: isBlockingSchemaSourceError(error),
69374
70361
  message: error.message
69375
70362
  })),
70363
+ warnings: status.warnings,
69376
70364
  records: status.changes.map(
69377
70365
  (change) => recordChangeJsonV4(change, status, options)
69378
70366
  ),
@@ -70051,6 +71039,9 @@ async function main() {
70051
71039
  for (const error of status.parseErrors) {
70052
71040
  console.log(`${sym.warn} ${error.message}`);
70053
71041
  }
71042
+ for (const message of status.warnings) {
71043
+ console.log(`${sym.warn} ${message}`);
71044
+ }
70054
71045
  for (const group of groupProjectStatusChangesV4(status)) {
70055
71046
  console.log(color.bold(group.source));
70056
71047
  for (const change of group.changes) {