@neocompose/cli 0.31.11 → 0.31.12

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.31.12] - 2026-08-17
4
+
5
+ ### Fixed
6
+
7
+ - Re-emit stored variant member selections through canonical
8
+ `<Class>.Variants...` paths, including variants nested in class and list
9
+ rows, instead of failing with an unsupported numeric value kind.
10
+
3
11
  ## [0.31.11] - 2026-08-17
4
12
 
5
13
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -49634,6 +49634,93 @@ var init_structured_leaf_source = __esm({
49634
49634
  }
49635
49635
  });
49636
49636
 
49637
+ // src/project-source/variant-selection-source.ts
49638
+ function isObject2(value) {
49639
+ return typeof value === "object" && value !== null && !Array.isArray(value);
49640
+ }
49641
+ function buildVariantPathsByIdV4(manifest) {
49642
+ const variants = manifest.variants ?? [];
49643
+ if (variants.length === 0) return /* @__PURE__ */ new Map();
49644
+ const classNames = new Map(
49645
+ manifest.classes.map((entry) => [entry.id, entry.name])
49646
+ );
49647
+ const folderPaths = new Map(
49648
+ (manifest.variantFolders ?? []).map(
49649
+ (entry) => [entry.id, entry.path]
49650
+ )
49651
+ );
49652
+ const paths = /* @__PURE__ */ new Map();
49653
+ for (const variant of variants) {
49654
+ const className = classNames.get(variant.classId);
49655
+ if (className === void 0) continue;
49656
+ paths.set(
49657
+ variant.id,
49658
+ neoVariantDottedPath({
49659
+ className,
49660
+ folderPath: variant.folderId === null ? "" : folderPaths.get(variant.folderId) ?? "",
49661
+ variantName: variant.name
49662
+ })
49663
+ );
49664
+ }
49665
+ return paths;
49666
+ }
49667
+ function renderVariantSelectionV4(context, memberName, value) {
49668
+ if (value === null) return "null";
49669
+ if (!isObject2(value)) {
49670
+ throw new Error(
49671
+ `Variant member ${memberName} stores ${JSON.stringify(value)}, which is not a { classId, variantId } selection.`
49672
+ );
49673
+ }
49674
+ const variantId = value.variantId;
49675
+ if (variantId === null) {
49676
+ const classId = value.classId;
49677
+ if (typeof classId !== "string") {
49678
+ throw new Error(
49679
+ `Variant member ${memberName} selects the base entry without naming a class.`
49680
+ );
49681
+ }
49682
+ const className = context.classes.get(classId)?.name;
49683
+ if (className === void 0) {
49684
+ throw new Error(
49685
+ `Variant member ${memberName} selects class ${JSON.stringify(classId)}, which this project does not declare.`
49686
+ );
49687
+ }
49688
+ return neoVariantDottedPath({
49689
+ className,
49690
+ folderPath: "",
49691
+ variantName: null
49692
+ });
49693
+ }
49694
+ if (typeof variantId !== "string") {
49695
+ throw new Error(
49696
+ `Variant member ${memberName} stores a variant id that is neither a string nor null.`
49697
+ );
49698
+ }
49699
+ const path = context.variantPathsById.get(variantId);
49700
+ if (path === void 0) {
49701
+ throw new Error(
49702
+ `Variant member ${memberName} selects variant ${JSON.stringify(variantId)}, which this project does not declare.`
49703
+ );
49704
+ }
49705
+ const rowValueId = value.rowValueId;
49706
+ if (rowValueId === null || rowValueId === void 0) return path;
49707
+ if (typeof rowValueId !== "string") {
49708
+ throw new Error(
49709
+ `Variant member ${memberName} stores a malformed rowValueId.`
49710
+ );
49711
+ }
49712
+ const rootPath = context.collectionValuePaths.get(rowValueId);
49713
+ const reference2 = rootPath === void 0 ? `Reference(id: ${quoteNeoString(rowValueId)})` : `Reference(${rootPath})`;
49714
+ return `${path}.Bind(${reference2})`;
49715
+ }
49716
+ var init_variant_selection_source = __esm({
49717
+ "src/project-source/variant-selection-source.ts"() {
49718
+ "use strict";
49719
+ init_src();
49720
+ init_source_format();
49721
+ }
49722
+ });
49723
+
49637
49724
  // src/project-source/emitter.ts
49638
49725
  function emitProjectSourcesV4(manifest, options = {}) {
49639
49726
  const context = {
@@ -49649,7 +49736,7 @@ function emitProjectSourcesV4(manifest, options = {}) {
49649
49736
  defaultInitializers: options.defaultInitializers ?? /* @__PURE__ */ new Map(),
49650
49737
  fileSymbols: options.fileSymbols ?? /* @__PURE__ */ new Map(),
49651
49738
  collectionValuePaths: options.collectionValuePaths ?? /* @__PURE__ */ new Map(),
49652
- variantPathsById: buildVariantPathsById(manifest),
49739
+ variantPathsById: buildVariantPathsByIdV4(manifest),
49653
49740
  templateNames: new Map(
49654
49741
  [...manifest.textureTemplates, ...manifest.audioTemplates].map(
49655
49742
  (template) => [template.id, template.name]
@@ -50525,7 +50612,7 @@ function renderDefault(context, member) {
50525
50612
  return renderDefaultValue(context, member, value);
50526
50613
  }
50527
50614
  function renderActionListeners(context, member, value) {
50528
- if (!isObject2(value)) {
50615
+ if (!isObject3(value)) {
50529
50616
  throw new Error(
50530
50617
  `NeoAction member ${member.name} stores a default that is not a listener set.`
50531
50618
  );
@@ -50537,7 +50624,7 @@ function renderActionListeners(context, member, value) {
50537
50624
  );
50538
50625
  }
50539
50626
  const names = listeners.map((listener, index) => {
50540
- if (!isObject2(listener)) {
50627
+ if (!isObject3(listener)) {
50541
50628
  throw new Error(
50542
50629
  `NeoAction member ${member.name} listener ${index} is not a member target.`
50543
50630
  );
@@ -50598,7 +50685,7 @@ function renderDefaultValue(context, member, wrapper) {
50598
50685
  const entry = required(context.members, member.entryMemberId, "list entry");
50599
50686
  return `[${value.map((item) => renderNestedValue(context, entry, item)).join(", ")}]`;
50600
50687
  }
50601
- if (member.kind === "dictionary" && isObject2(value)) {
50688
+ if (member.kind === "dictionary" && isObject3(value)) {
50602
50689
  const entry = required(
50603
50690
  context.members,
50604
50691
  member.entryMemberId,
@@ -50608,7 +50695,7 @@ function renderDefaultValue(context, member, wrapper) {
50608
50695
  ([key, item]) => `${quote(key)}: ${renderNestedValue(context, entry, item)}`
50609
50696
  ).join(", ")} }`;
50610
50697
  }
50611
- if (member.kind === "class" && isObject2(value)) {
50698
+ if (member.kind === "class" && isObject3(value)) {
50612
50699
  const target = required(context.classes, member.classId, "class");
50613
50700
  const assignments = Object.entries(value).map(([key, item]) => {
50614
50701
  const childId = target.schema[key];
@@ -50625,9 +50712,9 @@ function renderDefaultValue(context, member, wrapper) {
50625
50712
  return renderActionListeners(context, member, value);
50626
50713
  }
50627
50714
  if (member.kind === "variant") {
50628
- return renderVariantSelection(context, member, value);
50715
+ return renderVariantSelectionV4(context, member.name, value);
50629
50716
  }
50630
- if (member.kind === "functionRef" && isObject2(value) && typeof value.functionMemberId === "string") {
50717
+ if (member.kind === "functionRef" && isObject3(value) && typeof value.functionMemberId === "string") {
50631
50718
  return required(
50632
50719
  context.members,
50633
50720
  value.functionMemberId,
@@ -50663,7 +50750,7 @@ function renderDefaultValue(context, member, wrapper) {
50663
50750
  return `new { ${assignments.join(", ")} }`;
50664
50751
  }
50665
50752
  if (member.kind === "sprite" || member.kind === "audio") {
50666
- if (!isObject2(value)) return jsonValue2(value);
50753
+ if (!isObject3(value)) return jsonValue2(value);
50667
50754
  if (typeof value.fileId !== "string") {
50668
50755
  throw new Error(
50669
50756
  `${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.`
@@ -50682,7 +50769,7 @@ function renderDefaultValue(context, member, wrapper) {
50682
50769
  const target = symbol ?? (member.kind === "sprite" ? `Reference<NeoImage>(id: ${quote(value.fileId)})` : `Reference<NeoAudioClip>(id: ${quote(value.fileId)})`);
50683
50770
  return member.kind === "sprite" ? `${target}.Slice(${typeof value.sliceIndex === "number" ? value.sliceIndex : 0})` : target;
50684
50771
  }
50685
- if (leafKind !== null && isObject2(value)) {
50772
+ if (leafKind !== null && isObject3(value)) {
50686
50773
  return `new(${structuredLeafFieldKeys(leafKind).map((key) => jsonValue2(value[key])).join(", ")})`;
50687
50774
  }
50688
50775
  return jsonValue2(value);
@@ -50977,7 +51064,7 @@ function jsonValue2(value) {
50977
51064
  if (typeof value === "boolean") return String(value);
50978
51065
  if (value === null || value === void 0) return "null";
50979
51066
  if (Array.isArray(value)) return `[${value.map(jsonValue2).join(", ")}]`;
50980
- if (isObject2(value))
51067
+ if (isObject3(value))
50981
51068
  return `{ ${Object.entries(value).map(([key, entry]) => `${quote(key)}: ${jsonValue2(entry)}`).join(", ")} }`;
50982
51069
  throw new Error(`Cannot emit Neo value ${String(value)}.`);
50983
51070
  }
@@ -51182,81 +51269,9 @@ function required(map, key, kind) {
51182
51269
  if (!value) throw new Error(`Unknown ${kind} ${quote(key)}.`);
51183
51270
  return value;
51184
51271
  }
51185
- function isObject2(value) {
51272
+ function isObject3(value) {
51186
51273
  return typeof value === "object" && value !== null && !Array.isArray(value);
51187
51274
  }
51188
- function buildVariantPathsById(manifest) {
51189
- if (manifest.variants.length === 0) return /* @__PURE__ */ new Map();
51190
- const classNames = new Map(
51191
- manifest.classes.map((entry) => [entry.id, entry.name])
51192
- );
51193
- const folderPaths = new Map(
51194
- manifest.variantFolders.map((entry) => [entry.id, entry.path])
51195
- );
51196
- const paths = /* @__PURE__ */ new Map();
51197
- for (const variant of manifest.variants) {
51198
- const className = classNames.get(variant.classId);
51199
- if (className === void 0) continue;
51200
- paths.set(
51201
- variant.id,
51202
- neoVariantDottedPath({
51203
- className,
51204
- folderPath: variant.folderId === null ? "" : folderPaths.get(variant.folderId) ?? "",
51205
- variantName: variant.name
51206
- })
51207
- );
51208
- }
51209
- return paths;
51210
- }
51211
- function renderVariantSelection(context, member, value) {
51212
- if (value === null) return "null";
51213
- if (!isObject2(value)) {
51214
- throw new Error(
51215
- `Variant member ${member.name} stores ${JSON.stringify(value)}, which is not a { classId, variantId } selection.`
51216
- );
51217
- }
51218
- const variantId = value.variantId;
51219
- if (variantId === null) {
51220
- const classId = value.classId;
51221
- if (typeof classId !== "string") {
51222
- throw new Error(
51223
- `Variant member ${member.name} selects the base entry without naming a class.`
51224
- );
51225
- }
51226
- const className = context.classes.get(classId)?.name;
51227
- if (className === void 0) {
51228
- throw new Error(
51229
- `Variant member ${member.name} selects class ${JSON.stringify(classId)}, which this project does not declare.`
51230
- );
51231
- }
51232
- return neoVariantDottedPath({
51233
- className,
51234
- folderPath: "",
51235
- variantName: null
51236
- });
51237
- }
51238
- if (typeof variantId !== "string") {
51239
- throw new Error(
51240
- `Variant member ${member.name} stores a variant id that is neither a string nor null.`
51241
- );
51242
- }
51243
- const path = context.variantPathsById.get(variantId);
51244
- if (path === void 0) {
51245
- throw new Error(
51246
- `Variant member ${member.name} selects variant ${JSON.stringify(variantId)}, which this project does not declare.`
51247
- );
51248
- }
51249
- const rowValueId = value.rowValueId;
51250
- if (rowValueId === null || rowValueId === void 0) return path;
51251
- if (typeof rowValueId !== "string") {
51252
- throw new Error(
51253
- `Variant member ${member.name} stores a malformed rowValueId.`
51254
- );
51255
- }
51256
- const rootPath = context.collectionValuePaths.get(rowValueId);
51257
- const reference2 = rootPath === void 0 ? `Reference(id: ${quote(rowValueId)})` : `Reference(${rootPath})`;
51258
- return `${path}.Bind(${reference2})`;
51259
- }
51260
51275
  var SPECIALIZED_RELATION_PROPERTIES;
51261
51276
  var init_emitter = __esm({
51262
51277
  "src/project-source/emitter.ts"() {
@@ -51267,6 +51282,7 @@ var init_emitter = __esm({
51267
51282
  init_class_generics();
51268
51283
  init_members();
51269
51284
  init_structured_leaf_source();
51285
+ init_variant_selection_source();
51270
51286
  SPECIALIZED_RELATION_PROPERTIES = /* @__PURE__ */ new Map([
51271
51287
  ["world.grid.tile-import", { property: "tileImports", collection: true }],
51272
51288
  ["world.grid.object-import", { property: "objectImports", collection: true }],
@@ -73235,7 +73251,7 @@ async function fetchProjectDocumentPartitionRecords(runner, mapKey) {
73235
73251
  });
73236
73252
  }
73237
73253
  function readProjectDocumentManifestPage(value) {
73238
- if (!isObject3(value)) {
73254
+ if (!isObject4(value)) {
73239
73255
  throw new Error("Convex project document manifest page must be an object.");
73240
73256
  }
73241
73257
  const continueCursor = value.continueCursor;
@@ -73245,7 +73261,7 @@ function readProjectDocumentManifestPage(value) {
73245
73261
  );
73246
73262
  }
73247
73263
  const metadata = value.metadata ?? null;
73248
- if (metadata !== null && !isObject3(metadata)) {
73264
+ if (metadata !== null && !isObject4(metadata)) {
73249
73265
  throw new Error(
73250
73266
  "Convex project document manifest page metadata must be an object or null."
73251
73267
  );
@@ -73270,7 +73286,7 @@ function readProjectDocumentManifestRecords(value) {
73270
73286
  );
73271
73287
  }
73272
73288
  return value.map((record3, index) => {
73273
- if (!isObject3(record3)) {
73289
+ if (!isObject4(record3)) {
73274
73290
  throw new Error(
73275
73291
  `Convex project document manifest record at index ${index} was not an object.`
73276
73292
  );
@@ -73304,7 +73320,7 @@ function readProjectDocumentManifestRecords(value) {
73304
73320
  });
73305
73321
  }
73306
73322
  function readProjectDocumentManifestPageRecords(value) {
73307
- if (!isObject3(value)) {
73323
+ if (!isObject4(value)) {
73308
73324
  throw new Error("Convex project document manifest page must be an object.");
73309
73325
  }
73310
73326
  return readProjectDocumentManifestRecords(value.records);
@@ -73342,7 +73358,7 @@ async function fetchProjectDocumentSnapshots(fetchBatch, snapshotIds) {
73342
73358
  return snapshotsById;
73343
73359
  }
73344
73360
  function readProjectDocumentSnapshotBatch(value, requestedIds) {
73345
- if (!isObject3(value)) {
73361
+ if (!isObject4(value)) {
73346
73362
  throw new Error("Convex project record snapshot batch must be an object.");
73347
73363
  }
73348
73364
  const snapshots = value.snapshots;
@@ -73362,7 +73378,7 @@ function readProjectDocumentSnapshotBatch(value, requestedIds) {
73362
73378
  );
73363
73379
  }
73364
73380
  return snapshots.map((snapshot, index) => {
73365
- if (!isObject3(snapshot)) {
73381
+ if (!isObject4(snapshot)) {
73366
73382
  throw new Error(
73367
73383
  `Convex project record snapshot at index ${index} was not an object.`
73368
73384
  );
@@ -73544,7 +73560,7 @@ function appendBucketRecord(buckets, recordKind, data) {
73544
73560
  buckets[bucket].push(data);
73545
73561
  }
73546
73562
  function readProjectDocument(value, options = {}) {
73547
- if (!isObject3(value)) {
73563
+ if (!isObject4(value)) {
73548
73564
  throw new Error("Convex project document query returned a non-object.");
73549
73565
  }
73550
73566
  const localizationConfig = options.localizationConfig === "nullable" ? readOptionalNullableField(
@@ -73634,7 +73650,7 @@ function readProjectDocument(value, options = {}) {
73634
73650
  };
73635
73651
  }
73636
73652
  function readProjectDocumentRevisionMarker(value) {
73637
- if (!isObject3(value)) return null;
73653
+ if (!isObject4(value)) return null;
73638
73654
  if (!("documentRevision" in value)) return null;
73639
73655
  const marker = value.documentRevision;
73640
73656
  if (isProjectDocumentRevisionMarker(marker)) return marker;
@@ -73643,7 +73659,7 @@ function readProjectDocumentRevisionMarker(value) {
73643
73659
  );
73644
73660
  }
73645
73661
  function readProjectDocumentContentHashHeads(value) {
73646
- if (!isObject3(value)) {
73662
+ if (!isObject4(value)) {
73647
73663
  throw new Error(
73648
73664
  "Convex project document content hash source must be an object."
73649
73665
  );
@@ -73651,7 +73667,7 @@ function readProjectDocumentContentHashHeads(value) {
73651
73667
  return readArrayField(value, "projectDocumentContentHashHeads", isHashHead);
73652
73668
  }
73653
73669
  function isHashHead(value) {
73654
- if (!isObject3(value)) return false;
73670
+ if (!isObject4(value)) return false;
73655
73671
  if (!isProjectRecordKind(value.recordKind)) return false;
73656
73672
  if (typeof value.recordId !== "string") return false;
73657
73673
  if (typeof value.deleted !== "boolean") return false;
@@ -73687,12 +73703,12 @@ function readOptionalArrayField(source, key, isValid) {
73687
73703
  if (!Object.prototype.hasOwnProperty.call(source, key)) return [];
73688
73704
  return readArrayField(source, key, isValid);
73689
73705
  }
73690
- function isObject3(value) {
73706
+ function isObject4(value) {
73691
73707
  if (typeof value !== "object") return false;
73692
73708
  return value !== null;
73693
73709
  }
73694
73710
  function describeInvalidReadItem(value) {
73695
- if (!isObject3(value)) {
73711
+ if (!isObject4(value)) {
73696
73712
  return `received ${typeof value}`;
73697
73713
  }
73698
73714
  const parts = [];
@@ -95207,6 +95223,7 @@ function buildValueEmitContext(records2, manifest) {
95207
95223
  mainLocale: projectMainLocale(records2),
95208
95224
  symbolsByValueId,
95209
95225
  rootPathsByValueId: rootValuePathsByValueId(records2.values()),
95226
+ variantPathsById: buildVariantPathsByIdV4(manifest),
95210
95227
  symbolsByDialogueId: new Map(
95211
95228
  [...dialogues].flatMap(
95212
95229
  ([id2, dialogue]) => typeof dialogue.sourceName === "string" ? [[id2, dialogue.sourceName]] : []
@@ -100098,6 +100115,17 @@ function emitValueBody(context, member, value, visited, targetTyped, environment
100098
100115
  if (kind === 24) return functionReferenceValue(context, body);
100099
100116
  if (kind === 25) return delegateValue(context, body);
100100
100117
  if (kind === 26) return actionValue(context, body);
100118
+ if (kind === 27 /* Variant */) {
100119
+ return renderVariantSelectionV4(
100120
+ {
100121
+ classes: context.manifestClasses,
100122
+ variantPathsById: context.variantPathsById,
100123
+ collectionValuePaths: context.rootPathsByValueId
100124
+ },
100125
+ String(member.name ?? member.id),
100126
+ body
100127
+ );
100128
+ }
100101
100129
  if (kind === 12) {
100102
100130
  return fileValue(
100103
100131
  context,
@@ -100120,8 +100148,9 @@ function emitValueBody(context, member, value, visited, targetTyped, environment
100120
100148
  targetTyped,
100121
100149
  environment
100122
100150
  );
100151
+ const kindName = MemberKind[kind];
100123
100152
  throw new Error(
100124
- `Stored member ${String(member.name ?? member.id)} has unsupported value kind ${kind}.`
100153
+ typeof kindName === "string" ? `Stored member ${String(member.name ?? member.id)} uses unsupported ${kindName} value kind (${kind}).` : `Stored member ${String(member.name ?? member.id)} has unknown value kind ${kind}.`
100125
100154
  );
100126
100155
  }
100127
100156
  function classValue(context, member, value, visited, targetTyped, outerEnvironment) {
@@ -101690,6 +101719,7 @@ var init_value_sources = __esm({
101690
101719
  init_structured_leaf_source();
101691
101720
  init_member_kind_type_names();
101692
101721
  init_initializer_replay();
101722
+ init_variant_selection_source();
101693
101723
  INFERRED_GENERIC_CLASS_PREFIX = "__inferred_class__:";
101694
101724
  MEMBER_KIND_DICTIONARY2 = 5;
101695
101725
  MEMBER_KIND_LIST2 = 6;
@@ -111519,7 +111549,7 @@ var init_registry2 = __esm({
111519
111549
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
111520
111550
  formatVersion: 3,
111521
111551
  contractVersion: "3.13",
111522
- cliVersion: "0.31.11",
111552
+ cliVersion: "0.31.12",
111523
111553
  projectFileUploadBatchSize: 32,
111524
111554
  documentRecords: {
111525
111555
  member: {
@@ -118114,7 +118144,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
118114
118144
  async function main() {
118115
118145
  const args = parseArgs(process.argv.slice(2));
118116
118146
  if (args.command === "--version") {
118117
- console.log("0.31.11");
118147
+ console.log("0.31.12");
118118
118148
  return;
118119
118149
  }
118120
118150
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.31.11",
3
+ "version": "0.31.12",
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.31.11 -->
12
+ <!-- reviewed-through-cli: 0.31.12 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -83,7 +83,7 @@ wrappers.
83
83
  The marker near the top of `SKILL.md` must exactly match the package version:
84
84
 
85
85
  ```html
86
- <!-- reviewed-through-cli: 0.31.11 -->
86
+ <!-- reviewed-through-cli: 0.31.12 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale
@@ -56,6 +56,17 @@ or narrowing guard before the next access.
56
56
 
57
57
  Treat lookup-return values with the same nullable narrowing rules.
58
58
 
59
+ A declaration pattern introduced by `is` stays available after a guard when
60
+ every path that falls through matched the pattern, including when the guard's
61
+ other paths return from a nested block:
62
+
63
+ ```neo
64
+ if (!(item is BedItem bedItem)) {
65
+ throw "Expected a bed item";
66
+ }
67
+ return bedItem.Name;
68
+ ```
69
+
59
70
  Use `SpriteInfo.Empty` when a required sprite should intentionally render no
60
71
  image. It is the canonical non-null empty sprite: it has no backing file and
61
72
  uses slice index zero.