@neocompose/cli 0.21.2 → 0.21.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.21.4] - 2026-08-03
4
+
5
+ ### Fixed
6
+
7
+ - `neo pull --reset` emits the canonical `SpriteInfo.Empty` spelling for the
8
+ required empty-sprite sentinel and does not mistake its empty file id for a
9
+ missing project file.
10
+ - Parameterized declaration templates compile with their class-header scope
11
+ without being evaluated before a concrete invocation supplies arguments.
12
+ - Canonical lowering reuses persisted generic argument identities, so a fresh
13
+ reset remains a semantic no-op instead of creating replacement binding rows.
14
+
15
+ ## [0.21.3] - 2026-08-03
16
+
17
+ ### Fixed
18
+
19
+ - `neo push` paints its preparation spinner before workspace discovery,
20
+ lowering, and NeoScript compilation begin, so large projects no longer look
21
+ stalled before the push plan appears.
22
+ - Animation segment frame values are emitted through the required `value`
23
+ constructor argument. Required sprites use the canonical `SpriteInfo.Empty`
24
+ value; `null` is reserved for nullable `SpriteInfo?` values.
25
+
3
26
  ## [0.21.2] - 2026-08-03
4
27
 
5
28
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -842,7 +842,7 @@ function vectorTypes() {
842
842
  members: structuredLeafFieldProperties(name)
843
843
  }));
844
844
  }
845
- var NEOSCRIPT_KEYWORDS, NEOSCRIPT_INFERRED_LOCAL_KEYWORD, NEOSCRIPT_CONSTRUCTOR_KEYWORD, NEOSCRIPT_PRIMITIVE_TYPES, NEOSCRIPT_BUILTIN_TYPES, NEOSCRIPT_OPERATORS, NEOSCRIPT_STATEMENT_SNIPPETS, STRING_TYPE, INT_TYPE, FLOAT_TYPE, BOOL_TYPE, IMAGE_REF_TYPE, FLOAT_OPTIONAL_TYPE, NEOSCRIPT_STRUCTURED_LEAF_FIELDS, NEOSCRIPT_IMAGE_REGISTRY_TYPE, NEOSCRIPT_AUDIO_CLIP_REGISTRY_TYPE, NEOSCRIPT_PENDING_SYMBOL_ID_PREFIX, REGISTRY_ENTRY_TYPES, NEOSCRIPT_GLOBALS, NEOSCRIPT_BUILTIN_TYPE_SYMBOLS;
845
+ var NEOSCRIPT_KEYWORDS, NEOSCRIPT_INFERRED_LOCAL_KEYWORD, NEOSCRIPT_CONSTRUCTOR_KEYWORD, NEOSCRIPT_PRIMITIVE_TYPES, NEOSCRIPT_BUILTIN_TYPES, NEOSCRIPT_OPERATORS, NEOSCRIPT_STATEMENT_SNIPPETS, STRING_TYPE, INT_TYPE, FLOAT_TYPE, BOOL_TYPE, IMAGE_REF_TYPE, SPRITE_INFO_TYPE, FLOAT_OPTIONAL_TYPE, NEOSCRIPT_STRUCTURED_LEAF_FIELDS, NEOSCRIPT_IMAGE_REGISTRY_TYPE, NEOSCRIPT_AUDIO_CLIP_REGISTRY_TYPE, NEOSCRIPT_PENDING_SYMBOL_ID_PREFIX, REGISTRY_ENTRY_TYPES, NEOSCRIPT_GLOBALS, NEOSCRIPT_SPRITE_INFO_EMPTY_MEMBER_ID, NEOSCRIPT_SPRITE_INFO_EMPTY_VALUE, NEOSCRIPT_BUILTIN_TYPE_SYMBOLS;
846
846
  var init_language_spec = __esm({
847
847
  "../packages/neoscript-language/src/language-spec.ts"() {
848
848
  "use strict";
@@ -1006,6 +1006,10 @@ var init_language_spec = __esm({
1006
1006
  kind: "primitive",
1007
1007
  name: "ImageRef"
1008
1008
  };
1009
+ SPRITE_INFO_TYPE = {
1010
+ kind: "primitive",
1011
+ name: "SpriteInfo"
1012
+ };
1009
1013
  FLOAT_OPTIONAL_TYPE = {
1010
1014
  kind: "primitive",
1011
1015
  name: "float",
@@ -1148,12 +1152,26 @@ var init_language_spec = __esm({
1148
1152
  "Returns whether a dialogue pointer has been visited."
1149
1153
  )
1150
1154
  ];
1155
+ NEOSCRIPT_SPRITE_INFO_EMPTY_MEMBER_ID = "builtin:SpriteInfo:property:Empty";
1156
+ NEOSCRIPT_SPRITE_INFO_EMPTY_VALUE = Object.freeze({
1157
+ fileId: "",
1158
+ sliceIndex: 0
1159
+ });
1151
1160
  NEOSCRIPT_BUILTIN_TYPE_SYMBOLS = [
1152
1161
  {
1153
1162
  id: "builtin:SpriteInfo",
1154
1163
  name: "SpriteInfo",
1155
1164
  kind: "builtin",
1156
1165
  members: [
1166
+ {
1167
+ ...property(
1168
+ "SpriteInfo",
1169
+ "Empty",
1170
+ SPRITE_INFO_TYPE,
1171
+ "An authored empty sprite at slice zero."
1172
+ ),
1173
+ static: true
1174
+ },
1157
1175
  // `FileId` and `SliceIndex` come from the field table above — they are
1158
1176
  // the writable, addressable fields, not derived properties (D11).
1159
1177
  ...structuredLeafFieldProperties("SpriteInfo"),
@@ -9027,7 +9045,8 @@ var init_strict_resolver = __esm({
9027
9045
  );
9028
9046
  case "call":
9029
9047
  if (expression.callee.kind === "ident" && expression.callee.name === "Reference" && Array.isArray(expression.typeArguments)) {
9030
- if (expression.typeArguments.length !== 1) {
9048
+ const [referenceTypeArgument] = expression.typeArguments;
9049
+ if (expression.typeArguments.length !== 1 || referenceTypeArgument === void 0) {
9031
9050
  throw new CompileError(
9032
9051
  "Reference requires exactly one generic target type.",
9033
9052
  expression.pos
@@ -9039,7 +9058,7 @@ var init_strict_resolver = __esm({
9039
9058
  expression.pos
9040
9059
  );
9041
9060
  }
9042
- const referencedType = this.resolveType(expression.typeArguments[0]);
9061
+ const referencedType = this.resolveType(referenceTypeArgument);
9043
9062
  if (expected !== void 0 && !isNeoScriptTypeAssignable(referencedType, expected, this.project)) {
9044
9063
  throw new CompileError(
9045
9064
  `Reference target ${this.describe(referencedType)} is not assignable to ${this.describe(expected)}.`,
@@ -9093,7 +9112,7 @@ var init_strict_resolver = __esm({
9093
9112
  if (requiredConstructor === null && !callMatchesDeclaredOverloadNames(declared, expression)) {
9094
9113
  this.rejectConstructorProjectionInConstructionBody(expression);
9095
9114
  }
9096
- const projectionCall = requiredConstructor === null && expression.argumentNames === void 0 && callHasPositionalArguments(expression) && declaredTarget?.constructorSignature !== void 0;
9115
+ const projectionCall = requiredConstructor === null && declaredTarget?.constructorSignature !== void 0 && (expression.argumentNames === void 0 && callHasPositionalArguments(expression) || expression.argumentNames !== void 0 && declaredTarget.constructorSignature.namedProjections !== void 0);
9097
9116
  if (declared.length > 0 && !projectionCall) {
9098
9117
  return this.resolveDeclaredConstructor(
9099
9118
  className,
@@ -9102,13 +9121,13 @@ var init_strict_resolver = __esm({
9102
9121
  expression.pos
9103
9122
  );
9104
9123
  }
9105
- if (expression.argumentNames) {
9124
+ if (expression.argumentNames && !projectionCall) {
9106
9125
  throw new CompileError(
9107
9126
  "Named constructor arguments require a declaration-aware project source context.",
9108
9127
  expression.pos
9109
9128
  );
9110
9129
  }
9111
- if (expression.initializer && expression.initializer.length > 0) {
9130
+ if (expression.initializer && expression.initializer.length > 0 && !projectionCall) {
9112
9131
  throw new CompileError(
9113
9132
  "Object initializers are available to project source construction and are not executable NeoScript constructors.",
9114
9133
  expression.pos
@@ -9116,9 +9135,10 @@ var init_strict_resolver = __esm({
9116
9135
  }
9117
9136
  return this.resolveClassConstructor(
9118
9137
  className,
9119
- expression.args,
9138
+ expression,
9120
9139
  scope,
9121
- expression.pos
9140
+ expression.pos,
9141
+ expected
9122
9142
  );
9123
9143
  }
9124
9144
  case "annotated":
@@ -9739,6 +9759,12 @@ var init_strict_resolver = __esm({
9739
9759
  }
9740
9760
  resolveMemberNatural(receiverAst, name, scope, pos, optional) {
9741
9761
  if (receiverAst.kind === "ident" && !scope.lookup(receiverAst.name)) {
9762
+ if (receiverAst.name === "SpriteInfo" && name === "Empty") {
9763
+ return literal(
9764
+ { kind: "primitive", name: "SpriteInfo" },
9765
+ { ...NEOSCRIPT_SPRITE_INFO_EMPTY_VALUE }
9766
+ );
9767
+ }
9742
9768
  const staticType = this.project.typeByName.get(receiverAst.name);
9743
9769
  if (staticType && staticType.kind !== "enum") {
9744
9770
  if (optional) {
@@ -9769,6 +9795,11 @@ var init_strict_resolver = __esm({
9769
9795
  pos
9770
9796
  );
9771
9797
  }
9798
+ if (member.id === NEOSCRIPT_SPRITE_INFO_EMPTY_MEMBER_ID) {
9799
+ return literal(member.type, {
9800
+ ...NEOSCRIPT_SPRITE_INFO_EMPTY_VALUE
9801
+ });
9802
+ }
9772
9803
  if (member.computed === true) {
9773
9804
  return {
9774
9805
  pointer: {
@@ -10515,7 +10546,7 @@ var init_strict_resolver = __esm({
10515
10546
  type: optional ? { ...delegateType.returnType, nullable: true } : delegateType.returnType
10516
10547
  };
10517
10548
  }
10518
- resolveClassConstructor(className, argumentsList2, scope, pos) {
10549
+ resolveClassConstructor(className, expression, scope, pos, expected) {
10519
10550
  const schemaClass2 = this.project.typeByName.get(className);
10520
10551
  if (!schemaClass2 || schemaClass2.kind !== "class") {
10521
10552
  throw new CompileError(
@@ -10537,26 +10568,86 @@ var init_strict_resolver = __esm({
10537
10568
  pos
10538
10569
  );
10539
10570
  }
10571
+ const targetType = this.constructorTargetType(
10572
+ schemaClass2,
10573
+ expression,
10574
+ expected
10575
+ );
10576
+ const parameters = signature.parameters.map((parameter3) => ({
10577
+ ...parameter3,
10578
+ valueType: substituteTypeParameters(
10579
+ parameter3.valueType,
10580
+ targetType,
10581
+ schemaClass2
10582
+ ),
10583
+ parameterType: substituteTypeParameters(
10584
+ parameter3.parameterType,
10585
+ targetType,
10586
+ schemaClass2
10587
+ )
10588
+ }));
10589
+ const argumentsList2 = expression.args;
10590
+ const namedProjections = signature.namedProjections;
10591
+ if (expression.argumentNames !== void 0) {
10592
+ if (namedProjections === void 0) {
10593
+ throw new CompileError(
10594
+ `Class '${className}' exposes no named constructor projection.`,
10595
+ pos
10596
+ );
10597
+ }
10598
+ if (argumentsList2.length !== namedProjections.length) {
10599
+ throw new CompileError(
10600
+ `${className} constructor requires ${namedProjections.length} named argument${namedProjections.length === 1 ? "" : "s"} but got ${argumentsList2.length}.`,
10601
+ pos
10602
+ );
10603
+ }
10604
+ if (expression.argumentNames.length !== argumentsList2.length) {
10605
+ throw new CompileError(
10606
+ `${className} constructor projection requires every argument to be named.`,
10607
+ pos
10608
+ );
10609
+ }
10610
+ }
10540
10611
  const requiredCount = signature.parameters.filter(
10541
10612
  (parameter3) => parameter3.required
10542
10613
  ).length;
10543
- if (argumentsList2.length < requiredCount) {
10614
+ if (expression.argumentNames === void 0 && argumentsList2.length < requiredCount) {
10544
10615
  throw new CompileError(
10545
10616
  `${className} constructor requires ${requiredCount} argument${requiredCount === 1 ? "" : "s"} but got ${argumentsList2.length}.`,
10546
10617
  pos
10547
10618
  );
10548
10619
  }
10549
- if (argumentsList2.length > signature.parameters.length) {
10620
+ if (expression.argumentNames === void 0 && argumentsList2.length > parameters.length) {
10550
10621
  throw new CompileError(
10551
- `${className} constructor accepts at most ${signature.parameters.length} argument${signature.parameters.length === 1 ? "" : "s"} but got ${argumentsList2.length}.`,
10622
+ `${className} constructor accepts at most ${parameters.length} argument${parameters.length === 1 ? "" : "s"} but got ${argumentsList2.length}.`,
10552
10623
  pos
10553
10624
  );
10554
10625
  }
10555
10626
  const fields = argumentsList2.map((argument2, index) => {
10556
- const parameter3 = signature.parameters[index];
10627
+ const projectionName = expression.argumentNames?.[index];
10628
+ const projection = projectionName === void 0 ? void 0 : namedProjections?.find(
10629
+ (candidate) => candidate.name === projectionName
10630
+ );
10631
+ if (projectionName !== void 0 && projection === void 0) {
10632
+ throw new CompileError(
10633
+ `Class '${className}' has no constructor projection named '${projectionName}'.`,
10634
+ argument2.pos
10635
+ );
10636
+ }
10637
+ if (projection !== void 0 && expression.argumentNames?.findIndex(
10638
+ (candidate) => candidate === projection.name
10639
+ ) !== index) {
10640
+ throw new CompileError(
10641
+ `Constructor argument '${projection.name}' is supplied more than once.`,
10642
+ argument2.pos
10643
+ );
10644
+ }
10645
+ const parameter3 = projection === void 0 ? parameters[index] : parameters.find(
10646
+ (candidate) => candidate.memberId === projection.memberId
10647
+ );
10557
10648
  if (!parameter3) {
10558
10649
  throw new Error(
10559
- `NeoScript constructor ${className} lost parameter ${index + 1} after argument-count validation.`
10650
+ projection === void 0 ? `NeoScript constructor ${className} lost parameter ${index + 1} after argument-count validation.` : `NeoScript constructor ${className} projection '${projection.name}' names missing member '${projection.memberId}'.`
10560
10651
  );
10561
10652
  }
10562
10653
  const value = this.resolveExpression(
@@ -10576,7 +10667,20 @@ var init_strict_resolver = __esm({
10576
10667
  valuePointer: value.pointer
10577
10668
  };
10578
10669
  });
10579
- const schemaClassInfo = toWireType(signature.returnType, this.project);
10670
+ if (namedProjections !== void 0 && expression.argumentNames !== void 0) {
10671
+ for (const projection of namedProjections) {
10672
+ if (!expression.argumentNames.includes(projection.name)) {
10673
+ throw new CompileError(
10674
+ `${className} constructor is missing named argument '${projection.name}'.`,
10675
+ pos
10676
+ );
10677
+ }
10678
+ }
10679
+ }
10680
+ fields.push(
10681
+ ...this.resolveCallSiteInitializerFields(schemaClass2, expression, scope)
10682
+ );
10683
+ const schemaClassInfo = toWireType(targetType, this.project);
10580
10684
  if (schemaClassInfo.type !== 7 /* Class */) {
10581
10685
  throw new Error(
10582
10686
  `NeoScript constructor ${className} has non-Class return type ${this.describe(signature.returnType)}.`
@@ -10590,10 +10694,35 @@ var init_strict_resolver = __esm({
10590
10694
  info: { schemaClassInfo, fields }
10591
10695
  }
10592
10696
  },
10593
- type: { ...signature.returnType, nullable: false },
10697
+ type: { ...targetType, nullable: false },
10594
10698
  writability: "session" /* Session */
10595
10699
  };
10596
10700
  }
10701
+ constructorTargetType(schemaClass2, expression, expected) {
10702
+ const typeArguments = expression.typeArguments?.map(
10703
+ (argument2) => this.resolveType(argument2)
10704
+ );
10705
+ if (typeArguments !== void 0 && typeArguments.length !== (schemaClass2.typeParameters?.length ?? 0)) {
10706
+ throw new CompileError(
10707
+ `Class '${schemaClass2.name}' expects ${schemaClass2.typeParameters?.length ?? 0} type argument${schemaClass2.typeParameters?.length === 1 ? "" : "s"} but got ${typeArguments.length}.`,
10708
+ expression.pos
10709
+ );
10710
+ }
10711
+ if (typeArguments !== void 0) {
10712
+ return {
10713
+ kind: "named",
10714
+ typeId: schemaClass2.id,
10715
+ typeArguments
10716
+ };
10717
+ }
10718
+ if (expected?.kind === "named" && expected.typeId === schemaClass2.id) {
10719
+ return { ...expected, nullable: false };
10720
+ }
10721
+ return {
10722
+ kind: "named",
10723
+ typeId: schemaClass2.id
10724
+ };
10725
+ }
10597
10726
  /**
10598
10727
  * P43 §7.1. P29's `new T(id: "…")` projection names a class-default row,
10599
10728
  * which is exactly the wrong identity inside a construction body: member
@@ -38004,10 +38133,10 @@ function isSpriteValue(value) {
38004
38133
  if (!Number.isInteger(sliceIndex)) return false;
38005
38134
  return sliceIndex >= 0;
38006
38135
  }
38007
- function pendingSpriteValue() {
38136
+ function emptySpriteValue() {
38008
38137
  return { fileId: "", sliceIndex: 0 };
38009
38138
  }
38010
- function isPendingSpriteValue(value) {
38139
+ function isEmptySpriteValue(value) {
38011
38140
  if (!value || typeof value !== "object") return false;
38012
38141
  return getField(value, "fileId") === "" && getField(value, "sliceIndex") === 0;
38013
38142
  }
@@ -38910,6 +39039,7 @@ var init_world_system_classes_generated = __esm({
38910
39039
  {
38911
39040
  memberId: "system_cbd6db9a-f473-44b5-b913-7cdc06452f35",
38912
39041
  memberKind: "sprite",
39042
+ defaultValue: { fileId: "", sliceIndex: 0 },
38913
39043
  required: true,
38914
39044
  schemaKey: "Sprite"
38915
39045
  },
@@ -39032,6 +39162,7 @@ var init_world_system_classes_generated = __esm({
39032
39162
  {
39033
39163
  memberId: "system_e9288ba9-f5a2-4485-8443-6afb155b31e0",
39034
39164
  memberKind: "sprite",
39165
+ defaultValue: { fileId: "", sliceIndex: 0 },
39035
39166
  required: true,
39036
39167
  schemaKey: "Sprite"
39037
39168
  },
@@ -39516,6 +39647,12 @@ var init_world_system_classes_generated = __esm({
39516
39647
  docsText: "One value at a frame index inside a segment. It holds until the next row\nyou author, or until the segment's Duration runs out.",
39517
39648
  extendsWorldKind: "animationFrameBase",
39518
39649
  isAbstract: false,
39650
+ constructorProjections: [
39651
+ {
39652
+ memberId: "system_b71d1f73-da72-49de-ab9a-911cc9bffa43",
39653
+ parameterName: "value"
39654
+ }
39655
+ ],
39519
39656
  genericParams: [
39520
39657
  { id: "system_887b11d3-e56a-47ff-8d01-e2fcefb846a1", name: "T" }
39521
39658
  ],
@@ -42586,7 +42723,7 @@ function stampGenericBindings(value, member, args) {
42586
42723
  }
42587
42724
  function shouldSkipRequiredChildDefault(member) {
42588
42725
  if (member.defaultValue !== void 0) return false;
42589
- return isMemberSpriteBase(member) || isMemberAudioBase(member);
42726
+ return isMemberAudioBase(member);
42590
42727
  }
42591
42728
  function shouldMaterializeChildDefault(member) {
42592
42729
  if (isMemberClassBase(member) && member.partial === true) return false;
@@ -42658,7 +42795,7 @@ function primitiveFallbackValue(document, member) {
42658
42795
  if (isMemberLookupBase(member)) return { value: [] };
42659
42796
  if (isMemberDialogueLookupBase(member)) return { value: [] };
42660
42797
  if (isMemberSpriteBase(member)) {
42661
- return { value: pendingSpriteValue() };
42798
+ return { value: emptySpriteValue() };
42662
42799
  }
42663
42800
  if (isMemberAudioBase(member)) {
42664
42801
  return { value: pendingAudioValue() };
@@ -42682,7 +42819,7 @@ function primitiveFallbackValue(document, member) {
42682
42819
  function isPendingFileMemberValue(member, body) {
42683
42820
  const value = body.value;
42684
42821
  if (isMemberSpriteBase(member)) {
42685
- return isPendingSpriteValue(value);
42822
+ return isEmptySpriteValue(value);
42686
42823
  }
42687
42824
  if (isMemberAudioBase(member)) {
42688
42825
  return isPendingAudioValue(value);
@@ -42768,11 +42905,18 @@ function validateDecimalMemberValue(member, body) {
42768
42905
  }
42769
42906
  function validateSpriteMemberValue(document, member, body) {
42770
42907
  const spriteValue = body.value;
42908
+ if (spriteValue === null) {
42909
+ if (!member.required) return;
42910
+ throw new Error(
42911
+ `Sprite member "${member.name}" is required and cannot be null.`
42912
+ );
42913
+ }
42771
42914
  if (!isSpriteValue(spriteValue)) {
42772
42915
  throw new Error(
42773
42916
  "Sprite value must be { fileId: string, sliceIndex: integer >= 0 }."
42774
42917
  );
42775
42918
  }
42919
+ if (isEmptySpriteValue(spriteValue)) return;
42776
42920
  validateSpriteFileRegistration(document, member, spriteValue.fileId);
42777
42921
  }
42778
42922
  function validateSpriteFileRegistration(document, member, fileId) {
@@ -44456,7 +44600,13 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
44456
44600
  classId,
44457
44601
  ...partial ? { partial: true } : {},
44458
44602
  schemaKeyOrder: base?.kind === "class" ? base.schemaKeyOrder : null,
44459
- classArguments: lowerClassArguments(context, ownerClass, id2, fieldType)
44603
+ classArguments: lowerClassArguments(
44604
+ context,
44605
+ ownerClass,
44606
+ id2,
44607
+ fieldType,
44608
+ base?.kind === "class" ? base.classArguments : void 0
44609
+ )
44460
44610
  };
44461
44611
  }
44462
44612
  throw new Error(
@@ -44993,6 +45143,9 @@ function lowerExpressionValue(context, expression, declaredExpected, ownerClass,
44993
45143
  }
44994
45144
  switch (expression.kind) {
44995
45145
  case "litNull":
45146
+ if (!expected.nullable) {
45147
+ throw new Error(`Required value ${path} cannot be null.`);
45148
+ }
44996
45149
  return null;
44997
45150
  case "litBool":
44998
45151
  case "litString":
@@ -45212,6 +45365,9 @@ function lowerExpressionValue(context, expression, declaredExpected, ownerClass,
45212
45365
  );
45213
45366
  }
45214
45367
  case "member": {
45368
+ if (expected.name === "SpriteInfo" && expression.name === "Empty" && expression.receiver.kind === "ident" && expression.receiver.name === "SpriteInfo") {
45369
+ return { fileId: "", sliceIndex: 0 };
45370
+ }
45215
45371
  const fileId = projectFileIdForPath(context, expression);
45216
45372
  if (fileId !== null) return { fileId };
45217
45373
  if (expression.name === "Slice" || expression.receiver.kind === "call") {
@@ -45525,7 +45681,7 @@ function genericParameterName(context, genericParamId) {
45525
45681
  }
45526
45682
  return "object";
45527
45683
  }
45528
- function lowerClassArguments(context, ownerClass, ownerMemberId, type) {
45684
+ function lowerClassArguments(context, ownerClass, ownerMemberId, type, baseArguments) {
45529
45685
  const classId = context.classIdsByName.get(type.name);
45530
45686
  if (!classId) return {};
45531
45687
  const parameters = [
@@ -45541,7 +45697,8 @@ function lowerClassArguments(context, ownerClass, ownerMemberId, type) {
45541
45697
  result[parameterId] = { kind: "generic", genericParamId: ownerParameter };
45542
45698
  continue;
45543
45699
  }
45544
- const memberId = derivedGenericArgumentMemberId(ownerMemberId, parameterId);
45700
+ const baseBinding = baseArguments?.[parameterId];
45701
+ const memberId = baseBinding?.kind === "member" ? baseBinding.memberId : derivedGenericArgumentMemberId(ownerMemberId, parameterId);
45545
45702
  result[parameterId] = { kind: "member", memberId };
45546
45703
  lowerGenericArgument(
45547
45704
  context,
@@ -45561,6 +45718,7 @@ function lowerExtendsBindings(context, declaration, baseClassId, argumentsValue)
45561
45718
  ];
45562
45719
  const result = {};
45563
45720
  const declarationId = materializedId(declaration, "class", declaration.name);
45721
+ const baseBindings = context.baseClasses.get(declarationId)?.extendsGenericBindings ?? {};
45564
45722
  for (const [[parameterName, parameterId], argumentType] of zip(
45565
45723
  parameters,
45566
45724
  argumentsValue
@@ -45570,7 +45728,8 @@ function lowerExtendsBindings(context, declaration, baseClassId, argumentsValue)
45570
45728
  result[parameterId] = { kind: "generic", genericParamId: local };
45571
45729
  continue;
45572
45730
  }
45573
- const memberId = derivedGenericArgumentMemberId(declarationId, parameterId);
45731
+ const baseBinding = baseBindings[parameterId];
45732
+ const memberId = baseBinding?.kind === "member" ? baseBinding.memberId : derivedGenericArgumentMemberId(declarationId, parameterId);
45574
45733
  result[parameterId] = { kind: "member", memberId };
45575
45734
  lowerGenericArgument(
45576
45735
  context,
@@ -47596,6 +47755,15 @@ function renderDefaultValue(context, member, wrapper) {
47596
47755
  `${member.kind === "sprite" ? "Sprite" : "Audio"} member ${JSON.stringify(member.name)} default stores a file value with no fileId. Emitting it as null would delete the default on the next push.`
47597
47756
  );
47598
47757
  }
47758
+ if (value.fileId.length === 0) {
47759
+ const sliceIndex = typeof value.sliceIndex === "number" ? value.sliceIndex : 0;
47760
+ if (member.kind === "sprite" && sliceIndex === 0) {
47761
+ return "SpriteInfo.Empty";
47762
+ }
47763
+ throw new Error(
47764
+ `${member.kind === "sprite" ? "Sprite" : "Audio"} member ${JSON.stringify(member.name)} default stores an invalid empty file value.`
47765
+ );
47766
+ }
47599
47767
  const symbol = context.fileSymbols.get(value.fileId);
47600
47768
  const target = symbol ?? (member.kind === "sprite" ? `Reference<NeoImage>(id: ${quote(value.fileId)})` : `Reference<NeoAudioClip>(id: ${quote(value.fileId)})`);
47601
47769
  return member.kind === "sprite" ? `${target}.Slice(${typeof value.sliceIndex === "number" ? value.sliceIndex : 0})` : target;
@@ -48532,6 +48700,7 @@ function validateFinalizedFileAssignmentsV4(records2, options = {}) {
48532
48700
  return;
48533
48701
  }
48534
48702
  if (!isObjectRecord2(value)) return;
48703
+ if (isEmptySpriteValue(value)) return;
48535
48704
  if (isStructuredLeafPartialValue(value)) {
48536
48705
  visitSpriteFileField(
48537
48706
  value[STRUCTURED_LEAF_PARTIAL_KEY].fileId,
@@ -52532,7 +52701,10 @@ function classToLanguageType(schemaClass2, context) {
52532
52701
  }
52533
52702
  function constructorSignature(schemaClass2, members, context, genericEnvironment) {
52534
52703
  if (schemaClass2.isAbstract) return {};
52535
- if (!isClosedClass(schemaClass2.id, context.vm.classes)) return {};
52704
+ const projections = inheritedConstructorProjections(schemaClass2, context);
52705
+ if (projections.length === 0 && !isClosedClass(schemaClass2.id, context.vm.classes)) {
52706
+ return {};
52707
+ }
52536
52708
  if (resolveAllowedStorage(schemaClass2.id, context.vm.classes) === "immutable" /* Immutable */) {
52537
52709
  return { constructorUnavailableReason: "immutableOnly" };
52538
52710
  }
@@ -52553,11 +52725,43 @@ function constructorSignature(schemaClass2, members, context, genericEnvironment
52553
52725
  defaultValue: "defaultValue" in resolved ? resolved.defaultValue : void 0
52554
52726
  };
52555
52727
  } catch {
52728
+ if (projections.some((projection) => projection.memberId === memberId)) {
52729
+ return { required: true };
52730
+ }
52556
52731
  return null;
52557
52732
  }
52558
52733
  }
52559
52734
  );
52560
- return { constructorSignature: signature };
52735
+ return {
52736
+ constructorSignature: {
52737
+ ...signature,
52738
+ ...projections.length === 0 ? {} : {
52739
+ namedProjections: projections.map((projection) => ({
52740
+ name: projection.parameterName,
52741
+ memberId: projection.memberId
52742
+ }))
52743
+ }
52744
+ }
52745
+ };
52746
+ }
52747
+ function inheritedConstructorProjections(schemaClass2, context) {
52748
+ const classesById = new Map(
52749
+ context.vm.classes.map((candidate) => [candidate.id, candidate])
52750
+ );
52751
+ const projections = [];
52752
+ const claimed = /* @__PURE__ */ new Set();
52753
+ const visited = /* @__PURE__ */ new Set();
52754
+ let current = schemaClass2;
52755
+ while (current !== void 0 && !visited.has(current.id)) {
52756
+ visited.add(current.id);
52757
+ for (const projection of current.constructorProjections ?? []) {
52758
+ if (claimed.has(projection.parameterName)) continue;
52759
+ claimed.add(projection.parameterName);
52760
+ projections.push(projection);
52761
+ }
52762
+ current = current.extendsClassId ? classesById.get(current.extendsClassId) : void 0;
52763
+ }
52764
+ return projections;
52561
52765
  }
52562
52766
  function declaredConstructors(schemaClass2, context) {
52563
52767
  const requiredConstructorId2 = schemaClass2.requiredConstructorId;
@@ -54266,13 +54470,37 @@ function throwNamedBodyCompileFailure(args) {
54266
54470
  });
54267
54471
  }
54268
54472
  function resolveMemberDeclaredTypeInfo(member, members) {
54473
+ return resolveMemberDeclaredTypeInfoInternal(member, members, /* @__PURE__ */ new Set());
54474
+ }
54475
+ function resolveMemberDeclaredTypeInfoInternal(member, members, seen) {
54269
54476
  const resolved = resolveMember2(member, members);
54477
+ const memberId = getField(resolved, "id");
54478
+ if (typeof memberId === "string" && seen.has(memberId)) return null;
54479
+ const nextSeen = typeof memberId === "string" ? /* @__PURE__ */ new Set([...seen, memberId]) : new Set(seen);
54270
54480
  if (isMemberNSPropertyBase(resolved)) return resolved.returnTypeInfo;
54271
54481
  if (isMemberClassBase(resolved)) {
54482
+ const typeArguments = Object.fromEntries(
54483
+ Object.entries(resolved.classArguments ?? {}).flatMap(
54484
+ ([genericParamId, binding]) => {
54485
+ if (binding.kind !== "member") return [];
54486
+ const argumentMember = members.find(
54487
+ (candidate) => candidate.id === binding.memberId
54488
+ );
54489
+ if (argumentMember === void 0) return [];
54490
+ const argumentType = resolveMemberDeclaredTypeInfoInternal(
54491
+ argumentMember,
54492
+ members,
54493
+ nextSeen
54494
+ );
54495
+ return argumentType === null ? [] : [[genericParamId, { ...argumentType, required: true }]];
54496
+ }
54497
+ )
54498
+ );
54272
54499
  return {
54273
54500
  type: 7 /* Class */,
54274
54501
  required: resolved.required,
54275
- classId: resolved.classId
54502
+ classId: resolved.classId,
54503
+ ...Object.keys(typeArguments).length === 0 ? {} : { typeArguments }
54276
54504
  };
54277
54505
  }
54278
54506
  if (isMemberEnumBase(resolved)) {
@@ -54299,7 +54527,11 @@ function resolveMemberDeclaredTypeInfo(member, members) {
54299
54527
  (candidate) => candidate.id === resolved.entryMemberId
54300
54528
  );
54301
54529
  if (entry === void 0) return null;
54302
- const entryTypeInfo = resolveMemberDeclaredTypeInfo(entry, members);
54530
+ const entryTypeInfo = resolveMemberDeclaredTypeInfoInternal(
54531
+ entry,
54532
+ members,
54533
+ nextSeen
54534
+ );
54303
54535
  if (entryTypeInfo === null) return null;
54304
54536
  return {
54305
54537
  type: resolved.kind,
@@ -54315,7 +54547,7 @@ function resolveMemberDeclaredTypeInfo(member, members) {
54315
54547
  if (collection === void 0) return null;
54316
54548
  return lookupDeclaredTypeInfo(
54317
54549
  resolved,
54318
- resolveMemberDeclaredTypeInfo(collection, members)
54550
+ resolveMemberDeclaredTypeInfoInternal(collection, members, nextSeen)
54319
54551
  );
54320
54552
  }
54321
54553
  if (!memberKindSupportsStorage(resolved.kind)) return null;
@@ -75266,15 +75498,16 @@ function replayStoredConstructionV4(args) {
75266
75498
  member: compileMember,
75267
75499
  valueRow: candidate,
75268
75500
  valueId: args.valueId,
75269
- // P61 instance calls are self-contained and never inherit a declaration's
75270
- // required-constructor parameter envelope.
75271
- initializerOwnerClass: null
75501
+ // Instance calls are self-contained. Declaration calls inherit the class
75502
+ // header parameters that are in lexical scope at their source site.
75503
+ initializerOwnerClass: args.initializerOwnerClass ?? null
75272
75504
  });
75273
75505
  if (!isMemberValue(candidate) || !isInitValueContent(candidate)) {
75274
75506
  throw new Error(
75275
75507
  `Construction replay for value "${args.valueId}" did not compile an initializer row.`
75276
75508
  );
75277
75509
  }
75510
+ if (args.evaluate === false) return /* @__PURE__ */ new Map();
75278
75511
  const materialized = materializeInitializerValue({
75279
75512
  document,
75280
75513
  // Constructor-only rows have no structural member owner. Use the same
@@ -76384,7 +76617,7 @@ function lowerRowBackedDefault(context, member, backed, baseData3, binding) {
76384
76617
  const assignmentSlices = objectInitializerSlices(binding.initializer);
76385
76618
  for (const assignment of expression.initializer ?? []) {
76386
76619
  const childMember = classMemberByName(context, classId, assignment.name);
76387
- if (inheritedConstructorProjections(context.classes, schemaClass2.id).some(
76620
+ if (inheritedConstructorProjections2(context.classes, schemaClass2.id).some(
76388
76621
  (projection) => projection.memberId === childMember.id
76389
76622
  )) {
76390
76623
  throw new Error(
@@ -76797,7 +77030,7 @@ function lowerSeedRow(context, member, sourceExpression, source, valueId, path,
76797
77030
  effectiveClass.id,
76798
77031
  assignment.name
76799
77032
  );
76800
- if (inheritedConstructorProjections(
77033
+ if (inheritedConstructorProjections2(
76801
77034
  context.classes,
76802
77035
  effectiveClass.id
76803
77036
  ).some((projection) => projection.memberId === childMember.id)) {
@@ -77095,15 +77328,17 @@ function lowerValueRow(context, member, sourceExpression, expectedValueId, sourc
77095
77328
  `Value ${expectedValueId} stores constructor arguments but its declared member ${resolvedMember.name} is not a Class.`
77096
77329
  );
77097
77330
  }
77098
- context.initializerReconciliations.set(expectedValueId, {
77099
- valueId: expectedValueId,
77100
- code,
77101
- storedConstructorArgs: storedConstructorArgs === null ? null : structuredClone(storedConstructorArgs),
77102
- settleFields: expression.kind === "new" ? (expression.initializer ?? []).map(
77103
- (assignment) => assignment.name
77104
- ) : [],
77105
- site: referenceSite(source, expression)
77106
- });
77331
+ if (source.purpose === "stored") {
77332
+ context.initializerReconciliations.set(expectedValueId, {
77333
+ valueId: expectedValueId,
77334
+ code,
77335
+ storedConstructorArgs: storedConstructorArgs === null ? null : structuredClone(storedConstructorArgs),
77336
+ settleFields: expression.kind === "new" ? (expression.initializer ?? []).map(
77337
+ (assignment) => assignment.name
77338
+ ) : [],
77339
+ site: referenceSite(source, expression)
77340
+ });
77341
+ }
77107
77342
  const value2 = lowerClassValue(
77108
77343
  context,
77109
77344
  resolvedMember,
@@ -77143,7 +77378,12 @@ function lowerValueRow(context, member, sourceExpression, expectedValueId, sourc
77143
77378
  }
77144
77379
  function lowerValueBody(context, member, expression, base, source, environment, authoredSlice) {
77145
77380
  const next = valueFileFields(base);
77146
- if (expression.kind === "litNull") return { ...next, value: null };
77381
+ if (expression.kind === "litNull") {
77382
+ if (member.required) {
77383
+ throw new Error(`Required member ${member.name} cannot store null.`);
77384
+ }
77385
+ return { ...next, value: null };
77386
+ }
77147
77387
  switch (member.kind) {
77148
77388
  case "null":
77149
77389
  return { ...next, value: null };
@@ -77293,7 +77533,7 @@ function lowerClassValue(context, member, expression, base, source, outerEnviron
77293
77533
  }
77294
77534
  for (const assignment of expression.initializer ?? []) {
77295
77535
  const childMember = classMemberByName(context, classId, assignment.name);
77296
- if (inheritedConstructorProjections(context.classes, schemaClass2.id).some(
77536
+ if (inheritedConstructorProjections2(context.classes, schemaClass2.id).some(
77297
77537
  (projection) => projection.memberId === childMember.id
77298
77538
  )) {
77299
77539
  throw new Error(
@@ -77475,7 +77715,7 @@ function classInheritanceChain(classes, classId) {
77475
77715
  }
77476
77716
  return chain;
77477
77717
  }
77478
- function inheritedConstructorProjections(classes, classId) {
77718
+ function inheritedConstructorProjections2(classes, classId) {
77479
77719
  const resolved = [];
77480
77720
  const claimed = /* @__PURE__ */ new Set();
77481
77721
  for (const schemaClass2 of classInheritanceChain(classes, classId)) {
@@ -77496,7 +77736,7 @@ function inheritedProjectionSchemaKey(classes, classId, memberId) {
77496
77736
  return null;
77497
77737
  }
77498
77738
  function resolveConstructorProjectionArguments(context, schemaClass2, expression, ownerValueId, environment, source) {
77499
- const projections = inheritedConstructorProjections(
77739
+ const projections = inheritedConstructorProjections2(
77500
77740
  context.classes,
77501
77741
  schemaClass2.id
77502
77742
  );
@@ -78127,6 +78367,9 @@ function expressionAnchorPos(expression) {
78127
78367
  }
78128
78368
  function lowerFileValue(context, member, expression) {
78129
78369
  if (member.kind === "sprite") {
78370
+ if (isSpriteInfoEmptyExpression(expression)) {
78371
+ return { fileId: "", sliceIndex: 0 };
78372
+ }
78130
78373
  if (expression.kind !== "call" || expression.callee.kind !== "member" || expression.callee.name !== "Slice") {
78131
78374
  throw new Error(
78132
78375
  `Sprite member ${member.name} requires Image.Slice(index).`
@@ -79114,7 +79357,8 @@ function emitValueBody(context, member, value, visited, targetTyped, environment
79114
79357
  context,
79115
79358
  kind,
79116
79359
  `Audio member ${JSON.stringify(String(member.name ?? member.id))}`,
79117
- body
79360
+ body,
79361
+ member.required === true
79118
79362
  );
79119
79363
  }
79120
79364
  if (kind === 5)
@@ -79364,7 +79608,12 @@ function storedValueConstructor(context, schemaClass2, constructorArgs) {
79364
79608
  return selected2;
79365
79609
  }
79366
79610
  function storedConstructorArgumentSource(context, type, value, environment, visited, cloneAggregateArguments = false) {
79367
- if (value === null) return "null";
79611
+ if (value === null) {
79612
+ if (!type.nullable) {
79613
+ throw new Error("Required constructor argument stores null.");
79614
+ }
79615
+ return "null";
79616
+ }
79368
79617
  switch (type.kind) {
79369
79618
  case "null":
79370
79619
  return "null";
@@ -79396,7 +79645,8 @@ function storedConstructorArgumentSource(context, type, value, environment, visi
79396
79645
  {
79397
79646
  id: "__constructor_arg__",
79398
79647
  name: "argument",
79399
- kind: 11 /* Sprite */
79648
+ kind: 11 /* Sprite */,
79649
+ required: !type.nullable
79400
79650
  },
79401
79651
  value
79402
79652
  );
@@ -79405,7 +79655,8 @@ function storedConstructorArgumentSource(context, type, value, environment, visi
79405
79655
  context,
79406
79656
  12 /* Audio */,
79407
79657
  "constructor argument",
79408
- value
79658
+ value,
79659
+ !type.nullable
79409
79660
  );
79410
79661
  case "enum":
79411
79662
  return enumValue(
@@ -79790,7 +80041,7 @@ function manifestMemberTypeName(context, member) {
79790
80041
  return fixedSourceTypeNameForKind(member.kind) ?? member.kind;
79791
80042
  }
79792
80043
  function constructorProjectionSource(context, classId, body, visited, environment) {
79793
- const projections = inheritedConstructorProjections(
80044
+ const projections = inheritedConstructorProjections2(
79794
80045
  context.manifestClasses,
79795
80046
  classId
79796
80047
  );
@@ -80180,8 +80431,13 @@ function delegateValue(context, body) {
80180
80431
  }
80181
80432
  return target.name;
80182
80433
  }
80183
- function fileValue(context, kind, label, body) {
80184
- if (body === null || body === void 0) return "null";
80434
+ function fileValue(context, kind, label, body, required2) {
80435
+ if (body === null || body === void 0) {
80436
+ if (required2) {
80437
+ throw new Error(`${label} is required and cannot store null.`);
80438
+ }
80439
+ return "null";
80440
+ }
80185
80441
  if (!isObjectRecord2(body)) {
80186
80442
  throw new Error(
80187
80443
  `${label} stores ${JSON.stringify(body)}, which is not a file value record.`
@@ -80193,12 +80449,18 @@ function fileValue(context, kind, label, body) {
80193
80449
  );
80194
80450
  }
80195
80451
  if (body.fileId.length === 0) {
80196
- throw new Error(`${label} stores an empty fileId.`);
80452
+ if (kind === 11 && numberOr2(body.sliceIndex, 0) === 0) {
80453
+ return "SpriteInfo.Empty";
80454
+ }
80455
+ throw new Error(`${label} stores an invalid empty file value.`);
80197
80456
  }
80198
80457
  const base = fileRegistrySource(context, kind, body.fileId);
80199
80458
  if (kind !== 11) return base;
80200
80459
  return `${base}.Slice(${numberOr2(body.sliceIndex, 0)})`;
80201
80460
  }
80461
+ function isSpriteInfoEmptyExpression(expression) {
80462
+ return expression.kind === "member" && expression.name === "Empty" && expression.receiver.kind === "ident" && expression.receiver.name === "SpriteInfo";
80463
+ }
80202
80464
  function fileRegistrySource(context, kind, fileId) {
80203
80465
  const symbol = context.fileSymbols.get(fileId);
80204
80466
  if (symbol !== void 0) {
@@ -80223,7 +80485,9 @@ function structuredLeafSource(context, kind, member, body) {
80223
80485
  ${assignments.map((entry) => indentNeoSourceNonEmptyLines(entry, 2)).join(",\n")}
80224
80486
  }`;
80225
80487
  }
80226
- if (kind === 11 /* Sprite */) return fileValue(context, 11, label, body);
80488
+ if (kind === 11 /* Sprite */) {
80489
+ return fileValue(context, 11, label, body, member.required === true);
80490
+ }
80227
80491
  if (body === null) return "null";
80228
80492
  if (!isObjectRecord2(body)) return "new()";
80229
80493
  return `new(${structuredLeafFieldKeys(kind).map((key) => scalar(body[key])).join(", ")})`;
@@ -83940,11 +84204,12 @@ function replayAnimationDeclarationInitializersV4(records2, document) {
83940
84204
  });
83941
84205
  if (initializers.length === 0) return document;
83942
84206
  const rootKinds = /* @__PURE__ */ new Map();
84207
+ const rootOwners = /* @__PURE__ */ new Map();
83943
84208
  const owners = resolveOwnerMembersForValues(
83944
84209
  document,
83945
84210
  new Set(initializers.map(({ row }) => row.id)),
83946
84211
  void 0,
83947
- void 0,
84212
+ rootOwners,
83948
84213
  rootKinds
83949
84214
  );
83950
84215
  const pulled = /* @__PURE__ */ new Map();
@@ -83969,12 +84234,20 @@ function replayAnimationDeclarationInitializersV4(records2, document) {
83969
84234
  if (!classBelongsToAnimationFamily(document.classes, declaredClassId)) {
83970
84235
  continue;
83971
84236
  }
84237
+ const rootOwner = rootOwners.get(row.id);
84238
+ const rootOwnerId = Reflect.get(rootOwner ?? {}, "id");
84239
+ const initializerOwnerClass = typeof rootOwnerId !== "string" ? null : findSchemaPlacement(rootOwnerId, document.classes)?.ownerClass ?? null;
83972
84240
  const replay = replayStoredConstructionV4({
83973
84241
  records: pulled,
83974
84242
  valueId: row.id,
83975
84243
  code,
83976
84244
  member: owner,
83977
- compileDocumentBodies: true
84245
+ compileDocumentBodies: true,
84246
+ initializerOwnerClass,
84247
+ // A parameterized declaration is a template. Its arguments exist only
84248
+ // at concrete construction sites, so validate the authored body here
84249
+ // without fabricating values for the class header parameters.
84250
+ evaluate: initializerOwnerClass?.requiredConstructorId === void 0 || initializerOwnerClass.requiredConstructorId === null
83978
84251
  });
83979
84252
  for (const [id2, value] of replay) {
83980
84253
  if (!isMemberValue(value)) {
@@ -84358,6 +84631,7 @@ var init_workspace_status = __esm({
84358
84631
  init_project_documents();
84359
84632
  init_animation_clips();
84360
84633
  init_classes();
84634
+ init_inheritance();
84361
84635
  init_members();
84362
84636
  init_value_row_owner_members();
84363
84637
  init_project2();
@@ -94858,7 +95132,7 @@ async function downloadProjectVersionTransactionResult(args) {
94858
95132
  cursor = page.continueCursor;
94859
95133
  }
94860
95134
  }
94861
- async function runPush(workspace, options) {
95135
+ async function preparePushStatus(workspace, options) {
94862
95136
  const status = computeWorkspaceStatus(workspace);
94863
95137
  if (status.conflictedFiles.length > 0) {
94864
95138
  throw new Error(
@@ -94887,22 +95161,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
94887
95161
  changes: status.changes,
94888
95162
  binaryChanges: status.binaryChanges ?? []
94889
95163
  });
94890
- if (status.changes.length === 0) {
94891
- if (options.json === true) {
94892
- console.log(
94893
- JSON.stringify({
94894
- type: "push-plan",
94895
- dryRun: options.dryRun,
94896
- changeCount: 0,
94897
- staticValueSeedCount: 0,
94898
- changes: []
94899
- })
94900
- );
94901
- } else {
94902
- console.log("Nothing to push \u2014 working copy is clean.");
94903
- }
94904
- return;
94905
- }
95164
+ if (status.changes.length === 0) return status;
94906
95165
  const initConversions = status.initConversions ?? [];
94907
95166
  if (initConversions.length > 0 && options.json !== true) {
94908
95167
  for (const line of describeInitConversions(initConversions)) warn(line);
@@ -94974,8 +95233,40 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
94974
95233
  } catch (error) {
94975
95234
  reportDryRunFailure(error, options.json === true);
94976
95235
  process.exitCode = 1;
94977
- return;
95236
+ return null;
95237
+ }
95238
+ }
95239
+ return status;
95240
+ }
95241
+ async function runPush(workspace, options, preparationOverride) {
95242
+ const preparation = preparationOverride === void 0 ? process.stdout.isTTY === true && options.json !== true ? spinner("Preparing push\u2026") : null : preparationOverride;
95243
+ let status;
95244
+ try {
95245
+ status = await preparePushStatus(workspace, options);
95246
+ } catch (error) {
95247
+ preparation?.fail("Push preparation failed.");
95248
+ throw error;
95249
+ }
95250
+ if (status === null) {
95251
+ preparation?.fail("Push preparation failed.");
95252
+ return;
95253
+ }
95254
+ preparation?.stop();
95255
+ if (status.changes.length === 0) {
95256
+ if (options.json === true) {
95257
+ console.log(
95258
+ JSON.stringify({
95259
+ type: "push-plan",
95260
+ dryRun: options.dryRun,
95261
+ changeCount: 0,
95262
+ staticValueSeedCount: 0,
95263
+ changes: []
95264
+ })
95265
+ );
95266
+ } else {
95267
+ console.log("Nothing to push \u2014 working copy is clean.");
94978
95268
  }
95269
+ return;
94979
95270
  }
94980
95271
  if (options.json === true) {
94981
95272
  console.log(
@@ -98100,14 +98391,29 @@ async function main() {
98100
98391
  return;
98101
98392
  }
98102
98393
  case "push": {
98103
- const workspace = loadWorkspaceForCommand(args);
98104
- const { runPush: runPush2 } = await Promise.resolve().then(() => (init_push(), push_exports));
98105
- await runPush2(workspace, {
98106
- dryRun: boolFlag(args, "dry-run"),
98107
- summary: stringFlag(args, "summary"),
98108
- acceptBump: boolFlag(args, "accept-bump"),
98109
- json: boolFlag(args, "json")
98110
- });
98394
+ const json = boolFlag(args, "json");
98395
+ const preparation = process.stdout.isTTY === true && json !== true ? spinner("Preparing push\u2026") : null;
98396
+ let handedOffPreparation = false;
98397
+ try {
98398
+ const workspace = loadWorkspaceForCommand(args);
98399
+ const { runPush: runPush2 } = await Promise.resolve().then(() => (init_push(), push_exports));
98400
+ handedOffPreparation = true;
98401
+ await runPush2(
98402
+ workspace,
98403
+ {
98404
+ dryRun: boolFlag(args, "dry-run"),
98405
+ summary: stringFlag(args, "summary"),
98406
+ acceptBump: boolFlag(args, "accept-bump"),
98407
+ json
98408
+ },
98409
+ preparation
98410
+ );
98411
+ } catch (error) {
98412
+ if (!handedOffPreparation) {
98413
+ preparation?.fail("Push preparation failed.");
98414
+ }
98415
+ throw error;
98416
+ }
98111
98417
  return;
98112
98418
  }
98113
98419
  case "status": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.21.2",
3
+ "version": "0.21.4",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.21.2 -->
12
+ <!-- reviewed-through-cli: 0.21.4 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -66,12 +66,15 @@ Use complete values in normal member initialization:
66
66
 
67
67
  ```neo
68
68
  Sprite = Images.Hero.Slice(0);
69
+ Placeholder = SpriteInfo.Empty;
69
70
  Position = new(0, 0, 0);
70
71
  Tint = Color(1, 1, 1, 1);
71
72
  ```
72
73
 
73
74
  A bare `Images.Hero` yields a file ID only in a file-valued field context.
74
75
  Use `.Slice(index)` for a complete `SpriteInfo`.
76
+ Use `SpriteInfo.Empty` for an authored non-null empty sprite. Use `null` only
77
+ when the declared type is nullable `SpriteInfo?`.
75
78
 
76
79
  NeoScript may read or write structured fields when the receiver's effective
77
80
  storage permits it:
@@ -73,7 +73,7 @@ wrappers.
73
73
  The marker near the top of `SKILL.md` must exactly match the package version:
74
74
 
75
75
  ```html
76
- <!-- reviewed-through-cli: 0.21.2 -->
76
+ <!-- reviewed-through-cli: 0.21.4 -->
77
77
  ```
78
78
 
79
79
  The quoted version above is checked too, so this instruction cannot go stale
@@ -37,6 +37,10 @@ validation — so a rejection that used to cost a real push and return an opaque
37
37
  `preparation-failed` now reports before anything is sent. It does not prompt,
38
38
  upload, commit, or rewrite source.
39
39
 
40
+ Interactive pushes show `Preparing push…` before workspace discovery,
41
+ lowering, and compilation begin. JSON and non-TTY output remain structured and
42
+ do not emit that terminal-only spinner.
43
+
40
44
  A clean dry-run does not promise a clean push. The workspace holds its
41
45
  last-pulled state, so everything that grades the live head is still server-only:
42
46
  re-verification of the uploaded bundle bytes, project-file staging, the