@neocompose/cli 0.70.1 → 0.70.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/neo.mjs CHANGED
@@ -4658,6 +4658,11 @@ var init_executable_parser = __esm({
4658
4658
  this.expect("operator", "<");
4659
4659
  if (t.text === "Dictionary") {
4660
4660
  const key = this.parseType();
4661
+ if (this.peek().text === ">")
4662
+ throw new CompileError(
4663
+ "Dictionary requires a key type and a value type, for example Dictionary<string, int>.",
4664
+ at(this.peek())
4665
+ );
4661
4666
  this.expect("punctuation", ",");
4662
4667
  const value = this.parseType();
4663
4668
  this.expect("operator", ">");
@@ -5244,8 +5249,16 @@ var init_executable_parser = __esm({
5244
5249
  parseObjectInitializer() {
5245
5250
  this.expect("punctuation", "{");
5246
5251
  const entries = [];
5252
+ const names = /* @__PURE__ */ new Set();
5247
5253
  while (!(this.peek().kind === "punctuation" && this.peek().text === "}")) {
5248
5254
  const name = this.expectMemberName();
5255
+ if (names.has(name.text)) {
5256
+ throw new CompileError(
5257
+ `Initializer assigns '${name.text}' more than once. Remove the duplicate assignment.`,
5258
+ at(name)
5259
+ );
5260
+ }
5261
+ names.add(name.text);
5249
5262
  this.expect("operator", "=");
5250
5263
  entries.push({
5251
5264
  name: name.text,
@@ -5599,6 +5612,8 @@ var init_project_local_values = __esm({
5599
5612
  "LocalizationStatus",
5600
5613
  "ImageRegistry",
5601
5614
  "AudioClipRegistry",
5615
+ "NeoImage",
5616
+ "NeoAudioClip",
5602
5617
  "PriorityGroup",
5603
5618
  "DialogueGroup",
5604
5619
  "Migration"
@@ -9578,11 +9593,13 @@ function collectGlobalChildEntries(sourceText, declaration) {
9578
9593
  if ((token.kind === "type" || token.kind === "identifier") && isProjectNameToken(name) && isValidNeoProjectMemberIdentifier(name.text) && tokens[index + 2]?.text === "=" && tokens[index + 3]?.text !== "=" && // A pending annotation or statement boundary precedes a declaration;
9579
9594
  // `key: value` fields and expression member accesses never do.
9580
9595
  tokens[index - 1]?.text !== "." && tokens[index - 1]?.text !== ":") {
9596
+ const value = tokens[index + 3];
9581
9597
  entries.push({
9582
9598
  typeName: token.text,
9583
9599
  name: name.text,
9584
9600
  nameRange: name.range,
9585
- annotations: pendingAnnotations
9601
+ annotations: pendingAnnotations,
9602
+ ...value?.kind === "identifier" && tokens[index + 4]?.text === ";" ? { reference: value.text } : {}
9586
9603
  });
9587
9604
  pendingAnnotations = [];
9588
9605
  index += 2;
@@ -9907,9 +9924,9 @@ function requiredFirstConstructorParameters(parameters) {
9907
9924
  ...parameters.filter((parameter4) => !parameter4.required)
9908
9925
  ];
9909
9926
  }
9910
- function buildNeoScriptConstructorParameterDescriptors(classId, sources) {
9927
+ function buildNeoScriptConstructorParameterDescriptors(classId, sources2) {
9911
9928
  return requiredFirstConstructorParameters(
9912
- sources.flatMap((source) => {
9929
+ sources2.flatMap((source) => {
9913
9930
  const name = generatedCSharpMemberNameForSchemaKey(source.schemaKey);
9914
9931
  if (!isGeneratedCSharpIdentifier(name)) return [];
9915
9932
  return [
@@ -9930,7 +9947,7 @@ function buildNeoScriptConstructorParameterDescriptors(classId, sources) {
9930
9947
  );
9931
9948
  }
9932
9949
  function buildNeoScriptConstructorSignature(classId, members, memberDescriptor) {
9933
- const sources = members.flatMap((member) => {
9950
+ const sources2 = members.flatMap((member) => {
9934
9951
  if (member.static === true || member.computed === true) return [];
9935
9952
  if (member.kind === "function" || member.kind === "method") return [];
9936
9953
  const descriptor = memberDescriptor(member.id);
@@ -9949,7 +9966,7 @@ function buildNeoScriptConstructorSignature(classId, members, memberDescriptor)
9949
9966
  });
9950
9967
  const parameters = buildNeoScriptConstructorParameterDescriptors(
9951
9968
  classId,
9952
- sources
9969
+ sources2
9953
9970
  ).map((parameter4) => ({
9954
9971
  id: parameter4.id,
9955
9972
  name: parameter4.name,
@@ -10126,6 +10143,52 @@ var init_project = __esm({
10126
10143
  }
10127
10144
  });
10128
10145
 
10146
+ // ../packages/neoscript-language/src/runtime-source.ts
10147
+ function registerRuntimeSource(node, source) {
10148
+ if (!sources.has(node)) sources.set(node, source);
10149
+ }
10150
+ function runtimeSource(node) {
10151
+ let current = node;
10152
+ while (current !== void 0) {
10153
+ const source = sources.get(current);
10154
+ if (source !== void 0) return source;
10155
+ current = origins.get(current);
10156
+ }
10157
+ return void 0;
10158
+ }
10159
+ function cloneWithRuntimeSources(value) {
10160
+ const clone = structuredClone(value);
10161
+ copyRuntimeSources(value, clone);
10162
+ return clone;
10163
+ }
10164
+ function copyRuntimeSources(original, copied) {
10165
+ const pending = [[original, copied]];
10166
+ const seen = /* @__PURE__ */ new WeakSet();
10167
+ while (pending.length > 0) {
10168
+ const [sourceNode, targetNode] = pending.pop();
10169
+ if (typeof sourceNode !== "object" || sourceNode === null || typeof targetNode !== "object" || targetNode === null)
10170
+ continue;
10171
+ if (seen.has(sourceNode)) continue;
10172
+ seen.add(sourceNode);
10173
+ const source = runtimeSource(sourceNode);
10174
+ if (source !== void 0) sources.set(targetNode, source);
10175
+ else if (sourceNode !== targetNode) origins.set(targetNode, sourceNode);
10176
+ for (const key of Object.keys(sourceNode))
10177
+ pending.push([
10178
+ Reflect.get(sourceNode, key),
10179
+ Reflect.get(targetNode, key)
10180
+ ]);
10181
+ }
10182
+ }
10183
+ var sources, origins;
10184
+ var init_runtime_source = __esm({
10185
+ "../packages/neoscript-language/src/runtime-source.ts"() {
10186
+ "use strict";
10187
+ sources = /* @__PURE__ */ new WeakMap();
10188
+ origins = /* @__PURE__ */ new WeakMap();
10189
+ }
10190
+ });
10191
+
10129
10192
  // ../packages/neoscript-language/src/type-substitution.ts
10130
10193
  function substituteTypeParameters(member, receiver, declaring) {
10131
10194
  const parameters = declaring.typeParameters ?? [];
@@ -12518,6 +12581,7 @@ var CONSTRUCTING_FUNCTION_KINDS, VARIANT_INITIALIZE_NAME, VARIANT_APPLY_NAME, MA
12518
12581
  var init_strict_resolver = __esm({
12519
12582
  "../packages/neoscript-language/src/strict-resolver.ts"() {
12520
12583
  "use strict";
12584
+ init_runtime_source();
12521
12585
  init_listener_annotation();
12522
12586
  init_type_substitution();
12523
12587
  init_cell_pattern();
@@ -12665,8 +12729,9 @@ var init_strict_resolver = __esm({
12665
12729
  valueId;
12666
12730
  };
12667
12731
  StrictNeoScriptResolver = class _StrictNeoScriptResolver {
12668
- constructor(context, projectIndex, syntax) {
12732
+ constructor(context, projectIndex, syntax, sourceUri) {
12669
12733
  this.context = context;
12734
+ this.sourceUri = sourceUri;
12670
12735
  this.project = projectIndex ?? createProjectIndex(context.project);
12671
12736
  this.listenerParameters = createListenerAnnotationProvider(
12672
12737
  this.project,
@@ -12710,6 +12775,7 @@ var init_strict_resolver = __esm({
12710
12775
  );
12711
12776
  }
12712
12777
  context;
12778
+ sourceUri;
12713
12779
  project;
12714
12780
  listenerParameters;
12715
12781
  thisVariable;
@@ -13055,6 +13121,20 @@ var init_strict_resolver = __esm({
13055
13121
  }
13056
13122
  }
13057
13123
  resolveStatement(statement, scope) {
13124
+ const instruction = this.resolveStatementCore(statement, scope);
13125
+ if (this.source !== void 0) {
13126
+ const source = {
13127
+ ...statement.pos,
13128
+ text: this.source,
13129
+ uri: this.sourceUri
13130
+ };
13131
+ registerRuntimeSource(instruction, source);
13132
+ if (instruction.type === "functionCall" /* FunctionCall */)
13133
+ registerRuntimeSource(instruction.call, source);
13134
+ }
13135
+ return instruction;
13136
+ }
13137
+ resolveStatementCore(statement, scope) {
13058
13138
  switch (statement.kind) {
13059
13139
  case "varDecl": {
13060
13140
  if (this.valueVariable && statement.name === "value") {
@@ -13706,7 +13786,12 @@ var init_strict_resolver = __esm({
13706
13786
  statement.pos
13707
13787
  );
13708
13788
  }
13709
- this.requireAssignable(value.type, expected, statement.pos, "return");
13789
+ this.requireAssignable(
13790
+ value.type,
13791
+ expected,
13792
+ statement.expr.pos,
13793
+ "return"
13794
+ );
13710
13795
  return {
13711
13796
  type: "return" /* Return */,
13712
13797
  pointer: value.pointer
@@ -14647,6 +14732,16 @@ var init_strict_resolver = __esm({
14647
14732
  }
14648
14733
  }
14649
14734
  resolveExpression(expression, scope, expected) {
14735
+ const resolved = this.resolveExpressionCore(expression, scope, expected);
14736
+ if (this.source !== void 0)
14737
+ registerRuntimeSource(resolved.pointer, {
14738
+ ...expression.pos,
14739
+ text: this.source,
14740
+ uri: this.sourceUri
14741
+ });
14742
+ return resolved;
14743
+ }
14744
+ resolveExpressionCore(expression, scope, expected) {
14650
14745
  if (expected?.kind === "delegate") {
14651
14746
  if (expression.kind === "lambda") {
14652
14747
  return this.resolveDelegateLambda(expression, expected, scope);
@@ -14858,13 +14953,13 @@ var init_strict_resolver = __esm({
14858
14953
  }
14859
14954
  if (expression.typeArguments) {
14860
14955
  throw new CompileError(
14861
- "Generic intrinsic calls require a declaration-aware project source context.",
14956
+ "Type arguments are not supported for this call. Remove the explicit type arguments.",
14862
14957
  expression.pos
14863
14958
  );
14864
14959
  }
14865
14960
  if (expression.argumentNames) {
14866
14961
  throw new CompileError(
14867
- "Named arguments require a declaration-aware project source call context.",
14962
+ "Named arguments are not supported for this call. Pass arguments in declaration order, without names.",
14868
14963
  expression.pos
14869
14964
  );
14870
14965
  }
@@ -18195,7 +18290,7 @@ var init_strict_resolver = __esm({
18195
18290
  this.requireAssignable(
18196
18291
  resolved.type,
18197
18292
  expected,
18198
- pos,
18293
+ argument2.pos,
18199
18294
  `argument ${index + 1}`
18200
18295
  );
18201
18296
  }
@@ -23918,6 +24013,9 @@ function annotationContextForDeclaration(declaration) {
23918
24013
  if (declaration.kind === "global" && isNeoVariantTypeName(declaration.type.name)) {
23919
24014
  return { kind: "variantGlobal", declaration };
23920
24015
  }
24016
+ if (declaration.kind === "global" && (declaration.type.name === "NeoImage" || declaration.type.name === "NeoAudioClip")) {
24017
+ return { kind: "projectFile", typeName: declaration.type.name };
24018
+ }
23921
24019
  return { kind: "schema" };
23922
24020
  }
23923
24021
  function contextualAnnotationFields(document, position) {
@@ -24697,7 +24795,7 @@ function validateAnnotations(uri, annotations, context, environment, diagnostics
24697
24795
  }
24698
24796
  function fieldsForAnnotation(name, context, environment) {
24699
24797
  if (name === "id") {
24700
- return context.kind === "class" || context.kind === "schema" || context.kind === "member" || context.kind === "variantGlobal" || context.kind === "flowMember" || context.kind === "flowBinding" || context.kind === "flowGraph" ? [{ name: "id", type: "string" }] : null;
24798
+ return context.kind === "class" || context.kind === "schema" || context.kind === "member" || context.kind === "variantGlobal" || context.kind === "projectFile" || context.kind === "flowMember" || context.kind === "flowBinding" || context.kind === "flowGraph" ? [{ name: "id", type: "string" }] : null;
24701
24799
  }
24702
24800
  if (name === "primary") return context.kind === "flowBinding" ? [] : null;
24703
24801
  if (name === "incomplete") {
@@ -24735,6 +24833,15 @@ function settingsFields(context) {
24735
24833
  }
24736
24834
  ];
24737
24835
  }
24836
+ if (context.kind === "projectFile") {
24837
+ return [
24838
+ {
24839
+ name: "template",
24840
+ type: context.typeName === "NeoImage" ? "TextureTemplate" : "AudioTemplate"
24841
+ },
24842
+ { name: "name", type: "string" }
24843
+ ];
24844
+ }
24738
24845
  if (context.kind === "class") {
24739
24846
  if (context.declaration.baseTypes.some((type2) => type2.name === "Dialogue")) {
24740
24847
  return [
@@ -25285,7 +25392,7 @@ function validateExpression(expression, expected, scope, environment, uri, range
25285
25392
  pushDiagnostic(
25286
25393
  diagnostics,
25287
25394
  uri,
25288
- range2,
25395
+ anchor ? anchorSpan(anchor, expression.pos, 1) : range2,
25289
25396
  "project-expression-type-mismatch",
25290
25397
  `Expected ${describeType(expected)} but received ${describeType(actual)}.`
25291
25398
  );
@@ -26499,6 +26606,7 @@ function findTypeAnnotation(type, position) {
26499
26606
  function describeAnnotationContext(context) {
26500
26607
  if (context.kind === "member" || context.kind === "type" || context.kind === "flowBinding" || context.kind === "flowMember")
26501
26608
  return `${context.type.name} ${context.kind}`;
26609
+ if (context.kind === "projectFile") return `${context.typeName} declaration`;
26502
26610
  return context.kind;
26503
26611
  }
26504
26612
  function lowerFirst2(value) {
@@ -26706,7 +26814,8 @@ function compileNeoScriptStrict(document, context, options = {}) {
26706
26814
  const emitted = new StrictNeoScriptResolver(
26707
26815
  resolverContext,
26708
26816
  projectIndex,
26709
- syntax.lexed
26817
+ syntax.lexed,
26818
+ document.uri
26710
26819
  ).compile(ast);
26711
26820
  const ir = attachDependencyManifest(
26712
26821
  emitted,
@@ -27942,6 +28051,13 @@ function innerBodyRange(source, range2) {
27942
28051
  }
27943
28052
  function collectRegistryMembers(documents) {
27944
28053
  const byRegistryType = /* @__PURE__ */ new Map();
28054
+ const fileDeclarations = /* @__PURE__ */ new Map();
28055
+ for (const document of documents.values()) {
28056
+ for (const declaration of document.declarations) {
28057
+ if (declaration.kind === "global" && neoScriptRegistryReferenceType(declaration.type.name) !== null)
28058
+ fileDeclarations.set(declaration.name, declaration);
28059
+ }
28060
+ }
27945
28061
  for (const [uri, document] of documents) {
27946
28062
  for (const declaration of document.declarations) {
27947
28063
  if (declaration.kind !== "global") continue;
@@ -27955,9 +28071,10 @@ function collectRegistryMembers(documents) {
27955
28071
  declaration
27956
28072
  )) {
27957
28073
  if (entry.typeName !== registry.entryType) continue;
28074
+ const listed = entry.reference === void 0 ? void 0 : fileDeclarations.get(entry.reference);
27958
28075
  members.push({
27959
28076
  id: sourceIdentity2(
27960
- entry.annotations,
28077
+ listed?.type.name === registry.entryType ? listed.annotations : entry.annotations,
27961
28078
  `${declaration.type.name}:${uri}:${entry.name}`
27962
28079
  ),
27963
28080
  name: entry.name,
@@ -30825,6 +30942,7 @@ function compileNeoProjectSources(inputs) {
30825
30942
  const schemaEnums = [];
30826
30943
  const configuration = [];
30827
30944
  const files = [];
30945
+ const fileDeclarations = [];
30828
30946
  const values = [];
30829
30947
  const dialogueGroups = [];
30830
30948
  const variants = [];
@@ -30863,6 +30981,8 @@ function compileNeoProjectSources(inputs) {
30863
30981
  configuration.push(global);
30864
30982
  } else if (FILE_REGISTRY_TYPES.has(declaration.type.name)) {
30865
30983
  files.push(global);
30984
+ } else if (FILE_DECLARATION_TYPES.has(declaration.type.name)) {
30985
+ fileDeclarations.push(global);
30866
30986
  } else if (GROUP_TYPES.has(declaration.type.name)) {
30867
30987
  dialogueGroups.push(global);
30868
30988
  } else if (isNeoVariantFamilyTypeName(declaration.type.name)) {
@@ -30885,7 +31005,7 @@ function compileNeoProjectSources(inputs) {
30885
31005
  },
30886
31006
  relations: { globals: relations },
30887
31007
  configuration: { globals: configuration },
30888
- files: { registries: files },
31008
+ files: { registries: files, declarations: fileDeclarations },
30889
31009
  values: { globals: values },
30890
31010
  dialogueGroups: { globals: dialogueGroups },
30891
31011
  variants: { globals: variants },
@@ -31013,6 +31133,7 @@ function memberManifest(uri, member) {
31013
31133
  function globalManifest(uri, declaration) {
31014
31134
  return {
31015
31135
  ...identity(uri, declaration.range, declaration.annotations),
31136
+ initializerSource: { uri, range: declaration.initializer.range },
31016
31137
  name: declaration.name,
31017
31138
  type: manifestType(declaration.type),
31018
31139
  annotations: manifestAnnotations(declaration.annotations),
@@ -31191,12 +31312,13 @@ function assertProjectSourceAnalysisV4(value) {
31191
31312
  "globals",
31192
31313
  assertGlobal
31193
31314
  );
31194
- assertArrayEnvelope(
31195
- analysis.files,
31196
- "analysis.files",
31197
- "registries",
31198
- assertGlobal
31199
- );
31315
+ const files = record(analysis.files, "analysis.files");
31316
+ exactKeys(files, "analysis.files", ["registries", "declarations"]);
31317
+ for (const field of ["registries", "declarations"]) {
31318
+ array(files[field], `analysis.files.${field}`).forEach(
31319
+ (entry, index) => assertGlobal(entry, `analysis.files.${field}[${index}]`)
31320
+ );
31321
+ }
31200
31322
  assertArrayEnvelope(
31201
31323
  analysis.values,
31202
31324
  "analysis.values",
@@ -31499,9 +31621,12 @@ function assertGlobal(value, path) {
31499
31621
  "name",
31500
31622
  "type",
31501
31623
  "annotations",
31502
- "initializer"
31624
+ "initializer",
31625
+ "initializerSource"
31503
31626
  ]);
31504
31627
  assertIdentity(global, path);
31628
+ if (global.initializerSource !== void 0)
31629
+ assertLocation(global.initializerSource, `${path}.initializerSource`);
31505
31630
  string(global.name, `${path}.name`);
31506
31631
  assertType(global.type, `${path}.type`);
31507
31632
  array(global.annotations, `${path}.annotations`).forEach(
@@ -31783,7 +31908,7 @@ function string(value, path) {
31783
31908
  }
31784
31909
  return value;
31785
31910
  }
31786
- var CONFIGURATION_TYPES, FILE_REGISTRY_TYPES, GROUP_TYPES, MIGRATION_TYPES;
31911
+ var CONFIGURATION_TYPES, FILE_REGISTRY_TYPES, FILE_DECLARATION_TYPES, GROUP_TYPES, MIGRATION_TYPES;
31787
31912
  var init_project_source_manifest = __esm({
31788
31913
  "../packages/neoscript-language/src/project-source-manifest.ts"() {
31789
31914
  "use strict";
@@ -31800,6 +31925,7 @@ var init_project_source_manifest = __esm({
31800
31925
  "LocalizationStatus"
31801
31926
  ]);
31802
31927
  FILE_REGISTRY_TYPES = /* @__PURE__ */ new Set(["ImageRegistry", "AudioClipRegistry"]);
31928
+ FILE_DECLARATION_TYPES = /* @__PURE__ */ new Set(["NeoImage", "NeoAudioClip"]);
31803
31929
  GROUP_TYPES = /* @__PURE__ */ new Set(["PriorityGroup", "DialogueGroup"]);
31804
31930
  MIGRATION_TYPES = /* @__PURE__ */ new Set(["Migration"]);
31805
31931
  }
@@ -31811,7 +31937,7 @@ var init_registry = __esm({
31811
31937
  "../packages/manifest/schema-contract/registry.mjs"() {
31812
31938
  "use strict";
31813
31939
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
31814
- cliVersion: "0.70.1",
31940
+ cliVersion: "0.70.3",
31815
31941
  projectFileUploadBatchSize: 32,
31816
31942
  documentRecords: {
31817
31943
  member: {
@@ -54583,10 +54709,10 @@ ${schemaClass2.genericParameters.map((parameter4) => {
54583
54709
  return { member, source };
54584
54710
  });
54585
54711
  const constructorIds = schemaClass2.constructorIds ?? [];
54586
- const sources = rendered.map((entry) => entry.source);
54712
+ const sources2 = rendered.map((entry) => entry.source);
54587
54713
  for (const memberId of memberIds)
54588
54714
  for (const statement of listenerStatementsByMember?.get(memberId) ?? [])
54589
- sources.push(indentNeoSourceNonEmptyLines(statement, 2));
54715
+ sources2.push(indentNeoSourceNonEmptyLines(statement, 2));
54590
54716
  const spliced = [
54591
54717
  ...constructorIds.map(
54592
54718
  (constructorId) => emitConstructor(
@@ -54600,7 +54726,7 @@ ${schemaClass2.genericParameters.map((parameter4) => {
54600
54726
  if (spliced.length === 0) {
54601
54727
  return `${emitDocsText(schemaClass2.docsText)}${annotations.join("\n")}
54602
54728
  ${header}
54603
- ${sources.join("\n\n")}
54729
+ ${sources2.join("\n\n")}
54604
54730
  }
54605
54731
  `;
54606
54732
  }
@@ -54609,9 +54735,9 @@ ${sources.join("\n\n")}
54609
54735
  -1
54610
54736
  );
54611
54737
  const members = [
54612
- ...sources.slice(0, lastValueMemberIndex + 1),
54738
+ ...sources2.slice(0, lastValueMemberIndex + 1),
54613
54739
  ...spliced.map((source) => indentNeoSourceNonEmptyLines(source, 2)),
54614
- ...sources.slice(lastValueMemberIndex + 1)
54740
+ ...sources2.slice(lastValueMemberIndex + 1)
54615
54741
  ];
54616
54742
  return `${emitDocsText(schemaClass2.docsText)}${annotations.join("\n")}
54617
54743
  ${header}
@@ -60587,8 +60713,9 @@ function validateProjectFileDeclarationSymbolsV4(declarations) {
60587
60713
  for (const declaration of declarations) {
60588
60714
  const expected = expectedSymbols.get(declaration.recordId);
60589
60715
  if (expected === void 0 || declaration.symbol === expected) continue;
60590
- throw new Error(
60591
- `Project file ${declaration.symbol} must use registry symbol ${expected}, derived from its persisted name ${JSON.stringify(declaration.persistedName)}. Rename the declaration, or set @settings(name: ...) to a file name ending in ${JSON.stringify(sourceExtension(declaration.persistedName))} that derives ${declaration.symbol}.`
60716
+ throw projectFileSourceError(
60717
+ `Project file ${declaration.symbol} must use registry symbol ${expected}, derived from its persisted name ${JSON.stringify(declaration.persistedName)}. Rename the declaration, or set @settings(name: ...) to a file name ending in ${JSON.stringify(sourceExtension(declaration.persistedName))} that derives ${declaration.symbol}.`,
60718
+ declaration
60592
60719
  );
60593
60720
  }
60594
60721
  }
@@ -60606,7 +60733,24 @@ function projectFileUnityImportSettingsV4(kind, templateId) {
60606
60733
  values: {}
60607
60734
  };
60608
60735
  }
60609
- function emitProjectFileRegistrySourcesV4(records2, binaryPaths = /* @__PURE__ */ new Map()) {
60736
+ function projectFileSourceError(error, declaration) {
60737
+ if (error instanceof SchemaSourceError) return error;
60738
+ return new SchemaSourceError(
60739
+ error instanceof Error ? error.message : String(error),
60740
+ declaration.sourceFile,
60741
+ declaration.sourceLine,
60742
+ declaration.sourceColumn ?? 1
60743
+ );
60744
+ }
60745
+ function emitProjectFileRegistrySourcesV4(records2, binaryPaths = /* @__PURE__ */ new Map(), placements = []) {
60746
+ const listed = /* @__PURE__ */ new Map();
60747
+ for (const placement of placements) {
60748
+ const fileId = placement.valueIds[0];
60749
+ if (placement.type !== "NeoImage" && placement.type !== "NeoAudioClip")
60750
+ continue;
60751
+ if (fileId !== void 0) listed.set(fileId, placement);
60752
+ }
60753
+ const declarationFiles = [];
60610
60754
  const liveFiles = [...records2.values()].filter((record6) => record6.recordKind === "project-file" && !record6.deleted).map((record6) => ({ record: record6, data: requireFileData(record6) }));
60611
60755
  assertUniqueProjectBinaryPaths(liveFiles);
60612
60756
  const symbols = assignDeterministicFileSymbols(
@@ -60617,10 +60761,11 @@ function emitProjectFileRegistrySourcesV4(records2, binaryPaths = /* @__PURE__ *
60617
60761
  );
60618
60762
  const textureTemplates = templateNames(records2, "unity-texture-template");
60619
60763
  const audioTemplates = templateNames(records2, "unity-audio-clip-template");
60620
- return ["image", "audio"].map((kind) => {
60764
+ const registries2 = ["image", "audio"].map((kind) => {
60621
60765
  const registryType = kind === "image" ? "ImageRegistry" : "AudioClipRegistry";
60622
60766
  const registryName = kind === "image" ? "Images" : "AudioClips";
60623
60767
  const valueType = kind === "image" ? "NeoImage" : "NeoAudioClip";
60768
+ const registryKeys = [];
60624
60769
  const entries = liveFiles.filter(({ data }) => data.fileType === kind).sort((left, right) => {
60625
60770
  const leftSymbol = symbols.get(left.record.recordId) ?? "";
60626
60771
  const rightSymbol = symbols.get(right.record.recordId) ?? "";
@@ -60645,10 +60790,22 @@ function emitProjectFileRegistrySourcesV4(records2, binaryPaths = /* @__PURE__ *
60645
60790
  ...templateName ? [`template: ${templateName}`] : [],
60646
60791
  ...data.name === sourceBasename(path) ? [] : [`name: ${JSON.stringify(data.name)}`]
60647
60792
  ];
60648
- const annotation2 = options.length === 0 ? "" : ` @settings(${options.join(", ")})
60649
- `;
60650
- return ` @id(${JSON.stringify(record6.recordId)})
60651
- ${annotation2} ${valueType} ${symbol} = new(${JSON.stringify(path)});`;
60793
+ const declare = (name, indent2) => `${indent2}@id(${JSON.stringify(record6.recordId)})
60794
+ ${options.length === 0 ? "" : `${indent2}@settings(${options.join(", ")})
60795
+ `}${indent2}${valueType} ${name} = new(${JSON.stringify(path)});`;
60796
+ const key = `project-file:${record6.recordId}`;
60797
+ const placement = listed.get(record6.recordId);
60798
+ if (placement?.type === valueType) {
60799
+ declarationFiles.push({
60800
+ path: placement.file,
60801
+ content: `${declare(placement.name, "")}
60802
+ `,
60803
+ recordKeys: [key]
60804
+ });
60805
+ return ` ${valueType} ${symbol} = ${placement.name};`;
60806
+ }
60807
+ registryKeys.push(key);
60808
+ return declare(symbol, " ");
60652
60809
  });
60653
60810
  return {
60654
60811
  path: `Files/${registryName}.neo`,
@@ -60657,14 +60814,42 @@ ${entries.join("\n\n")}
60657
60814
  `}
60658
60815
  }
60659
60816
  `,
60660
- recordKeys: liveFiles.filter(({ data }) => data.fileType === kind).map(({ record: record6 }) => `project-file:${record6.recordId}`)
60817
+ recordKeys: registryKeys
60661
60818
  };
60662
60819
  });
60820
+ return [...registries2, ...declarationFiles];
60663
60821
  }
60664
60822
  function projectFileDeclarationsV4(analysis) {
60665
60823
  const templateIds = fileTemplateIdsByName(analysis);
60666
- return analysis.files.registries.flatMap(
60667
- (registry) => parseRegistry(registry, templateIds)
60824
+ const globals = new Map(
60825
+ analysis.files.declarations.map((global) => [global.name, global])
60826
+ );
60827
+ const listed = /* @__PURE__ */ new Set();
60828
+ const declarations = analysis.files.registries.flatMap(
60829
+ (registry) => parseRegistry(registry, templateIds, globals, listed)
60830
+ );
60831
+ for (const global of globals.values()) {
60832
+ if (listed.has(global.name)) continue;
60833
+ const registry = global.type.name === "NeoImage" ? "Images" : "AudioClips";
60834
+ throw new SchemaSourceError(
60835
+ `${global.type.name} ${global.name} in ${global.source.uri} is not listed in ${registry}. Add "${global.type.name} <Symbol> = ${global.name};" to Files/${registry}.neo.`,
60836
+ global.source.uri,
60837
+ global.source.range.start.line + 1,
60838
+ global.source.range.start.character + 1
60839
+ );
60840
+ }
60841
+ return declarations;
60842
+ }
60843
+ function projectFilePlacementsV4(analysis) {
60844
+ return projectFileDeclarationsV4(analysis).flatMap(
60845
+ (declaration) => declaration.global === void 0 ? [] : [
60846
+ {
60847
+ name: declaration.global,
60848
+ type: declaration.kind === "image" ? "NeoImage" : "NeoAudioClip",
60849
+ file: declaration.sourceFile,
60850
+ valueIds: [declaration.recordId]
60851
+ }
60852
+ ]
60668
60853
  );
60669
60854
  }
60670
60855
  function lowerTrustedProjectFileRegistrySourcesV4(state, analysis, trustedPendingFiles = /* @__PURE__ */ new Map()) {
@@ -60673,73 +60858,77 @@ function lowerTrustedProjectFileRegistrySourcesV4(state, analysis, trustedPendin
60673
60858
  const seenPaths = /* @__PURE__ */ new Map();
60674
60859
  const seenSymbols = /* @__PURE__ */ new Map();
60675
60860
  return declarations.map((declaration) => {
60676
- const normalizedPath = normalizeTrustedProjectBinaryPath(
60677
- declaration.path,
60678
- declaration.kind
60679
- );
60680
- const pathKey = normalizedPath.toLowerCase();
60681
- const previousPath = seenPaths.get(pathKey);
60682
- if (previousPath) {
60683
- throw new Error(
60684
- `Project binary path ${JSON.stringify(normalizedPath)} is declared by both ${previousPath.symbol} and ${declaration.symbol}.`
60685
- );
60686
- }
60687
- seenPaths.set(pathKey, declaration);
60688
- const symbolKey = `${declaration.kind}:${declaration.symbol.toLowerCase()}`;
60689
- if (seenSymbols.has(symbolKey)) {
60690
- throw new Error(
60691
- `Project file symbol ${declaration.symbol} is declared more than once.`
60692
- );
60693
- }
60694
- seenSymbols.set(symbolKey, declaration);
60695
- const baseState = state[`project-file:${declaration.recordId}`];
60696
- const base = isObjectRecord2(baseState?.data) ? baseState.data : null;
60697
- const trustedPending = trustedPendingFiles.get(declaration.recordId);
60698
- if (base === null && trustedPending === void 0) {
60699
- throw new Error(
60700
- `Pending project file ${declaration.symbol} has no server-verified staged bytes.`
60701
- );
60702
- }
60703
- const now = Date.now();
60704
- const settingsField = declaration.kind === "image" ? "unityTextureSettings" : "unityAudioClipSettings";
60705
- const otherSettingsField = declaration.kind === "image" ? "unityAudioClipSettings" : "unityTextureSettings";
60706
- const baseSettings = base !== null && isObjectRecord2(base[settingsField]) ? base[settingsField] : null;
60707
- const importSettingsChanged = base === null || base.fileType !== declaration.kind || optionalString(baseSettings?.templateId) !== declaration.templateId;
60708
- const next = {
60709
- ...base ?? {},
60710
- id: declaration.recordId,
60711
- name: declaration.persistedName,
60712
- fileType: declaration.kind,
60713
- ...base === null && trustedPending !== void 0 ? {
60714
- mimeType: trustedPending.mimeType,
60715
- byteLength: trustedPending.byteLength,
60716
- contentSha256: trustedPending.sha256
60717
- } : {},
60718
- [otherSettingsField]: null
60719
- };
60720
- if (base === null) {
60721
- Object.assign(next, {
60722
- projectId: "",
60723
- status: "pending-upload",
60724
- storageKey: "",
60725
- storageETag: null,
60726
- createdAt: now,
60727
- updatedAt: now
60728
- });
60729
- }
60730
- if (importSettingsChanged) {
60731
- next[settingsField] = projectFileUnityImportSettingsV4(
60732
- declaration.kind,
60733
- declaration.templateId
60861
+ try {
60862
+ const normalizedPath = normalizeTrustedProjectBinaryPath(
60863
+ declaration.path,
60864
+ declaration.kind
60734
60865
  );
60866
+ const pathKey = normalizedPath.toLowerCase();
60867
+ const previousPath = seenPaths.get(pathKey);
60868
+ if (previousPath) {
60869
+ throw new Error(
60870
+ `Project binary path ${JSON.stringify(normalizedPath)} is declared by both ${previousPath.symbol} and ${declaration.symbol}.`
60871
+ );
60872
+ }
60873
+ seenPaths.set(pathKey, declaration);
60874
+ const symbolKey = `${declaration.kind}:${declaration.symbol.toLowerCase()}`;
60875
+ if (seenSymbols.has(symbolKey)) {
60876
+ throw new Error(
60877
+ `Project file symbol ${declaration.symbol} is declared more than once.`
60878
+ );
60879
+ }
60880
+ seenSymbols.set(symbolKey, declaration);
60881
+ const baseState = state[`project-file:${declaration.recordId}`];
60882
+ const base = isObjectRecord2(baseState?.data) ? baseState.data : null;
60883
+ const trustedPending = trustedPendingFiles.get(declaration.recordId);
60884
+ if (base === null && trustedPending === void 0) {
60885
+ throw new Error(
60886
+ `Pending project file ${declaration.symbol} has no server-verified staged bytes.`
60887
+ );
60888
+ }
60889
+ const now = Date.now();
60890
+ const settingsField = declaration.kind === "image" ? "unityTextureSettings" : "unityAudioClipSettings";
60891
+ const otherSettingsField = declaration.kind === "image" ? "unityAudioClipSettings" : "unityTextureSettings";
60892
+ const baseSettings = base !== null && isObjectRecord2(base[settingsField]) ? base[settingsField] : null;
60893
+ const importSettingsChanged = base === null || base.fileType !== declaration.kind || optionalString(baseSettings?.templateId) !== declaration.templateId;
60894
+ const next = {
60895
+ ...base ?? {},
60896
+ id: declaration.recordId,
60897
+ name: declaration.persistedName,
60898
+ fileType: declaration.kind,
60899
+ ...base === null && trustedPending !== void 0 ? {
60900
+ mimeType: trustedPending.mimeType,
60901
+ byteLength: trustedPending.byteLength,
60902
+ contentSha256: trustedPending.sha256
60903
+ } : {},
60904
+ [otherSettingsField]: null
60905
+ };
60906
+ if (base === null) {
60907
+ Object.assign(next, {
60908
+ projectId: "",
60909
+ status: "pending-upload",
60910
+ storageKey: "",
60911
+ storageETag: null,
60912
+ createdAt: now,
60913
+ updatedAt: now
60914
+ });
60915
+ }
60916
+ if (importSettingsChanged) {
60917
+ next[settingsField] = projectFileUnityImportSettingsV4(
60918
+ declaration.kind,
60919
+ declaration.templateId
60920
+ );
60921
+ }
60922
+ return {
60923
+ recordKind: "project-file",
60924
+ recordId: declaration.recordId,
60925
+ fileFields: next,
60926
+ file: declaration.sourceFile,
60927
+ line: declaration.sourceLine
60928
+ };
60929
+ } catch (error) {
60930
+ throw projectFileSourceError(error, declaration);
60735
60931
  }
60736
- return {
60737
- recordKind: "project-file",
60738
- recordId: declaration.recordId,
60739
- fileFields: next,
60740
- file: declaration.sourceFile,
60741
- line: declaration.sourceLine
60742
- };
60743
60932
  });
60744
60933
  }
60745
60934
  function validateFinalizedFileAssignmentsV4(records2, options = {}) {
@@ -60862,7 +61051,7 @@ function fileTemplateIdsByName(analysis) {
60862
61051
  }
60863
61052
  return templateIds;
60864
61053
  }
60865
- function parseRegistry(registry, templateIds) {
61054
+ function parseRegistry(registry, templateIds, globals, listed) {
60866
61055
  const kind = registry.type.name === "ImageRegistry" ? "image" : registry.type.name === "AudioClipRegistry" ? "audio" : null;
60867
61056
  if (kind === null) {
60868
61057
  throw new Error(
@@ -60875,53 +61064,150 @@ function parseRegistry(registry, templateIds) {
60875
61064
  `${registry.type.name} must use the canonical global name ${expectedName}.`
60876
61065
  );
60877
61066
  }
60878
- const body = objectInitializerBody(registry.initializer, registry.type.name);
60879
- const result = [];
60880
- let cursor = 0;
60881
- while (cursor < body.length) {
60882
- const whitespace = /^(?:\s|\/\/[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)+/.exec(
60883
- body.slice(cursor)
60884
- );
60885
- if (whitespace) cursor += whitespace[0].length;
60886
- if (cursor >= body.length) break;
60887
- REGISTRY_ENTRY.lastIndex = cursor;
60888
- const match = REGISTRY_ENTRY.exec(body);
60889
- if (!match) {
60890
- throw new Error(
60891
- `${registry.type.name} ${registry.name} contains an invalid file declaration near ${JSON.stringify(body.slice(cursor, cursor + 80))}.`
60892
- );
60893
- }
60894
- const entryType = match[3];
60895
- if (kind === "image" && entryType !== "NeoImage" || kind === "audio" && entryType !== "NeoAudioClip") {
60896
- throw new Error(
60897
- `${registry.type.name} cannot contain ${entryType} declarations.`
60898
- );
60899
- }
60900
- const settings = parseFileSettings(match[2], templateIds);
60901
- const path = JSON.parse(match[5]);
60902
- const recordId = match[1] ? JSON.parse(match[1]) : `__pending__:project-file:${encodeURIComponent(registry.source.uri)}:${registry.source.range.start.line}:${encodeURIComponent(match[4])}`;
60903
- const fileName2 = sourceBasename(path);
60904
- if (settings.name !== null) {
60905
- const extension = sourceExtension(fileName2);
60906
- if (!settings.name.toLowerCase().endsWith(extension.toLowerCase())) {
61067
+ const source = lex(registry.initializer).source;
61068
+ const origin = registry.initializerSource?.range.start ?? registry.source.range.start;
61069
+ let errorOffset = 0;
61070
+ let declarationSource;
61071
+ const positioned = (offset) => {
61072
+ const point = source.positionAt(offset);
61073
+ return {
61074
+ sourceFile: registry.source.uri,
61075
+ sourceLine: origin.line + point.line + 1,
61076
+ sourceColumn: point.character + 1 + (point.line === 0 ? origin.character : 0)
61077
+ };
61078
+ };
61079
+ try {
61080
+ const body = objectInitializerBody(
61081
+ registry.initializer,
61082
+ registry.type.name
61083
+ );
61084
+ const result = [];
61085
+ let cursor = 0;
61086
+ while (cursor < body.length) {
61087
+ const whitespace = /^(?:\s|\/\/[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)+/.exec(
61088
+ body.slice(cursor)
61089
+ );
61090
+ if (whitespace) cursor += whitespace[0].length;
61091
+ if (cursor >= body.length) break;
61092
+ declarationSource = void 0;
61093
+ errorOffset = registry.initializer.indexOf("{") + 1 + cursor;
61094
+ REGISTRY_ENTRY.lastIndex = cursor;
61095
+ const match = REGISTRY_ENTRY.exec(body);
61096
+ if (!match) {
60907
61097
  throw new Error(
60908
- `Project file ${match[4]} @settings(name: ${JSON.stringify(settings.name)}) must end with ${JSON.stringify(extension)}, the extension of ${JSON.stringify(path)}.`
61098
+ `${registry.type.name} ${registry.name} contains an invalid file declaration near ${JSON.stringify(body.slice(cursor, cursor + 80))}.`
60909
61099
  );
60910
61100
  }
61101
+ const bodyOffset = registry.initializer.indexOf("{") + 1;
61102
+ const entryOffset = bodyOffset + (match.indices?.[3]?.[0] ?? cursor);
61103
+ errorOffset = entryOffset;
61104
+ const entryType = match[3];
61105
+ if (kind === "image" && entryType !== "NeoImage" || kind === "audio" && entryType !== "NeoAudioClip") {
61106
+ throw new Error(
61107
+ `${registry.type.name} cannot contain ${entryType} declarations.`
61108
+ );
61109
+ }
61110
+ errorOffset = bodyOffset + (match.indices?.[2]?.[0] ?? match.indices?.[3]?.[0] ?? cursor);
61111
+ const entry = match[6] === void 0 ? {
61112
+ id: match[1] === void 0 ? null : JSON.parse(match[1]),
61113
+ settings: match[2],
61114
+ pathLiteral: match[5],
61115
+ name: match[4],
61116
+ source: registry.source
61117
+ } : listedFileDeclaration(match, entryType, globals, listed);
61118
+ if (match[6] !== void 0) declarationSource = entry.source;
61119
+ const settings = parseFileSettings(entry.settings, templateIds);
61120
+ const path = JSON.parse(entry.pathLiteral);
61121
+ const recordId = entry.id ?? `__pending__:project-file:${encodeURIComponent(entry.source.uri)}:${entry.source.range.start.line}:${encodeURIComponent(entry.name)}`;
61122
+ const fileName2 = sourceBasename(path);
61123
+ if (settings.name !== null) {
61124
+ const extension = sourceExtension(fileName2);
61125
+ if (!settings.name.toLowerCase().endsWith(extension.toLowerCase())) {
61126
+ throw new Error(
61127
+ `Project file ${match[4]} @settings(name: ${JSON.stringify(settings.name)}) must end with ${JSON.stringify(extension)}, the extension of ${JSON.stringify(path)}.`
61128
+ );
61129
+ }
61130
+ }
61131
+ result.push({
61132
+ recordId,
61133
+ symbol: match[4],
61134
+ path,
61135
+ kind,
61136
+ templateId: settings.templateId,
61137
+ persistedName: settings.name ?? fileName2,
61138
+ ...match[6] === void 0 ? positioned(entryOffset) : {
61139
+ sourceFile: entry.source.uri,
61140
+ sourceLine: entry.source.range.start.line + 1,
61141
+ sourceColumn: entry.source.range.start.character + 1,
61142
+ global: match[6]
61143
+ }
61144
+ });
61145
+ cursor = REGISTRY_ENTRY.lastIndex;
60911
61146
  }
60912
- result.push({
60913
- recordId,
60914
- symbol: match[4],
60915
- path,
60916
- kind,
60917
- templateId: settings.templateId,
60918
- persistedName: settings.name ?? fileName2,
60919
- sourceFile: registry.source.uri,
60920
- sourceLine: registry.source.range.start.line + 1
60921
- });
60922
- cursor = REGISTRY_ENTRY.lastIndex;
61147
+ return result;
61148
+ } catch (error) {
61149
+ throw projectFileSourceError(
61150
+ error,
61151
+ declarationSource === void 0 ? positioned(errorOffset) : {
61152
+ sourceFile: declarationSource.uri,
61153
+ sourceLine: declarationSource.range.start.line + 1,
61154
+ sourceColumn: declarationSource.range.start.character + 1
61155
+ }
61156
+ );
60923
61157
  }
60924
- return result;
61158
+ }
61159
+ function listedFileDeclaration(match, entryType, globals, listed) {
61160
+ const symbol = match[4];
61161
+ const name = match[6];
61162
+ if (match[1] !== void 0) {
61163
+ throw new Error(
61164
+ `Project file ${symbol} lists ${name}, so its @id belongs on the ${name} declaration.`
61165
+ );
61166
+ }
61167
+ if (match[2] !== void 0) {
61168
+ throw new Error(
61169
+ `Project file ${symbol} lists ${name}, so its @settings belong on the ${name} declaration.`
61170
+ );
61171
+ }
61172
+ const global = globals.get(name);
61173
+ if (global === void 0) {
61174
+ throw new Error(
61175
+ `Project file ${symbol} lists ${name}, which is not a top-level NeoImage or NeoAudioClip declaration.`
61176
+ );
61177
+ }
61178
+ if (global.type.name !== entryType) {
61179
+ throw new Error(
61180
+ `Project file ${symbol} is a ${entryType}, but ${name} is a ${global.type.name}.`
61181
+ );
61182
+ }
61183
+ if (listed.has(name)) {
61184
+ throw new Error(`${global.type.name} ${name} is listed more than once.`);
61185
+ }
61186
+ listed.add(name);
61187
+ const initializer = FILE_DECLARATION_INITIALIZER.exec(
61188
+ global.initializer.trim()
61189
+ );
61190
+ if (initializer === null) {
61191
+ const source = global.initializerSource ?? global.source;
61192
+ throw new SchemaSourceError(
61193
+ `${global.type.name} ${name} must be initialized with new("<path>").`,
61194
+ source.uri,
61195
+ source.range.start.line + 1,
61196
+ source.range.start.character + 1
61197
+ );
61198
+ }
61199
+ const settings = global.annotations.find(
61200
+ (annotation2) => annotation2.name === "settings"
61201
+ );
61202
+ return {
61203
+ id: global.id,
61204
+ settings: settings?.arguments.map(
61205
+ (argument2) => argument2.name === void 0 ? argument2.expression : `${argument2.name}: ${argument2.expression}`
61206
+ ).join(", "),
61207
+ pathLiteral: initializer[1],
61208
+ name,
61209
+ source: global.source
61210
+ };
60925
61211
  }
60926
61212
  function parseFileSettings(source, templateIds) {
60927
61213
  if (!source) return { templateId: null, name: null };
@@ -61028,7 +61314,7 @@ function normalizeTrustedProjectBinaryPath(path, kind) {
61028
61314
  throw new Error("Project file path contains a null byte.");
61029
61315
  if (normalized.split("/").some((part) => part === ".." || part === "." || part === "")) {
61030
61316
  throw new Error(
61031
- `Project file path ${JSON.stringify(path)} contains an invalid path segment.`
61317
+ `Project file path ${JSON.stringify(path)} contains an invalid path segment. Use a path relative to neo.json without '..', '.', or empty segments.`
61032
61318
  );
61033
61319
  }
61034
61320
  if (neoProjectPathHasIgnoredDirectory(normalized)) {
@@ -61045,10 +61331,12 @@ function normalizeTrustedProjectBinaryPath(path, kind) {
61045
61331
  }
61046
61332
  return normalized;
61047
61333
  }
61048
- var REGISTRY_ENTRY, assignDeterministicFileSymbols;
61334
+ var REGISTRY_ENTRY, FILE_DECLARATION_INITIALIZER, assignDeterministicFileSymbols;
61049
61335
  var init_project_file_source = __esm({
61050
61336
  "src/project-source/project-file-source.ts"() {
61051
61337
  "use strict";
61338
+ init_lexer();
61339
+ init_source_diagnostics();
61052
61340
  init_executable_parser();
61053
61341
  init_language_spec();
61054
61342
  init_value_comparison();
@@ -61056,7 +61344,8 @@ var init_project_file_source = __esm({
61056
61344
  init_project_file_registry();
61057
61345
  init_members();
61058
61346
  init_unity_import_settings();
61059
- REGISTRY_ENTRY = /(?:@id\(\s*("(?:[^"\\]|\\.)*")\s*\)\s*)?(?:@settings\(([\s\S]*?)\)\s*)?(NeoImage|NeoAudioClip)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*new\(\s*("(?:[^"\\]|\\.)*")\s*\)\s*;/gy;
61347
+ REGISTRY_ENTRY = /(?:@id\(\s*("(?:[^"\\]|\\.)*")\s*\)\s*)?(?:@settings\(([\s\S]*?)\)\s*)?(NeoImage|NeoAudioClip)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:new\(\s*("(?:[^"\\]|\\.)*")\s*\)|([A-Za-z_][A-Za-z0-9_]*))\s*;/dgy;
61348
+ FILE_DECLARATION_INITIALIZER = /^new\(\s*("(?:[^"\\]|\\.)*")\s*\)$/;
61060
61349
  assignDeterministicFileSymbols = assignDeterministicProjectFileSymbols;
61061
61350
  }
61062
61351
  });
@@ -61078,7 +61367,13 @@ function emitSupplementalProjectSourcesV4(records2, binaryPaths, options = {}) {
61078
61367
  options.manifest?.members.map((entry) => [entry.id, entry])
61079
61368
  )
61080
61369
  };
61081
- files.push(...emitProjectFileRegistrySourcesV4(records2, binaryPaths));
61370
+ files.push(
61371
+ ...emitProjectFileRegistrySourcesV4(
61372
+ records2,
61373
+ binaryPaths,
61374
+ options.localValues
61375
+ )
61376
+ );
61082
61377
  const project = live.find((record6) => record6.recordKind === "project");
61083
61378
  if (project) {
61084
61379
  const data = recordData(project);
@@ -61487,8 +61782,12 @@ function lowerDialogueGroup(baseValue, global, priorityIds, dialogueIds, uiLogic
61487
61782
  const path = `${collection.receiver.name}.${collection.name}`;
61488
61783
  collectionMemberId = resolveQualifiedMemberId(memberContext, path);
61489
61784
  if (collectionMemberId === null) {
61490
- throw new Error(
61491
- `Lookup dialogue group ${global.name} names unknown collection ${path}.`
61785
+ const origin = global.initializerSource ?? global.source;
61786
+ throw new SchemaSourceError(
61787
+ `Lookup dialogue group ${global.name} names unknown collection ${path}. Use an existing collection member.`,
61788
+ origin.uri,
61789
+ origin.range.start.line + collection.pos.line,
61790
+ collection.pos.column + (collection.pos.line === 1 ? origin.range.start.character : 0)
61492
61791
  );
61493
61792
  }
61494
61793
  } else {
@@ -62078,6 +62377,7 @@ var PRIORITY_OPTION_PATTERN;
62078
62377
  var init_supplemental_records = __esm({
62079
62378
  "src/project-source/supplemental-records.ts"() {
62080
62379
  "use strict";
62380
+ init_source_diagnostics();
62081
62381
  init_project_source_identifiers();
62082
62382
  init_lexer();
62083
62383
  init_executable_parser();
@@ -65952,14 +66252,14 @@ function createNeoScriptProjectInScope(context) {
65952
66252
  if (context.dialogueContext) {
65953
66253
  languageTypes.push(createDialogueContextType(context));
65954
66254
  }
65955
- const registries = projectFileRegistryProjection(
66255
+ const registries2 = projectFileRegistryProjection(
65956
66256
  context.vm.projectFiles ?? []
65957
66257
  );
65958
- languageTypes.push(...registries.types);
66258
+ languageTypes.push(...registries2.types);
65959
66259
  return {
65960
66260
  version: projectLanguageVersion(context),
65961
66261
  types: languageTypes,
65962
- globals: registries.globals,
66262
+ globals: registries2.globals,
65963
66263
  roots: [
65964
66264
  {
65965
66265
  id: "neoscript:root",
@@ -67313,10 +67613,33 @@ var NSGetterRuntimeError, UncompiledInitializerRuntimeError;
67313
67613
  var init_NSGetterRuntimeError = __esm({
67314
67614
  "../src/runtime/neoscript/NSGetterRuntimeError.ts"() {
67315
67615
  "use strict";
67616
+ init_runtime_source();
67316
67617
  NSGetterRuntimeError = class extends Error {
67618
+ /** Keep catch-visible runtime messages stable; add guidance for diagnostic hosts. */
67619
+ get diagnosticMessage() {
67620
+ if (this.message === "Division by zero")
67621
+ return `${this.message}. Check that the divisor is nonzero before dividing.`;
67622
+ if (this.message === "Modulo by zero")
67623
+ return `${this.message}. Check that the divisor is nonzero before taking the remainder.`;
67624
+ return this.message;
67625
+ }
67626
+ at(node) {
67627
+ if (this.node === void 0) {
67628
+ this.node = node;
67629
+ this.source = runtimeSource(node);
67630
+ }
67631
+ return this;
67632
+ }
67317
67633
  constructor(message, options) {
67318
67634
  super(message, options);
67319
67635
  this.name = "NSGetterRuntimeError";
67636
+ Object.defineProperties(this, {
67637
+ source: { writable: true, value: void 0 },
67638
+ node: { writable: true, value: void 0 },
67639
+ callerNode: { writable: true, value: void 0 },
67640
+ initializerValueId: { writable: true, value: void 0 },
67641
+ sourceOwnerId: { writable: true, value: void 0 }
67642
+ });
67320
67643
  }
67321
67644
  };
67322
67645
  UncompiledInitializerRuntimeError = class extends NSGetterRuntimeError {
@@ -67353,9 +67676,13 @@ function materializeInitializerDependencies(pending, materialize) {
67353
67676
  if (!(error instanceof PendingInitializerDependency)) throw error;
67354
67677
  if (!pending.has(error.valueId)) throw error;
67355
67678
  if (active.has(error.valueId)) {
67356
- throw new Error(
67357
- `Initializer dependency cycle: ${[...stack, error.valueId].join(" -> ")}.`
67679
+ const cycle = new NSGetterRuntimeError(
67680
+ `Initializer dependency cycle: ${[...stack, error.valueId].join(" -> ")}. Remove a dependency so these values can be initialized in order.`
67358
67681
  );
67682
+ cycle.initializerValueId = valueId;
67683
+ cycle.node = error.node;
67684
+ cycle.source = error.source;
67685
+ throw cycle;
67359
67686
  }
67360
67687
  active.add(error.valueId);
67361
67688
  stack.push(error.valueId);
@@ -73750,326 +74077,351 @@ function validateTryInstruction(instruction) {
73750
74077
  }
73751
74078
  function evalInstructions(instructions, scope, ctx, options) {
73752
74079
  for (const ins of instructions) {
73753
- consumeBudget(ctx, "workUnits", 1, "work unit");
73754
- switch (ins.type) {
73755
- case "variable" /* variable */: {
73756
- let value = evalPointer(ins.variable.pointer, scope, ctx);
73757
- if (ins.variable.typeInfo.type === 20 /* Decimal */ && typeof value === "number") {
73758
- value = coerceDecimalOperand(value, "variable initialization");
73759
- }
73760
- scope.setLocal(ins.variable.id, value);
73761
- break;
73762
- }
73763
- case "if" /* if */: {
73764
- let matched = false;
73765
- for (const branch of ins.branches) {
73766
- if (evalBooleanExpression(branch.expression, scope, ctx)) {
73767
- matched = true;
73768
- const r = evalInstructions(
73769
- branch.instructions,
73770
- scope,
73771
- ctx,
73772
- options
73773
- );
73774
- if (r.kind !== "fallthrough") return r;
73775
- break;
74080
+ try {
74081
+ consumeBudget(ctx, "workUnits", 1, "work unit");
74082
+ switch (ins.type) {
74083
+ case "variable" /* variable */: {
74084
+ let value = evalPointer(ins.variable.pointer, scope, ctx);
74085
+ if (ins.variable.typeInfo.type === 20 /* Decimal */ && typeof value === "number") {
74086
+ value = coerceDecimalOperand(value, "variable initialization");
73776
74087
  }
74088
+ scope.setLocal(ins.variable.id, value);
74089
+ break;
73777
74090
  }
73778
- if (!matched && ins.else) {
73779
- const r = evalInstructions(ins.else, scope, ctx, options);
73780
- if (r.kind !== "fallthrough") return r;
74091
+ case "if" /* if */: {
74092
+ let matched = false;
74093
+ for (const branch of ins.branches) {
74094
+ if (evalBooleanExpression(branch.expression, scope, ctx)) {
74095
+ matched = true;
74096
+ const r = evalInstructions(
74097
+ branch.instructions,
74098
+ scope,
74099
+ ctx,
74100
+ options
74101
+ );
74102
+ if (r.kind !== "fallthrough") return r;
74103
+ break;
74104
+ }
74105
+ }
74106
+ if (!matched && ins.else) {
74107
+ const r = evalInstructions(ins.else, scope, ctx, options);
74108
+ if (r.kind !== "fallthrough") return r;
74109
+ }
74110
+ break;
73781
74111
  }
73782
- break;
73783
- }
73784
- case "return" /* return */: {
73785
- const value = ins.pointer === null ? null : evalPointer(ins.pointer, scope, ctx);
73786
- return { kind: "return", value };
73787
- }
73788
- case "throw" /* throw */: {
73789
- const message = evalPointer(ins.pointer, scope, ctx);
73790
- throw new NSGetterRuntimeError(String(message));
73791
- }
73792
- case "assign" /* assign */: {
73793
- let value = evalPointer(ins.pointer, scope, ctx);
73794
- if (ins.target.typeInfo.type === 20 /* Decimal */ && typeof value === "number") {
73795
- value = coerceDecimalOperand(value, "assignment");
74112
+ case "return" /* return */: {
74113
+ const value = ins.pointer === null ? null : evalPointer(ins.pointer, scope, ctx);
74114
+ return { kind: "return", value };
73796
74115
  }
73797
- if (ins.target.pointer.type === "variable" /* variable */) {
73798
- assertVariableBindingWritable(scope, ins.target.pointer.variableId);
73799
- scope.setLocal(ins.target.pointer.variableId, value);
73800
- break;
74116
+ case "throw" /* throw */: {
74117
+ const message = evalPointer(ins.pointer, scope, ctx);
74118
+ throw new NSGetterRuntimeError(String(message));
73801
74119
  }
73802
- if (ins.target.writability === "setter" /* Setter */) {
73803
- dispatchNSSetter(
74120
+ case "assign" /* assign */: {
74121
+ let value = evalPointer(ins.pointer, scope, ctx);
74122
+ if (ins.target.typeInfo.type === 20 /* Decimal */ && typeof value === "number") {
74123
+ value = coerceDecimalOperand(value, "assignment");
74124
+ }
74125
+ if (ins.target.pointer.type === "variable" /* variable */) {
74126
+ assertVariableBindingWritable(scope, ins.target.pointer.variableId);
74127
+ scope.setLocal(ins.target.pointer.variableId, value);
74128
+ break;
74129
+ }
74130
+ if (ins.target.writability === "setter" /* Setter */) {
74131
+ dispatchNSSetter(
74132
+ ins.target.pointer,
74133
+ value,
74134
+ scope,
74135
+ ctx,
74136
+ options.writes
74137
+ );
74138
+ break;
74139
+ }
74140
+ const destination = validateAssignmentTarget(
73804
74141
  ins.target.pointer,
73805
- value,
73806
74142
  scope,
73807
- ctx,
73808
- options.writes
74143
+ ctx
73809
74144
  );
73810
- break;
73811
- }
73812
- const destination = validateAssignmentTarget(
73813
- ins.target.pointer,
73814
- scope,
73815
- ctx
73816
- );
73817
- const member = assignmentTargetMember(
73818
- ins.target.pointer,
73819
- scope,
73820
- ctx,
73821
- destination
73822
- );
73823
- const lookupAssignment = member !== null && isMemberLookupBase(member);
73824
- if (lookupAssignment) {
73825
- value = encodeLookupInitializerSelection(member, value, [], ctx);
73826
- }
73827
- let targetTypeInfo = ins.target.typeInfo;
73828
- if (lookupAssignment && targetTypeInfo.type !== 9 /* Lookup */ && targetTypeInfo.type !== 18 /* DialogueLookup */) {
73829
- targetTypeInfo = {
73830
- type: 9 /* Lookup */,
73831
- required: targetTypeInfo.required,
73832
- entryTypeInfo: targetTypeInfo,
73833
- collectionMemberId: member.collectionMemberId,
73834
- collectionValueId: member.collectionValueId
73835
- };
73836
- }
73837
- const assignedReferenceValueId = targetTypeInfo.type === 7 /* Class */ || targetTypeInfo.type === 6 /* List */ || targetTypeInfo.type === 5 /* Dictionary */ ? findKnownRowIdByValueReference(value, ctx) : null;
73838
- applyOverlayAssignment(
73839
- ins.target.pointer,
73840
- targetTypeInfo,
73841
- value,
73842
- scope,
73843
- ctx,
73844
- destination
73845
- );
73846
- const unorderedReplacement = assignedReferenceValueId === null && member !== null && isMemberListBase(member) && listKindOf(member) === 1 /* Unordered */ && Array.isArray(value) ? unorderedReplacementProjection(
73847
- ins.target.pointer,
73848
- destination,
73849
- scope,
73850
- ctx
73851
- ) : null;
73852
- options.writes.push({
73853
- kind: "assign",
73854
- target: targetTypeInfo === ins.target.typeInfo ? ins.target : { ...ins.target, typeInfo: targetTypeInfo },
73855
- value: unorderedReplacement ?? value,
73856
- ...targetTypeInfo.type === 7 /* Class */ || targetTypeInfo.type === 6 /* List */ || targetTypeInfo.type === 5 /* Dictionary */ ? {
73857
- valueId: value === null || value === void 0 ? void 0 : assignedReferenceValueId ?? void 0
73858
- } : {},
73859
- ...resolvedDestinationFieldsForAssign(
74145
+ const member = assignmentTargetMember(
73860
74146
  ins.target.pointer,
73861
74147
  scope,
73862
74148
  ctx,
73863
74149
  destination
73864
- )
73865
- });
73866
- break;
73867
- }
73868
- case "addActionListener" /* addActionListener */:
73869
- case "removeActionListener" /* removeActionListener */: {
73870
- evalActionListenerInstruction(ins, scope, ctx, options);
73871
- break;
73872
- }
73873
- case "addChangeListener" /* addChangeListener */:
73874
- case "removeChangeListener" /* removeChangeListener */: {
73875
- evalChangeListenerInstruction(ins, scope, ctx, options);
73876
- break;
73877
- }
73878
- case "collectionCall" /* collectionCall */: {
73879
- evalCollectionMutationInstruction(ins, scope, ctx, options);
73880
- break;
73881
- }
73882
- case "functionCall" /* functionCall */: {
73883
- evalPointer(ins.call, scope, ctx, "discarded");
73884
- break;
73885
- }
73886
- case "while" /* while */:
73887
- case "doWhile" /* doWhile */: {
73888
- let firstIteration = ins.type === "doWhile" /* doWhile */;
73889
- while (firstIteration || evalBooleanExpression(ins.condition, scope, ctx)) {
73890
- firstIteration = false;
73891
- consumeLoopIteration(ctx);
73892
- const result = evalInstructionsInChildScope(
73893
- ins.instructions,
74150
+ );
74151
+ const lookupAssignment = member !== null && isMemberLookupBase(member);
74152
+ if (lookupAssignment) {
74153
+ value = encodeLookupInitializerSelection(member, value, [], ctx);
74154
+ }
74155
+ let targetTypeInfo = ins.target.typeInfo;
74156
+ if (lookupAssignment && targetTypeInfo.type !== 9 /* Lookup */ && targetTypeInfo.type !== 18 /* DialogueLookup */) {
74157
+ targetTypeInfo = {
74158
+ type: 9 /* Lookup */,
74159
+ required: targetTypeInfo.required,
74160
+ entryTypeInfo: targetTypeInfo,
74161
+ collectionMemberId: member.collectionMemberId,
74162
+ collectionValueId: member.collectionValueId
74163
+ };
74164
+ }
74165
+ const assignedReferenceValueId = targetTypeInfo.type === 7 /* Class */ || targetTypeInfo.type === 6 /* List */ || targetTypeInfo.type === 5 /* Dictionary */ ? findKnownRowIdByValueReference(value, ctx) : null;
74166
+ applyOverlayAssignment(
74167
+ ins.target.pointer,
74168
+ targetTypeInfo,
74169
+ value,
73894
74170
  scope,
73895
74171
  ctx,
73896
- options
73897
- );
73898
- if (result.kind === "return") return result;
73899
- if (result.kind === "break") break;
73900
- }
73901
- break;
73902
- }
73903
- case "for" /* for */: {
73904
- const parentBindingIds = [...scope.keys()];
73905
- const loopScope = createChildScope(scope);
73906
- try {
73907
- const initialValue = coerceAssignmentValue(
73908
- evalPointer(ins.initializer.pointer, loopScope, ctx),
73909
- ins.initializer.typeInfo,
73910
- "variable initialization"
74172
+ destination
73911
74173
  );
73912
- loopScope.setLocal(ins.initializer.id, initialValue);
73913
- while (evalBooleanExpression(ins.condition, loopScope, ctx)) {
73914
- consumeLoopIteration(ctx);
73915
- const result = evalInstructionsInChildScope(
73916
- ins.instructions,
73917
- loopScope,
74174
+ const unorderedReplacement = assignedReferenceValueId === null && member !== null && isMemberListBase(member) && listKindOf(member) === 1 /* Unordered */ && Array.isArray(value) ? unorderedReplacementProjection(
74175
+ ins.target.pointer,
74176
+ destination,
74177
+ scope,
74178
+ ctx
74179
+ ) : null;
74180
+ options.writes.push({
74181
+ kind: "assign",
74182
+ target: targetTypeInfo === ins.target.typeInfo ? ins.target : { ...ins.target, typeInfo: targetTypeInfo },
74183
+ value: unorderedReplacement ?? value,
74184
+ ...targetTypeInfo.type === 7 /* Class */ || targetTypeInfo.type === 6 /* List */ || targetTypeInfo.type === 5 /* Dictionary */ ? {
74185
+ valueId: value === null || value === void 0 ? void 0 : assignedReferenceValueId ?? void 0
74186
+ } : {},
74187
+ ...resolvedDestinationFieldsForAssign(
74188
+ ins.target.pointer,
74189
+ scope,
73918
74190
  ctx,
73919
- options
73920
- );
73921
- if (result.kind === "return") return result;
73922
- if (result.kind === "break") break;
73923
- evalInstructions([ins.iterator], loopScope, ctx, options);
73924
- }
73925
- } finally {
73926
- synchronizeExistingBindings(scope, loopScope, parentBindingIds);
74191
+ destination
74192
+ )
74193
+ });
74194
+ break;
73927
74195
  }
73928
- break;
73929
- }
73930
- case "forEach" /* forEach */: {
73931
- const collection = evalPointer(ins.collectionPointer, scope, ctx);
73932
- const membership = snapshotCollectionMembership(collection, ctx);
73933
- const parentBindingIds = [...scope.keys()];
73934
- const loopScope = createChildScope(scope);
73935
- markReadonlyBinding(
73936
- loopScope,
73937
- ins.binding.id,
73938
- READONLY_FOREACH_BINDING_ERROR
73939
- );
73940
- try {
73941
- for (const rawEntry of membership) {
74196
+ case "addActionListener" /* addActionListener */:
74197
+ case "removeActionListener" /* removeActionListener */: {
74198
+ evalActionListenerInstruction(ins, scope, ctx, options);
74199
+ break;
74200
+ }
74201
+ case "addChangeListener" /* addChangeListener */:
74202
+ case "removeChangeListener" /* removeChangeListener */: {
74203
+ evalChangeListenerInstruction(ins, scope, ctx, options);
74204
+ break;
74205
+ }
74206
+ case "collectionCall" /* collectionCall */: {
74207
+ evalCollectionMutationInstruction(ins, scope, ctx, options);
74208
+ break;
74209
+ }
74210
+ case "functionCall" /* functionCall */: {
74211
+ evalPointer(ins.call, scope, ctx, "discarded");
74212
+ break;
74213
+ }
74214
+ case "while" /* while */:
74215
+ case "doWhile" /* doWhile */: {
74216
+ let firstIteration = ins.type === "doWhile" /* doWhile */;
74217
+ while (firstIteration || evalBooleanExpression(ins.condition, scope, ctx)) {
74218
+ firstIteration = false;
73942
74219
  consumeLoopIteration(ctx);
73943
- loopScope.setLocal(
73944
- ins.binding.id,
73945
- coerceAssignmentValue(
73946
- rawEntry.resolveAsReference ? resolveValueIfId(rawEntry.raw, ctx) : rawEntry.raw,
73947
- ins.binding.typeInfo,
73948
- "foreach binding"
73949
- )
73950
- );
73951
74220
  const result = evalInstructionsInChildScope(
73952
74221
  ins.instructions,
73953
- loopScope,
74222
+ scope,
73954
74223
  ctx,
73955
74224
  options
73956
74225
  );
73957
74226
  if (result.kind === "return") return result;
73958
74227
  if (result.kind === "break") break;
73959
74228
  }
73960
- } finally {
73961
- synchronizeExistingBindings(scope, loopScope, parentBindingIds);
73962
- }
73963
- break;
73964
- }
73965
- case "switch" /* switch */: {
73966
- validateSwitchInstruction(ins);
73967
- const selector = evalPointer(ins.selector, scope, ctx);
73968
- assertSwitchSelectorValue(selector, ins.selectorTypeInfo, ctx);
73969
- const matchedSection = ins.sections.find(
73970
- (section) => section.labels.some((label) => jsEqual(selector, label.value))
73971
- );
73972
- const selectedInstructions = matchedSection?.instructions ?? ins.defaultInstructions;
73973
- if (selectedInstructions === void 0 || selectedInstructions === null) {
73974
74229
  break;
73975
74230
  }
73976
- const parentBindingIds = [...scope.keys()];
73977
- const sectionScope = createChildScope(scope);
73978
- const result = (() => {
74231
+ case "for" /* for */: {
74232
+ const parentBindingIds = [...scope.keys()];
74233
+ const loopScope = createChildScope(scope);
73979
74234
  try {
73980
- return evalInstructions(
73981
- selectedInstructions,
73982
- sectionScope,
73983
- ctx,
73984
- options
74235
+ const initialValue = coerceAssignmentValue(
74236
+ evalPointer(ins.initializer.pointer, loopScope, ctx),
74237
+ ins.initializer.typeInfo,
74238
+ "variable initialization"
73985
74239
  );
74240
+ loopScope.setLocal(ins.initializer.id, initialValue);
74241
+ while (evalBooleanExpression(ins.condition, loopScope, ctx)) {
74242
+ consumeLoopIteration(ctx);
74243
+ const result = evalInstructionsInChildScope(
74244
+ ins.instructions,
74245
+ loopScope,
74246
+ ctx,
74247
+ options
74248
+ );
74249
+ if (result.kind === "return") return result;
74250
+ if (result.kind === "break") break;
74251
+ evalInstructions([ins.iterator], loopScope, ctx, options);
74252
+ }
73986
74253
  } finally {
73987
- synchronizeExistingBindings(scope, sectionScope, parentBindingIds);
74254
+ synchronizeExistingBindings(scope, loopScope, parentBindingIds);
73988
74255
  }
73989
- })();
73990
- if (result.kind === "break") break;
73991
- if (result.kind === "fallthrough") {
73992
- throw new CorruptNeoScriptIRError(
73993
- "Corrupt NeoScript switch IR: selected section reached its end."
73994
- );
74256
+ break;
73995
74257
  }
73996
- return result;
73997
- }
73998
- case "try" /* try */: {
73999
- validateTryInstruction(ins);
74000
- const parentBindingIds = [...scope.keys()];
74001
- const tryScope = createChildScope(scope);
74002
- let tryResult;
74003
- try {
74258
+ case "forEach" /* forEach */: {
74259
+ const collection = evalPointer(ins.collectionPointer, scope, ctx);
74260
+ const membership = snapshotCollectionMembership(collection, ctx);
74261
+ const parentBindingIds = [...scope.keys()];
74262
+ const loopScope = createChildScope(scope);
74263
+ markReadonlyBinding(
74264
+ loopScope,
74265
+ ins.binding.id,
74266
+ READONLY_FOREACH_BINDING_ERROR
74267
+ );
74004
74268
  try {
74005
- tryResult = evalInstructions(
74006
- ins.instructions,
74007
- tryScope,
74008
- ctx,
74009
- options
74010
- );
74269
+ for (const rawEntry of membership) {
74270
+ consumeLoopIteration(ctx);
74271
+ loopScope.setLocal(
74272
+ ins.binding.id,
74273
+ coerceAssignmentValue(
74274
+ rawEntry.resolveAsReference ? resolveValueIfId(rawEntry.raw, ctx) : rawEntry.raw,
74275
+ ins.binding.typeInfo,
74276
+ "foreach binding"
74277
+ )
74278
+ );
74279
+ const result = evalInstructionsInChildScope(
74280
+ ins.instructions,
74281
+ loopScope,
74282
+ ctx,
74283
+ options
74284
+ );
74285
+ if (result.kind === "return") return result;
74286
+ if (result.kind === "break") break;
74287
+ }
74011
74288
  } finally {
74012
- synchronizeExistingBindings(scope, tryScope, parentBindingIds);
74289
+ synchronizeExistingBindings(scope, loopScope, parentBindingIds);
74013
74290
  }
74014
- } catch (error) {
74015
- if (!isCatchableNeoScriptRuntimeError(error)) throw error;
74016
- let matched = false;
74017
- for (const clause of ins.catches) {
74018
- const catchScope = createChildScope(scope);
74019
- catchScope.setLocal(clause.binding.id, error.message);
74020
- markReadonlyBinding(
74021
- catchScope,
74022
- clause.binding.id,
74023
- READONLY_CATCH_BINDING_ERROR
74024
- );
74025
- const filter = clause.filter;
74026
- let filterMatches = filter === null || filter === void 0;
74027
- if (filter !== null && filter !== void 0) {
74028
- try {
74029
- filterMatches = evalBooleanExpression(filter, catchScope, ctx);
74030
- } catch (filterError) {
74031
- if (!isCatchableNeoScriptRuntimeError(filterError)) {
74032
- synchronizeExistingBindings(
74033
- scope,
74034
- catchScope,
74035
- parentBindingIds
74036
- );
74037
- throw filterError;
74038
- }
74039
- filterMatches = false;
74040
- }
74041
- }
74042
- if (!filterMatches) {
74043
- synchronizeExistingBindings(scope, catchScope, parentBindingIds);
74044
- continue;
74291
+ break;
74292
+ }
74293
+ case "switch" /* switch */: {
74294
+ validateSwitchInstruction(ins);
74295
+ const selector = evalPointer(ins.selector, scope, ctx);
74296
+ assertSwitchSelectorValue(selector, ins.selectorTypeInfo, ctx);
74297
+ const matchedSection = ins.sections.find(
74298
+ (section) => section.labels.some((label) => jsEqual(selector, label.value))
74299
+ );
74300
+ const selectedInstructions = matchedSection?.instructions ?? ins.defaultInstructions;
74301
+ if (selectedInstructions === void 0 || selectedInstructions === null) {
74302
+ break;
74303
+ }
74304
+ const parentBindingIds = [...scope.keys()];
74305
+ const sectionScope = createChildScope(scope);
74306
+ const result = (() => {
74307
+ try {
74308
+ return evalInstructions(
74309
+ selectedInstructions,
74310
+ sectionScope,
74311
+ ctx,
74312
+ options
74313
+ );
74314
+ } finally {
74315
+ synchronizeExistingBindings(
74316
+ scope,
74317
+ sectionScope,
74318
+ parentBindingIds
74319
+ );
74045
74320
  }
74046
- matched = true;
74047
- let catchResult;
74321
+ })();
74322
+ if (result.kind === "break") break;
74323
+ if (result.kind === "fallthrough") {
74324
+ throw new CorruptNeoScriptIRError(
74325
+ "Corrupt NeoScript switch IR: selected section reached its end."
74326
+ );
74327
+ }
74328
+ return result;
74329
+ }
74330
+ case "try" /* try */: {
74331
+ validateTryInstruction(ins);
74332
+ const parentBindingIds = [...scope.keys()];
74333
+ const tryScope = createChildScope(scope);
74334
+ let tryResult;
74335
+ try {
74048
74336
  try {
74049
- catchResult = evalInstructions(
74050
- clause.instructions,
74051
- catchScope,
74337
+ tryResult = evalInstructions(
74338
+ ins.instructions,
74339
+ tryScope,
74052
74340
  ctx,
74053
74341
  options
74054
74342
  );
74055
74343
  } finally {
74056
- synchronizeExistingBindings(scope, catchScope, parentBindingIds);
74344
+ synchronizeExistingBindings(scope, tryScope, parentBindingIds);
74345
+ }
74346
+ } catch (error) {
74347
+ if (!isCatchableNeoScriptRuntimeError(error)) throw error;
74348
+ let matched = false;
74349
+ for (const clause of ins.catches) {
74350
+ const catchScope = createChildScope(scope);
74351
+ catchScope.setLocal(clause.binding.id, error.message);
74352
+ markReadonlyBinding(
74353
+ catchScope,
74354
+ clause.binding.id,
74355
+ READONLY_CATCH_BINDING_ERROR
74356
+ );
74357
+ const filter = clause.filter;
74358
+ let filterMatches = filter === null || filter === void 0;
74359
+ if (filter !== null && filter !== void 0) {
74360
+ try {
74361
+ filterMatches = evalBooleanExpression(
74362
+ filter,
74363
+ catchScope,
74364
+ ctx
74365
+ );
74366
+ } catch (filterError) {
74367
+ if (!isCatchableNeoScriptRuntimeError(filterError)) {
74368
+ synchronizeExistingBindings(
74369
+ scope,
74370
+ catchScope,
74371
+ parentBindingIds
74372
+ );
74373
+ throw filterError;
74374
+ }
74375
+ filterMatches = false;
74376
+ }
74377
+ }
74378
+ if (!filterMatches) {
74379
+ synchronizeExistingBindings(
74380
+ scope,
74381
+ catchScope,
74382
+ parentBindingIds
74383
+ );
74384
+ continue;
74385
+ }
74386
+ matched = true;
74387
+ let catchResult;
74388
+ try {
74389
+ catchResult = evalInstructions(
74390
+ clause.instructions,
74391
+ catchScope,
74392
+ ctx,
74393
+ options
74394
+ );
74395
+ } finally {
74396
+ synchronizeExistingBindings(
74397
+ scope,
74398
+ catchScope,
74399
+ parentBindingIds
74400
+ );
74401
+ }
74402
+ if (catchResult.kind !== "fallthrough") return catchResult;
74403
+ break;
74057
74404
  }
74058
- if (catchResult.kind !== "fallthrough") return catchResult;
74405
+ if (!matched) throw error;
74059
74406
  break;
74060
74407
  }
74061
- if (!matched) throw error;
74408
+ if (tryResult.kind !== "fallthrough") return tryResult;
74062
74409
  break;
74063
74410
  }
74064
- if (tryResult.kind !== "fallthrough") return tryResult;
74065
- break;
74411
+ case "break" /* break */:
74412
+ return { kind: "break" };
74413
+ case "continue" /* continue */:
74414
+ return { kind: "continue" };
74415
+ }
74416
+ void touchUnknown(ins);
74417
+ } catch (error) {
74418
+ if (error instanceof NSGetterRuntimeError) {
74419
+ error.at(ins);
74420
+ if (ins.type === "assign" /* assign */ && ins.target.writability === "setter" /* Setter */)
74421
+ error.callerNode = ins;
74066
74422
  }
74067
- case "break" /* break */:
74068
- return { kind: "break" };
74069
- case "continue" /* continue */:
74070
- return { kind: "continue" };
74423
+ throw error;
74071
74424
  }
74072
- void touchUnknown(ins);
74073
74425
  }
74074
74426
  return { kind: "fallthrough" };
74075
74427
  }
@@ -74538,7 +74890,7 @@ function validateAssignmentTarget(target, scope, ctx, destination) {
74538
74890
  );
74539
74891
  }
74540
74892
  if (index < 0 || index >= receiver.length) {
74541
- throw new NSGetterRuntimeError(`List index out of bounds: ${index}`);
74893
+ throw new NSGetterRuntimeError(listIndexError(index, receiver.length));
74542
74894
  }
74543
74895
  }
74544
74896
  return resolved;
@@ -74644,15 +74996,21 @@ function dispatchNSSetter(target, value, scope, ctx, writes) {
74644
74996
  }
74645
74997
  const innerStack = new Set(stack);
74646
74998
  innerStack.add(memberId);
74647
- evaluateNSSetter(
74648
- setter,
74649
- {
74650
- ...bindThisValue(ctx, receiver),
74651
- __setterCallStack: innerStack
74652
- },
74653
- value,
74654
- writes
74655
- );
74999
+ try {
75000
+ evaluateNSSetter(
75001
+ setter,
75002
+ {
75003
+ ...bindThisValue(ctx, receiver),
75004
+ __setterCallStack: innerStack
75005
+ },
75006
+ value,
75007
+ writes
75008
+ );
75009
+ } catch (error) {
75010
+ if (error instanceof NSGetterRuntimeError)
75011
+ error.sourceOwnerId ??= compiledBodyOwnerId(memberId, setter, ctx);
75012
+ throw error;
75013
+ }
74656
75014
  }
74657
75015
  function resolveEffectiveNSPropertyMemberId(staticMemberId, receiver, ctx) {
74658
75016
  const schemaKey = cachedSchemaKeyForMember(staticMemberId, ctx);
@@ -74966,7 +75324,7 @@ function applyLocalCollectionMutation(targetValue, mutation, args, ctx, unique =
74966
75324
  const list = listArg(targetValue, "RemoveAt target");
74967
75325
  const index = numberArg(args[0], "RemoveAt index");
74968
75326
  if (index < 0 || index >= list.length) {
74969
- throw new NSGetterRuntimeError(`List index out of bounds: ${index}`);
75327
+ throw new NSGetterRuntimeError(listIndexError(index, list.length));
74970
75328
  }
74971
75329
  list.splice(index, 1);
74972
75330
  return index;
@@ -75036,7 +75394,7 @@ function validateCollectionMutation(targetValue, mutation, args) {
75036
75394
  );
75037
75395
  if (index < 0 || index > list.length) {
75038
75396
  throw new NSGetterRuntimeError(
75039
- `List insertion index out of bounds: ${index}`
75397
+ `List insertion index out of bounds: ${index}; use an index from 0 to ${list.length}.`
75040
75398
  );
75041
75399
  }
75042
75400
  return;
@@ -75045,7 +75403,7 @@ function validateCollectionMutation(targetValue, mutation, args) {
75045
75403
  const list = listArg(targetValue, "RemoveAt target");
75046
75404
  const index = numberArg(args[0], "RemoveAt index");
75047
75405
  if (index < 0 || index >= list.length) {
75048
- throw new NSGetterRuntimeError(`List index out of bounds: ${index}`);
75406
+ throw new NSGetterRuntimeError(listIndexError(index, list.length));
75049
75407
  }
75050
75408
  return;
75051
75409
  }
@@ -75069,6 +75427,11 @@ function validateCollectionMutation(targetValue, mutation, args) {
75069
75427
  }
75070
75428
  listArg(targetValue, `${mutation} target`);
75071
75429
  }
75430
+ function listIndexError(index, length) {
75431
+ if (length === 0)
75432
+ return `List index out of bounds: ${String(index)}; the list is empty. Add an entry before indexing it.`;
75433
+ return `List index out of bounds: ${String(index)}; list length is ${length}. Use an index from 0 to ${length - 1}.`;
75434
+ }
75072
75435
  function listArg(value, name) {
75073
75436
  if (Array.isArray(value)) return value;
75074
75437
  throw new NSGetterRuntimeError(`${name} must be a list`);
@@ -75165,6 +75528,17 @@ function evalStaticMember(memberId, ctx) {
75165
75528
  return shouldUnwrapSingleLookupValue(member) ? unwrapSingleLookupValue(readRowValue(row, ctx), ctx) : resolveLocalizedRowValueForMember(row, member, ctx);
75166
75529
  }
75167
75530
  function evalPointer(pointer, scope, ctx, resultUsage = "value") {
75531
+ try {
75532
+ return evalPointerCore(pointer, scope, ctx, resultUsage);
75533
+ } catch (error) {
75534
+ if (error instanceof NSGetterRuntimeError) {
75535
+ error.at(pointer);
75536
+ error.callerNode = pointer;
75537
+ }
75538
+ throw error;
75539
+ }
75540
+ }
75541
+ function evalPointerCore(pointer, scope, ctx, resultUsage = "value") {
75168
75542
  switch (pointer.type) {
75169
75543
  case "value" /* value */: {
75170
75544
  const value = pointer.value.value;
@@ -75262,7 +75636,7 @@ function evalPointer(pointer, scope, ctx, resultUsage = "value") {
75262
75636
  const v = evalPointer(pointer.pointer, scope, ctx);
75263
75637
  if (v === null || v === void 0) {
75264
75638
  throw new NSGetterRuntimeError(
75265
- "Unexpectedly found null while force-unwrapping a value"
75639
+ "Cannot unwrap null with '!'. Check for null first or provide a fallback with '??'."
75266
75640
  );
75267
75641
  }
75268
75642
  return v;
@@ -75416,6 +75790,10 @@ function evalPointer(pointer, scope, ctx, resultUsage = "value") {
75416
75790
  runtimeSignature.returnTypeInfo.type === NS_TYPE_VOID,
75417
75791
  runtimeSignature
75418
75792
  );
75793
+ } catch (error) {
75794
+ if (error instanceof NSGetterRuntimeError)
75795
+ error.sourceOwnerId ??= compiledBodyOwnerId(member.id, action, ctx);
75796
+ throw error;
75419
75797
  } finally {
75420
75798
  state.functionStack.pop();
75421
75799
  }
@@ -75701,13 +76079,19 @@ function invokeDelegateValue(value, args, ctx, callSiteId, ownerReceiver, result
75701
76079
  );
75702
76080
  }
75703
76081
  const runtimeSignature = receiver === null ? signature : substituteCallableSignatureForReceiver(signature, receiver, ctx);
75704
- return executeCompiledFunction(
75705
- action,
75706
- bindThisValue(ctx, receiver),
75707
- args,
75708
- runtimeSignature.returnTypeInfo.type === NS_TYPE_VOID,
75709
- runtimeSignature
75710
- );
76082
+ try {
76083
+ return executeCompiledFunction(
76084
+ action,
76085
+ bindThisValue(ctx, receiver),
76086
+ args,
76087
+ runtimeSignature.returnTypeInfo.type === NS_TYPE_VOID,
76088
+ runtimeSignature
76089
+ );
76090
+ } catch (error) {
76091
+ if (error instanceof NSGetterRuntimeError)
76092
+ error.sourceOwnerId ??= compiledBodyOwnerId(member.id, action, ctx);
76093
+ throw error;
76094
+ }
75711
76095
  }
75712
76096
  if (member.kind === 25 /* NSDelegate */) {
75713
76097
  let nested;
@@ -76285,6 +76669,14 @@ function runtimeTypeInfoForMember(rawMember, env, ctx, seen) {
76285
76669
  required: member.kind === 0 /* Null */ ? false : isRequiredMember2(member)
76286
76670
  };
76287
76671
  }
76672
+ function compiledBodyOwnerId(memberId, body, ctx) {
76673
+ return walkExtendsMemberChain(
76674
+ memberId,
76675
+ ctx.vm.members,
76676
+ (member) => getField(member, "action") === body || getField(member, "getter") === body || getField(member, "setter") === body ? member.id : void 0,
76677
+ { memberById: (id2) => evalMemberById(ctx.vm, id2) }
76678
+ ) ?? memberId;
76679
+ }
76288
76680
  function resolveCompiledNSFunction(memberId, ctx) {
76289
76681
  const cache = evaluatorResolutionCache(ctx).compiledFunctionByMemberId;
76290
76682
  if (cache.has(memberId)) return cache.get(memberId) ?? null;
@@ -76421,15 +76813,21 @@ function dispatchNSGetterById(memberId, receiver, ctx) {
76421
76813
  }
76422
76814
  const innerStack = new Set(stack);
76423
76815
  innerStack.add(memberId);
76424
- return evaluateNSGetterWithEffects(
76425
- getter,
76426
- {
76427
- ...bindThisValue(ctx, receiver),
76428
- __getterCallStack: innerStack
76429
- },
76430
- [],
76431
- { sessionValueExport: "defer" }
76432
- ).value;
76816
+ try {
76817
+ return evaluateNSGetterWithEffects(
76818
+ getter,
76819
+ {
76820
+ ...bindThisValue(ctx, receiver),
76821
+ __getterCallStack: innerStack
76822
+ },
76823
+ [],
76824
+ { sessionValueExport: "defer" }
76825
+ ).value;
76826
+ } catch (error) {
76827
+ if (error instanceof NSGetterRuntimeError)
76828
+ error.sourceOwnerId ??= compiledBodyOwnerId(memberId, getter, ctx);
76829
+ throw error;
76830
+ }
76433
76831
  }
76434
76832
  function resolveCompiledGetter(memberId, ctx) {
76435
76833
  const cache = evaluatorResolutionCache(ctx).compiledGetterByMemberId;
@@ -76648,10 +77046,12 @@ function evalKeyOf(keyOf, scope, ctx, optional2 = false, memberId, onDestination
76648
77046
  );
76649
77047
  }
76650
77048
  const idx = typeof key === "number" ? key : NaN;
76651
- if (!Number.isInteger(idx) || idx < 0 || idx >= receiver.length) {
77049
+ if (!Number.isInteger(idx))
76652
77050
  throw new NSGetterRuntimeError(
76653
- `List index out of bounds: ${String(key)}`
77051
+ `List index must be an integer; received ${String(key)}.`
76654
77052
  );
77053
+ if (idx < 0 || idx >= receiver.length) {
77054
+ throw new NSGetterRuntimeError(listIndexError(key, receiver.length));
76655
77055
  }
76656
77056
  return resolveCollectionEntry(receiver, receiver[idx], idx, ctx);
76657
77057
  }
@@ -76682,7 +77082,9 @@ function evalKeyOf(keyOf, scope, ctx, optional2 = false, memberId, onDestination
76682
77082
  if (expanded !== null && Object.prototype.hasOwnProperty.call(expanded, k)) {
76683
77083
  return resolveValueIfId(expanded[k], ctx);
76684
77084
  }
76685
- throw new NSGetterRuntimeError(`Missing key '${k}' on object`);
77085
+ throw new NSGetterRuntimeError(
77086
+ `Missing key '${k}' on object. Check that the key exists before reading it.`
77087
+ );
76686
77088
  }
76687
77089
  throw new NSGetterRuntimeError(
76688
77090
  `Cannot index into ${typeof receiver} with key '${String(key)}'`
@@ -78535,7 +78937,7 @@ function evalFunction(fn, scope, ctx) {
78535
78937
  if (found !== sentinel) return found;
78536
78938
  if (fn.type === "first" /* first */) {
78537
78939
  throw new NSGetterRuntimeError(
78538
- innerFn ? "First() found no matching entry" : "First() called on an empty collection"
78940
+ innerFn ? "First() found no matching entry. Use FirstOrDefault() and handle null if no match is expected." : "First() called on an empty collection. Check that it has entries, or use FirstOrDefault() and handle null."
78539
78941
  );
78540
78942
  }
78541
78943
  return null;
@@ -79596,12 +79998,18 @@ function runDeclaredConstructorChain(args) {
79596
79998
  });
79597
79999
  }
79598
80000
  args.applyBaseInitializerBlock(record6, args.argumentValues, args.thisValue);
79599
- executeCompiledFunction(
79600
- record6.action,
79601
- bindThisValue(ctx, args.thisValue),
79602
- args.argumentValues,
79603
- true
79604
- );
80001
+ try {
80002
+ executeCompiledFunction(
80003
+ record6.action,
80004
+ bindThisValue(ctx, args.thisValue),
80005
+ args.argumentValues,
80006
+ true
80007
+ );
80008
+ } catch (error) {
80009
+ if (error instanceof NSGetterRuntimeError)
80010
+ error.sourceOwnerId ??= record6.id;
80011
+ throw error;
80012
+ }
79605
80013
  }
79606
80014
  function evaluateBaseInitializerFields(args) {
79607
80015
  const { record: record6, ctx } = args;
@@ -83055,15 +83463,27 @@ function evaluateMemberInitializer(args) {
83055
83463
  __constructedFieldSchemaKeysByValue: /* @__PURE__ */ new WeakMap(),
83056
83464
  __createdStorageKeyDeclarations: args.storageKeyDeclarations ?? /* @__PURE__ */ new Map()
83057
83465
  };
83058
- const result = evaluateNSGetterWithEffects(
83059
- compiled,
83060
- ctx,
83061
- args.argumentValues ?? [],
83062
- {
83063
- exportInitializerValues: true,
83064
- initializerResult: { member: args.member, genericEnv: args.genericEnv }
83466
+ const result = (() => {
83467
+ try {
83468
+ return evaluateNSGetterWithEffects(
83469
+ compiled,
83470
+ ctx,
83471
+ args.argumentValues ?? [],
83472
+ {
83473
+ exportInitializerValues: true,
83474
+ initializerResult: {
83475
+ member: args.member,
83476
+ genericEnv: args.genericEnv
83477
+ }
83478
+ }
83479
+ );
83480
+ } catch (error) {
83481
+ if (error instanceof NSGetterRuntimeError) {
83482
+ error.initializerValueId ??= args.sourceValueId ?? void 0;
83483
+ }
83484
+ throw error;
83065
83485
  }
83066
- );
83486
+ })();
83067
83487
  const created = result.createdSessionValues ?? [];
83068
83488
  if (isMemberLookupBase(args.member)) {
83069
83489
  return encodeLookupInitializerResult(
@@ -83106,6 +83526,7 @@ var evaluatorLookupsByDocument, headlessResolversByLookupKey, indexedEvaluatorLo
83106
83526
  var init_evaluateInitializer = __esm({
83107
83527
  "../src/runtime/neoscript/evaluateInitializer.ts"() {
83108
83528
  "use strict";
83529
+ init_NSGetterRuntimeError();
83109
83530
  init_declaration_default_source();
83110
83531
  init_member_change_listeners();
83111
83532
  init_initializer_dependencies();
@@ -87352,7 +87773,7 @@ function compileStrict(code, context) {
87352
87773
  if (cached !== void 0) {
87353
87774
  neoScriptBodyCompileCache.delete(cacheKey);
87354
87775
  neoScriptBodyCompileCache.set(cacheKey, cached);
87355
- return structuredClone(cached);
87776
+ return cloneWithRuntimeSources(cached);
87356
87777
  }
87357
87778
  const compiled = assertNeoScriptCompiles(
87358
87779
  {
@@ -87369,7 +87790,7 @@ function compileStrict(code, context) {
87369
87790
  "A standalone NeoScript compiler entry point returned property units."
87370
87791
  );
87371
87792
  }
87372
- neoScriptBodyCompileCache.set(cacheKey, structuredClone(compiled));
87793
+ neoScriptBodyCompileCache.set(cacheKey, cloneWithRuntimeSources(compiled));
87373
87794
  while (neoScriptBodyCompileCache.size > NEOSCRIPT_BODY_COMPILE_CACHE_LIMIT) {
87374
87795
  const oldest = neoScriptBodyCompileCache.keys().next().value;
87375
87796
  if (typeof oldest !== "string") break;
@@ -87422,6 +87843,7 @@ var NEOSCRIPT_BODY_COMPILE_CACHE_LIMIT, neoScriptBodyCompileCache, neoScriptProj
87422
87843
  var init_compile = __esm({
87423
87844
  "../src/database/neoscript/compile.ts"() {
87424
87845
  "use strict";
87846
+ init_runtime_source();
87425
87847
  init_compiler();
87426
87848
  init_strict_compile_error();
87427
87849
  init_project();
@@ -90561,9 +90983,9 @@ var init_project_record_migrations = __esm({
90561
90983
  });
90562
90984
 
90563
90985
  // ../src/database/member-default-reference-ids.ts
90564
- function collectCandidateValueIds(sources) {
90986
+ function collectCandidateValueIds(sources2) {
90565
90987
  const candidates = [];
90566
- for (const source of sources) {
90988
+ for (const source of sources2) {
90567
90989
  if (typeof source !== "object" || source === null || !("value" in source))
90568
90990
  continue;
90569
90991
  const value = source.value;
@@ -116860,9 +117282,9 @@ function liveReferenceSourcesByTarget(records2) {
116860
117282
  record6.recordKind,
116861
117283
  record6.data
116862
117284
  )) {
116863
- const sources = sourcesByTarget.get(target);
116864
- if (sources === void 0) sourcesByTarget.set(target, [key]);
116865
- else sources.push(key);
117285
+ const sources2 = sourcesByTarget.get(target);
117286
+ if (sources2 === void 0) sourcesByTarget.set(target, [key]);
117287
+ else sources2.push(key);
116866
117288
  }
116867
117289
  }
116868
117290
  return sourcesByTarget;
@@ -117255,6 +117677,116 @@ var init_schema_source_validation = __esm({
117255
117677
  }
117256
117678
  });
117257
117679
 
117680
+ // src/project-source/runtime-diagnostic.ts
117681
+ function initializerRuntimeDiagnostic(error, sites, texts, analysis) {
117682
+ if (error instanceof SchemaSourceError) return error;
117683
+ const valueId = error instanceof NSGetterRuntimeError ? error.initializerValueId : error instanceof StoredClosureCaptureError ? error.valueId : void 0;
117684
+ const site = valueId === void 0 ? void 0 : sites.get(valueId);
117685
+ let position = site === void 0 ? void 0 : composeReferenceSitePosition(site, texts);
117686
+ if (error instanceof NSGetterRuntimeError && error.source !== void 0) {
117687
+ const source = error.source;
117688
+ if (source.uri !== void 0 && texts.get(source.uri) === source.text) {
117689
+ position = { file: source.uri, line: source.line, column: source.column };
117690
+ } else {
117691
+ const bodyLex = lex(source.text);
117692
+ let tokens = bodyLex.tokens.filter(
117693
+ (token) => token.kind !== "comment" && token.kind !== "eof"
117694
+ );
117695
+ const targetOffset = bodyLex.source.offsetAt({
117696
+ line: source.line - 1,
117697
+ character: source.column - 1
117698
+ });
117699
+ if (tokens[0]?.text === "return" && tokens.at(-1)?.text === ";")
117700
+ tokens = tokens.slice(1, -1);
117701
+ const target = tokens.findIndex((token) => token.end > targetOffset);
117702
+ const matches = [];
117703
+ if (tokens.length > 0 && target >= 0) {
117704
+ const prefix = new Array(tokens.length).fill(0);
117705
+ for (let i2 = 1, j = 0; i2 < tokens.length; i2++) {
117706
+ while (j > 0 && tokens[i2].text !== tokens[j].text)
117707
+ j = prefix[j - 1];
117708
+ if (tokens[i2].text === tokens[j].text) j++;
117709
+ prefix[i2] = j;
117710
+ }
117711
+ for (const [file, text] of texts) {
117712
+ const fileLex = lex(text);
117713
+ const candidates = fileLex.tokens.filter(
117714
+ (token) => token.kind !== "comment" && token.kind !== "eof"
117715
+ );
117716
+ for (let i2 = 0, j = 0; i2 < candidates.length; i2++) {
117717
+ while (j > 0 && candidates[i2].text !== tokens[j].text)
117718
+ j = prefix[j - 1];
117719
+ if (candidates[i2].text === tokens[j].text) j++;
117720
+ if (j !== tokens.length) continue;
117721
+ const matched = candidates[i2 - j + 1 + target];
117722
+ const point = fileLex.source.positionAt(
117723
+ matched.start + Math.max(0, targetOffset - tokens[target].start)
117724
+ );
117725
+ matches.push({
117726
+ file,
117727
+ line: point.line + 1,
117728
+ column: point.character + 1
117729
+ });
117730
+ j = prefix[j - 1];
117731
+ }
117732
+ }
117733
+ }
117734
+ const owner = sourceOwnerLocation(analysis, error.sourceOwnerId);
117735
+ const scoped = owner === void 0 ? matches : matches.filter((match) => {
117736
+ if (match.file !== owner.uri) return false;
117737
+ const line = match.line - 1;
117738
+ const column = match.column - 1;
117739
+ return (line > owner.range.start.line || line === owner.range.start.line && column >= owner.range.start.character) && (line < owner.range.end.line || line === owner.range.end.line && column < owner.range.end.character);
117740
+ });
117741
+ if (scoped.length === 1) position = scoped[0];
117742
+ else if (matches.length === 1) position = matches[0];
117743
+ }
117744
+ }
117745
+ let message = String(error);
117746
+ if (error instanceof NSGetterRuntimeError) message = error.diagnosticMessage;
117747
+ else if (error instanceof Error) message = error.message;
117748
+ return new SchemaSourceError(
117749
+ position === void 0 && valueId !== void 0 ? `Initializer for value "${valueId}": ${message}` : message,
117750
+ position?.file ?? "<initializer-materialization>",
117751
+ position?.line ?? 1,
117752
+ position?.column ?? 1
117753
+ );
117754
+ }
117755
+ function sourceOwnerLocation(analysis, id2) {
117756
+ if (analysis === null || analysis === void 0 || id2 === void 0)
117757
+ return void 0;
117758
+ for (const declaration of analysis.schema.classes) {
117759
+ for (const member of declaration.members) {
117760
+ if (materializedId(
117761
+ member,
117762
+ "member",
117763
+ `${declaration.name}.${member.name}`
117764
+ ) === id2)
117765
+ return member.source;
117766
+ }
117767
+ for (const constructor2 of declaration.constructors) {
117768
+ if (declaredConstructorId2(declaration, constructor2) === id2)
117769
+ return constructor2.source;
117770
+ }
117771
+ }
117772
+ const body = analysis.compiledBodies.find(
117773
+ (candidate) => candidate.memberId === id2
117774
+ );
117775
+ return body === void 0 ? void 0 : { uri: body.uri, range: body.range };
117776
+ }
117777
+ var init_runtime_diagnostic = __esm({
117778
+ "src/project-source/runtime-diagnostic.ts"() {
117779
+ "use strict";
117780
+ init_lower_support();
117781
+ init_lower_constructors();
117782
+ init_NSGetterRuntimeError();
117783
+ init_lexer();
117784
+ init_source_diagnostics();
117785
+ init_value_sources();
117786
+ init_stored_closure_captures();
117787
+ }
117788
+ });
117789
+
117258
117790
  // src/project-source/listener-lowering.ts
117259
117791
  function lowerListenerBindings(args) {
117260
117792
  const failures = [];
@@ -123668,21 +124200,40 @@ function materializedInitializerReconciliationFailuresV4(args) {
123668
124200
  );
123669
124201
  }
123670
124202
  const replayRows = /* @__PURE__ */ new Map();
123671
- for (const [id2, row] of replayStoredConstructionV4({
123672
- records: records2,
123673
- document: replayDocument,
123674
- replayContext,
123675
- valueId: reconciliation.valueId,
123676
- code: reconciliation.code,
123677
- member: owner,
123678
- ...compilationProject === void 0 ? {} : { compilationProject }
123679
- })) {
123680
- if (!isMemberValue(row)) {
123681
- throw new Error(
123682
- `Construction replay produced invalid value row "${id2}".`
123683
- );
124203
+ try {
124204
+ for (const [id2, row] of replayStoredConstructionV4({
124205
+ records: records2,
124206
+ document: replayDocument,
124207
+ replayContext,
124208
+ valueId: reconciliation.valueId,
124209
+ code: reconciliation.code,
124210
+ member: owner,
124211
+ ...compilationProject === void 0 ? {} : { compilationProject }
124212
+ })) {
124213
+ if (!isMemberValue(row)) {
124214
+ throw new Error(
124215
+ `Construction replay produced invalid value row "${id2}".`
124216
+ );
124217
+ }
124218
+ replayRows.set(id2, row);
124219
+ }
124220
+ } catch (error) {
124221
+ if (error instanceof NSGetterRuntimeError && error.source === void 0 && args.forceRecompile !== true) {
124222
+ return materializedInitializerReconciliationFailuresV4({
124223
+ ...args,
124224
+ forceRecompile: true,
124225
+ useBuildCaches: false
124226
+ });
123684
124227
  }
123685
- replayRows.set(id2, row);
124228
+ failures.push(
124229
+ initializerRuntimeDiagnostic(
124230
+ error,
124231
+ /* @__PURE__ */ new Map([[reconciliation.valueId, reconciliation.site]]),
124232
+ args.sourceTextByUri,
124233
+ args.analysis
124234
+ )
124235
+ );
124236
+ continue;
123686
124237
  }
123687
124238
  assertPulledConstructorsCompiled(replayDocument, replayContext);
123688
124239
  currentRows ??= new Map(currentDocument.values.map((row) => [row.id, row]));
@@ -124086,6 +124637,8 @@ function variantOwnershipRootRecords(records2) {
124086
124637
  var init_local_initializer_materialization = __esm({
124087
124638
  "src/project-source/local-initializer-materialization.ts"() {
124088
124639
  "use strict";
124640
+ init_NSGetterRuntimeError();
124641
+ init_runtime_diagnostic();
124089
124642
  init_evaluateInitializer();
124090
124643
  init_stored_closure_captures();
124091
124644
  init_initializer_dependencies();
@@ -124408,6 +124961,11 @@ function computeWorkspaceStatus(workspace, options) {
124408
124961
  if (positioned.severity === "error") parseErrors.push(positioned);
124409
124962
  else parseWarnings.push(positioned);
124410
124963
  }
124964
+ if (analysis.diagnostics.some(
124965
+ (diagnostic2) => diagnostic2.code === "duplicate-project-id" && diagnostic2.severity === "error"
124966
+ )) {
124967
+ return refusedStatus(conflictedFiles, parseErrors, parseWarnings);
124968
+ }
124411
124969
  const rootDeclaration = analysis.configuration.globals.find(
124412
124970
  (global) => global.type.name === "Root"
124413
124971
  );
@@ -124808,11 +125366,21 @@ function computeWorkspaceStatus(workspace, options) {
124808
125366
  variantValueBindingsV4(manifest, projectAnalysisV4),
124809
125367
  { analysis: projectAnalysisV4, registry: valueLowerRegistry }
124810
125368
  );
124811
- const supplementalRecords = options.lowerSupplementalRecords(
124812
- loweringRecords,
124813
- projectAnalysisV4,
124814
- options.trustedPendingProjectFiles
124815
- );
125369
+ let supplementalRecords;
125370
+ try {
125371
+ supplementalRecords = options.lowerSupplementalRecords(
125372
+ loweringRecords,
125373
+ projectAnalysisV4,
125374
+ options.trustedPendingProjectFiles
125375
+ );
125376
+ } catch (error) {
125377
+ parseErrors.push(sourceDiagnosticMapper(projectAnalysisV4)(error));
125378
+ return refusedStatus(
125379
+ conflictedFiles,
125380
+ uniqueSourceErrors(parseErrors),
125381
+ parseWarnings
125382
+ );
125383
+ }
124816
125384
  resolveRootPathDialogueGroupCollectionValuesV4({
124817
125385
  records: supplementalRecords,
124818
125386
  manifest,
@@ -125258,6 +125826,7 @@ function computeWorkspaceStatus(workspace, options) {
125258
125826
  authoredValueSeeds,
125259
125827
  reconciliations: valueLowerRegistry.initializerReconciliations,
125260
125828
  manifest,
125829
+ analysis: projectAnalysisV4,
125261
125830
  forceRecompile: options.forceRecompile,
125262
125831
  useBuildCaches: options.useBuildCaches,
125263
125832
  sourceTextByUri: new Map(
@@ -125373,7 +125942,7 @@ function computeWorkspaceStatus(workspace, options) {
125373
125942
  )
125374
125943
  );
125375
125944
  }
125376
- if (options.skipInitializerMaterialization !== true) {
125945
+ if (options.skipInitializerMaterialization !== true && !parseErrors.some(isBlockingSchemaSourceError)) {
125377
125946
  try {
125378
125947
  materializeWorkspaceStatusInitializers(
125379
125948
  workspace,
@@ -125381,14 +125950,23 @@ function computeWorkspaceStatus(workspace, options) {
125381
125950
  options.forceRecompile
125382
125951
  );
125383
125952
  } catch (error) {
125384
- const site = error instanceof StoredClosureCaptureError ? valueLowerRegistry.initializerSites.get(error.valueId) : void 0;
125385
- const position = site === void 0 ? { file: "<initializer-materialization>", line: 1, column: 1 } : composeReferenceSitePosition(site, sourceTextByUri);
125953
+ if (error instanceof NSGetterRuntimeError && error.source === void 0 && options.forceRecompile !== true) {
125954
+ try {
125955
+ materializeWorkspaceStatusInitializers(
125956
+ workspace,
125957
+ { changes, authoredValueSeeds },
125958
+ true
125959
+ );
125960
+ } catch (replayedError) {
125961
+ error = replayedError;
125962
+ }
125963
+ }
125386
125964
  parseErrors.push(
125387
- new SchemaSourceError(
125388
- error instanceof Error ? error.message : String(error),
125389
- position.file,
125390
- position.line,
125391
- position.column
125965
+ initializerRuntimeDiagnostic(
125966
+ error,
125967
+ valueLowerRegistry.initializerSites,
125968
+ sourceTextByUri,
125969
+ projectAnalysisV4
125392
125970
  )
125393
125971
  );
125394
125972
  }
@@ -125409,7 +125987,10 @@ function computeWorkspaceStatus(workspace, options) {
125409
125987
  analysis: projectAnalysisV4
125410
125988
  }
125411
125989
  },
125412
- localValues: valueLowerRegistry.localValues?.placements(),
125990
+ localValues: [
125991
+ ...valueLowerRegistry.localValues?.placements() ?? [],
125992
+ ...projectFilePlacementsV4(projectAnalysisV4)
125993
+ ],
125413
125994
  changes,
125414
125995
  conflictedFiles,
125415
125996
  parseErrors: uniqueSourceErrors(positionedErrors),
@@ -126461,12 +127042,13 @@ var ANIMATION_DOCUMENT_RECORD_KINDS, ANIMATION_REPLAY_RECORD_KINDS, PROSPECTIVE_
126461
127042
  var init_workspace_status_core = __esm({
126462
127043
  "src/project-source/workspace-status-core.ts"() {
126463
127044
  "use strict";
127045
+ init_runtime_diagnostic();
127046
+ init_NSGetterRuntimeError();
126464
127047
  init_lower_support();
126465
127048
  init_listener_lowering();
126466
127049
  init_inheritance_flags_validation();
126467
127050
  init_schema_source_validation();
126468
127051
  init_members();
126469
- init_stored_closure_captures();
126470
127052
  init_collection_binding_materialization();
126471
127053
  init_localizable_member_value_writes();
126472
127054
  init_localized_instance_override_writes();
@@ -128877,14 +129459,14 @@ function indexInitializerAuthoredRowIds(analysis, declaredConstructors2, classes
128877
129459
  continue;
128878
129460
  }
128879
129461
  const label = `${sourceClass.name}.${declaration.name}`;
128880
- const sources = initializerSourcesRequiringEvaluation(
129462
+ const sources2 = initializerSourcesRequiringEvaluation(
128881
129463
  declaration.initializer,
128882
129464
  declaration.type,
128883
129465
  declaredConstructors2,
128884
129466
  classesByName,
128885
129467
  sourceClass.name
128886
129468
  );
128887
- for (const source of sources) indexAuthoredRowIds(index, source, label);
129469
+ for (const source of sources2) indexAuthoredRowIds(index, source, label);
128888
129470
  }
128889
129471
  }
128890
129472
  return index;
@@ -136509,7 +137091,11 @@ function emitProjectDocumentSourcesV4(records2, options = {}) {
136509
137091
  const supplementalFiles = emitSupplementalProjectSourcesV4(
136510
137092
  records2,
136511
137093
  options.binaryPaths,
136512
- { manifest, collectionValuePaths: indexes3.rootPathsByValueId }
137094
+ {
137095
+ manifest,
137096
+ collectionValuePaths: indexes3.rootPathsByValueId,
137097
+ localValues: options.localValues
137098
+ }
136513
137099
  );
136514
137100
  const materializedConstructors = new Map(
136515
137101
  staticValues.materializedConstructors
@@ -136665,14 +137251,14 @@ var init_project_documents = __esm({
136665
137251
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, renameSync as renameSync5, writeFileSync as writeFileSync6 } from "node:fs";
136666
137252
  import { createHash as createHash6 } from "node:crypto";
136667
137253
  import { dirname as dirname6, join as join5 } from "node:path";
136668
- function readProjectSourceAnalysisBuildCacheV4(root, sources) {
137254
+ function readProjectSourceAnalysisBuildCacheV4(root, sources2) {
136669
137255
  try {
136670
137256
  const parsed = JSON.parse(
136671
137257
  readFileSync6(join5(root, PROJECT_SOURCE_BUILD_CACHE_PATH), "utf8")
136672
137258
  );
136673
137259
  if (parsed === null || typeof parsed !== "object") return null;
136674
137260
  const cache = parsed;
136675
- if (cache.revision !== PROJECT_SOURCE_BUILD_CACHE_REVISION || cache.fingerprint !== projectSourceFingerprint(sources)) {
137261
+ if (cache.revision !== PROJECT_SOURCE_BUILD_CACHE_REVISION || cache.fingerprint !== projectSourceFingerprint(sources2)) {
136676
137262
  return null;
136677
137263
  }
136678
137264
  assertProjectSourceAnalysisV4(cache.analysis);
@@ -136681,12 +137267,12 @@ function readProjectSourceAnalysisBuildCacheV4(root, sources) {
136681
137267
  return null;
136682
137268
  }
136683
137269
  }
136684
- function writeProjectSourceAnalysisCacheV4(root, analysis, sources) {
137270
+ function writeProjectSourceAnalysisCacheV4(root, analysis, sources2) {
136685
137271
  const file = join5(root, PROJECT_SOURCE_ANALYSIS_V4_CACHE_PATH);
136686
137272
  mkdirSync6(dirname6(file), { recursive: true });
136687
137273
  writeFileSync6(file, `${JSON.stringify(analysis, null, 2)}
136688
137274
  `, "utf8");
136689
- if (sources === void 0) return;
137275
+ if (sources2 === void 0) return;
136690
137276
  const buildFile = join5(root, PROJECT_SOURCE_BUILD_CACHE_PATH);
136691
137277
  mkdirSync6(dirname6(buildFile), { recursive: true });
136692
137278
  const temporary = `${buildFile}.${process.pid}.tmp`;
@@ -136694,7 +137280,7 @@ function writeProjectSourceAnalysisCacheV4(root, analysis, sources) {
136694
137280
  temporary,
136695
137281
  `${JSON.stringify({
136696
137282
  revision: PROJECT_SOURCE_BUILD_CACHE_REVISION,
136697
- fingerprint: projectSourceFingerprint(sources),
137283
+ fingerprint: projectSourceFingerprint(sources2),
136698
137284
  analysis
136699
137285
  })}
136700
137286
  `,
@@ -136702,14 +137288,14 @@ function writeProjectSourceAnalysisCacheV4(root, analysis, sources) {
136702
137288
  );
136703
137289
  renameSync5(temporary, buildFile);
136704
137290
  }
136705
- function projectSourceFingerprint(sources) {
137291
+ function projectSourceFingerprint(sources2) {
136706
137292
  const hash = createHash6("sha256");
136707
137293
  hash.update(String(PROJECT_SOURCE_BUILD_CACHE_REVISION));
136708
137294
  hash.update("\0");
136709
137295
  hash.update(String(NEOSCRIPT_COMPILER_REVISION));
136710
137296
  hash.update("\0");
136711
137297
  hash.update(PROJECT_SCHEMA_CONTRACT.cliVersion);
136712
- for (const source of sources) {
137298
+ for (const source of sources2) {
136713
137299
  hash.update("\0");
136714
137300
  hash.update(source.kind);
136715
137301
  hash.update("\0");
@@ -136728,7 +137314,7 @@ var init_project_document_cache = __esm({
136728
137314
  init_registry();
136729
137315
  init_project_documents();
136730
137316
  PROJECT_SOURCE_BUILD_CACHE_PATH = ".neo/build/project-source-analysis-v4.json";
136731
- PROJECT_SOURCE_BUILD_CACHE_REVISION = 4;
137317
+ PROJECT_SOURCE_BUILD_CACHE_REVISION = 6;
136732
137318
  }
136733
137319
  });
136734
137320
 
@@ -136750,84 +137336,88 @@ function lowerProjectFileRegistrySourcesV4(root, state, analysis, options = {})
136750
137336
  const seenPaths = /* @__PURE__ */ new Map();
136751
137337
  const seenSymbols = /* @__PURE__ */ new Map();
136752
137338
  return declarations.map((declaration) => {
136753
- const normalizedPath = normalizeTrustedProjectBinaryPath(
136754
- declaration.path,
136755
- declaration.kind
136756
- );
136757
- const pathKey = normalizedPath.toLowerCase();
136758
- const previousPath = seenPaths.get(pathKey);
136759
- if (previousPath) {
136760
- throw new Error(
136761
- `Project binary path ${JSON.stringify(normalizedPath)} is declared by both ${previousPath.symbol} and ${declaration.symbol}.`
136762
- );
136763
- }
136764
- seenPaths.set(pathKey, declaration);
136765
- const symbolKey = `${declaration.kind}:${declaration.symbol.toLowerCase()}`;
136766
- const previousSymbol = seenSymbols.get(symbolKey);
136767
- if (previousSymbol) {
136768
- throw new Error(
136769
- `Project file symbol ${declaration.symbol} is declared more than once.`
136770
- );
136771
- }
136772
- seenSymbols.set(symbolKey, declaration);
136773
- const baseState = state[`project-file:${declaration.recordId}`];
136774
- const base = isObjectRecord2(baseState?.data) ? baseState.data : null;
136775
- const absolute = createWorkspaceSourcePathResolver(root)(normalizedPath);
136776
- const trustedPending = options.trustedPendingFiles?.get(
136777
- declaration.recordId
136778
- );
136779
- if (base === null && !existsSync5(absolute) && trustedPending === void 0) {
136780
- throw new Error(
136781
- `Pending project file ${declaration.symbol} is missing bytes at ${normalizedPath}.`
137339
+ try {
137340
+ const normalizedPath = normalizeTrustedProjectBinaryPath(
137341
+ declaration.path,
137342
+ declaration.kind
136782
137343
  );
136783
- }
136784
- const binary = existsSync5(absolute) ? inspectBinaryFile(absolute, normalizedPath) : trustedPending === void 0 ? null : {
136785
- path: normalizedPath,
136786
- kind: declaration.kind,
136787
- mimeType: trustedPending.mimeType,
136788
- byteLength: trustedPending.byteLength,
136789
- sha256: trustedPending.sha256
136790
- };
136791
- const now = Date.now();
136792
- const settingsField = declaration.kind === "image" ? "unityTextureSettings" : "unityAudioClipSettings";
136793
- const otherSettingsField = declaration.kind === "image" ? "unityAudioClipSettings" : "unityTextureSettings";
136794
- const baseSettings = base !== null && isObjectRecord2(base[settingsField]) ? base[settingsField] : null;
136795
- const importSettingsChanged = base === null || base.fileType !== declaration.kind || optionalString3(baseSettings?.templateId) !== declaration.templateId;
136796
- const next = {
136797
- ...base ?? {},
136798
- id: declaration.recordId,
136799
- name: declaration.persistedName,
136800
- fileType: declaration.kind,
136801
- ...base === null && binary ? {
136802
- mimeType: binary.mimeType,
136803
- byteLength: binary.byteLength,
136804
- contentSha256: binary.sha256
136805
- } : {},
136806
- [otherSettingsField]: null
136807
- };
136808
- if (base === null) {
136809
- Object.assign(next, {
136810
- projectId: "",
136811
- status: "pending-upload",
136812
- storageKey: "",
136813
- storageETag: null,
136814
- createdAt: now,
136815
- updatedAt: now
136816
- });
136817
- }
136818
- if (importSettingsChanged) {
136819
- next[settingsField] = projectFileUnityImportSettingsV4(
136820
- declaration.kind,
136821
- declaration.templateId
137344
+ const pathKey = normalizedPath.toLowerCase();
137345
+ const previousPath = seenPaths.get(pathKey);
137346
+ if (previousPath) {
137347
+ throw new Error(
137348
+ `Project binary path ${JSON.stringify(normalizedPath)} is declared by both ${previousPath.symbol} and ${declaration.symbol}.`
137349
+ );
137350
+ }
137351
+ seenPaths.set(pathKey, declaration);
137352
+ const symbolKey = `${declaration.kind}:${declaration.symbol.toLowerCase()}`;
137353
+ const previousSymbol = seenSymbols.get(symbolKey);
137354
+ if (previousSymbol) {
137355
+ throw new Error(
137356
+ `Project file symbol ${declaration.symbol} is declared more than once.`
137357
+ );
137358
+ }
137359
+ seenSymbols.set(symbolKey, declaration);
137360
+ const baseState = state[`project-file:${declaration.recordId}`];
137361
+ const base = isObjectRecord2(baseState?.data) ? baseState.data : null;
137362
+ const absolute = createWorkspaceSourcePathResolver(root)(normalizedPath);
137363
+ const trustedPending = options.trustedPendingFiles?.get(
137364
+ declaration.recordId
136822
137365
  );
137366
+ if (base === null && !existsSync5(absolute) && trustedPending === void 0) {
137367
+ throw new Error(
137368
+ `Pending project file ${declaration.symbol} is missing bytes at ${normalizedPath}. Add the file there or correct the declaration path.`
137369
+ );
137370
+ }
137371
+ const binary = existsSync5(absolute) ? inspectBinaryFile(absolute, normalizedPath) : trustedPending === void 0 ? null : {
137372
+ path: normalizedPath,
137373
+ kind: declaration.kind,
137374
+ mimeType: trustedPending.mimeType,
137375
+ byteLength: trustedPending.byteLength,
137376
+ sha256: trustedPending.sha256
137377
+ };
137378
+ const now = Date.now();
137379
+ const settingsField = declaration.kind === "image" ? "unityTextureSettings" : "unityAudioClipSettings";
137380
+ const otherSettingsField = declaration.kind === "image" ? "unityAudioClipSettings" : "unityTextureSettings";
137381
+ const baseSettings = base !== null && isObjectRecord2(base[settingsField]) ? base[settingsField] : null;
137382
+ const importSettingsChanged = base === null || base.fileType !== declaration.kind || optionalString3(baseSettings?.templateId) !== declaration.templateId;
137383
+ const next = {
137384
+ ...base ?? {},
137385
+ id: declaration.recordId,
137386
+ name: declaration.persistedName,
137387
+ fileType: declaration.kind,
137388
+ ...base === null && binary ? {
137389
+ mimeType: binary.mimeType,
137390
+ byteLength: binary.byteLength,
137391
+ contentSha256: binary.sha256
137392
+ } : {},
137393
+ [otherSettingsField]: null
137394
+ };
137395
+ if (base === null) {
137396
+ Object.assign(next, {
137397
+ projectId: "",
137398
+ status: "pending-upload",
137399
+ storageKey: "",
137400
+ storageETag: null,
137401
+ createdAt: now,
137402
+ updatedAt: now
137403
+ });
137404
+ }
137405
+ if (importSettingsChanged) {
137406
+ next[settingsField] = projectFileUnityImportSettingsV4(
137407
+ declaration.kind,
137408
+ declaration.templateId
137409
+ );
137410
+ }
137411
+ return {
137412
+ recordKind: "project-file",
137413
+ recordId: declaration.recordId,
137414
+ fileFields: next,
137415
+ file: declaration.sourceFile,
137416
+ line: declaration.sourceLine
137417
+ };
137418
+ } catch (error) {
137419
+ throw projectFileSourceError(error, declaration);
136823
137420
  }
136824
- return {
136825
- recordKind: "project-file",
136826
- recordId: declaration.recordId,
136827
- fileFields: next,
136828
- file: declaration.sourceFile,
136829
- line: declaration.sourceLine
136830
- };
136831
137421
  });
136832
137422
  }
136833
137423
  function lowerDiscoveredProjectBinariesV4(root, state, analysis) {
@@ -136839,7 +137429,7 @@ function lowerDiscoveredProjectBinariesV4(root, state, analysis) {
136839
137429
  return discoverProjectBinariesV4(
136840
137430
  root,
136841
137431
  explicit,
136842
- trackedProjectBinaryPaths(state)
137432
+ trackedProjectBinaryPaths(state, explicit)
136843
137433
  ).map((binary) => {
136844
137434
  const recordId = pendingDiscoveredFileId(binary.path);
136845
137435
  const templateId = binary.kind === "image" ? optionalString3(projectData.defaultTextureTemplateId) : optionalString3(projectData.defaultAudioClipTemplateId);
@@ -136908,7 +137498,7 @@ function inspectProjectBinaryStatusV4(root, state, analysis) {
136908
137498
  for (const binary of discoverProjectBinariesV4(
136909
137499
  root,
136910
137500
  explicit,
136911
- trackedProjectBinaryPaths(state)
137501
+ trackedProjectBinaryPaths(state, explicit)
136912
137502
  )) {
136913
137503
  result.push({
136914
137504
  fileId: pendingDiscoveredFileId(binary.path),
@@ -137027,9 +137617,10 @@ function normalizeSha256V4(value, owner) {
137027
137617
  }
137028
137618
  return value.toLowerCase();
137029
137619
  }
137030
- function trackedProjectBinaryPaths(state) {
137620
+ function trackedProjectBinaryPaths(state, explicit) {
137621
+ const declared = new Set(explicit.map((entry) => entry.recordId));
137031
137622
  return Object.values(state).flatMap(
137032
- (entry) => entry.recordKind === "project-file" && entry.projectBinary ? [entry.projectBinary.path] : []
137623
+ (entry) => entry.recordKind === "project-file" && entry.projectBinary && !declared.has(entry.recordId) ? [entry.projectBinary.path] : []
137033
137624
  );
137034
137625
  }
137035
137626
  function inspectBinaryFile(absolute, path) {
@@ -138060,6 +138651,218 @@ var init_status_output = __esm({
138060
138651
  }
138061
138652
  });
138062
138653
 
138654
+ // src/project-source/project-file-source-preservation.ts
138655
+ function applySourceEdits(source, edits, start = 0, end = source.length) {
138656
+ const chunks = [];
138657
+ let cursor = start;
138658
+ for (const edit of edits) {
138659
+ chunks.push(source.slice(cursor, edit.start), edit.text);
138660
+ cursor = edit.end;
138661
+ }
138662
+ chunks.push(source.slice(cursor, end));
138663
+ return chunks.join("");
138664
+ }
138665
+ function entryIdentity(entry) {
138666
+ if (entry.reference !== void 0) return `reference:${entry.reference}`;
138667
+ return entry.id === null ? null : `id:${entry.id}`;
138668
+ }
138669
+ function preserveProjectFileRegistrySource(existing, emitted, records2 = /* @__PURE__ */ new Map()) {
138670
+ if (!/\b(?:ImageRegistry|AudioClipRegistry)\b/.test(existing) || !/\b(?:ImageRegistry|AudioClipRegistry)\b/.test(emitted))
138671
+ return emitted;
138672
+ const templateIds = /* @__PURE__ */ new Map();
138673
+ for (const record6 of records2.values()) {
138674
+ if (!record6.deleted && (record6.recordKind === "unity-texture-template" || record6.recordKind === "unity-audio-clip-template") && isObjectRecord2(record6.data) && typeof record6.data.name === "string")
138675
+ templateIds.set(record6.data.name, record6.recordId);
138676
+ }
138677
+ const authoredDocument = parseNeoProjectSource(existing, "definition");
138678
+ const emittedDocument = parseNeoProjectSource(emitted, "definition");
138679
+ const authored = registries(authoredDocument, existing, templateIds);
138680
+ if (authored.length === 0) return emitted;
138681
+ const canonicalRegistries = registries(emittedDocument, emitted, templateIds);
138682
+ const authoredByName = new Map(
138683
+ authored.map((registry) => [registry.name, registry])
138684
+ );
138685
+ const canonicalNames = new Set(
138686
+ canonicalRegistries.map((registry) => registry.name)
138687
+ );
138688
+ const registryOnly = authoredDocument.declarations.length === authored.length && emittedDocument.declarations.length === canonicalRegistries.length && authored.every((registry) => canonicalNames.has(registry.name));
138689
+ const replacements = [];
138690
+ for (const canonical of canonicalRegistries) {
138691
+ const prior = authoredByName.get(canonical.name);
138692
+ if (prior === void 0) {
138693
+ if (registryOnly)
138694
+ replacements.push({
138695
+ start: existing.length,
138696
+ end: existing.length,
138697
+ text: "\n" + emitted.slice(canonical.start, canonical.end) + "\n"
138698
+ });
138699
+ continue;
138700
+ }
138701
+ const live = new Map(
138702
+ canonical.entries.map((entry) => [entryIdentity(entry), entry])
138703
+ );
138704
+ const bySymbol = new Map(
138705
+ canonical.entries.map((entry) => [entry.symbol, entry])
138706
+ );
138707
+ const edits = [];
138708
+ for (const entry of prior.entries) {
138709
+ const next = entryIdentity(entry) === null ? bySymbol.get(entry.symbol) : live.get(entryIdentity(entry));
138710
+ if (next !== void 0) live.delete(entryIdentity(next));
138711
+ if (next?.signature === entry.signature) {
138712
+ if (entry.id === null && next.id !== null) {
138713
+ const lineStart = existing.lastIndexOf("\n", entry.start - 1) + 1;
138714
+ const indentation = existing.slice(lineStart, entry.start).match(/^\s*/)?.[0] ?? "";
138715
+ const newline = existing.includes("\r\n") ? "\r\n" : "\n";
138716
+ edits.push({
138717
+ start: entry.start,
138718
+ end: entry.start,
138719
+ text: `@id(${JSON.stringify(next.id)})${newline}${indentation}`
138720
+ });
138721
+ }
138722
+ continue;
138723
+ }
138724
+ edits.push({
138725
+ start: entry.start,
138726
+ end: entry.end,
138727
+ text: next === void 0 ? "" : emitted.slice(next.start, next.end)
138728
+ });
138729
+ }
138730
+ if (live.size > 0) {
138731
+ const newline = existing.includes("\r\n") ? "\r\n" : "\n";
138732
+ const additions = [...live.values()].map(
138733
+ (entry) => emitted.slice(entry.start, entry.end).replace(/\r?\n/g, newline)
138734
+ ).join(newline + newline + " ");
138735
+ edits.push({
138736
+ start: prior.close,
138737
+ end: prior.close,
138738
+ text: ` ${additions}${newline}`
138739
+ });
138740
+ }
138741
+ const content = applySourceEdits(existing, edits, prior.start, prior.end);
138742
+ replacements.push({
138743
+ start: registryOnly ? prior.start : canonical.start,
138744
+ end: registryOnly ? prior.end : canonical.end,
138745
+ text: content
138746
+ });
138747
+ }
138748
+ if (registryOnly) emitted = existing;
138749
+ return applySourceEdits(
138750
+ emitted,
138751
+ replacements.sort((a, b) => a.start - b.start)
138752
+ );
138753
+ }
138754
+ function registries(document, source, templateIds) {
138755
+ if (document.diagnostics.some((diagnostic) => diagnostic.severity === "error"))
138756
+ return [];
138757
+ const result = [];
138758
+ for (const declaration of document.declarations) {
138759
+ if (declaration.kind !== "global" || !["ImageRegistry", "AudioClipRegistry"].includes(declaration.type.name))
138760
+ continue;
138761
+ const start = document.syntax.source.offsetAt(declaration.range.start);
138762
+ const end = document.syntax.source.offsetAt(declaration.range.end);
138763
+ const tokens = lex(source.slice(start, end)).tokens.filter(
138764
+ (token) => token.kind !== "comment" && token.kind !== "eof"
138765
+ );
138766
+ const open = tokens.findIndex((token) => token.text === "{");
138767
+ const close = tokens.findLast((token) => token.text === "}");
138768
+ if (open < 0 || close === void 0) continue;
138769
+ const entries = [];
138770
+ let first = open + 1;
138771
+ for (let index = first; index < tokens.length && tokens[index].start < close.start; index++) {
138772
+ if (tokens[index].text !== ";") continue;
138773
+ const entry = readEntry(
138774
+ tokens.slice(first, index + 1),
138775
+ start,
138776
+ templateIds
138777
+ );
138778
+ if (entry === null) return [];
138779
+ entries.push(entry);
138780
+ first = index + 1;
138781
+ }
138782
+ result.push({
138783
+ name: declaration.name,
138784
+ start,
138785
+ end,
138786
+ close: start + close.start,
138787
+ entries
138788
+ });
138789
+ }
138790
+ return result;
138791
+ }
138792
+ function readEntry(tokens, offset, templateIds) {
138793
+ const declaration = tokens.findIndex(
138794
+ (token) => token.text === "NeoImage" || token.text === "NeoAudioClip"
138795
+ );
138796
+ if (declaration < 0) return null;
138797
+ const idIndex = tokens.findIndex(
138798
+ (token, index) => token.text === "id" && tokens[index - 1]?.text === "@"
138799
+ );
138800
+ const id2 = idIndex < 0 ? null : tokens[idIndex + 2]?.value;
138801
+ if (id2 === void 0) return null;
138802
+ const initializer = tokens[declaration + 3];
138803
+ if (initializer !== void 0 && initializer.text !== "new") {
138804
+ if (tokens[declaration + 4]?.text !== ";") return null;
138805
+ return {
138806
+ id: null,
138807
+ reference: initializer.text,
138808
+ symbol: tokens[declaration + 1].text,
138809
+ start: offset + tokens[0].start,
138810
+ end: offset + tokens.at(-1).end,
138811
+ signature: JSON.stringify(
138812
+ tokens.slice(declaration).map((token) => token.text)
138813
+ )
138814
+ };
138815
+ }
138816
+ const path = tokens[declaration + 5]?.value;
138817
+ if (path === void 0) return null;
138818
+ let name = path.slice(path.lastIndexOf("/") + 1);
138819
+ let template = "null";
138820
+ const settingsIndex = tokens.findIndex(
138821
+ (token, index) => token.text === "settings" && tokens[index - 1]?.text === "@"
138822
+ );
138823
+ if (settingsIndex >= 0) {
138824
+ const settings = tokens.slice(settingsIndex + 1, declaration).map((token) => token.raw).join(" ");
138825
+ const expression = parseExpression(`new${settings}`);
138826
+ if (expression.kind !== "new") return null;
138827
+ for (let index = 0; index < expression.args.length; index++) {
138828
+ const argument2 = expression.args[index];
138829
+ if (expression.argumentNames?.[index] === "name" && argument2.kind === "litString")
138830
+ name = argument2.value;
138831
+ if (expression.argumentNames?.[index] === "template") {
138832
+ if (argument2.kind === "ident")
138833
+ template = templateIds.get(argument2.name) ?? argument2.name;
138834
+ else if (argument2.kind === "call" && argument2.callee.kind === "ident" && argument2.callee.name === "Reference") {
138835
+ const idArgument = argument2.args[argument2.argumentNames?.indexOf("id") ?? 0];
138836
+ if (idArgument?.kind !== "litString") return null;
138837
+ template = idArgument.value;
138838
+ } else if (argument2.kind !== "litNull") return null;
138839
+ }
138840
+ }
138841
+ }
138842
+ return {
138843
+ id: id2,
138844
+ symbol: tokens[declaration + 1].text,
138845
+ start: offset + tokens[0].start,
138846
+ end: offset + tokens.at(-1).end,
138847
+ signature: JSON.stringify([
138848
+ tokens[declaration].text,
138849
+ tokens[declaration + 1].text,
138850
+ path,
138851
+ name,
138852
+ template
138853
+ ])
138854
+ };
138855
+ }
138856
+ var init_project_file_source_preservation = __esm({
138857
+ "src/project-source/project-file-source-preservation.ts"() {
138858
+ "use strict";
138859
+ init_value_comparison();
138860
+ init_project_source_parser();
138861
+ init_lexer();
138862
+ init_executable_parser();
138863
+ }
138864
+ });
138865
+
138063
138866
  // src/project-source/source-layout.ts
138064
138867
  import { readFileSync as readFileSync10 } from "node:fs";
138065
138868
  import { relative as relative4, sep as sep4 } from "node:path";
@@ -138158,12 +138961,18 @@ function readWorkspaceLayoutStatusV4(workspace, document) {
138158
138961
  const content = readFileSync10(path, "utf8");
138159
138962
  if (neoProjectSourceKind(path, content) !== "definition") continue;
138160
138963
  const document2 = parseNeoProjectSource(content, "definition", path);
138161
- if (document2.declarations.some(
138162
- (declaration) => declaration.kind === "global" && isLocalValueGlobal(declaration)
138163
- )) {
138164
- throw new Error(
138165
- `Cannot recover local initializer placement before reset: ${error instanceof Error ? error.message : String(error)}`
138166
- );
138964
+ for (const declaration of document2.declarations) {
138965
+ if (declaration.kind !== "global") continue;
138966
+ if (isLocalValueGlobal(declaration)) {
138967
+ throw new Error(
138968
+ `Cannot recover local initializer placement before reset: ${error instanceof Error ? error.message : String(error)}`
138969
+ );
138970
+ }
138971
+ if (neoScriptRegistryReferenceType(declaration.type.name) !== null) {
138972
+ throw new Error(
138973
+ `Cannot recover project file declaration placement before reset: ${error instanceof Error ? error.message : String(error)}`
138974
+ );
138975
+ }
138167
138976
  }
138168
138977
  }
138169
138978
  return null;
@@ -140070,8 +140879,8 @@ async function download(data, fileId, client, workspace) {
140070
140879
  }
140071
140880
  return { bytes: response.bytes, sha256: computed };
140072
140881
  }
140073
- function committedProjectBinaryStateV4(previous, data) {
140074
- return isObjectRecord2(data) ? binaryState(previous.path, readSha256(data), data) : previous;
140882
+ function committedProjectBinaryStateV4(path, data) {
140883
+ return binaryState(path, readSha256(data), isObjectRecord2(data) ? data : {});
140075
140884
  }
140076
140885
  function binaryState(path, sha2562, data) {
140077
140886
  return {
@@ -151487,7 +152296,7 @@ function detachedReadExpression(ctx, member, schemaKey, saved, writeGuardMemberN
151487
152296
  "entryMemberId"
151488
152297
  );
151489
152298
  if (entryMember === null) return [];
151490
- const readEntry = detachedValueExpression(
152299
+ const readEntry2 = detachedValueExpression(
151491
152300
  ctx,
151492
152301
  entryMember,
151493
152302
  "entry",
@@ -151496,14 +152305,14 @@ function detachedReadExpression(ctx, member, schemaKey, saved, writeGuardMemberN
151496
152305
  genericEnv
151497
152306
  );
151498
152307
  const entryType = csharpValueType(ctx, entryMember, saved, genericEnv);
151499
- if (readEntry === null || entryType === null) return [];
152308
+ if (readEntry2 === null || entryType === null) return [];
151500
152309
  const guardArguments = writeGuardMemberName == null ? "" : `, () => ThrowIfReadOnly("${escapeCSharpString(writeGuardMemberName)}"), () => IsReadOnly`;
151501
152310
  const [viewType, view] = saved ? [
151502
152311
  `NeoList<${entryType}>`,
151503
- `DetachedList<${entryType}>("${key}", entry => ${readEntry}, (client, child) => ${childConverter(ctx, entryMember, true, writeGuardMemberName != null, genericEnv)}, item => ${serializeItemExpression(ctx, entryMember, "item")}${guardArguments})`
152312
+ `DetachedList<${entryType}>("${key}", entry => ${readEntry2}, (client, child) => ${childConverter(ctx, entryMember, true, writeGuardMemberName != null, genericEnv)}, item => ${serializeItemExpression(ctx, entryMember, "item")}${guardArguments})`
151504
152313
  ] : [
151505
152314
  `NeoReadOnlyList<${entryType}>`,
151506
- `DetachedReadOnlyList<${entryType}>("${key}", entry => ${readEntry}, (client, child) => ${childConverter(ctx, entryMember, false, false, genericEnv)})`
152315
+ `DetachedReadOnlyList<${entryType}>("${key}", entry => ${readEntry2}, (client, child) => ${childConverter(ctx, entryMember, false, false, genericEnv)})`
151507
152316
  ];
151508
152317
  return [
151509
152318
  `if (TryReadDetached("${key}", out _))`,
@@ -156312,15 +157121,19 @@ async function readExportSource(workspace, identityLedger, onPhase) {
156312
157121
  onPhase
156313
157122
  });
156314
157123
  const prepared = candidate.preparedLocal;
157124
+ const { status } = candidate;
157125
+ const declaredPaths = new Map(
157126
+ (status.binaryFiles ?? []).map((binary) => [binary.fileId, binary.path])
157127
+ );
156315
157128
  if (prepared === null) {
156316
157129
  return {
156317
157130
  records: workspace.state.records,
156318
157131
  localChanges: null,
156319
157132
  localFiles: {},
157133
+ declaredPaths,
156320
157134
  identityLedger: {}
156321
157135
  };
156322
157136
  }
156323
- const { status } = candidate;
156324
157137
  const downloads = (status.binaryChanges ?? []).filter(
156325
157138
  (binary) => binary.action === "download"
156326
157139
  );
@@ -156371,6 +157184,7 @@ async function readExportSource(workspace, identityLedger, onPhase) {
156371
157184
  records: records2,
156372
157185
  localChanges: status.changes.length + status.authoredValueSeeds.size,
156373
157186
  localFiles,
157187
+ declaredPaths,
156374
157188
  identityLedger: ledger
156375
157189
  };
156376
157190
  }
@@ -156403,7 +157217,8 @@ async function exportUnityProject(args) {
156403
157217
  const files = exportFiles(
156404
157218
  workspace,
156405
157219
  Object.keys(unityExport.files),
156406
- source.localFiles
157220
+ source.localFiles,
157221
+ source.declaredPaths
156407
157222
  );
156408
157223
  const recordHashes = {};
156409
157224
  for (const [key, record6] of Object.entries(records2)) {
@@ -156540,7 +157355,7 @@ function sortedRecords(records2) {
156540
157355
  function compareOrdinal(left, right) {
156541
157356
  return left < right ? -1 : left > right ? 1 : 0;
156542
157357
  }
156543
- function exportFiles(workspace, fileIds, localFiles) {
157358
+ function exportFiles(workspace, fileIds, localFiles, declaredPaths) {
156544
157359
  const resolvePath = createWorkspaceSourcePathResolver(workspace.root);
156545
157360
  const files = {};
156546
157361
  const failures = [];
@@ -156555,20 +157370,21 @@ function exportFiles(workspace, fileIds, localFiles) {
156555
157370
  failures.push(`${fileId}: the pull recorded no bytes for it`);
156556
157371
  continue;
156557
157372
  }
157373
+ const relativePath = declaredPaths.get(fileId) ?? binary.path;
156558
157374
  if (binary.sha256 === null) {
156559
- failures.push(`${binary.path}: the pull recorded no hash for it`);
157375
+ failures.push(`${relativePath}: the pull recorded no hash for it`);
156560
157376
  continue;
156561
157377
  }
156562
- const path = resolvePath(binary.path);
157378
+ const path = resolvePath(relativePath);
156563
157379
  if (!existsSync13(path)) {
156564
- failures.push(`${binary.path}: missing`);
157380
+ failures.push(`${relativePath}: missing`);
156565
157381
  continue;
156566
157382
  }
156567
157383
  if (sha256File(path) !== binary.sha256) {
156568
- failures.push(`${binary.path}: its bytes differ from the pulled file`);
157384
+ failures.push(`${relativePath}: its bytes differ from the pulled file`);
156569
157385
  continue;
156570
157386
  }
156571
- files[fileId] = { path: binary.path, sha256: binary.sha256 };
157387
+ files[fileId] = { path: relativePath, sha256: binary.sha256 };
156572
157388
  }
156573
157389
  if (failures.length > 0) {
156574
157390
  throw new Error(
@@ -158782,6 +159598,18 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
158782
159598
  data: local.fullData
158783
159599
  });
158784
159600
  }
159601
+ for (const binary of localStatus.binaryFiles ?? []) {
159602
+ const key = recordStateKey(
159603
+ "project-file",
159604
+ rewriteAssignedString(binary.fileId, replacements)
159605
+ );
159606
+ const record6 = nextRecords[key];
159607
+ if (record6 === void 0 || !changedKeys.has(key)) continue;
159608
+ nextRecords[key] = {
159609
+ ...record6,
159610
+ projectBinary: committedProjectBinaryStateV4(binary.path, record6.data)
159611
+ };
159612
+ }
158785
159613
  const previousState = workspace.state;
158786
159614
  workspace.state = {
158787
159615
  ...previousState,
@@ -159014,14 +159842,13 @@ function foldChangedRecords(previous, changedRecords) {
159014
159842
  `Changed record "${key}" response is missing its snapshot content hash.`
159015
159843
  );
159016
159844
  }
159017
- const projectBinary = records2[key]?.projectBinary;
159018
159845
  records2[key] = {
159019
159846
  recordKind,
159020
159847
  recordId,
159021
159848
  contentHash: snapshot.contentHash,
159022
159849
  data: changed.data,
159023
159850
  file: records2[key]?.file,
159024
- projectBinary: projectBinary && committedProjectBinaryStateV4(projectBinary, changed.data)
159851
+ projectBinary: records2[key]?.projectBinary
159025
159852
  };
159026
159853
  }
159027
159854
  return records2;
@@ -159100,15 +159927,20 @@ function rewriteFilesFromState(workspace, records2 = new Map(
159100
159927
  workspace.root,
159101
159928
  result.formAlternates
159102
159929
  );
159930
+ const authoredSources = new Map(
159931
+ sourceSnapshot?.inputs.map((input) => [input.uri, input.text])
159932
+ );
159103
159933
  const files = result.files.map((file) => {
159104
159934
  const preserved = preservedSourceFiles.get(file.path);
159105
159935
  if (preserved !== void 0 && preservedFileMatchesEmission(preserved, file.content)) {
159106
159936
  return { ...file, content: preserved };
159107
159937
  }
159938
+ const path = join17(workspace.root, file.path);
159939
+ const authored = preserved ?? authoredSources.get(file.path) ?? (existsSync15(path) ? readFileSync19(path, "utf8") : void 0);
159108
159940
  return {
159109
159941
  ...file,
159110
159942
  content: restoreAuthoredMemberForms(
159111
- file.content,
159943
+ authored === void 0 ? file.content : preserveProjectFileRegistrySource(authored, file.content, records2),
159112
159944
  authoredForms.get(file.path) ?? []
159113
159945
  )
159114
159946
  };
@@ -159994,6 +160826,7 @@ var compileNSVoidBody2, compileNSFunction2, compileNSGetter2, compileNSSetter2,
159994
160826
  var init_push = __esm({
159995
160827
  "src/commands/push.ts"() {
159996
160828
  "use strict";
160829
+ init_project_file_source_preservation();
159997
160830
  init_packed_value_parity();
159998
160831
  init_member_kinds();
159999
160832
  init_stored_value_placements();
@@ -160156,7 +160989,7 @@ async function fetchProjectDocument(workspace) {
160156
160989
  deleted: head.deleted
160157
160990
  });
160158
160991
  }
160159
- const sources = [
160992
+ const sources2 = [
160160
160993
  {
160161
160994
  recordKind: ProjectRecordKind.Project,
160162
160995
  entries: readRawSingleton(raw, "project"),
@@ -160288,7 +161121,7 @@ async function fetchProjectDocument(workspace) {
160288
161121
  }
160289
161122
  ];
160290
161123
  const records2 = /* @__PURE__ */ new Map();
160291
- for (const source of sources) {
161124
+ for (const source of sources2) {
160292
161125
  for (const entry of source.entries) {
160293
161126
  if (!source.guard(entry)) {
160294
161127
  throw new Error(
@@ -161412,8 +162245,11 @@ async function finishFormat4Pull(args) {
161412
162245
  const serverByPath = new Map(
161413
162246
  (serverResult?.files ?? []).map((file) => [file.path, file])
161414
162247
  );
161415
- const localRecordFiles = declaringSourcePathsV4(localResult);
161416
- const serverRecordFiles = serverResult === null ? /* @__PURE__ */ new Map() : declaringSourcePathsV4(serverResult);
162248
+ const registryPaths = localResult.analysis.files.registries.map(
162249
+ (registry) => registry.source.uri
162250
+ );
162251
+ const localRecordFiles = declaringSourcePathsV4(localResult, registryPaths);
162252
+ const serverRecordFiles = serverResult === null ? /* @__PURE__ */ new Map() : declaringSourcePathsV4(serverResult, registryPaths);
161417
162253
  const conflictPaths = /* @__PURE__ */ new Set();
161418
162254
  const conflictPathsByKey = /* @__PURE__ */ new Map();
161419
162255
  const authoredLocalConflictKeysByPath = /* @__PURE__ */ new Map();
@@ -161444,6 +162280,7 @@ async function finishFormat4Pull(args) {
161444
162280
  plans,
161445
162281
  localRecordFiles,
161446
162282
  serverRecordFiles,
162283
+ registryPaths,
161447
162284
  previousRecords: workspace.state.records,
161448
162285
  emittedPaths,
161449
162286
  mainLocale: workspaceMainLocale(workspace.state.records)
@@ -161464,7 +162301,14 @@ async function finishFormat4Pull(args) {
161464
162301
  )}: its prior source file ${JSON.stringify(path)} no longer exists.`
161465
162302
  );
161466
162303
  }
161467
- const local = unprojectedConflictKeys.length > 0 ? existing ?? void 0 : localByPath.get(path)?.content;
162304
+ const local = unprojectedConflictKeys.length > 0 ? existing ?? void 0 : (() => {
162305
+ const emitted = localByPath.get(path)?.content;
162306
+ return existing !== null && emitted !== void 0 ? preserveProjectFileRegistrySource(
162307
+ existing,
162308
+ emitted,
162309
+ localRecords
162310
+ ) : emitted;
162311
+ })();
161468
162312
  const server = serverByPath.get(path)?.content;
161469
162313
  const content = conflictPaths.has(path) ? renderConflictFileV4({
161470
162314
  localContent: local,
@@ -161743,7 +162587,7 @@ function cursorsEqual(left, right) {
161743
162587
  (transactionId, index) => transactionId === right.transactionIds[index]
161744
162588
  );
161745
162589
  }
161746
- function declaringSourcePathsV4(emission) {
162590
+ function declaringSourcePathsV4(emission, registryPaths) {
161747
162591
  const paths = /* @__PURE__ */ new Map();
161748
162592
  const add = (key, path) => {
161749
162593
  const existing = paths.get(key);
@@ -161757,6 +162601,12 @@ function declaringSourcePathsV4(emission) {
161757
162601
  for (const key of file.recordKeys) add(key, file.path);
161758
162602
  }
161759
162603
  for (const [key, path] of emission.recordFiles) add(key, path);
162604
+ for (const [key, declaring] of paths) {
162605
+ if (!key.startsWith("project-file:")) continue;
162606
+ for (const path of registryPaths) {
162607
+ if (!declaring.includes(path)) declaring.push(path);
162608
+ }
162609
+ }
161760
162610
  return paths;
161761
162611
  }
161762
162612
  function projectSourcePathsRequiringRewriteV4(args) {
@@ -161779,10 +162629,13 @@ function projectSourcePathsRequiringRewriteV4(args) {
161779
162629
  }
161780
162630
  }
161781
162631
  for (const [key, previous] of Object.entries(args.previousRecords)) {
161782
- if (previous.file === void 0) continue;
161783
- if (!args.emittedPaths.has(previous.file)) continue;
161784
162632
  if (args.localRecordFiles.has(key)) continue;
161785
162633
  if (!args.localStatus.reconstructed.has(key)) continue;
162634
+ if (previous.recordKind === "project-file") {
162635
+ for (const path of args.registryPaths) result.add(path);
162636
+ }
162637
+ if (previous.file === void 0) continue;
162638
+ if (!args.emittedPaths.has(previous.file)) continue;
161786
162639
  result.add(previous.file);
161787
162640
  }
161788
162641
  return result;
@@ -161849,6 +162702,7 @@ function composeProspectivePullRecord(recordKind, recordId, data, serverRecord)
161849
162702
  var init_pull = __esm({
161850
162703
  "src/commands/pull.ts"() {
161851
162704
  "use strict";
162705
+ init_project_file_source_preservation();
161852
162706
  init_push();
161853
162707
  init_source_layout();
161854
162708
  init_project_read_base();
@@ -170775,19 +171629,19 @@ function analyzeManifestScripts(manifest, options = {}) {
170775
171629
  ...options.rootMemberIds ? { rootMemberIds: options.rootMemberIds } : {},
170776
171630
  ...options.sourceUri ? { sourceUri: options.sourceUri } : {}
170777
171631
  });
170778
- const sources = /* @__PURE__ */ new Map();
171632
+ const sources2 = /* @__PURE__ */ new Map();
170779
171633
  const scriptPathByMemberId = /* @__PURE__ */ new Map();
170780
171634
  for (const member of manifest.members) {
170781
171635
  if (member.kind !== "computed" && member.kind !== "scriptFunction" || member.script === null) {
170782
171636
  continue;
170783
171637
  }
170784
171638
  const key = inlineScriptKey(member.script.path, member.id);
170785
- sources.set(key, member.script.sourceText);
171639
+ sources2.set(key, member.script.sourceText);
170786
171640
  scriptPathByMemberId.set(member.id, member.script.path);
170787
171641
  }
170788
171642
  const documents = /* @__PURE__ */ new Map();
170789
171643
  for (const [path, context] of projection.scripts) {
170790
- const source = sources.get(path);
171644
+ const source = sources2.get(path);
170791
171645
  if (source === void 0) continue;
170792
171646
  const uri = manifestScriptUri(path, options.scriptUri);
170793
171647
  documents.set(path, {
@@ -172938,6 +173792,7 @@ function assertMatcher(condition, expectationValue, messageKind, expected, recei
172938
173792
  }
172939
173793
  }
172940
173794
  function errorMessage2(error) {
173795
+ if (error instanceof NSGetterRuntimeError) return error.diagnosticMessage;
172941
173796
  return error instanceof Error ? error.message : String(error);
172942
173797
  }
172943
173798
  function findDelegateTargetMemberId(value) {
@@ -173177,11 +174032,19 @@ function compileSpec(workspace, compile, absolutePath, projectCompilationHash2)
173177
174032
  }
173178
174033
  } catch {
173179
174034
  }
173180
- const compiled = {
173181
- path,
173182
- source,
173183
- action: compile(`file://${absolutePath}`, source)
173184
- };
174035
+ let action;
174036
+ try {
174037
+ action = compile(`file://${absolutePath}`, source);
174038
+ } catch (error) {
174039
+ if (error instanceof CompileError)
174040
+ ERROR_SOURCE_POSITIONS.set(error, {
174041
+ file: path,
174042
+ line: error.line,
174043
+ column: error.column
174044
+ });
174045
+ throw error;
174046
+ }
174047
+ const compiled = { path, source, action };
173185
174048
  atomicWrite(
173186
174049
  artifactPath,
173187
174050
  `${JSON.stringify({
@@ -173313,7 +174176,7 @@ function callSitePosition(callSiteId) {
173313
174176
  }
173314
174177
  function portableDelegate(value) {
173315
174178
  try {
173316
- return structuredClone(value);
174179
+ return cloneWithRuntimeSources(value);
173317
174180
  } catch (error) {
173318
174181
  throw new NeoTestRegistrationError(
173319
174182
  `Test callback could not be captured deterministically: ${errorMessage2(error)}.`
@@ -173840,8 +174703,15 @@ function runCallback(environment, delegate, deadlineMs) {
173840
174703
  }
173841
174704
  }
173842
174705
  function failureFor(error, file, position, member = null) {
174706
+ let runtimePosition;
174707
+ if (error instanceof NSGetterRuntimeError) {
174708
+ const caller = error.callerNode === void 0 ? void 0 : runtimeSource(error.callerNode);
174709
+ runtimePosition = [error.source, caller].find(
174710
+ (source) => source?.uri?.endsWith(`/${file}`)
174711
+ );
174712
+ }
173843
174713
  const timeout = error instanceof NeoScriptWallClockTimeoutError;
173844
- const sourcePosition = error instanceof NativeFunctionDelegateUnavailableError && error.callSiteId !== void 0 ? callSitePosition(error.callSiteId) : error instanceof Error ? ERROR_SOURCE_POSITIONS.get(error) : void 0;
174714
+ const sourcePosition = error instanceof NativeFunctionDelegateUnavailableError && error.callSiteId !== void 0 ? callSitePosition(error.callSiteId) : error instanceof Error ? ERROR_SOURCE_POSITIONS.get(error) ?? runtimePosition : void 0;
173845
174715
  return {
173846
174716
  kind: error instanceof NeoTestAssertionError ? "assertion" : timeout ? "timeout" : "runtime",
173847
174717
  message: error instanceof NativeFunctionDelegateUnavailableError ? `${error.message} Configure mock.native(...) or mock.function(...) for this exact member before calling production code.` : errorMessage2(error),
@@ -173873,6 +174743,21 @@ async function executeTestFile(spec, document, selected, timeoutMs, interrupted
173873
174743
  );
173874
174744
  }
173875
174745
  async function executeRegisteredSpec(registered, document, selected, timeoutMs, interrupted) {
174746
+ let restoredRuntimeSources = false;
174747
+ const reportFailure = (error, position, member = null) => {
174748
+ if (error instanceof NSGetterRuntimeError && error.node !== void 0 && (error.source === void 0 || error.callerNode !== void 0 && runtimeSource(error.callerNode) === void 0)) {
174749
+ if (!restoredRuntimeSources) {
174750
+ const compiled = lazySpecCompiler(document)(
174751
+ `file:///${registered.spec.path}`,
174752
+ registered.spec.source
174753
+ );
174754
+ copyRuntimeSources(compiled, registered.spec.action);
174755
+ restoredRuntimeSources = true;
174756
+ }
174757
+ error.source ??= runtimeSource(error.node);
174758
+ }
174759
+ return failureFor(error, registered.spec.path, position, member);
174760
+ };
173876
174761
  const results = [];
173877
174762
  const fileFailures = [];
173878
174763
  let testDurationMs = 0;
@@ -173886,9 +174771,7 @@ async function executeRegisteredSpec(registered, document, selected, timeoutMs,
173886
174771
  try {
173887
174772
  runCallback(fixture, hook, suiteDeadline);
173888
174773
  } catch (error) {
173889
- suiteFailures.push(
173890
- failureFor(error, registered.spec.path, suite, "beforeAll")
173891
- );
174774
+ suiteFailures.push(reportFailure(error, suite, "beforeAll"));
173892
174775
  } finally {
173893
174776
  testDurationMs += performance.now() - hookStarted;
173894
174777
  }
@@ -173908,9 +174791,7 @@ async function executeRegisteredSpec(registered, document, selected, timeoutMs,
173908
174791
  try {
173909
174792
  runCallback(environment, hook, deadline);
173910
174793
  } catch (error) {
173911
- failures.push(
173912
- failureFor(error, registered.spec.path, test, "beforeEach")
173913
- );
174794
+ failures.push(reportFailure(error, test, "beforeEach"));
173914
174795
  setupFailed = true;
173915
174796
  break;
173916
174797
  }
@@ -173922,7 +174803,7 @@ async function executeRegisteredSpec(registered, document, selected, timeoutMs,
173922
174803
  try {
173923
174804
  runCallback(environment, test.body, deadline);
173924
174805
  } catch (error) {
173925
- failures.push(failureFor(error, registered.spec.path, test));
174806
+ failures.push(reportFailure(error, test));
173926
174807
  }
173927
174808
  }
173928
174809
  for (const current of [...test.suites].reverse()) {
@@ -173930,9 +174811,7 @@ async function executeRegisteredSpec(registered, document, selected, timeoutMs,
173930
174811
  try {
173931
174812
  runCallback(environment, hook, Date.now() + timeoutMs);
173932
174813
  } catch (error) {
173933
- failures.push(
173934
- failureFor(error, registered.spec.path, test, "afterEach")
173935
- );
174814
+ failures.push(reportFailure(error, test, "afterEach"));
173936
174815
  }
173937
174816
  }
173938
174817
  }
@@ -173965,9 +174844,7 @@ async function executeRegisteredSpec(registered, document, selected, timeoutMs,
173965
174844
  try {
173966
174845
  runCallback(fixture, hook, Date.now() + timeoutMs);
173967
174846
  } catch (error) {
173968
- fileFailures.push(
173969
- failureFor(error, registered.spec.path, suite, "afterAll")
173970
- );
174847
+ fileFailures.push(reportFailure(error, suite, "afterAll"));
173971
174848
  } finally {
173972
174849
  testDurationMs += performance.now() - hookStarted;
173973
174850
  }
@@ -174282,13 +175159,14 @@ async function runTest(workspace, options, dependencies = {}) {
174282
175159
  } catch (error) {
174283
175160
  const diagnosticCode = interrupted || error instanceof NeoTestInterruptedError ? "NEO_TEST_INTERRUPTED" : error instanceof NeoTestPreparedCandidateError ? "NEO_TEST_PREPARED_CANDIDATE" : error instanceof NeoTestUsageError ? "NEO_TEST_USAGE" : error instanceof NeoTestNoTestsError ? "NEO_TEST_NO_TESTS" : error instanceof NeoTestRegistrationError ? "NEO_TEST_REGISTRATION" : phase === "prepare" ? "NEO_TEST_PREPARATION" : phase === "compile" ? "NEO_TEST_COMPILE" : "NEO_TEST_RUNNER";
174284
175161
  exitCode = interrupted || error instanceof NeoTestInterruptedError ? 130 : error instanceof NeoTestUsageError || error instanceof NeoTestReportWriteError || error instanceof NeoTestPreparedCandidateError || diagnosticCode === "NEO_TEST_RUNNER" ? 2 : 1;
175162
+ const errorPosition = error instanceof Error ? ERROR_SOURCE_POSITIONS.get(error) : void 0;
174285
175163
  diagnostics.push({
174286
175164
  severity: "error",
174287
175165
  code: diagnosticCode,
174288
175166
  message: interrupted ? "Neo test was interrupted by SIGINT; partial results were written." : errorMessage2(error),
174289
- file: null,
174290
- line: null,
174291
- column: null,
175167
+ file: errorPosition?.file ?? null,
175168
+ line: errorPosition?.line ?? null,
175169
+ column: errorPosition?.column ?? null,
174292
175170
  related: []
174293
175171
  });
174294
175172
  }
@@ -174501,6 +175379,8 @@ var TEST_BUILD_CACHE_LIMIT_BYTES, ABANDONED_TEMP_MAX_AGE_MS, TEST_CANDIDATE_CACH
174501
175379
  var init_test = __esm({
174502
175380
  "src/commands/test.ts"() {
174503
175381
  "use strict";
175382
+ init_strict_compile_error();
175383
+ init_runtime_source();
174504
175384
  init_test_worker_pool();
174505
175385
  init_project();
174506
175386
  init_test_prelude();
@@ -178067,7 +178947,7 @@ async function commitLocalizedTextLocaleValueEdits(ctx, edits, options) {
178067
178947
  ))
178068
178948
  primaryTextIds.add(textId);
178069
178949
  }
178070
- let sources;
178950
+ let sources2;
178071
178951
  const primaryTargets = /* @__PURE__ */ new Map();
178072
178952
  const changes = [];
178073
178953
  for (const [textId, edit] of composed) {
@@ -178076,8 +178956,8 @@ async function commitLocalizedTextLocaleValueEdits(ctx, edits, options) {
178076
178956
  statusWrite: edit.statusWrite
178077
178957
  });
178078
178958
  if (primaryTextIds.has(textId)) {
178079
- sources ??= indexPrimaryAssignmentSources(document, primaryTextIds);
178080
- const link = edit.assignmentLink === void 0 ? requireSinglePrimaryAssignment(edit.original, sources) : primaryTextAssignmentLink(edit.original, edit.assignmentLink);
178959
+ sources2 ??= indexPrimaryAssignmentSources(document, primaryTextIds);
178960
+ const link = edit.assignmentLink === void 0 ? requireSinglePrimaryAssignment(edit.original, sources2) : primaryTextAssignmentLink(edit.original, edit.assignmentLink);
178081
178961
  primaryTargets.set(textId, localizedTextReferenceTarget(link));
178082
178962
  details = { assignmentLink: link };
178083
178963
  }
@@ -178227,10 +179107,10 @@ function getChangedLocalizedText(changedRecords, textId) {
178227
179107
  );
178228
179108
  }
178229
179109
  function indexPrimaryAssignmentSources(document, textIds) {
178230
- const sources = /* @__PURE__ */ new Map();
179110
+ const sources2 = /* @__PURE__ */ new Map();
178231
179111
  const add = (textId, source) => {
178232
- const list = sources.get(textId);
178233
- if (list === void 0) sources.set(textId, [source]);
179112
+ const list = sources2.get(textId);
179113
+ if (list === void 0) sources2.set(textId, [source]);
178234
179114
  else list.push(source);
178235
179115
  };
178236
179116
  for (const member of document.members) {
@@ -178260,7 +179140,7 @@ function indexPrimaryAssignmentSources(document, textIds) {
178260
179140
  });
178261
179141
  }
178262
179142
  }
178263
- if (candidates.length === 0) return sources;
179143
+ if (candidates.length === 0) return sources2;
178264
179144
  const owners = resolveLocalizableStringWriteOwners(document, candidates);
178265
179145
  for (const [valueId, textId] of candidateTextIds) {
178266
179146
  const member = owners.get(valueId);
@@ -178272,11 +179152,11 @@ function indexPrimaryAssignmentSources(document, textIds) {
178272
179152
  label: `${member.name} value (${valueId})`
178273
179153
  });
178274
179154
  }
178275
- return sources;
179155
+ return sources2;
178276
179156
  }
178277
- function requireSinglePrimaryAssignment(text, sources) {
179157
+ function requireSinglePrimaryAssignment(text, sources2) {
178278
179158
  if (text.archivedAt != null) return primaryTextAssignmentLink(text);
178279
- const found = sources.get(text.id) ?? [];
179159
+ const found = sources2.get(text.id) ?? [];
178280
179160
  if (found.length > 1)
178281
179161
  throw new Error(
178282
179162
  `Choose the source assignment for primary text "${text.id}": ${found.map((source) => source.label).join(", ")}.`
@@ -180879,15 +181759,19 @@ __export(resolve_exports, {
180879
181759
  import { readFileSync as readFileSync28, rmSync as rmSync11, writeFileSync as writeFileSync17 } from "node:fs";
180880
181760
  import { join as join23, relative as relative12 } from "node:path";
180881
181761
  function runResolve(workspace, side) {
180882
- const marked = readMarkedProjectSourcesV4(workspace.root);
181762
+ const sources2 = readResolvedProjectSourcesV4(workspace.root, side);
181763
+ const marked = sources2.filter((file) => file.marked);
180883
181764
  assertRecordConflictsResolveTogetherV4(
180884
181765
  workspace,
180885
181766
  new Set(marked.map((file) => file.path))
180886
181767
  );
181768
+ const declaredPaths = Object.values(workspace.state.records).some(
181769
+ (state) => state.projectBinary?.conflict !== void 0
181770
+ ) ? declaredProjectFilePaths(sources2) : /* @__PURE__ */ new Map();
180887
181771
  const resolvedRecords = adoptServerConflictBases(workspace);
180888
181772
  let resolvedFiles = 0;
180889
181773
  for (const file of marked) {
180890
- writeFileSync17(file.absolutePath, resolveMarkers(file.source, side), "utf8");
181774
+ writeFileSync17(file.absolutePath, file.text, "utf8");
180891
181775
  resolvedFiles += 1;
180892
181776
  }
180893
181777
  let resolvedBinaries = 0;
@@ -180895,6 +181779,7 @@ function runResolve(workspace, side) {
180895
181779
  const binary = state.projectBinary;
180896
181780
  const conflict2 = binary?.conflict;
180897
181781
  if (binary === void 0 || conflict2 === void 0) continue;
181782
+ binary.path = declaredPaths.get(state.recordId) ?? binary.path;
180898
181783
  const destination = join23(workspace.root, binary.path);
180899
181784
  if (side === "theirs") {
180900
181785
  if (conflict2.remoteSha256 !== null && conflict2.artifactPath !== void 0) {
@@ -180926,18 +181811,32 @@ function runResolve(workspace, side) {
180926
181811
  `Resolved ${resolvedFiles} source file(s), ${resolvedRecords} record conflict(s), and ${resolvedBinaries} binary file(s) keeping the "${side}" side. Review with "neo diff", then "neo push".`
180927
181812
  );
180928
181813
  }
180929
- function readMarkedProjectSourcesV4(root) {
180930
- const files = [];
180931
- for (const absolutePath of listProjectSourceFilesV4(root)) {
181814
+ function declaredProjectFilePaths(sources2) {
181815
+ const analysis = compileNeoProjectSources(
181816
+ sources2.map(({ path, text }) => ({
181817
+ uri: path,
181818
+ kind: requiredProjectSourceKindV4(path, text),
181819
+ text
181820
+ }))
181821
+ );
181822
+ return new Map(
181823
+ projectFileDeclarationsV4(analysis).map((declaration) => [
181824
+ declaration.recordId,
181825
+ declaration.path
181826
+ ])
181827
+ );
181828
+ }
181829
+ function readResolvedProjectSourcesV4(root, side) {
181830
+ return listProjectSourceFilesV4(root).map((absolutePath) => {
180932
181831
  const source = readFileSync28(absolutePath, "utf8");
180933
- if (detectConflictMarkers(source) === null) continue;
180934
- files.push({
181832
+ const marked = detectConflictMarkers(source) !== null;
181833
+ return {
180935
181834
  path: normalizeWorkspaceSourcePath(relative12(root, absolutePath)),
180936
181835
  absolutePath,
180937
- source
180938
- });
180939
- }
180940
- return files;
181836
+ text: marked ? resolveMarkers(source, side) : source,
181837
+ marked
181838
+ };
181839
+ });
180941
181840
  }
180942
181841
  function assertRecordConflictsResolveTogetherV4(workspace, markedPaths) {
180943
181842
  for (const [key, record6] of Object.entries(workspace.state.records)) {
@@ -181013,10 +181912,12 @@ function resolveMarkers(source, side) {
181013
181912
  var init_resolve = __esm({
181014
181913
  "src/commands/resolve.ts"() {
181015
181914
  "use strict";
181915
+ init_project_source_manifest();
181016
181916
  init_workspace();
181017
181917
  init_workspace_status();
181018
181918
  init_source_diagnostics();
181019
181919
  init_workspace_source_path();
181920
+ init_project_file_source();
181020
181921
  init_project_files();
181021
181922
  }
181022
181923
  });
@@ -181274,7 +182175,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
181274
182175
  async function main() {
181275
182176
  const args = parseArgs(process.argv.slice(2));
181276
182177
  if (args.command === "--version") {
181277
- console.log("0.70.1");
182178
+ console.log("0.70.3");
181278
182179
  return;
181279
182180
  }
181280
182181
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {