@neocompose/cli 0.31.9 → 0.31.11

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,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.31.11] - 2026-08-17
4
+
5
+ ### Fixed
6
+
7
+ - Resolve lookup variants declared in root-path folders from member defaults
8
+ without adding an unaddressable empty path segment.
9
+ - Allow stored static class rows to assign variant members with canonical
10
+ `<Class>.Variants...` selections.
11
+ - Recognize nested value returns in variant `initialize` delegates and retain
12
+ declared `is` bindings after terminating guards.
13
+
14
+ ## [0.31.10] - 2026-08-17
15
+
16
+ ### Fixed
17
+
18
+ - Allow a normal pull to merge remote changes while local source contains new
19
+ members whose server-owned identity envelope has not been assigned yet.
20
+ - Recompile locally edited constructors after pull merge instead of rejecting
21
+ stale server IR compiled for their previous parameter list.
22
+
3
23
  ## [0.31.9] - 2026-08-17
4
24
 
5
25
  ### Added
package/dist/neo.mjs CHANGED
@@ -7482,6 +7482,42 @@ var init_strict_parser = __esm({
7482
7482
  }
7483
7483
  });
7484
7484
 
7485
+ // ../packages/neoscript-language/src/strict-ast.ts
7486
+ function collectReturnStatements(statements) {
7487
+ const result = [];
7488
+ for (const statement of statements) {
7489
+ if (statement.kind === "return") result.push(statement);
7490
+ if (statement.kind === "if") {
7491
+ for (const branch of statement.branches) {
7492
+ result.push(...collectReturnStatements(branch.body));
7493
+ }
7494
+ if (statement.elseBody) {
7495
+ result.push(...collectReturnStatements(statement.elseBody));
7496
+ }
7497
+ }
7498
+ if (statement.kind === "for" || statement.kind === "forEach") {
7499
+ result.push(...collectReturnStatements(statement.body));
7500
+ }
7501
+ if (statement.kind === "switch") {
7502
+ for (const section of statement.sections) {
7503
+ result.push(...collectReturnStatements(section.body));
7504
+ }
7505
+ }
7506
+ if (statement.kind === "try") {
7507
+ result.push(...collectReturnStatements(statement.body));
7508
+ for (const clause of statement.catches) {
7509
+ result.push(...collectReturnStatements(clause.body));
7510
+ }
7511
+ }
7512
+ }
7513
+ return result;
7514
+ }
7515
+ var init_strict_ast = __esm({
7516
+ "../packages/neoscript-language/src/strict-ast.ts"() {
7517
+ "use strict";
7518
+ }
7519
+ });
7520
+
7485
7521
  // ../packages/neoscript-language/src/declared-constructors.ts
7486
7522
  function neoScriptArgumentNameSetKey(names) {
7487
7523
  return [...names].map((name) => name.toLowerCase()).sort().join(",");
@@ -8152,7 +8188,7 @@ function mergeFactSets(sets) {
8152
8188
  return first.filter(
8153
8189
  (fact) => sets.slice(1).every(
8154
8190
  (set) => set.some(
8155
- (candidate) => candidate.path === fact.path && candidate.nullValue === fact.nullValue && typeAnnotationsEqual(candidate.targetType, fact.targetType)
8191
+ (candidate) => candidate.path === fact.path && candidate.nullValue === fact.nullValue && candidate.bindingName === fact.bindingName && candidate.loose === fact.loose && typeAnnotationsEqual(candidate.targetType, fact.targetType)
8156
8192
  )
8157
8193
  )
8158
8194
  );
@@ -9106,31 +9142,6 @@ function instructionsPropagatePastSwitch(instructions) {
9106
9142
  }
9107
9143
  return false;
9108
9144
  }
9109
- function collectReturns(statements) {
9110
- const result = [];
9111
- for (const statement of statements) {
9112
- if (statement.kind === "return") result.push(statement);
9113
- if (statement.kind === "if") {
9114
- for (const branch of statement.branches)
9115
- result.push(...collectReturns(branch.body));
9116
- if (statement.elseBody)
9117
- result.push(...collectReturns(statement.elseBody));
9118
- }
9119
- if (statement.kind === "for" || statement.kind === "forEach") {
9120
- result.push(...collectReturns(statement.body));
9121
- }
9122
- if (statement.kind === "switch") {
9123
- for (const section of statement.sections)
9124
- result.push(...collectReturns(section.body));
9125
- }
9126
- if (statement.kind === "try") {
9127
- result.push(...collectReturns(statement.body));
9128
- for (const clause of statement.catches)
9129
- result.push(...collectReturns(clause.body));
9130
- }
9131
- }
9132
- return result;
9133
- }
9134
9145
  function functionArityMessage(name, minimum, maximum, got) {
9135
9146
  if (minimum === maximum) {
9136
9147
  return `Function '${name}' expects ${maximum} argument${maximum === 1 ? "" : "s"}, got ${got}.`;
@@ -9192,6 +9203,7 @@ var init_strict_resolver = __esm({
9192
9203
  "use strict";
9193
9204
  init_project();
9194
9205
  init_language_spec();
9206
+ init_strict_ast();
9195
9207
  init_strict_lexer();
9196
9208
  init_declared_constructors();
9197
9209
  init_strict_compile_error();
@@ -9595,6 +9607,7 @@ var init_strict_resolver = __esm({
9595
9607
  }
9596
9608
  case "if": {
9597
9609
  const cumulativeFalseFacts = [];
9610
+ const branchExitFacts = [];
9598
9611
  const branchExitNarrowings = [];
9599
9612
  const branchExitOwnershipScopes = [];
9600
9613
  const branches = statement.branches.map((branch) => {
@@ -9603,12 +9616,14 @@ var init_strict_resolver = __esm({
9603
9616
  branch.cond.pos
9604
9617
  );
9605
9618
  const child = new Scope(scope);
9606
- this.applyFactsToScope(scope, child, [
9619
+ const branchFacts = [
9607
9620
  ...cumulativeFalseFacts,
9608
9621
  ...factsWhenTrue(branch.cond)
9609
- ]);
9622
+ ];
9623
+ this.applyFactsToScope(scope, child, branchFacts);
9610
9624
  const instructions = this.resolveStatements(branch.body, child);
9611
9625
  if (!instructionsTerminate(instructions)) {
9626
+ branchExitFacts.push(branchFacts);
9612
9627
  branchExitNarrowings.push(new Map(child.narrowedPaths));
9613
9628
  branchExitOwnershipScopes.push(child);
9614
9629
  }
@@ -9624,12 +9639,14 @@ var init_strict_resolver = __esm({
9624
9639
  this.applyFactsToScope(scope, elseScope, cumulativeFalseFacts);
9625
9640
  otherwise = this.resolveStatements(statement.elseBody, elseScope);
9626
9641
  if (!instructionsTerminate(otherwise)) {
9642
+ branchExitFacts.push([...cumulativeFalseFacts]);
9627
9643
  branchExitNarrowings.push(new Map(elseScope.narrowedPaths));
9628
9644
  branchExitOwnershipScopes.push(elseScope);
9629
9645
  }
9630
9646
  } else {
9631
9647
  const noBranchScope = new Scope(scope);
9632
9648
  this.applyFactsToScope(scope, noBranchScope, cumulativeFalseFacts);
9649
+ branchExitFacts.push([...cumulativeFalseFacts]);
9633
9650
  branchExitNarrowings.push(new Map(noBranchScope.narrowedPaths));
9634
9651
  branchExitOwnershipScopes.push(noBranchScope);
9635
9652
  }
@@ -9645,6 +9662,12 @@ var init_strict_resolver = __esm({
9645
9662
  }
9646
9663
  }
9647
9664
  mergeBranchOwnership(scope, branchExitOwnershipScopes);
9665
+ this.applyFactsToScope(
9666
+ scope,
9667
+ scope,
9668
+ mergeFactSets(branchExitFacts),
9669
+ "bindings-only"
9670
+ );
9648
9671
  return {
9649
9672
  type: "if" /* If */,
9650
9673
  branches,
@@ -11468,8 +11491,9 @@ var init_strict_resolver = __esm({
11468
11491
  pos
11469
11492
  );
11470
11493
  }
11471
- applyFactsToScope(outerScope, targetScope, facts) {
11494
+ applyFactsToScope(outerScope, targetScope, facts, mode = "all") {
11472
11495
  for (const fact of facts) {
11496
+ if (mode === "bindings-only" && !fact.bindingName) continue;
11473
11497
  const rootEntry = outerScope.lookup(pathRoot(fact.path));
11474
11498
  if (!rootEntry) continue;
11475
11499
  const natural = this.resolveExpression(fact.expression, outerScope);
@@ -11490,8 +11514,13 @@ var init_strict_resolver = __esm({
11490
11514
  const naturalDefinition = natural.type.kind === "named" ? this.project.typeById.get(natural.type.typeId) : void 0;
11491
11515
  const retainConcreteClass = !fact.bindingName && naturalDefinition?.kind === "class" && targetDefinition?.kind === "interface";
11492
11516
  const narrowed = retainConcreteClass ? { ...natural.type, nullable: false } : { ...target, nullable: false };
11493
- targetScope.narrow(fact.path, rootEntry, narrowed);
11517
+ if (mode === "all") {
11518
+ targetScope.narrow(fact.path, rootEntry, narrowed);
11519
+ }
11494
11520
  if (fact.bindingName) {
11521
+ if (mode === "bindings-only" && targetScope.lookup(fact.bindingName) !== null) {
11522
+ continue;
11523
+ }
11495
11524
  targetScope.define({
11496
11525
  name: fact.bindingName,
11497
11526
  type: narrowed,
@@ -13763,7 +13792,7 @@ var init_strict_resolver = __esm({
13763
13792
  }
13764
13793
  }
13765
13794
  inferLambdaReturn(lambda, collection, source, outerScope) {
13766
- const returns = collectReturns(lambda.body);
13795
+ const returns = collectReturnStatements(lambda.body);
13767
13796
  if (returns.length === 0) {
13768
13797
  throw new CompileError("Select lambda must return a value.", lambda.pos);
13769
13798
  }
@@ -20552,7 +20581,7 @@ function validateInitialize(variant, argument2, diagnostics) {
20552
20581
  )
20553
20582
  );
20554
20583
  }
20555
- const returns = lambda.body.some(
20584
+ const returns = collectReturnStatements(lambda.body).some(
20556
20585
  (statement) => statement.kind === "return" && statement.expr !== null
20557
20586
  );
20558
20587
  if (returns) return;
@@ -20579,7 +20608,7 @@ function validateApply(variant, argument2, diagnostics) {
20579
20608
  )
20580
20609
  );
20581
20610
  }
20582
- const returnsValue = lambda.body.some(
20611
+ const returnsValue = collectReturnStatements(lambda.body).some(
20583
20612
  (statement) => statement.kind === "return" && statement.expr !== null
20584
20613
  );
20585
20614
  if (!returnsValue) return;
@@ -20827,6 +20856,7 @@ var init_project_source_variants = __esm({
20827
20856
  init_generated_csharp_identifiers();
20828
20857
  init_language_spec();
20829
20858
  init_strict_parser();
20859
+ init_strict_ast();
20830
20860
  NEO_VARIANT_CONSTRUCTOR_ARGUMENTS = [
20831
20861
  "initialize",
20832
20862
  "apply",
@@ -33083,13 +33113,6 @@ var init_service = __esm({
33083
33113
  }
33084
33114
  });
33085
33115
 
33086
- // ../packages/neoscript-language/src/strict-ast.ts
33087
- var init_strict_ast = __esm({
33088
- "../packages/neoscript-language/src/strict-ast.ts"() {
33089
- "use strict";
33090
- }
33091
- });
33092
-
33093
33116
  // ../packages/neoscript-language/src/strict-parity-fixture.ts
33094
33117
  function location2(uri, line, startCharacter, endCharacter) {
33095
33118
  return {
@@ -58785,6 +58808,285 @@ var init_lower_members = __esm({
58785
58808
  }
58786
58809
  });
58787
58810
 
58811
+ // src/project-source/lower-variants.ts
58812
+ function lowerVariants(context, analysis) {
58813
+ const folders = lowerVariantFolders(context, analysis);
58814
+ const foldersBySymbol = new Map(
58815
+ analysis.variants.globals.filter((global) => isNeoVariantFolderTypeName(global.type.name)).map(
58816
+ (global) => [
58817
+ folderSymbolKey(owningClassName(global), global.name),
58818
+ variantRecordId(global, global.type.name)
58819
+ ]
58820
+ )
58821
+ );
58822
+ const variants = variantGlobals(analysis).map((global) => {
58823
+ const className = owningClassName(global);
58824
+ const classId = requiredName2(context.classIdsByName, className, "class");
58825
+ const variantId = variantRecordId(global, global.type.name);
58826
+ return {
58827
+ id: variantId,
58828
+ source: sourceIdentity2(global, "variant", global.name),
58829
+ classId,
58830
+ name: global.name,
58831
+ folderId: variantFolderId2(global, className, foldersBySymbol),
58832
+ valueId: variantRootValueId(context, variantId, global, className)
58833
+ };
58834
+ });
58835
+ return { variants, variantFolders: folders };
58836
+ }
58837
+ function variantValueBindingsV4(manifest, analysis) {
58838
+ if (manifest.variants.length === 0) return [];
58839
+ const variantsById = new Map(
58840
+ manifest.variants.map((variant) => [variant.id, variant])
58841
+ );
58842
+ const classNames = new Map(
58843
+ manifest.classes.map((entry) => [entry.id, entry.name])
58844
+ );
58845
+ return variantGlobals(analysis).flatMap((global) => {
58846
+ const variant = variantsById.get(
58847
+ variantRecordId(global, NEO_VARIANT_TYPE_NAME)
58848
+ );
58849
+ if (variant === void 0) return [];
58850
+ return [
58851
+ {
58852
+ variantId: variant.id,
58853
+ targetClassId: variant.classId,
58854
+ targetClassName: requiredClassName2(classNames, variant.classId),
58855
+ variantTypeName: global.type.name,
58856
+ valueClassId: global.type.name === NEO_LOOKUP_VARIANT_TYPE_NAME ? requiredName2(
58857
+ new Map(
58858
+ manifest.classes.map((entry) => [entry.name, entry.id])
58859
+ ),
58860
+ valueClassName(global),
58861
+ "class"
58862
+ ) : null,
58863
+ valueId: variant.valueId,
58864
+ initializer: global.initializer,
58865
+ source: global.source,
58866
+ label: `${requiredClassName2(classNames, variant.classId)}.${NEO_VARIANT_SCOPE_NAME}.${variant.name}`
58867
+ }
58868
+ ];
58869
+ });
58870
+ }
58871
+ function requiredClassName2(classNames, classId) {
58872
+ const name = classNames.get(classId);
58873
+ if (name === void 0) {
58874
+ throw new Error(
58875
+ `Variant targets class ${JSON.stringify(classId)}, which this push does not declare.`
58876
+ );
58877
+ }
58878
+ return name;
58879
+ }
58880
+ function variantGlobals(analysis) {
58881
+ return analysis.variants.globals.filter(
58882
+ (global) => isNeoVariantTypeName(global.type.name)
58883
+ );
58884
+ }
58885
+ function lowerVariantFolders(context, analysis) {
58886
+ return analysis.variants.globals.filter((global) => isNeoVariantFolderTypeName(global.type.name)).map((global) => {
58887
+ const className = owningClassName(global);
58888
+ return {
58889
+ id: variantRecordId(global, NEO_VARIANT_FOLDER_TYPE_NAME),
58890
+ source: sourceIdentity2(global, "variantFolder", global.name),
58891
+ classId: requiredName2(context.classIdsByName, className, "class"),
58892
+ path: folderPath2(global),
58893
+ binding: folderBinding(context, global)
58894
+ };
58895
+ });
58896
+ }
58897
+ function owningClassName(global) {
58898
+ const argument2 = global.type.arguments[0]?.name;
58899
+ if (argument2 === void 0) {
58900
+ throw new Error(
58901
+ `${global.type.name} ${global.name} must name the class it belongs to as its single type argument.`
58902
+ );
58903
+ }
58904
+ return argument2;
58905
+ }
58906
+ function variantRecordId(global, typeName) {
58907
+ const kind = isNeoVariantTypeName(typeName) ? "variant" : "variant-folder";
58908
+ return materializedIdentityId(global, kind, global.name);
58909
+ }
58910
+ function variantRootValueId(context, variantId, global, className) {
58911
+ const stored = storedVariantValueId(context, variantId);
58912
+ if (stored !== null) return stored;
58913
+ return `__pending__:value:${encodeURIComponent(global.source.uri)}:${global.source.range.start.line}:${global.source.range.start.character}:${encodeURIComponent(`${className}.${NEO_VARIANT_SCOPE_NAME}.${global.name}`)}`;
58914
+ }
58915
+ function storedVariantValueId(context, variantId) {
58916
+ const stored = context.base.variants.find(
58917
+ (variant) => variant.id === variantId
58918
+ );
58919
+ return stored?.valueId ?? null;
58920
+ }
58921
+ function variantFolderId2(global, className, foldersBySymbol) {
58922
+ const symbol = neoVariantFolderAnnotationSymbol(
58923
+ annotationsForFolderLookup(global.annotations)
58924
+ );
58925
+ if (symbol === null) return null;
58926
+ const folderId = foldersBySymbol.get(folderSymbolKey(className, symbol));
58927
+ if (folderId === void 0) {
58928
+ throw new Error(
58929
+ `Variant ${className}.${NEO_VARIANT_SCOPE_NAME}.${global.name} names folder ${JSON.stringify(symbol)}, which no NeoVariantFolder<${className}> declares.`
58930
+ );
58931
+ }
58932
+ return folderId;
58933
+ }
58934
+ function annotationsForFolderLookup(annotations) {
58935
+ return annotations.map((annotation2) => ({
58936
+ name: annotation2.name,
58937
+ arguments: annotation2.arguments.map((argument2) => ({
58938
+ ...argument2.name === void 0 ? {} : { name: argument2.name },
58939
+ text: argument2.expression
58940
+ }))
58941
+ }));
58942
+ }
58943
+ function folderSymbolKey(className, symbol) {
58944
+ return `${className} ${symbol}`;
58945
+ }
58946
+ function folderPath2(global) {
58947
+ const expression = unwrapAnnotated2(parseExpression(global.initializer));
58948
+ if (expression.kind !== "new") {
58949
+ throw new Error(
58950
+ `Variant folder ${global.name} must be declared as new("Path").`
58951
+ );
58952
+ }
58953
+ const pathIndex = expression.argumentNames?.findIndex(
58954
+ (name) => name === "path"
58955
+ );
58956
+ const argument2 = expression.args[pathIndex !== void 0 && pathIndex >= 0 ? pathIndex : 0];
58957
+ if (argument2 === void 0 || argument2.kind !== "litString") {
58958
+ throw new Error(
58959
+ `Variant folder ${global.name} requires one string-literal path argument.`
58960
+ );
58961
+ }
58962
+ if (argument2.value.length === 0 && global.type.name !== NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME) {
58963
+ throw new Error(
58964
+ `Only a bound lookup variant folder may have an empty path.`
58965
+ );
58966
+ }
58967
+ return argument2.value;
58968
+ }
58969
+ function unwrapAnnotated2(expression) {
58970
+ let current = expression;
58971
+ while (current.kind === "annotated") current = current.expression;
58972
+ return current;
58973
+ }
58974
+ function buildVariantPathIndexV4(classIdsByName, analysis) {
58975
+ const index = /* @__PURE__ */ new Map();
58976
+ const foldersBySymbol = new Map(
58977
+ analysis.variants.globals.filter((global) => isNeoVariantFolderTypeName(global.type.name)).map(
58978
+ (global) => [
58979
+ folderSymbolKey(owningClassName(global), global.name),
58980
+ folderPath2(global)
58981
+ ]
58982
+ )
58983
+ );
58984
+ for (const [className, classId] of classIdsByName) {
58985
+ index.set(
58986
+ `${className}.${NEO_VARIANT_SCOPE_NAME}.${NEO_VARIANT_RESERVED_NAME}`,
58987
+ {
58988
+ classId,
58989
+ variantId: null,
58990
+ valueClassId: null
58991
+ }
58992
+ );
58993
+ }
58994
+ for (const global of variantGlobals(analysis)) {
58995
+ const className = owningClassName(global);
58996
+ const classId = classIdsByName.get(className);
58997
+ if (classId === void 0) continue;
58998
+ const symbol = neoVariantFolderAnnotationSymbol(
58999
+ annotationsForFolderLookup(global.annotations)
59000
+ );
59001
+ const path = symbol === null ? null : foldersBySymbol.get(folderSymbolKey(className, symbol)) ?? null;
59002
+ const scope = path === null || path === "" ? `${className}.${NEO_VARIANT_SCOPE_NAME}` : `${className}.${NEO_VARIANT_SCOPE_NAME}.${path.split(NEO_VARIANT_FOLDER_PATH_SEPARATOR).join(".")}`;
59003
+ index.set(`${scope}.${global.name}`, {
59004
+ classId,
59005
+ variantId: variantRecordId(global, global.type.name),
59006
+ valueClassId: global.type.name === NEO_LOOKUP_VARIANT_TYPE_NAME ? classIdsByName.get(valueClassName(global)) ?? null : null
59007
+ });
59008
+ }
59009
+ return index;
59010
+ }
59011
+ function valueClassName(global) {
59012
+ const argument2 = global.type.arguments[1]?.name;
59013
+ if (argument2 === void 0) {
59014
+ throw new Error(
59015
+ `${global.type.name} ${global.name} requires a TValue type argument.`
59016
+ );
59017
+ }
59018
+ return argument2;
59019
+ }
59020
+ function folderBinding(context, global) {
59021
+ if (global.type.name !== NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME) return null;
59022
+ const expression = unwrapAnnotated2(parseExpression(global.initializer));
59023
+ if (expression.kind !== "new") {
59024
+ throw new Error(
59025
+ `Lookup variant folder ${global.name} must be declared with new(...).`
59026
+ );
59027
+ }
59028
+ const named = /* @__PURE__ */ new Map();
59029
+ expression.args.forEach((argument2, index) => {
59030
+ const name = expression.argumentNames?.[index];
59031
+ if (name !== void 0 && name !== null) named.set(name, argument2);
59032
+ });
59033
+ const collection = named.get("collection");
59034
+ const collectionValue = named.get("collectionValue");
59035
+ const collectionPath = collection === void 0 ? null : dottedPath(collection);
59036
+ if (collectionPath === null) {
59037
+ throw new Error(
59038
+ `Lookup variant folder ${global.name} requires collection: Class.Member.`
59039
+ );
59040
+ }
59041
+ const collectionMemberId = resolveQualifiedMemberId(context, collectionPath);
59042
+ if (collectionMemberId === null) {
59043
+ throw new Error(
59044
+ `Lookup variant folder ${global.name} names unknown collection ${collectionPath}.`
59045
+ );
59046
+ }
59047
+ if (collectionValue === void 0) {
59048
+ throw new Error(
59049
+ `Lookup variant folder ${global.name} requires collectionValue: Reference(...).`
59050
+ );
59051
+ }
59052
+ const collectionValueId = lowerReferenceValue(collectionValue);
59053
+ if (collectionValueId === null) {
59054
+ throw new Error(
59055
+ `Lookup variant folder ${global.name} has an invalid collectionValue reference.`
59056
+ );
59057
+ }
59058
+ return { collectionMemberId, collectionValueId };
59059
+ }
59060
+ function lowerReferenceValue(expression) {
59061
+ if (expression.kind !== "call" || expression.callee.kind !== "ident" || expression.callee.name !== "Reference")
59062
+ return null;
59063
+ const idIndex = expression.argumentNames?.findIndex((name) => name === "id") ?? -1;
59064
+ const target = expression.args[idIndex >= 0 ? idIndex : 0];
59065
+ if (target?.kind === "litString") return target.value;
59066
+ const path = target === void 0 ? null : dottedPath(target);
59067
+ if (path === null || !path.startsWith("root.")) return null;
59068
+ return referenceId(`Reference(${path})`);
59069
+ }
59070
+ function dottedPath(expression) {
59071
+ const parts = [];
59072
+ let cursor = expression;
59073
+ while (cursor.kind === "member") {
59074
+ parts.unshift(cursor.name);
59075
+ cursor = cursor.receiver;
59076
+ }
59077
+ if (cursor.kind !== "ident") return null;
59078
+ parts.unshift(cursor.name);
59079
+ return parts.join(".");
59080
+ }
59081
+ var init_lower_variants = __esm({
59082
+ "src/project-source/lower-variants.ts"() {
59083
+ "use strict";
59084
+ init_src();
59085
+ init_lower_support();
59086
+ init_lower_members();
59087
+ }
59088
+ });
59089
+
58788
59090
  // src/project-source/root-value-paths.ts
58789
59091
  function rootValuePathsByValueId(records2) {
58790
59092
  const indexed = indexRecords(records2);
@@ -75068,285 +75370,6 @@ var init_lower_relations = __esm({
75068
75370
  }
75069
75371
  });
75070
75372
 
75071
- // src/project-source/lower-variants.ts
75072
- function lowerVariants(context, analysis) {
75073
- const folders = lowerVariantFolders(context, analysis);
75074
- const foldersBySymbol = new Map(
75075
- analysis.variants.globals.filter((global) => isNeoVariantFolderTypeName(global.type.name)).map(
75076
- (global) => [
75077
- folderSymbolKey(owningClassName(global), global.name),
75078
- variantRecordId(global, global.type.name)
75079
- ]
75080
- )
75081
- );
75082
- const variants = variantGlobals(analysis).map((global) => {
75083
- const className = owningClassName(global);
75084
- const classId = requiredName2(context.classIdsByName, className, "class");
75085
- const variantId = variantRecordId(global, global.type.name);
75086
- return {
75087
- id: variantId,
75088
- source: sourceIdentity2(global, "variant", global.name),
75089
- classId,
75090
- name: global.name,
75091
- folderId: variantFolderId2(global, className, foldersBySymbol),
75092
- valueId: variantRootValueId(context, variantId, global, className)
75093
- };
75094
- });
75095
- return { variants, variantFolders: folders };
75096
- }
75097
- function variantValueBindingsV4(manifest, analysis) {
75098
- if (manifest.variants.length === 0) return [];
75099
- const variantsById = new Map(
75100
- manifest.variants.map((variant) => [variant.id, variant])
75101
- );
75102
- const classNames = new Map(
75103
- manifest.classes.map((entry) => [entry.id, entry.name])
75104
- );
75105
- return variantGlobals(analysis).flatMap((global) => {
75106
- const variant = variantsById.get(
75107
- variantRecordId(global, NEO_VARIANT_TYPE_NAME)
75108
- );
75109
- if (variant === void 0) return [];
75110
- return [
75111
- {
75112
- variantId: variant.id,
75113
- targetClassId: variant.classId,
75114
- targetClassName: requiredClassName2(classNames, variant.classId),
75115
- variantTypeName: global.type.name,
75116
- valueClassId: global.type.name === NEO_LOOKUP_VARIANT_TYPE_NAME ? requiredName2(
75117
- new Map(
75118
- manifest.classes.map((entry) => [entry.name, entry.id])
75119
- ),
75120
- valueClassName(global),
75121
- "class"
75122
- ) : null,
75123
- valueId: variant.valueId,
75124
- initializer: global.initializer,
75125
- source: global.source,
75126
- label: `${requiredClassName2(classNames, variant.classId)}.${NEO_VARIANT_SCOPE_NAME}.${variant.name}`
75127
- }
75128
- ];
75129
- });
75130
- }
75131
- function requiredClassName2(classNames, classId) {
75132
- const name = classNames.get(classId);
75133
- if (name === void 0) {
75134
- throw new Error(
75135
- `Variant targets class ${JSON.stringify(classId)}, which this push does not declare.`
75136
- );
75137
- }
75138
- return name;
75139
- }
75140
- function variantGlobals(analysis) {
75141
- return analysis.variants.globals.filter(
75142
- (global) => isNeoVariantTypeName(global.type.name)
75143
- );
75144
- }
75145
- function lowerVariantFolders(context, analysis) {
75146
- return analysis.variants.globals.filter((global) => isNeoVariantFolderTypeName(global.type.name)).map((global) => {
75147
- const className = owningClassName(global);
75148
- return {
75149
- id: variantRecordId(global, NEO_VARIANT_FOLDER_TYPE_NAME),
75150
- source: sourceIdentity2(global, "variantFolder", global.name),
75151
- classId: requiredName2(context.classIdsByName, className, "class"),
75152
- path: folderPath2(global),
75153
- binding: folderBinding(context, global)
75154
- };
75155
- });
75156
- }
75157
- function owningClassName(global) {
75158
- const argument2 = global.type.arguments[0]?.name;
75159
- if (argument2 === void 0) {
75160
- throw new Error(
75161
- `${global.type.name} ${global.name} must name the class it belongs to as its single type argument.`
75162
- );
75163
- }
75164
- return argument2;
75165
- }
75166
- function variantRecordId(global, typeName) {
75167
- const kind = isNeoVariantTypeName(typeName) ? "variant" : "variant-folder";
75168
- return materializedIdentityId(global, kind, global.name);
75169
- }
75170
- function variantRootValueId(context, variantId, global, className) {
75171
- const stored = storedVariantValueId(context, variantId);
75172
- if (stored !== null) return stored;
75173
- return `__pending__:value:${encodeURIComponent(global.source.uri)}:${global.source.range.start.line}:${global.source.range.start.character}:${encodeURIComponent(`${className}.${NEO_VARIANT_SCOPE_NAME}.${global.name}`)}`;
75174
- }
75175
- function storedVariantValueId(context, variantId) {
75176
- const stored = context.base.variants.find(
75177
- (variant) => variant.id === variantId
75178
- );
75179
- return stored?.valueId ?? null;
75180
- }
75181
- function variantFolderId2(global, className, foldersBySymbol) {
75182
- const symbol = neoVariantFolderAnnotationSymbol(
75183
- annotationsForFolderLookup(global.annotations)
75184
- );
75185
- if (symbol === null) return null;
75186
- const folderId = foldersBySymbol.get(folderSymbolKey(className, symbol));
75187
- if (folderId === void 0) {
75188
- throw new Error(
75189
- `Variant ${className}.${NEO_VARIANT_SCOPE_NAME}.${global.name} names folder ${JSON.stringify(symbol)}, which no NeoVariantFolder<${className}> declares.`
75190
- );
75191
- }
75192
- return folderId;
75193
- }
75194
- function annotationsForFolderLookup(annotations) {
75195
- return annotations.map((annotation2) => ({
75196
- name: annotation2.name,
75197
- arguments: annotation2.arguments.map((argument2) => ({
75198
- ...argument2.name === void 0 ? {} : { name: argument2.name },
75199
- text: argument2.expression
75200
- }))
75201
- }));
75202
- }
75203
- function folderSymbolKey(className, symbol) {
75204
- return `${className} ${symbol}`;
75205
- }
75206
- function folderPath2(global) {
75207
- const expression = unwrapAnnotated2(parseExpression(global.initializer));
75208
- if (expression.kind !== "new") {
75209
- throw new Error(
75210
- `Variant folder ${global.name} must be declared as new("Path").`
75211
- );
75212
- }
75213
- const pathIndex = expression.argumentNames?.findIndex(
75214
- (name) => name === "path"
75215
- );
75216
- const argument2 = expression.args[pathIndex !== void 0 && pathIndex >= 0 ? pathIndex : 0];
75217
- if (argument2 === void 0 || argument2.kind !== "litString") {
75218
- throw new Error(
75219
- `Variant folder ${global.name} requires one string-literal path argument.`
75220
- );
75221
- }
75222
- if (argument2.value.length === 0 && global.type.name !== NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME) {
75223
- throw new Error(
75224
- `Only a bound lookup variant folder may have an empty path.`
75225
- );
75226
- }
75227
- return argument2.value;
75228
- }
75229
- function unwrapAnnotated2(expression) {
75230
- let current = expression;
75231
- while (current.kind === "annotated") current = current.expression;
75232
- return current;
75233
- }
75234
- function buildVariantPathIndexV4(classIdsByName, analysis) {
75235
- const index = /* @__PURE__ */ new Map();
75236
- const foldersBySymbol = new Map(
75237
- analysis.variants.globals.filter((global) => isNeoVariantFolderTypeName(global.type.name)).map(
75238
- (global) => [
75239
- folderSymbolKey(owningClassName(global), global.name),
75240
- folderPath2(global)
75241
- ]
75242
- )
75243
- );
75244
- for (const [className, classId] of classIdsByName) {
75245
- index.set(
75246
- `${className}.${NEO_VARIANT_SCOPE_NAME}.${NEO_VARIANT_RESERVED_NAME}`,
75247
- {
75248
- classId,
75249
- variantId: null,
75250
- valueClassId: null
75251
- }
75252
- );
75253
- }
75254
- for (const global of variantGlobals(analysis)) {
75255
- const className = owningClassName(global);
75256
- const classId = classIdsByName.get(className);
75257
- if (classId === void 0) continue;
75258
- const symbol = neoVariantFolderAnnotationSymbol(
75259
- annotationsForFolderLookup(global.annotations)
75260
- );
75261
- const path = symbol === null ? null : foldersBySymbol.get(folderSymbolKey(className, symbol)) ?? null;
75262
- const scope = path === null ? `${className}.${NEO_VARIANT_SCOPE_NAME}` : `${className}.${NEO_VARIANT_SCOPE_NAME}.${path.split(NEO_VARIANT_FOLDER_PATH_SEPARATOR).join(".")}`;
75263
- index.set(`${scope}.${global.name}`, {
75264
- classId,
75265
- variantId: variantRecordId(global, global.type.name),
75266
- valueClassId: global.type.name === NEO_LOOKUP_VARIANT_TYPE_NAME ? classIdsByName.get(valueClassName(global)) ?? null : null
75267
- });
75268
- }
75269
- return index;
75270
- }
75271
- function valueClassName(global) {
75272
- const argument2 = global.type.arguments[1]?.name;
75273
- if (argument2 === void 0) {
75274
- throw new Error(
75275
- `${global.type.name} ${global.name} requires a TValue type argument.`
75276
- );
75277
- }
75278
- return argument2;
75279
- }
75280
- function folderBinding(context, global) {
75281
- if (global.type.name !== NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME) return null;
75282
- const expression = unwrapAnnotated2(parseExpression(global.initializer));
75283
- if (expression.kind !== "new") {
75284
- throw new Error(
75285
- `Lookup variant folder ${global.name} must be declared with new(...).`
75286
- );
75287
- }
75288
- const named = /* @__PURE__ */ new Map();
75289
- expression.args.forEach((argument2, index) => {
75290
- const name = expression.argumentNames?.[index];
75291
- if (name !== void 0 && name !== null) named.set(name, argument2);
75292
- });
75293
- const collection = named.get("collection");
75294
- const collectionValue = named.get("collectionValue");
75295
- const collectionPath = collection === void 0 ? null : dottedPath(collection);
75296
- if (collectionPath === null) {
75297
- throw new Error(
75298
- `Lookup variant folder ${global.name} requires collection: Class.Member.`
75299
- );
75300
- }
75301
- const collectionMemberId = resolveQualifiedMemberId(context, collectionPath);
75302
- if (collectionMemberId === null) {
75303
- throw new Error(
75304
- `Lookup variant folder ${global.name} names unknown collection ${collectionPath}.`
75305
- );
75306
- }
75307
- if (collectionValue === void 0) {
75308
- throw new Error(
75309
- `Lookup variant folder ${global.name} requires collectionValue: Reference(...).`
75310
- );
75311
- }
75312
- const collectionValueId = lowerReferenceValue(collectionValue);
75313
- if (collectionValueId === null) {
75314
- throw new Error(
75315
- `Lookup variant folder ${global.name} has an invalid collectionValue reference.`
75316
- );
75317
- }
75318
- return { collectionMemberId, collectionValueId };
75319
- }
75320
- function lowerReferenceValue(expression) {
75321
- if (expression.kind !== "call" || expression.callee.kind !== "ident" || expression.callee.name !== "Reference")
75322
- return null;
75323
- const idIndex = expression.argumentNames?.findIndex((name) => name === "id") ?? -1;
75324
- const target = expression.args[idIndex >= 0 ? idIndex : 0];
75325
- if (target?.kind === "litString") return target.value;
75326
- const path = target === void 0 ? null : dottedPath(target);
75327
- if (path === null || !path.startsWith("root.")) return null;
75328
- return referenceId(`Reference(${path})`);
75329
- }
75330
- function dottedPath(expression) {
75331
- const parts = [];
75332
- let cursor = expression;
75333
- while (cursor.kind === "member") {
75334
- parts.unshift(cursor.name);
75335
- cursor = cursor.receiver;
75336
- }
75337
- if (cursor.kind !== "ident") return null;
75338
- parts.unshift(cursor.name);
75339
- return parts.join(".");
75340
- }
75341
- var init_lower_variants = __esm({
75342
- "src/project-source/lower-variants.ts"() {
75343
- "use strict";
75344
- init_src();
75345
- init_lower_support();
75346
- init_lower_members();
75347
- }
75348
- });
75349
-
75350
75373
  // src/project-source/lower-schema.ts
75351
75374
  function lowerProjectSchemaV4(base, analysis, options = {}) {
75352
75375
  const classIdsByName = identityMap(analysis.schema.classes, "class");
@@ -95005,10 +95028,40 @@ function readPulledProjectDocumentV4(records2) {
95005
95028
  const raw = structuredClone(
95006
95029
  pulledProjectDocumentRaw(replayWorkspace(records2))
95007
95030
  );
95008
- return readProjectDocument(completeProspectiveLocalizedTexts(raw), {
95009
- constructors: "authored-or-compiled",
95010
- identities: "prospective"
95011
- });
95031
+ return readProjectDocument(
95032
+ completeProspectiveMemberCreateEnvelopes(
95033
+ completeProspectiveLocalizedTexts(raw)
95034
+ ),
95035
+ {
95036
+ constructors: "authored-or-compiled",
95037
+ identities: "prospective"
95038
+ }
95039
+ );
95040
+ }
95041
+ function completeProspectiveMemberCreateEnvelopes(raw) {
95042
+ const members = raw.members;
95043
+ if (!Array.isArray(members)) return raw;
95044
+ if (!members.some(isPendingMemberWithoutCreateEnvelope)) return raw;
95045
+ const project = raw.project;
95046
+ if (!isObjectRecord2(project) || typeof project.id !== "string") {
95047
+ throw new Error(
95048
+ "Pending source-authored members need the server create envelope, but the project record has no id."
95049
+ );
95050
+ }
95051
+ return {
95052
+ ...raw,
95053
+ members: members.map(
95054
+ (member) => isPendingMemberWithoutCreateEnvelope(member) ? {
95055
+ projectId: project.id,
95056
+ createdAt: 0,
95057
+ updatedAt: 0,
95058
+ ...member
95059
+ } : member
95060
+ )
95061
+ };
95062
+ }
95063
+ function isPendingMemberWithoutCreateEnvelope(value) {
95064
+ return isObjectRecord2(value) && typeof value.id === "string" && value.id.startsWith("__pending__:") && (typeof value.projectId !== "string" || typeof value.createdAt !== "number" || typeof value.updatedAt !== "number");
95012
95065
  }
95013
95066
  function completeProspectiveLocalizedTexts(raw) {
95014
95067
  const localizedTexts = raw.localizedTexts;
@@ -95605,6 +95658,12 @@ function buildValueLowerContext(state, manifest, options = {}) {
95605
95658
  const classesByName = new Map(
95606
95659
  manifest.classes.map((entry) => [entry.name, entry])
95607
95660
  );
95661
+ const variantPathTargets = options.analysis === void 0 ? /* @__PURE__ */ new Map() : buildVariantPathIndexV4(
95662
+ new Map(
95663
+ manifest.classes.map((entry) => [entry.name, entry.id])
95664
+ ),
95665
+ options.analysis
95666
+ );
95608
95667
  const sourceConstructorIndex = options.analysis === void 0 ? EMPTY_DECLARED_CONSTRUCTOR_INDEX : buildDeclaredConstructorIndex(options.analysis);
95609
95668
  const declaredConstructorIndex = withPersistedConstructorSignatures(
95610
95669
  sourceConstructorIndex,
@@ -95631,6 +95690,7 @@ function buildValueLowerContext(state, manifest, options = {}) {
95631
95690
  ),
95632
95691
  staticValueIdsBySymbol: staticTargets,
95633
95692
  rootValueTargetsByPath: rootValueTargetsByPath(Object.values(state)),
95693
+ variantPathTargets,
95634
95694
  dialogueTargetsBySymbol: dialogueTargetsBySymbol(options.analysis),
95635
95695
  projectFileIdsBySymbol: projectFileIdsBySymbol(state, options.analysis),
95636
95696
  mainLocale: projectMainLocaleFromState(state),
@@ -97932,6 +97992,20 @@ function lowerValueBody(context, member, expression, base, source, environment,
97932
97992
  ...next,
97933
97993
  value: lowerActionValue(context, expression, source.ownerClassId)
97934
97994
  };
97995
+ case "variant":
97996
+ return {
97997
+ ...next,
97998
+ value: lowerVariantDefault(
97999
+ context,
98000
+ expression,
98001
+ {
98002
+ name: member.valueType === null ? NEO_VARIANT_TYPE_NAME : NEO_LOOKUP_VARIANT_TYPE_NAME,
98003
+ nullable: !member.required,
98004
+ arguments: []
98005
+ },
98006
+ `${source.label}.${member.name}`
98007
+ )
98008
+ };
97935
98009
  case "class":
97936
98010
  return lowerClassValue(
97937
98011
  context,
@@ -101599,6 +101673,7 @@ var init_value_sources = __esm({
101599
101673
  init_projection();
101600
101674
  init_project_file_source();
101601
101675
  init_lower_members();
101676
+ init_lower_variants();
101602
101677
  init_key_reference_spelling();
101603
101678
  init_member_records();
101604
101679
  init_class_generics();
@@ -104634,16 +104709,45 @@ function buildEmitRecordSet(document, plans, side) {
104634
104709
  if (data === void 0) continue;
104635
104710
  const serverRecord = document.records.get(key);
104636
104711
  const [recordKind, recordId] = key.split(/:(.+)/, 2);
104712
+ const resolvedRecordKind = serverRecord?.recordKind ?? recordKind;
104713
+ const resolvedRecordId = serverRecord?.recordId ?? recordId;
104637
104714
  records2.set(key, {
104638
- recordKind: serverRecord?.recordKind ?? recordKind,
104639
- recordId: serverRecord?.recordId ?? recordId,
104715
+ recordKind: resolvedRecordKind,
104716
+ recordId: resolvedRecordId,
104640
104717
  contentHash: plan.serverHash,
104641
104718
  deleted: false,
104642
- data
104719
+ data: side === "emit" ? composeProspectivePullRecord(
104720
+ resolvedRecordKind,
104721
+ resolvedRecordId,
104722
+ data,
104723
+ serverRecord
104724
+ ) : data
104643
104725
  });
104644
104726
  }
104645
104727
  return records2;
104646
104728
  }
104729
+ function composeProspectivePullRecord(recordKind, recordId, data, serverRecord) {
104730
+ if (!isSchemaRecordKindV4(recordKind) || !isObjectRecord2(data)) return data;
104731
+ let baseRecord = null;
104732
+ if (serverRecord !== void 0) {
104733
+ if (!isObjectRecord2(serverRecord.data)) {
104734
+ throw new Error(
104735
+ `Cannot compose pulled ${recordKind} record ${JSON.stringify(recordId)} because its server payload is not an object.`
104736
+ );
104737
+ }
104738
+ baseRecord = {
104739
+ recordKind,
104740
+ recordId,
104741
+ data: serverRecord.data
104742
+ };
104743
+ }
104744
+ return composeDocumentRecord(
104745
+ recordKind,
104746
+ recordId,
104747
+ partitionDocumentFields(recordKind, data).authored,
104748
+ baseRecord
104749
+ ).data;
104750
+ }
104647
104751
  var init_pull = __esm({
104648
104752
  "src/commands/pull.ts"() {
104649
104753
  "use strict";
@@ -111415,7 +111519,7 @@ var init_registry2 = __esm({
111415
111519
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
111416
111520
  formatVersion: 3,
111417
111521
  contractVersion: "3.13",
111418
- cliVersion: "0.31.9",
111522
+ cliVersion: "0.31.11",
111419
111523
  projectFileUploadBatchSize: 32,
111420
111524
  documentRecords: {
111421
111525
  member: {
@@ -118010,7 +118114,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
118010
118114
  async function main() {
118011
118115
  const args = parseArgs(process.argv.slice(2));
118012
118116
  if (args.command === "--version") {
118013
- console.log("0.31.9");
118117
+ console.log("0.31.11");
118014
118118
  return;
118015
118119
  }
118016
118120
  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.9",
3
+ "version": "0.31.11",
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.9 -->
12
+ <!-- reviewed-through-cli: 0.31.11 -->
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.9 -->
86
+ <!-- reviewed-through-cli: 0.31.11 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale