@thigasdevelopment/luam 0.19.6 → 0.19.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/luam.mjs +1556 -369
  2. package/package.json +1 -1
package/luam.mjs CHANGED
@@ -431,7 +431,7 @@ var require_help = __commonJS({
431
431
  if (option.argChoices) {
432
432
  extraInfo.push(
433
433
  // use stringify to match the display of the default value
434
- `choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}`
434
+ `choices: ${option.argChoices.map((choice2) => JSON.stringify(choice2)).join(", ")}`
435
435
  );
436
436
  }
437
437
  if (option.defaultValue !== void 0) {
@@ -464,7 +464,7 @@ var require_help = __commonJS({
464
464
  if (argument.argChoices) {
465
465
  extraInfo.push(
466
466
  // use stringify to match the display of the default value
467
- `choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}`
467
+ `choices: ${argument.argChoices.map((choice2) => JSON.stringify(choice2)).join(", ")}`
468
468
  );
469
469
  }
470
470
  if (argument.defaultValue !== void 0) {
@@ -3121,13 +3121,24 @@ function reportAssignedConflict(assigned, fromDirective, mapped, diagnostics) {
3121
3121
  const message = `${origin} "${assigned}" environment but the directive declares "${fromDirective}". The directive wins.`;
3122
3122
  diagnostics.push(createDiagnostic("checker", "env-path-directive-conflict", message, FILE_START, "warning"));
3123
3123
  }
3124
- function resolveEnvironment(filePath, directives, fromMapping = null) {
3124
+ function reportLockedConflict(assigned, fromDirective, diagnostics) {
3125
+ const subject = `A library declares this file as "${assigned}" but the directive declares "${fromDirective}"`;
3126
+ const message = `${subject}. A library declares its layout once, in the "luam" field of its "package.json". Remove the directive.`;
3127
+ diagnostics.push(createDiagnostic("checker", "env-library-directive", message, FILE_START));
3128
+ }
3129
+ function resolveEnvironment(filePath, directives, fromMapping = null, locked = false) {
3125
3130
  const diagnostics = [];
3126
3131
  const fromPath = filePath === null ? null : environmentFromPath(filePath);
3127
3132
  const found = collectDirectiveEnvironments(directives);
3128
3133
  const fromDirective = found[0] ?? null;
3129
3134
  const assigned = fromMapping ?? fromPath;
3130
3135
  reportConflictingDirectives(found, diagnostics);
3136
+ if (locked && assigned !== null) {
3137
+ if (fromDirective !== null && fromDirective !== assigned) {
3138
+ reportLockedConflict(assigned, fromDirective, diagnostics);
3139
+ }
3140
+ return { environment: assigned, fromPath, fromMapping, fromDirective, diagnostics };
3141
+ }
3131
3142
  if (assigned !== null && fromDirective !== null) {
3132
3143
  reportAssignedConflict(assigned, fromDirective, fromMapping !== null, diagnostics);
3133
3144
  }
@@ -4449,8 +4460,8 @@ function parseSuffixed(stream) {
4449
4460
  }
4450
4461
  function parseUnary(stream) {
4451
4462
  const token = stream.current();
4452
- const isUnary = (token.kind === "operator" || token.kind === "keyword") && UNARY_OPERATORS.has(token.value);
4453
- if (!isUnary) {
4463
+ const isUnary2 = (token.kind === "operator" || token.kind === "keyword") && UNARY_OPERATORS.has(token.value);
4464
+ if (!isUnary2) {
4454
4465
  return parseSuffixed(stream);
4455
4466
  }
4456
4467
  stream.next();
@@ -5175,6 +5186,8 @@ var ESCAPING_PATH = "config-escaping-path";
5175
5186
  var UNKNOWN_HELPER = "config-unknown-helper";
5176
5187
  var INVALID_PATTERN = "config-invalid-pattern";
5177
5188
  var INVALID_DEPENDENCY = "config-invalid-dependency";
5189
+ var INVALID_LIBRARY = "config-library-invalid";
5190
+ var DUPLICATE_LIBRARY = "config-library-duplicate";
5178
5191
  var INVALID_ENGINE_VERSION = "config-invalid-engine-version";
5179
5192
  var REMOVED_FIELD = "config-removed-field";
5180
5193
  var ALLOWED_STATEMENTS = 'A manifest holds only "local" declarations and assignments to configuration fields.';
@@ -5802,6 +5815,12 @@ var MANIFEST_FIELDS = [
5802
5815
  owner: "dependencies",
5803
5816
  allowEmpty: true
5804
5817
  }),
5818
+ field("libraries", STRING_LIST, "Luam library packages compiled into the resource, in the order they are emitted.", {
5819
+ defaultValue: [],
5820
+ rule: "package-name",
5821
+ owner: "libraries",
5822
+ allowEmpty: true
5823
+ }),
5805
5824
  field("contracts", STRING_TYPE, "Directory the export contract is written to and dependency contracts are read from.", {
5806
5825
  defaultValue: DEFAULT_CONTRACTS_DIR,
5807
5826
  rule: "contained-path",
@@ -5966,8 +5985,8 @@ function isNumber(type) {
5966
5985
  function isText(type) {
5967
5986
  return type.kind === "string" || type.kind === "string-literal" || type.kind === "any";
5968
5987
  }
5969
- function fail(operator, expected, left, right, describe2) {
5970
- const culprit = describe2(isNumber(left) || isText(left) ? right : left);
5988
+ function fail(operator, expected, left, right, describe3) {
5989
+ const culprit = describe3(isNumber(left) || isText(left) ? right : left);
5971
5990
  return { evaluated: null, error: `"${operator}" expects ${expected} on both sides but received ${culprit}.` };
5972
5991
  }
5973
5992
  function arithmetic(operator, left, right) {
@@ -6006,22 +6025,22 @@ function applyOr(left, right) {
6006
6025
  const value = isTruthy(left.value) ? left.value : right.value;
6007
6026
  return { value, type: unionOf([left.truthy, right.type]), truthy: unionOf([left.truthy, right.truthy]), falsy: right.falsy };
6008
6027
  }
6009
- function applyOrdering(operator, left, right, describe2) {
6028
+ function applyOrdering(operator, left, right, describe3) {
6010
6029
  const numeric = isNumber(left.type) && isNumber(right.type);
6011
6030
  const textual = isText(left.type) && isText(right.type);
6012
6031
  if (!numeric && !textual) {
6013
- return fail(operator, "two numbers or two strings", left.type, right.type, describe2);
6032
+ return fail(operator, "two numbers or two strings", left.type, right.type, describe3);
6014
6033
  }
6015
6034
  return { evaluated: booleanValue(compare(operator, left.value, right.value)), error: null };
6016
6035
  }
6017
- function applyConcat(left, right, describe2) {
6036
+ function applyConcat(left, right, describe3) {
6018
6037
  const usable = (type) => isText(type) || isNumber(type);
6019
6038
  if (!usable(left.type) || !usable(right.type)) {
6020
- return fail("..", "a string or a number", left.type, right.type, describe2);
6039
+ return fail("..", "a string or a number", left.type, right.type, describe3);
6021
6040
  }
6022
6041
  return { evaluated: stringValue(`${left.value}${right.value}`), error: null };
6023
6042
  }
6024
- function applyBinary(operator, left, right, describe2) {
6043
+ function applyBinary(operator, left, right, describe3) {
6025
6044
  if (operator === "and") {
6026
6045
  return { evaluated: applyAnd(left, right), error: null };
6027
6046
  }
@@ -6033,23 +6052,23 @@ function applyBinary(operator, left, right, describe2) {
6033
6052
  return { evaluated: booleanValue(operator === "==" ? same : !same), error: null };
6034
6053
  }
6035
6054
  if (ORDERING.includes(operator)) {
6036
- return applyOrdering(operator, left, right, describe2);
6055
+ return applyOrdering(operator, left, right, describe3);
6037
6056
  }
6038
6057
  if (operator === "..") {
6039
- return applyConcat(left, right, describe2);
6058
+ return applyConcat(left, right, describe3);
6040
6059
  }
6041
6060
  if (!isNumber(left.type) || !isNumber(right.type)) {
6042
- return fail(operator, "a number", left.type, right.type, describe2);
6061
+ return fail(operator, "a number", left.type, right.type, describe3);
6043
6062
  }
6044
6063
  return { evaluated: numberValue(arithmetic(operator, left.value, right.value)), error: null };
6045
6064
  }
6046
- function applyUnary(operator, operand, describe2) {
6065
+ function applyUnary(operator, operand, describe3) {
6047
6066
  if (operator === "not") {
6048
6067
  return { evaluated: booleanValue(!isTruthy(operand.value)), error: null };
6049
6068
  }
6050
6069
  if (operator === "-") {
6051
6070
  if (!isNumber(operand.type)) {
6052
- return { evaluated: null, error: `"-" expects a number but received ${describe2(operand.type)}.` };
6071
+ return { evaluated: null, error: `"-" expects a number but received ${describe3(operand.type)}.` };
6053
6072
  }
6054
6073
  return { evaluated: numberValue(-operand.value), error: null };
6055
6074
  }
@@ -6385,6 +6404,130 @@ function watchRoots(patterns) {
6385
6404
  return roots.filter((root) => !roots.some((other) => other.length < root.length && root.startsWith(`${other}/`)));
6386
6405
  }
6387
6406
 
6407
+ // ../compiler/src/project/library.ts
6408
+ function compareLibraryOrigins(left, right) {
6409
+ return left.packageIndex - right.packageIndex || left.index - right.index;
6410
+ }
6411
+ var LIBRARIES_DIRECTORY = "libs";
6412
+ var LIBRARY_FIELD = "luam";
6413
+ var MISSING_LIBRARY = "config-library-missing";
6414
+ var INVALID_LIBRARY2 = "config-library-invalid";
6415
+ var ESCAPING_LIBRARY = "config-library-escape";
6416
+ var MISSING_REQUIREMENT = "config-library-requirement-missing";
6417
+ var PACKAGE_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
6418
+ var SHAPE = `"${LIBRARY_FIELD}" must be a table with "sources" naming patterns per side and an optional "requires" list.`;
6419
+ function isPackageName(value) {
6420
+ return PACKAGE_NAME.test(value);
6421
+ }
6422
+ function libraryDirectory(name) {
6423
+ return name.replace(/^@/, "").replace(/\//g, "-");
6424
+ }
6425
+ function libraryFilePath(name, relativePath2) {
6426
+ return `${name}/${normalizePattern(relativePath2)}`;
6427
+ }
6428
+ function libraryOutputPath(name, environment, relativePath2) {
6429
+ const file = normalizePattern(relativePath2).replace(/\.luam$/, ".lua");
6430
+ return `${LIBRARIES_DIRECTORY}/${libraryDirectory(name)}/${environment}/${file}`;
6431
+ }
6432
+ function installCommand(name) {
6433
+ return `npm install ${name}`;
6434
+ }
6435
+ function isRecord(value) {
6436
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6437
+ }
6438
+ function stringList(value) {
6439
+ if (!Array.isArray(value)) {
6440
+ return null;
6441
+ }
6442
+ return value.every((entry) => typeof entry === "string") ? [...value] : null;
6443
+ }
6444
+ function patternProblems(name, environment, patterns, problems) {
6445
+ for (const pattern of patterns) {
6446
+ const problem = patternProblem(pattern);
6447
+ if (problem === null) {
6448
+ continue;
6449
+ }
6450
+ const code = problem === "absolute" || problem === "traversal" ? ESCAPING_LIBRARY : INVALID_LIBRARY2;
6451
+ const subject = `"${name}" declares the "${environment}" pattern "${pattern}", which ${patternProblemText(problem)}`;
6452
+ problems.push({ code, message: `${subject}. Every pattern stays inside the package directory.` });
6453
+ }
6454
+ }
6455
+ function readSources(name, value, problems) {
6456
+ if (!isRecord(value)) {
6457
+ problems.push({ code: INVALID_LIBRARY2, message: `"${name}" declares no "sources" table in its "${LIBRARY_FIELD}" field. ${SHAPE}` });
6458
+ return null;
6459
+ }
6460
+ const mapping = emptySourceMapping();
6461
+ for (const environment of SOURCE_SIDES) {
6462
+ const declared = value[environment];
6463
+ if (declared === void 0) {
6464
+ continue;
6465
+ }
6466
+ const patterns = stringList(declared);
6467
+ if (patterns === null) {
6468
+ problems.push({ code: INVALID_LIBRARY2, message: `"${name}" declares "sources.${environment}" as something other than a list of patterns. ${SHAPE}` });
6469
+ continue;
6470
+ }
6471
+ patternProblems(name, environment, patterns, problems);
6472
+ mapping[environment] = patterns.map(normalizePattern);
6473
+ }
6474
+ if (SOURCE_SIDES.every((environment) => mapping[environment].length === 0)) {
6475
+ problems.push({ code: INVALID_LIBRARY2, message: `"${name}" declares no source patterns for any side. ${SHAPE}` });
6476
+ return null;
6477
+ }
6478
+ return mapping;
6479
+ }
6480
+ function readRequires(name, value, problems) {
6481
+ if (value === void 0) {
6482
+ return [];
6483
+ }
6484
+ const requires = stringList(value);
6485
+ if (requires === null) {
6486
+ problems.push({ code: INVALID_LIBRARY2, message: `"${name}" declares "requires" as something other than a list of package names. ${SHAPE}` });
6487
+ return [];
6488
+ }
6489
+ for (const entry of requires) {
6490
+ if (!isPackageName(entry)) {
6491
+ problems.push({ code: INVALID_LIBRARY2, message: `"${name}" requires "${entry}", which is not a package name.` });
6492
+ }
6493
+ }
6494
+ return requires.filter(isPackageName);
6495
+ }
6496
+ function readLibraryDeclaration(name, manifest) {
6497
+ const problems = [];
6498
+ if (!isRecord(manifest)) {
6499
+ return { declaration: null, problems: [{ code: INVALID_LIBRARY2, message: `"${name}" has an unreadable "package.json". ${SHAPE}` }] };
6500
+ }
6501
+ const field2 = manifest[LIBRARY_FIELD];
6502
+ if (field2 === void 0) {
6503
+ const message = `"${name}" is listed in "libraries" but its "package.json" declares no "${LIBRARY_FIELD}" field, so it is not a Luam library.`;
6504
+ return { declaration: null, problems: [{ code: INVALID_LIBRARY2, message }] };
6505
+ }
6506
+ if (!isRecord(field2)) {
6507
+ return { declaration: null, problems: [{ code: INVALID_LIBRARY2, message: `"${name}" declares a "${LIBRARY_FIELD}" field that is not a table. ${SHAPE}` }] };
6508
+ }
6509
+ const sources = readSources(name, field2.sources, problems);
6510
+ const requires = readRequires(name, field2.requires, problems);
6511
+ if (sources === null) {
6512
+ return { declaration: null, problems };
6513
+ }
6514
+ return { declaration: { name, sources, requires }, problems };
6515
+ }
6516
+ function missingRequirements(declarations) {
6517
+ const listed = new Set(declarations.map((declaration) => declaration.name));
6518
+ const problems = [];
6519
+ for (const declaration of declarations) {
6520
+ for (const required of declaration.requires) {
6521
+ if (listed.has(required)) {
6522
+ continue;
6523
+ }
6524
+ const subject = `"${declaration.name}" requires "${required}", which "libraries" does not list`;
6525
+ problems.push({ code: MISSING_REQUIREMENT, message: `${subject}. Add it to "libraries" and install it with "${installCommand(required)}".` });
6526
+ }
6527
+ }
6528
+ return problems;
6529
+ }
6530
+
6388
6531
  // ../compiler/src/manifest/manifest-rules.ts
6389
6532
  var START = createPosition(1, 1, 0);
6390
6533
  var RESOURCE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
@@ -6477,6 +6620,9 @@ var RuleWalk = class {
6477
6620
  if (entry.rule === "dependency-name" && !isValidResourceName(value)) {
6478
6621
  this.report(INVALID_DEPENDENCY, `"${path}" must be a valid MTA resource name but received "${value}".`, key);
6479
6622
  }
6623
+ if (entry.rule === "package-name" && !isPackageName(value)) {
6624
+ this.report(INVALID_LIBRARY, `"${path}" must be the name of an installed npm package but received "${value}".`, key);
6625
+ }
6480
6626
  }
6481
6627
  version(entry, value, path, key) {
6482
6628
  if (entry.rule !== "engine-version" || value === LATEST_ENGINE_VERSION || ENGINE_VERSION.test(value)) {
@@ -6513,6 +6659,7 @@ function normalizeManifest(value, positions) {
6513
6659
  }
6514
6660
 
6515
6661
  // ../compiler/src/manifest/manifest-analysis.ts
6662
+ var MANIFEST_SCHEMA = { fields: MANIFEST_FIELDS, removed: REMOVED_FIELDS, normalize: normalizeManifest };
6516
6663
  var START2 = createPosition(1, 1, 0);
6517
6664
  function statementError(kind) {
6518
6665
  return `A manifest cannot contain ${kind}. ${ALLOWED_STATEMENTS}`;
@@ -6542,7 +6689,7 @@ function readLocal(pass, statement) {
6542
6689
  pass.declareLocal(declaration.name, value === void 0 ? nilValue() : pass.evaluate(value, null), declaration.position);
6543
6690
  }
6544
6691
  }
6545
- function readAssignment(pass, statement, assignments, value) {
6692
+ function readAssignment(pass, statement, assignments, value, schema) {
6546
6693
  const [target] = statement.targets;
6547
6694
  const [expression] = statement.values;
6548
6695
  if (statement.operator !== "=" || statement.targets.length !== 1 || statement.values.length !== 1 || target === void 0 || expression === void 0) {
@@ -6553,11 +6700,11 @@ function readAssignment(pass, statement, assignments, value) {
6553
6700
  pass.report(INVALID_STATEMENT, statementError("an assignment to anything but a configuration field"), target.position);
6554
6701
  return;
6555
6702
  }
6556
- const field2 = findField(MANIFEST_FIELDS, target.name);
6703
+ const field2 = findField(schema.fields, target.name);
6557
6704
  if (field2 === null) {
6558
- const replacement = REMOVED_FIELDS[target.name];
6705
+ const replacement = schema.removed[target.name];
6559
6706
  if (replacement === void 0) {
6560
- pass.report(UNKNOWN_FIELD, unknownNameMessage(target.name), target.position);
6707
+ pass.report(UNKNOWN_FIELD, (schema.unknownName ?? unknownNameMessage)(target.name), target.position);
6561
6708
  } else {
6562
6709
  pass.report(REMOVED_FIELD, `"${target.name}" is no longer a manifest field. ${replacement}`, target.position);
6563
6710
  }
@@ -6566,25 +6713,25 @@ function readAssignment(pass, statement, assignments, value) {
6566
6713
  assignments.push({ name: target.name, position: target.position, value: expression });
6567
6714
  value[target.name] = pass.expression(expression, { field: field2, path: target.name, key: target.name }).value;
6568
6715
  }
6569
- function readStatement(pass, statement, assignments, value) {
6716
+ function readStatement(pass, statement, assignments, value, schema) {
6570
6717
  if (statement.kind === "local-statement") {
6571
6718
  readLocal(pass, statement);
6572
6719
  return;
6573
6720
  }
6574
6721
  if (statement.kind === "assignment-statement") {
6575
- readAssignment(pass, statement, assignments, value);
6722
+ readAssignment(pass, statement, assignments, value, schema);
6576
6723
  return;
6577
6724
  }
6578
6725
  pass.report(INVALID_STATEMENT, statementError(STATEMENT_NAMES[statement.kind] ?? "this statement"), statement.position);
6579
6726
  }
6580
- function reportMissing(diagnostics, value) {
6581
- for (const field2 of requiredFields(MANIFEST_FIELDS)) {
6727
+ function reportMissing(diagnostics, value, schema) {
6728
+ for (const field2 of requiredFields(schema.fields)) {
6582
6729
  if (value[field2.name] === void 0) {
6583
6730
  diagnostics.push(manifestError(MISSING_FIELD, `The manifest requires a "${field2.name}" field. ${field2.summary}`, START2));
6584
6731
  }
6585
6732
  }
6586
6733
  }
6587
- function analyzeManifest(source, context) {
6734
+ function analyzeManifest(source, context, schema = MANIFEST_SCHEMA) {
6588
6735
  const parsed = parse(source);
6589
6736
  const pass = new ManifestPass(context);
6590
6737
  const assignments = [];
@@ -6593,15 +6740,15 @@ function analyzeManifest(source, context) {
6593
6740
  pass.report(INVALID_STATEMENT, statementError(`the "#!${directive}" directive`), START2);
6594
6741
  }
6595
6742
  for (const statement of parsed.program.body) {
6596
- readStatement(pass, statement, assignments, raw);
6743
+ readStatement(pass, statement, assignments, raw, schema);
6597
6744
  }
6598
- reportMissing(pass.diagnostics, raw);
6745
+ reportMissing(pass.diagnostics, raw, schema);
6599
6746
  pass.diagnostics.push(...pass.locals.unused());
6600
- const normalized = normalizeManifest(raw, pass.positions);
6747
+ const normalized = schema.normalize(raw, pass.positions);
6601
6748
  return {
6602
6749
  program: parsed.program,
6603
6750
  tokens: parsed.tokens,
6604
- diagnostics: sortDiagnostics([...parsed.diagnostics, ...pass.diagnostics, ...normalized.diagnostics]),
6751
+ diagnostics: sortDiagnostics([...parsed.diagnostics, ...pass.diagnostics, ...normalized.diagnostics]).map(schema.retag ?? ((entry) => entry)),
6605
6752
  value: normalized.value,
6606
6753
  raw,
6607
6754
  positions: pass.positions,
@@ -6673,6 +6820,9 @@ function readAssetMappings(value) {
6673
6820
  function readDependencies(value) {
6674
6821
  return [...new Set(readStrings(value, "dependencies"))].sort();
6675
6822
  }
6823
+ function readLibraries(value) {
6824
+ return readStrings(value, "libraries");
6825
+ }
6676
6826
  function readEngine(value) {
6677
6827
  const source = readTable(value, "engine") ?? EMPTY;
6678
6828
  return { minVersion: readString(source, "minVersion") ?? DEFAULT_ENGINE.minVersion };
@@ -6732,10 +6882,20 @@ function checkDependencies(name, dependencies, context) {
6732
6882
  }
6733
6883
  }
6734
6884
  }
6885
+ function checkLibraries(libraries, context) {
6886
+ const seen = /* @__PURE__ */ new Set();
6887
+ for (const [index, library] of libraries.entries()) {
6888
+ if (seen.has(library)) {
6889
+ context.error(DUPLICATE_LIBRARY, `"libraries" lists "${library}" more than once. Keep one entry.`, `libraries.${index}`);
6890
+ }
6891
+ seen.add(library);
6892
+ }
6893
+ }
6735
6894
  function validateConfig(value, positions) {
6736
6895
  const context = new ValidationContext(positions);
6737
6896
  const name = readString(value, "name") ?? "";
6738
6897
  const dependencies = readDependencies(value);
6898
+ const libraries = readLibraries(value);
6739
6899
  const config = {
6740
6900
  name,
6741
6901
  author: readString(value, "author"),
@@ -6745,6 +6905,7 @@ function validateConfig(value, positions) {
6745
6905
  sources: readSourceMapping(value),
6746
6906
  assets: readAssetMappings(value),
6747
6907
  dependencies,
6908
+ libraries,
6748
6909
  contracts: readString(value, "contracts") ?? "",
6749
6910
  engine: readEngine(value),
6750
6911
  environment: readEnvironmentFiles(value),
@@ -6757,6 +6918,7 @@ function validateConfig(value, positions) {
6757
6918
  development: readDevelopment(readTable(value, "development"))
6758
6919
  };
6759
6920
  checkDependencies(name, dependencies, context);
6921
+ checkLibraries(libraries, context);
6760
6922
  return { config, diagnostics: context.diagnostics };
6761
6923
  }
6762
6924
 
@@ -6810,7 +6972,7 @@ import { tmpdir } from "node:os";
6810
6972
  import { join as join2 } from "node:path";
6811
6973
 
6812
6974
  // src/cli/version.ts
6813
- var VERSION = true ? "0.19.6" : "0.0.0-dev";
6975
+ var VERSION = true ? "0.19.10" : "0.0.0-dev";
6814
6976
  var PROGRAM_NAME = "luam";
6815
6977
  var PROGRAM_DESCRIPTION = "luam \u2014 the Luam compiler for Multi Theft Auto resources.";
6816
6978
 
@@ -7344,6 +7506,9 @@ function manifestOption() {
7344
7506
  function colorOption() {
7345
7507
  return new Option("--no-color", "Print plain output with no colour and no emoji.");
7346
7508
  }
7509
+ function jsonOption() {
7510
+ return new Option("--json", "Write one machine-readable document to stdout instead of the human report.");
7511
+ }
7347
7512
  function offlineOption() {
7348
7513
  return new Option("--offline", "Skip the MTA release lookup and use the cached version, if there is one.");
7349
7514
  }
@@ -7459,15 +7624,15 @@ function knowsResource(contracts, resource) {
7459
7624
  }
7460
7625
  function buildResourceAbi(resource, contributions) {
7461
7626
  const exports = contributions.map((contribution) => {
7462
- const signature = contribution.signature;
7627
+ const signature2 = contribution.signature;
7463
7628
  return {
7464
7629
  name: contribution.name,
7465
7630
  side: contribution.side,
7466
7631
  http: contribution.http,
7467
- parameters: signature?.parameters ?? [],
7468
- minimumArguments: signature?.minimumArguments ?? 0,
7469
- variadic: signature?.variadic ?? true,
7470
- returns: signature?.returns ?? "any"
7632
+ parameters: signature2?.parameters ?? [],
7633
+ minimumArguments: signature2?.minimumArguments ?? 0,
7634
+ variadic: signature2?.variadic ?? true,
7635
+ returns: signature2?.returns ?? "any"
7471
7636
  };
7472
7637
  });
7473
7638
  return { abi: ABI_VERSION, resource, exports };
@@ -7524,6 +7689,175 @@ function readHelperSource(helper, file) {
7524
7689
  return readFileSync4(resolveHelperPath(helper, file), "utf8");
7525
7690
  }
7526
7691
 
7692
+ // src/build/library-resolution.ts
7693
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
7694
+ import { resolve as resolve6 } from "node:path";
7695
+
7696
+ // src/build/project-files.ts
7697
+ import { readdirSync, statSync as statSync2 } from "node:fs";
7698
+ import { join as join3, relative, resolve as resolve5 } from "node:path";
7699
+ function isDirectory(path) {
7700
+ try {
7701
+ return statSync2(path).isDirectory();
7702
+ } catch {
7703
+ return false;
7704
+ }
7705
+ }
7706
+ function relativePath(root, absolute) {
7707
+ return normalizePattern(relative(root, absolute));
7708
+ }
7709
+ function walk(root, directory, excluded, tree) {
7710
+ let entries3;
7711
+ try {
7712
+ entries3 = readdirSync(directory, { withFileTypes: true });
7713
+ } catch (error) {
7714
+ tree.errors.push(`"${relativePath(root, directory)}" could not be read: ${error instanceof Error ? error.message : String(error)}`);
7715
+ return;
7716
+ }
7717
+ for (const entry of entries3) {
7718
+ const absolute = join3(directory, entry.name);
7719
+ const path = relativePath(root, absolute);
7720
+ if (path.length === 0 || isExcludedPath(path, excluded)) {
7721
+ continue;
7722
+ }
7723
+ if (entry.isDirectory()) {
7724
+ walk(root, absolute, excluded, tree);
7725
+ } else if (entry.isFile()) {
7726
+ tree.files.push(path);
7727
+ }
7728
+ }
7729
+ }
7730
+ function listProjectFiles(root, roots, excluded = []) {
7731
+ const tree = { files: [], errors: [] };
7732
+ const seen = /* @__PURE__ */ new Set();
7733
+ for (const start of [...new Set(roots)].sort()) {
7734
+ const absolute = resolve5(root, start);
7735
+ if (!isDirectory(absolute) || seen.has(absolute)) {
7736
+ continue;
7737
+ }
7738
+ seen.add(absolute);
7739
+ walk(root, absolute, excluded, tree);
7740
+ }
7741
+ return { files: [...new Set(tree.files)].sort(), errors: tree.errors };
7742
+ }
7743
+
7744
+ // ../compiler/src/project/source-kind.ts
7745
+ var SOURCE_EXTENSION = ".luam";
7746
+ var DECLARATION_EXTENSION = ".d.luam";
7747
+ var TEST_EXTENSION = ".test.luam";
7748
+ function isDeclarationPath(path) {
7749
+ return normalizePath(path).endsWith(DECLARATION_EXTENSION);
7750
+ }
7751
+ function isSourcePath(path) {
7752
+ return normalizePath(path).endsWith(SOURCE_EXTENSION);
7753
+ }
7754
+ function isTestPath(path) {
7755
+ return normalizePath(path).endsWith(TEST_EXTENSION);
7756
+ }
7757
+
7758
+ // src/build/library-resolution.ts
7759
+ var LUA_EXTENSION = ".lua";
7760
+ var PACKAGE_MANIFEST = "package.json";
7761
+ var MODULES_DIRECTORY = "node_modules";
7762
+ function packageRoot(root, name) {
7763
+ return resolve6(root, MODULES_DIRECTORY, ...name.split("/"));
7764
+ }
7765
+ function readPackageManifest(path) {
7766
+ try {
7767
+ return JSON.parse(readFileSync5(path, "utf8"));
7768
+ } catch {
7769
+ return null;
7770
+ }
7771
+ }
7772
+ var SIDE_ORDER = ["shared", "server", "client"];
7773
+ function orderedPatterns(declaration) {
7774
+ return SIDE_ORDER.flatMap((environment) => declaration.sources[environment].map((pattern) => ({ environment, pattern })));
7775
+ }
7776
+ function matchFile(path, patterns) {
7777
+ const order = patterns.findIndex((entry) => matchesPattern(entry.pattern, path));
7778
+ const matched = patterns[order];
7779
+ if (matched === void 0) {
7780
+ return null;
7781
+ }
7782
+ return { relativePath: path, environment: matched.environment, order };
7783
+ }
7784
+ function matchedFiles(root, declaration, diagnostics) {
7785
+ const patterns = orderedPatterns(declaration);
7786
+ const roots = [...new Set(patterns.map((entry) => entry.pattern))];
7787
+ const tree = listProjectFiles(root, watchRoots(roots));
7788
+ const matched = [];
7789
+ for (const path of tree.files) {
7790
+ if (isTestPath(path) || !path.endsWith(SOURCE_EXTENSION) && !path.endsWith(LUA_EXTENSION)) {
7791
+ continue;
7792
+ }
7793
+ const found = matchFile(path, patterns);
7794
+ if (found !== null) {
7795
+ matched.push(found);
7796
+ }
7797
+ }
7798
+ for (const message of tree.errors) {
7799
+ diagnostics.push(cliError(INVALID_LIBRARY2, `"${declaration.name}" could not be read: ${message}`));
7800
+ }
7801
+ return matched.sort((left, right) => left.order - right.order || left.relativePath.localeCompare(right.relativePath));
7802
+ }
7803
+ function readLibraryFile(root, relativePath2, name, diagnostics) {
7804
+ try {
7805
+ return readFileSync5(resolve6(root, relativePath2), "utf8");
7806
+ } catch (error) {
7807
+ const reason2 = error instanceof Error ? error.message : String(error);
7808
+ diagnostics.push(cliError(INVALID_LIBRARY2, `"${libraryFilePath(name, relativePath2)}" could not be read: ${reason2}`));
7809
+ return null;
7810
+ }
7811
+ }
7812
+ function collectFiles(root, declaration, packageIndex, resolved2) {
7813
+ const matched = matchedFiles(root, declaration, resolved2.diagnostics);
7814
+ for (const [index, entry] of matched.entries()) {
7815
+ const content = readLibraryFile(root, entry.relativePath, declaration.name, resolved2.diagnostics);
7816
+ if (content === null) {
7817
+ continue;
7818
+ }
7819
+ const origin = { package: declaration.name, root, relativePath: entry.relativePath, packageIndex, index };
7820
+ if (entry.relativePath.endsWith(LUA_EXTENSION)) {
7821
+ resolved2.verbatim.push({ origin, environment: entry.environment, content });
7822
+ continue;
7823
+ }
7824
+ resolved2.files.push({ path: libraryFilePath(declaration.name, entry.relativePath), source: content, environment: entry.environment, origin });
7825
+ }
7826
+ }
7827
+ function resolveLibrary(root, name, packageIndex, resolved2) {
7828
+ const directory = packageRoot(root, name);
7829
+ const manifestPath = resolve6(directory, PACKAGE_MANIFEST);
7830
+ if (!existsSync4(manifestPath)) {
7831
+ const subject = `"${name}" is listed in "libraries" but is not installed`;
7832
+ resolved2.diagnostics.push(cliError(MISSING_LIBRARY, `${subject}. Install it with "${installCommand(name)}" and build again.`));
7833
+ return;
7834
+ }
7835
+ const declaration = readLibraryDeclaration(name, readPackageManifest(manifestPath));
7836
+ for (const problem of declaration.problems) {
7837
+ resolved2.diagnostics.push(cliError(problem.code, problem.message));
7838
+ }
7839
+ if (declaration.declaration === null) {
7840
+ return;
7841
+ }
7842
+ resolved2.libraries.push({ declaration: declaration.declaration, root: directory });
7843
+ collectFiles(directory, declaration.declaration, packageIndex, resolved2);
7844
+ }
7845
+ function resolveLibraries(root, names) {
7846
+ const resolved2 = { libraries: [], files: [], verbatim: [], diagnostics: [] };
7847
+ const seen = /* @__PURE__ */ new Set();
7848
+ for (const name of names) {
7849
+ if (seen.has(name)) {
7850
+ continue;
7851
+ }
7852
+ seen.add(name);
7853
+ resolveLibrary(root, name, seen.size - 1, resolved2);
7854
+ }
7855
+ for (const problem of missingRequirements(resolved2.libraries.map((library) => library.declaration))) {
7856
+ resolved2.diagnostics.push(cliError(problem.code, problem.message));
7857
+ }
7858
+ return resolved2;
7859
+ }
7860
+
7527
7861
  // src/build/build-phase.ts
7528
7862
  var COUNTED_PHASES = /* @__PURE__ */ new Set(["compile", "write"]);
7529
7863
  var PHASE_LABELS = {
@@ -7590,60 +7924,12 @@ function createPhaseTracker(listener = null) {
7590
7924
  }
7591
7925
 
7592
7926
  // src/build/project-inputs.ts
7593
- import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
7594
- import { resolve as resolve7 } from "node:path";
7927
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
7928
+ import { resolve as resolve8 } from "node:path";
7595
7929
 
7596
7930
  // src/build/asset-resolution.ts
7597
7931
  import { statSync as statSync3 } from "node:fs";
7598
- import { resolve as resolve6 } from "node:path";
7599
-
7600
- // src/build/project-files.ts
7601
- import { readdirSync, statSync as statSync2 } from "node:fs";
7602
- import { join as join3, relative, resolve as resolve5 } from "node:path";
7603
- function isDirectory(path) {
7604
- try {
7605
- return statSync2(path).isDirectory();
7606
- } catch {
7607
- return false;
7608
- }
7609
- }
7610
- function relativePath(root, absolute) {
7611
- return normalizePattern(relative(root, absolute));
7612
- }
7613
- function walk(root, directory, excluded, tree) {
7614
- let entries3;
7615
- try {
7616
- entries3 = readdirSync(directory, { withFileTypes: true });
7617
- } catch (error) {
7618
- tree.errors.push(`"${relativePath(root, directory)}" could not be read: ${error instanceof Error ? error.message : String(error)}`);
7619
- return;
7620
- }
7621
- for (const entry of entries3) {
7622
- const absolute = join3(directory, entry.name);
7623
- const path = relativePath(root, absolute);
7624
- if (path.length === 0 || isExcludedPath(path, excluded)) {
7625
- continue;
7626
- }
7627
- if (entry.isDirectory()) {
7628
- walk(root, absolute, excluded, tree);
7629
- } else if (entry.isFile()) {
7630
- tree.files.push(path);
7631
- }
7632
- }
7633
- }
7634
- function listProjectFiles(root, roots, excluded = []) {
7635
- const tree = { files: [], errors: [] };
7636
- const seen = /* @__PURE__ */ new Set();
7637
- for (const start of [...new Set(roots)].sort()) {
7638
- const absolute = resolve5(root, start);
7639
- if (!isDirectory(absolute) || seen.has(absolute)) {
7640
- continue;
7641
- }
7642
- seen.add(absolute);
7643
- walk(root, absolute, excluded, tree);
7644
- }
7645
- return { files: [...new Set(tree.files)].sort(), errors: tree.errors };
7646
- }
7932
+ import { resolve as resolve7 } from "node:path";
7647
7933
 
7648
7934
  // ../compiler/src/project/resource-map.ts
7649
7935
  var RESOURCE_MAP_VERSION = 1;
@@ -7717,11 +8003,12 @@ var ENVIRONMENTS2 = ["shared", "server", "client"];
7717
8003
  function bundlePath(environment) {
7718
8004
  return `${BUNDLE_DIRECTORY}/${environment}.lua`;
7719
8005
  }
7720
- function collectBundles(helpers, scripts, pinned) {
8006
+ function collectBundles(helpers, scripts, pinned, libraries = []) {
7721
8007
  const pinnedPaths = new Set(pinned.map((script) => script.path));
7722
8008
  return ENVIRONMENTS2.flatMap((environment) => {
7723
8009
  const environmentHelpers = helpers.filter((helper) => helper.environment === environment).map((helper) => ({ kind: "helper", helper }));
7724
8010
  const orderedModules = [
8011
+ ...libraries.filter((script) => script.environment === environment),
7725
8012
  ...pinned.filter((script) => script.environment === environment),
7726
8013
  ...scripts.filter((script) => script.environment === environment && !pinnedPaths.has(script.path))
7727
8014
  ].map((module) => ({ kind: "module", module }));
@@ -7779,6 +8066,9 @@ var OCCUPIED_SIDES = {
7779
8066
  function occupiedSides(contribution) {
7780
8067
  return OCCUPIED_SIDES[contribution.side];
7781
8068
  }
8069
+ function sidesOf(environment) {
8070
+ return OCCUPIED_SIDES[environment];
8071
+ }
7782
8072
  function exportSignature(type) {
7783
8073
  if (type === void 0 || type.kind !== "function") {
7784
8074
  return null;
@@ -7801,10 +8091,11 @@ function toContributions(directives, environment, globals) {
7801
8091
  var INDENT = " ";
7802
8092
  var RESOURCE_TYPE = "script";
7803
8093
  var DEFAULT_SIDE = "server";
7804
- var SIDE_ORDER = { shared: 0, server: 1, client: 2 };
7805
- var SCRIPT_GROUPS = ["library", "configuration", "source"];
8094
+ var SIDE_ORDER2 = { shared: 0, server: 1, client: 2 };
8095
+ var SCRIPT_GROUPS = ["library", "libraries", "configuration", "source"];
7806
8096
  var GROUP_COMMENTS = {
7807
8097
  library: "Runtime library",
8098
+ libraries: "Libraries",
7808
8099
  configuration: "Configuration",
7809
8100
  source: "Source scripts"
7810
8101
  };
@@ -7867,7 +8158,7 @@ function versionElement(version) {
7867
8158
  return `${INDENT}<min_mta_version ${attribute("server", version)} ${attribute("client", version)} />`;
7868
8159
  }
7869
8160
  function exportElements(contributions) {
7870
- return [...contributions].sort((left, right) => SIDE_ORDER[left.side] - SIDE_ORDER[right.side] || left.name.localeCompare(right.name)).map(exportElement);
8161
+ return [...contributions].sort((left, right) => SIDE_ORDER2[left.side] - SIDE_ORDER2[right.side] || left.name.localeCompare(right.name)).map(exportElement);
7871
8162
  }
7872
8163
  function scriptSections(scripts) {
7873
8164
  return SCRIPT_GROUPS.flatMap((group) => section(GROUP_COMMENTS[group], scripts.filter((script) => script.group === group).map(scriptElement)));
@@ -7951,16 +8242,39 @@ function collectDevelopmentLogHelpers(options) {
7951
8242
  replacements
7952
8243
  }));
7953
8244
  }
8245
+ function emitted(modules) {
8246
+ return modules.filter((module) => module.code !== null);
8247
+ }
7954
8248
  function collectScripts(modules) {
7955
- return modules.filter((module) => module.code !== null).map((module) => ({ path: outputPath(module.path), source: module.path, environment: module.environment, content: module.code, lines: module.lines })).sort((left, right) => ENVIRONMENT_ORDER[left.environment] - ENVIRONMENT_ORDER[right.environment] || left.path.localeCompare(right.path));
8249
+ return emitted(modules).filter((module) => module.origin === null).map((module) => ({ path: outputPath(module.path), source: module.path, environment: module.environment, content: module.code, lines: module.lines })).sort((left, right) => ENVIRONMENT_ORDER[left.environment] - ENVIRONMENT_ORDER[right.environment] || left.path.localeCompare(right.path));
8250
+ }
8251
+ function libraryScript(origin, environment, content, lines) {
8252
+ const script = {
8253
+ path: libraryOutputPath(origin.package, environment, origin.relativePath),
8254
+ source: libraryFilePath(origin.package, origin.relativePath),
8255
+ environment,
8256
+ content,
8257
+ lines
8258
+ };
8259
+ return { origin, script };
8260
+ }
8261
+ function collectLibraryScripts(modules, files = []) {
8262
+ const compiled = emitted(modules).filter((module) => module.origin !== null).map((module) => libraryScript(module.origin, module.environment, module.code, module.lines));
8263
+ const verbatim = files.map((file) => libraryScript(file.origin, file.environment, file.content, []));
8264
+ return [...compiled, ...verbatim].sort((left, right) => compareLibraryOrigins(left.origin, right.origin)).map((entry) => entry.script);
7956
8265
  }
7957
8266
  var MISSING_LOAD_ORDER = 'is listed in "loadOrder" but no source file or asset matches it. Remove the entry or correct the path.';
8267
+ var LIBRARY_LOAD_ORDER = 'is listed in "loadOrder" but belongs to a library. Library scripts load in the order "libraries" declares. Remove the entry.';
7958
8268
  function missingEntry(entry) {
7959
8269
  return { path: entry, diagnostic: createDiagnostic("project", "project-load-order-missing", `"${entry}" ${MISSING_LOAD_ORDER}`, FILE_START) };
7960
8270
  }
7961
- function resolveLoadOrder(entries3, scripts, assets) {
8271
+ function libraryEntry(entry) {
8272
+ return { path: entry, diagnostic: createDiagnostic("project", "project-load-order-library", `"${entry}" ${LIBRARY_LOAD_ORDER}`, FILE_START) };
8273
+ }
8274
+ function resolveLoadOrder(entries3, scripts, assets, libraries = []) {
7962
8275
  const byScript = new Map(scripts.map((script) => [normalizePath(script.source), script]));
7963
8276
  const byAsset = new Map(assets.map((asset) => [normalizePath(asset.source), asset]));
8277
+ const byLibrary = new Set(libraries.map((script) => normalizePath(script.source)));
7964
8278
  const resolved2 = { scripts: [], assets: [], diagnostics: [] };
7965
8279
  const seen = /* @__PURE__ */ new Set();
7966
8280
  for (const entry of entries3) {
@@ -7972,7 +8286,7 @@ function resolveLoadOrder(entries3, scripts, assets) {
7972
8286
  }
7973
8287
  seen.add(path);
7974
8288
  if (script === void 0 && asset === void 0) {
7975
- resolved2.diagnostics.push(missingEntry(entry));
8289
+ resolved2.diagnostics.push(byLibrary.has(path) ? libraryEntry(entry) : missingEntry(entry));
7976
8290
  continue;
7977
8291
  }
7978
8292
  if (script !== void 0) {
@@ -8133,9 +8447,9 @@ function withEnvironmentFile(helpers, file) {
8133
8447
  function helperEntry(helper) {
8134
8448
  return { src: helper.path, environment: helper.environment, group: "library" };
8135
8449
  }
8136
- function manifestScripts(helpers, configuration, sources) {
8450
+ function manifestScripts(helpers, libraries, configuration, sources) {
8137
8451
  const settings = configuration === null ? [] : [{ src: configuration.path, environment: "shared", group: "configuration" }];
8138
- return [...helpers.map(helperEntry), ...settings, ...sources];
8452
+ return [...helpers.map(helperEntry), ...libraries, ...settings, ...sources];
8139
8453
  }
8140
8454
  function collectContributions(project) {
8141
8455
  return project.modules.flatMap((module) => module.contributions);
@@ -8208,32 +8522,35 @@ function assembleResource(project, options, onStep) {
8208
8522
  const collected = [...collectHelpers(project.modules, options.helpers ?? []), ...collectDevelopmentLogHelpers(options.developmentLogs)];
8209
8523
  const helpers = withEnvironmentFile(collected, options.environmentFile);
8210
8524
  const scripts = collectScripts(project.modules);
8525
+ const libraries = collectLibraryScripts(project.modules, options.libraryFiles ?? []);
8211
8526
  const configuration = configurationScript(options.configuration);
8212
8527
  const deployment = configuration === null ? [] : [configuration];
8213
- const sorted = [...options.assets ?? []].sort((left, right) => left.path.localeCompare(right.path));
8214
- const order = resolveLoadOrder(options.loadOrder ?? [], scripts, sorted);
8528
+ const sorted2 = [...options.assets ?? []].sort((left, right) => left.path.localeCompare(right.path));
8529
+ const order = resolveLoadOrder(options.loadOrder ?? [], scripts, sorted2, libraries);
8215
8530
  const layout = options.layout ?? "tree";
8216
- const bundles = layout === "bundle" ? collectBundles(helpers, scripts, order.scripts) : [];
8217
- const duplicates = layout === "tree" ? findDuplicateOutputs([...scripts, ...deployment], sorted) : [...findDuplicateOutputs(scripts, []), ...findDuplicateOutputs(deployment, sorted)];
8218
- const collisions = layout === "bundle" ? bundleDiagnostics(project, bundles, scripts, sorted) : [];
8531
+ const bundles = layout === "bundle" ? collectBundles(helpers, scripts, order.scripts, libraries) : [];
8532
+ const duplicates = layout === "tree" ? findDuplicateOutputs([...libraries, ...scripts, ...deployment], sorted2) : [...findDuplicateOutputs([...libraries, ...scripts], []), ...findDuplicateOutputs(deployment, sorted2)];
8533
+ const collisions = layout === "bundle" ? bundleDiagnostics(project, bundles, scripts, sorted2) : [];
8219
8534
  const diagnostics = sortFileDiagnostics([...project.diagnostics, ...duplicates, ...collisions, ...order.diagnostics]);
8220
8535
  if (duplicates.length > 0 || collisions.length > 0 || order.diagnostics.length > 0) {
8221
8536
  return { build: null, diagnostics };
8222
8537
  }
8223
8538
  onStep?.("assembly");
8224
- const assets = orderAssets(sorted, order.assets);
8539
+ const assets = orderAssets(sorted2, order.assets);
8225
8540
  const sources = layout === "tree" ? sourceEntries(scripts, order.scripts).map((entry) => ({ ...entry, group: "source" })) : bundles.map((bundle) => ({ src: bundle.path, environment: bundle.environment, group: "source" }));
8226
8541
  const manifestHelpers = layout === "tree" ? helpers : [];
8542
+ const vendored = layout === "tree" ? libraries.map((script) => ({ src: script.path, environment: script.environment, group: "libraries" })) : [];
8227
8543
  const manifest = generateManifest(
8228
8544
  manifestInfo(options),
8229
- manifestScripts(manifestHelpers, configuration, sources),
8545
+ manifestScripts(manifestHelpers, vendored, configuration, sources),
8230
8546
  manifestFiles(assets),
8231
8547
  collectContributions(project),
8232
8548
  { oop: options.oop === true, minMtaVersion: options.minMtaVersion ?? null, dependencies: options.dependencies ?? [] }
8233
8549
  );
8234
8550
  onStep?.("manifest");
8235
- const map = layout === "tree" ? treeResourceMap(options.resourceName ?? "", scripts) : null;
8236
- return { build: { manifest, scripts: layout === "tree" ? scripts : [], helpers, configuration, assets, bundles, layout, map }, diagnostics };
8551
+ const written = [...libraries, ...scripts];
8552
+ const map = layout === "tree" ? treeResourceMap(options.resourceName ?? "", written) : null;
8553
+ return { build: { manifest, scripts: layout === "tree" ? written : [], helpers, configuration, assets, bundles, layout, map }, diagnostics };
8237
8554
  }
8238
8555
 
8239
8556
  // src/build/asset-resolution.ts
@@ -8243,14 +8560,14 @@ var MANIFEST_OUTPUT = "meta.xml";
8243
8560
  var HERE = ".";
8244
8561
  function isDirectory2(root, path) {
8245
8562
  try {
8246
- return statSync3(resolve6(root, path)).isDirectory();
8563
+ return statSync3(resolve7(root, path)).isDirectory();
8247
8564
  } catch {
8248
8565
  return false;
8249
8566
  }
8250
8567
  }
8251
8568
  function exists(root, path) {
8252
8569
  try {
8253
- statSync3(resolve6(root, path));
8570
+ statSync3(resolve7(root, path));
8254
8571
  return true;
8255
8572
  } catch {
8256
8573
  return false;
@@ -8318,10 +8635,10 @@ var CONFIGURATION_FILE = "config.lua";
8318
8635
  var MALFORMED_ENV = "build-env-malformed";
8319
8636
  var MISSING_ENV = "config-missing-env-file";
8320
8637
  function readTextFile(path) {
8321
- return existsSync4(path) ? readFileSync5(path, "utf8") : null;
8638
+ return existsSync5(path) ? readFileSync6(path, "utf8") : null;
8322
8639
  }
8323
8640
  function readEnvironment(root, file, required, diagnostics) {
8324
- const source = readTextFile(resolve7(root, file));
8641
+ const source = readTextFile(resolve8(root, file));
8325
8642
  if (source === null) {
8326
8643
  if (required) {
8327
8644
  diagnostics.push(cliError(MISSING_ENV, `"${file}" is configured under "environment" but does not exist. Create it or remove the setting.`));
@@ -8335,7 +8652,7 @@ function readEnvironment(root, file, required, diagnostics) {
8335
8652
  return parsed;
8336
8653
  }
8337
8654
  function readConfiguration(root, diagnostics) {
8338
- const content = readTextFile(resolve7(root, CONFIGURATION_FILE));
8655
+ const content = readTextFile(resolve8(root, CONFIGURATION_FILE));
8339
8656
  if (content === null) {
8340
8657
  return null;
8341
8658
  }
@@ -8354,14 +8671,14 @@ function readProjectInputs(root, options) {
8354
8671
  }
8355
8672
 
8356
8673
  // src/build/source-discovery.ts
8357
- import { readFileSync as readFileSync6 } from "node:fs";
8358
- import { resolve as resolve8 } from "node:path";
8674
+ import { readFileSync as readFileSync7 } from "node:fs";
8675
+ import { resolve as resolve9 } from "node:path";
8359
8676
 
8360
8677
  // ../compiler/src/project/source-mapping.ts
8361
8678
  function createSourceResolver(mapping) {
8362
8679
  const matchers = new Map(SOURCE_SIDES.map((environment) => [environment, createPatternMatcher(mapping[environment])]));
8363
8680
  const patterns = SOURCE_SIDES.flatMap((environment) => [...matchers.get(environment)?.patterns ?? []]);
8364
- function resolve21(path) {
8681
+ function resolve24(path) {
8365
8682
  const normalized = normalizePattern(path);
8366
8683
  const matches = [];
8367
8684
  for (const environment of SOURCE_SIDES) {
@@ -8375,25 +8692,14 @@ function createSourceResolver(mapping) {
8375
8692
  return {
8376
8693
  patterns,
8377
8694
  roots: watchRoots(patterns),
8378
- resolve: resolve21,
8379
- side: (path) => resolve21(path).environment
8695
+ resolve: resolve24,
8696
+ side: (path) => resolve24(path).environment
8380
8697
  };
8381
8698
  }
8382
8699
  function describeMatches(matches) {
8383
8700
  return matches.map((match) => `"${match.environment}" through "${match.pattern}"`).join(" and ");
8384
8701
  }
8385
8702
 
8386
- // ../compiler/src/project/source-kind.ts
8387
- var SOURCE_EXTENSION = ".luam";
8388
- var DECLARATION_EXTENSION = ".d.luam";
8389
- var TEST_EXTENSION = ".test.luam";
8390
- function isDeclarationPath(path) {
8391
- return normalizePath(path).endsWith(DECLARATION_EXTENSION);
8392
- }
8393
- function isTestPath(path) {
8394
- return normalizePath(path).endsWith(TEST_EXTENSION);
8395
- }
8396
-
8397
8703
  // src/build/source-discovery.ts
8398
8704
  var MISSING_SOURCE = "config-missing-source";
8399
8705
  var NO_SOURCES = "config-no-sources";
@@ -8419,7 +8725,7 @@ function checkLiterals(root, resolver, found, diagnostics) {
8419
8725
  }
8420
8726
  function readSource(root, path, diagnostics) {
8421
8727
  try {
8422
- return readFileSync6(resolve8(root, path), "utf8");
8728
+ return readFileSync7(resolve9(root, path), "utf8");
8423
8729
  } catch (error) {
8424
8730
  diagnostics.push(cliError(UNREADABLE_SOURCE, `The source file "${path}" could not be read: ${error instanceof Error ? error.message : String(error)}`));
8425
8731
  return null;
@@ -14990,12 +15296,12 @@ function failed(type) {
14990
15296
  function entries2(parts) {
14991
15297
  return `{ ${parts.join(", ")} }`;
14992
15298
  }
14993
- function combine(parts, render2) {
15299
+ function combine(parts, render3) {
14994
15300
  const broken = parts.find((part) => part.lua === null);
14995
15301
  if (broken !== void 0) {
14996
15302
  return broken;
14997
15303
  }
14998
- return { lua: render2(parts.map((part) => part.lua)), unreifiable: null };
15304
+ return { lua: render3(parts.map((part) => part.lua)), unreifiable: null };
14999
15305
  }
15000
15306
  function namedDescriptor(context, type, name, seen) {
15001
15307
  if (context.declarations.lookupClass(name) !== null) {
@@ -18610,60 +18916,60 @@ function selectCandidate(context, expression, forms) {
18610
18916
  });
18611
18917
  return found[0] ?? null;
18612
18918
  }
18613
- function specialize(signature, candidate) {
18919
+ function specialize(signature2, candidate) {
18614
18920
  const callback = candidate.form.callbackArgument;
18615
18921
  if (callback !== void 0) {
18616
- const parameters = [...signature.parameters];
18922
+ const parameters = [...signature2.parameters];
18617
18923
  parameters[callback] = candidate.handler;
18618
- return { ...signature, parameters };
18924
+ return { ...signature2, parameters };
18619
18925
  }
18620
18926
  const payload = candidate.form.payloadArgument;
18621
18927
  if (payload === void 0) {
18622
18928
  return null;
18623
18929
  }
18624
18930
  return createFunction(
18625
- [...signature.parameters.slice(0, payload), ...candidate.handler.parameters],
18626
- signature.returnType,
18931
+ [...signature2.parameters.slice(0, payload), ...candidate.handler.parameters],
18932
+ signature2.returnType,
18627
18933
  payload + candidate.handler.minimumArguments,
18628
18934
  candidate.handler.isVariadic,
18629
18935
  void 0,
18630
18936
  candidate.handler.variadicType
18631
18937
  );
18632
18938
  }
18633
- function specializeEventCall(context, expression, signature) {
18634
- if (signature.kind !== "function" || expression.method !== null || expression.callee.kind !== "identifier") {
18939
+ function specializeEventCall(context, expression, signature2) {
18940
+ if (signature2.kind !== "function" || expression.method !== null || expression.callee.kind !== "identifier") {
18635
18941
  return null;
18636
18942
  }
18637
18943
  const forms = eventCallForms(expression.callee.name);
18638
18944
  const candidate = selectCandidate(context, expression, forms);
18639
- return candidate === null ? null : specialize(signature, candidate);
18945
+ return candidate === null ? null : specialize(signature2, candidate);
18640
18946
  }
18641
18947
 
18642
18948
  // ../compiler/src/checker/generic-call.ts
18643
18949
  function countArguments(total) {
18644
18950
  return total === 1 ? "1 type argument" : `${total} type arguments`;
18645
18951
  }
18646
- function substituteSignature(signature, substitutions) {
18647
- const parameters = signature.parameters.map((parameter) => substituteType(parameter, substitutions));
18648
- const returnType = substituteType(signature.returnType, substitutions);
18649
- const variadicType = signature.variadicType === void 0 ? void 0 : substituteType(signature.variadicType, substitutions);
18650
- return createFunction(parameters, returnType, signature.minimumArguments, signature.isVariadic, signature.parameterNames, variadicType);
18952
+ function substituteSignature(signature2, substitutions) {
18953
+ const parameters = signature2.parameters.map((parameter) => substituteType(parameter, substitutions));
18954
+ const returnType = substituteType(signature2.returnType, substitutions);
18955
+ const variadicType = signature2.variadicType === void 0 ? void 0 : substituteType(signature2.variadicType, substitutions);
18956
+ return createFunction(parameters, returnType, signature2.minimumArguments, signature2.isVariadic, signature2.parameterNames, variadicType);
18651
18957
  }
18652
- function specializeCall(context, owner, signature, argumentTypes, typeArguments, position2) {
18653
- const names = signature.typeParameters ?? [];
18958
+ function specializeCall(context, owner, signature2, argumentTypes, typeArguments, position2) {
18959
+ const names = signature2.typeParameters ?? [];
18654
18960
  if (names.length === 0) {
18655
18961
  if (typeArguments.length > 0) {
18656
18962
  context.report("check-generic-arity", `${owner} does not accept type arguments.`, position2);
18657
18963
  }
18658
- return signature;
18964
+ return signature2;
18659
18965
  }
18660
18966
  const explicit = typeArguments.map((argument) => context.resolveAnnotation(argument));
18661
18967
  if (typeArguments.length > 0 && explicit.length !== names.length) {
18662
18968
  context.report("check-generic-arity", `${owner} expects ${countArguments(names.length)} but received ${explicit.length}.`, position2);
18663
18969
  }
18664
- const resolved2 = typeArguments.length > 0 ? names.map((unused, index) => explicit[index] ?? ANY_TYPE) : inferTypeArguments(names, signature.parameters, argumentTypes);
18665
- reportConstraintViolations(context, owner, names, signature.typeConstraints ?? [], resolved2, position2);
18666
- return substituteSignature(signature, new Map(names.map((name, index) => [name, resolved2[index] ?? ANY_TYPE])));
18970
+ const resolved2 = typeArguments.length > 0 ? names.map((unused, index) => explicit[index] ?? ANY_TYPE) : inferTypeArguments(names, signature2.parameters, argumentTypes);
18971
+ reportConstraintViolations(context, owner, names, signature2.typeConstraints ?? [], resolved2, position2);
18972
+ return substituteSignature(signature2, new Map(names.map((name, index) => [name, resolved2[index] ?? ANY_TYPE])));
18667
18973
  }
18668
18974
 
18669
18975
  // ../compiler/src/checker/method-receiver.ts
@@ -18675,13 +18981,13 @@ function resolveNonNominalMethod(receiver, method) {
18675
18981
  const member = receiver.members.get(method);
18676
18982
  return member !== void 0 && member.kind === "function" ? member : null;
18677
18983
  }
18678
- function withoutSelfParameter(signature) {
18679
- const names = signature.parameterNames;
18984
+ function withoutSelfParameter(signature2) {
18985
+ const names = signature2.parameterNames;
18680
18986
  if (names === void 0 || names[0] !== SELF_PARAMETER) {
18681
- return signature;
18987
+ return signature2;
18682
18988
  }
18683
- const minimum = Math.max(0, signature.minimumArguments - 1);
18684
- return createFunction(signature.parameters.slice(1), signature.returnType, minimum, signature.isVariadic, names.slice(1), signature.variadicType);
18989
+ const minimum = Math.max(0, signature2.minimumArguments - 1);
18990
+ return createFunction(signature2.parameters.slice(1), signature2.returnType, minimum, signature2.isVariadic, names.slice(1), signature2.variadicType);
18685
18991
  }
18686
18992
 
18687
18993
  // ../compiler/src/checker/resource-exports.ts
@@ -18979,6 +19285,7 @@ function resolveMtaMember(context, className, name, position2) {
18979
19285
  }
18980
19286
 
18981
19287
  // ../compiler/src/checker/members.ts
19288
+ var SELF_RECEIVER = "self";
18982
19289
  function checkEnumMember(context, name, members, expression) {
18983
19290
  if (members.includes(expression.property)) {
18984
19291
  return NUMBER_TYPE;
@@ -19039,6 +19346,35 @@ function resolveStaticMember(context, className, expression) {
19039
19346
  context.report("check-unknown-member", `Class "${className}" has no static member "${expression.property}".${hint}`, expression.position);
19040
19347
  return ANY_TYPE;
19041
19348
  }
19349
+ function isFullyDeclared(context, className) {
19350
+ const visited = /* @__PURE__ */ new Set();
19351
+ let current = context.declarations.lookupClass(className);
19352
+ while (current !== null && !visited.has(current.name)) {
19353
+ visited.add(current.name);
19354
+ if (current.hasUnresolvedParent === true || current.interfaces.some((name) => context.declarations.lookupInterface(name) === null)) {
19355
+ return false;
19356
+ }
19357
+ if (current.superClass === null) {
19358
+ return true;
19359
+ }
19360
+ current = context.declarations.lookupClass(current.superClass);
19361
+ }
19362
+ return current !== null;
19363
+ }
19364
+ function isSelfReceiver(receiver) {
19365
+ return receiver.kind === "identifier" && receiver.name === SELF_RECEIVER;
19366
+ }
19367
+ function reportUnknownClassMember(context, className, property, position2) {
19368
+ const names = context.declarations.collectMembers(className).map((member) => `"${member.name}"`);
19369
+ const known = names.length === 0 ? "It declares no members." : `Declared members: ${names.join(", ")}.`;
19370
+ context.report("check-unknown-member", `Class "${className}" has no member "${property}". ${known}`, position2);
19371
+ }
19372
+ function checkClassMember(context, className, expression) {
19373
+ if (!isSelfReceiver(expression.object)) {
19374
+ reportUnknownClassMember(context, className, expression.property, expression.position);
19375
+ }
19376
+ return ANY_TYPE;
19377
+ }
19042
19378
  function resolveNamedMember(context, receiver, expression) {
19043
19379
  const name = receiver.name;
19044
19380
  const enumeration = context.declarations.lookupEnum(name);
@@ -19064,13 +19400,39 @@ function resolveNamedMember(context, receiver, expression) {
19064
19400
  if (context.awaitsDeclaration(name)) {
19065
19401
  return ANY_TYPE;
19066
19402
  }
19067
- const contract = context.declarations.lookupClass(name) === null ? context.declarations.lookupInterface(name) : null;
19403
+ if (context.declarations.lookupClass(name) !== null) {
19404
+ return isFullyDeclared(context, name) ? checkClassMember(context, name, expression) : null;
19405
+ }
19406
+ const contract = context.declarations.lookupInterface(name);
19068
19407
  if (contract === null) {
19069
19408
  return null;
19070
19409
  }
19071
19410
  const members = new Map(context.declarations.collectMembers(name).map((member2) => [member2.name, member2]));
19072
19411
  return checkInterfaceMember(context, name, members, expression);
19073
19412
  }
19413
+ function reportUnknownMethod(context, receiver, callee, method, position2) {
19414
+ const name = receiver.name;
19415
+ if (context.declarations.lookupEnum(name) !== null || context.awaitsDeclaration(name) || isSelfReceiver(callee)) {
19416
+ return;
19417
+ }
19418
+ const staticMember = context.declarations.lookupStaticMember(name, method);
19419
+ if (staticMember !== null) {
19420
+ const message = `"${method}" is a static member of class "${name}". Call it as "${name}.${method}(...)".`;
19421
+ context.report("check-static-receiver", message, position2);
19422
+ return;
19423
+ }
19424
+ if (context.declarations.lookupClass(name) !== null) {
19425
+ if (isFullyDeclared(context, name)) {
19426
+ reportUnknownClassMember(context, name, method, position2);
19427
+ }
19428
+ return;
19429
+ }
19430
+ if (context.declarations.lookupInterface(name) === null) {
19431
+ return;
19432
+ }
19433
+ const keys = context.declarations.collectMembers(name).map((member) => `"${member.name}"`).join(", ");
19434
+ context.report("check-unknown-member", `Interface "${name}" has no member "${method}". Declared members: ${keys}.`, position2);
19435
+ }
19074
19436
  var NATIVE_CONSTRUCTOR = "new";
19075
19437
  function nativeConstructor(context, name) {
19076
19438
  const symbol = context.binder.lookup(name);
@@ -19127,9 +19489,9 @@ function checkNewExpression(context, expression) {
19127
19489
  const constructor = context.declarations.lookupMember(expression.className, "constructor");
19128
19490
  if (constructor !== null && constructor.type.kind === "function") {
19129
19491
  const substitutions = info === null ? /* @__PURE__ */ new Map() : classSubstitutions(info, typeArguments);
19130
- const signature = substituteType(constructor.type, substitutions);
19131
- if (signature.kind === "function") {
19132
- checkSignature(context, expression.args, signature, expression.position);
19492
+ const signature2 = substituteType(constructor.type, substitutions);
19493
+ if (signature2.kind === "function") {
19494
+ checkSignature(context, expression.args, signature2, expression.position);
19133
19495
  }
19134
19496
  } else {
19135
19497
  expression.args.forEach((argument) => checkExpression(context, argument));
@@ -19658,7 +20020,7 @@ function reportMissingReturn(context, declared, position2) {
19658
20020
  const repair = declared.kind === "tuple" ? "Add a return on every path." : `Add a return on every path, or declare "${name}?".`;
19659
20021
  context.report("check-missing-return", `This function declares "${name}" but can end without returning a value. ${repair}`, position2);
19660
20022
  }
19661
- function checkFunctionBody(context, parameters, returnAnnotation, body, signature, selfType2, position2 = ORIGIN) {
20023
+ function checkFunctionBody(context, parameters, returnAnnotation, body, signature2, selfType2, position2 = ORIGIN) {
19662
20024
  forgetAssignedPaths(context, body);
19663
20025
  const entry = context.flowState;
19664
20026
  context.setFlow(cloneFlow(entry));
@@ -19669,7 +20031,7 @@ function checkFunctionBody(context, parameters, returnAnnotation, body, signatur
19669
20031
  }
19670
20032
  let parameterIndex = 0;
19671
20033
  for (const parameter of parameters) {
19672
- const type = parameter.isVararg ? ANY_TYPE : signature.parameters[parameterIndex] ?? ANY_TYPE;
20034
+ const type = parameter.isVararg ? ANY_TYPE : signature2.parameters[parameterIndex] ?? ANY_TYPE;
19673
20035
  context.binder.declare({ name: parameter.name, type, isLocal: true, position: parameter.position, origin: "parameter" });
19674
20036
  if (!parameter.isVararg) {
19675
20037
  parameterIndex += 1;
@@ -19682,7 +20044,7 @@ function checkFunctionBody(context, parameters, returnAnnotation, body, signatur
19682
20044
  }
19683
20045
  const inferred = context.popReturnType();
19684
20046
  if (returnAnnotation === null) {
19685
- signature.returnType = inferred;
20047
+ signature2.returnType = inferred;
19686
20048
  }
19687
20049
  context.binder.popScope();
19688
20050
  context.setFlow(entry);
@@ -19982,32 +20344,32 @@ function countArguments2(total) {
19982
20344
  }
19983
20345
  function checkSignature(context, args, declared, position2, owner = "This call", typeArguments = []) {
19984
20346
  const argumentTypes = checkValueList(context, args, declared.parameters);
19985
- const signature = specializeCall(context, owner, declared, argumentTypes, typeArguments, position2);
19986
- if (argumentTypes.length < signature.minimumArguments) {
19987
- const message = `This call expects at least ${countArguments2(signature.minimumArguments)} but received ${argumentTypes.length}.`;
20347
+ const signature2 = specializeCall(context, owner, declared, argumentTypes, typeArguments, position2);
20348
+ if (argumentTypes.length < signature2.minimumArguments) {
20349
+ const message = `This call expects at least ${countArguments2(signature2.minimumArguments)} but received ${argumentTypes.length}.`;
19988
20350
  context.report("check-argument-count", message, position2);
19989
20351
  }
19990
- if (!signature.isVariadic && argumentTypes.length > signature.parameters.length) {
19991
- const message = `This call expects at most ${countArguments2(signature.parameters.length)} but received ${argumentTypes.length}.`;
20352
+ if (!signature2.isVariadic && argumentTypes.length > signature2.parameters.length) {
20353
+ const message = `This call expects at most ${countArguments2(signature2.parameters.length)} but received ${argumentTypes.length}.`;
19992
20354
  context.report("check-argument-count", message, position2);
19993
20355
  }
19994
- signature.parameters.forEach((parameter, index) => {
20356
+ signature2.parameters.forEach((parameter, index) => {
19995
20357
  const argumentType = argumentTypes[index];
19996
20358
  const argument = args[index];
19997
20359
  if (argumentType !== void 0 && argument !== void 0) {
19998
20360
  context.expectAssignable(argumentType, parameter, argument.position, `Argument ${index + 1}`);
19999
20361
  }
20000
20362
  });
20001
- if (signature.isVariadic && signature.variadicType !== void 0) {
20002
- for (let index = signature.parameters.length; index < argumentTypes.length; index += 1) {
20363
+ if (signature2.isVariadic && signature2.variadicType !== void 0) {
20364
+ for (let index = signature2.parameters.length; index < argumentTypes.length; index += 1) {
20003
20365
  const argumentType = argumentTypes[index];
20004
20366
  const argument = args[Math.min(index, args.length - 1)];
20005
20367
  if (argumentType !== void 0 && argument !== void 0) {
20006
- context.expectAssignable(argumentType, signature.variadicType, argument.position, `Argument ${index + 1}`);
20368
+ context.expectAssignable(argumentType, signature2.variadicType, argument.position, `Argument ${index + 1}`);
20007
20369
  }
20008
20370
  }
20009
20371
  }
20010
- return signature.returnType;
20372
+ return signature2.returnType;
20011
20373
  }
20012
20374
  function callOwner(expression) {
20013
20375
  const name = expression.method ?? (expression.callee.kind === "identifier" ? expression.callee.name : null);
@@ -20029,12 +20391,12 @@ function isLegacySuperCall(expression) {
20029
20391
  }
20030
20392
  function checkMethodCall(context, expression, method, receiver) {
20031
20393
  if (receiver.kind !== "named") {
20032
- const signature = resolveNonNominalMethod(receiver, method);
20033
- if (signature === null) {
20394
+ const signature2 = resolveNonNominalMethod(receiver, method);
20395
+ if (signature2 === null) {
20034
20396
  checkValueList(context, expression.args);
20035
20397
  return ANY_TYPE;
20036
20398
  }
20037
- return checkSignature(context, expression.args, withoutSelfParameter(signature), expression.position, callOwner(expression), expression.typeArguments);
20399
+ return checkSignature(context, expression.args, withoutSelfParameter(signature2), expression.position, callOwner(expression), expression.typeArguments);
20038
20400
  }
20039
20401
  const declared = memberOf(context, receiver, method);
20040
20402
  if (declared?.type.kind === "function") {
@@ -20044,6 +20406,9 @@ function checkMethodCall(context, expression, method, receiver) {
20044
20406
  return checkSignature(context, expression.args, declared.type, expression.position, callOwner(expression), expression.typeArguments);
20045
20407
  }
20046
20408
  if (!isMtaElement(context, receiver.name)) {
20409
+ if (declared === null) {
20410
+ reportUnknownMethod(context, receiver, expression.callee, method, expression.position);
20411
+ }
20047
20412
  checkValueList(context, expression.args);
20048
20413
  return ANY_TYPE;
20049
20414
  }
@@ -20104,8 +20469,8 @@ function checkTable(context, expression) {
20104
20469
  }
20105
20470
  return checkExpression(context, field2.value);
20106
20471
  });
20107
- const isRecord = expression.fields.every((field2) => field2.name !== null);
20108
- if (isRecord) {
20472
+ const isRecord2 = expression.fields.every((field2) => field2.name !== null);
20473
+ if (isRecord2) {
20109
20474
  const members = /* @__PURE__ */ new Map();
20110
20475
  expression.fields.forEach((field2, index) => {
20111
20476
  if (field2.name !== null) {
@@ -20328,18 +20693,22 @@ function checkMethodBody(context, info, member) {
20328
20693
  if (explicitSelf !== void 0) {
20329
20694
  context.report("check-explicit-self-parameter", 'Class methods receive "self" automatically; remove it from the parameter list.', explicitSelf.position);
20330
20695
  }
20331
- const signature = (member.isStatic ? info.statics : info.members).get(member.name)?.type;
20332
- if (signature?.kind !== "function") {
20696
+ const signature2 = (member.isStatic ? info.statics : info.members).get(member.name)?.type;
20697
+ if (signature2?.kind !== "function") {
20333
20698
  return;
20334
20699
  }
20335
20700
  if (member.isStatic) {
20336
- checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, null, member.position);
20701
+ checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature2, null, member.position);
20337
20702
  return;
20338
20703
  }
20339
20704
  context.pushClassMethod({ className: info.name, methodName: member.name });
20340
- checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, selfType(info), member.position);
20705
+ checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature2, selfType(info), member.position);
20341
20706
  context.popClassMethod();
20342
20707
  }
20708
+ function assignSuperClass(context, info, statement) {
20709
+ info.superClass = resolveSuperClass(context, statement);
20710
+ info.hasUnresolvedParent = statement.superClass !== null && info.superClass === null;
20711
+ }
20343
20712
  function resolveSuperClass(context, statement) {
20344
20713
  if (statement.superClass === null) {
20345
20714
  return null;
@@ -20394,7 +20763,7 @@ function declareLocalClass(context, statement) {
20394
20763
  if (info === null) {
20395
20764
  return null;
20396
20765
  }
20397
- info.superClass = resolveSuperClass(context, statement);
20766
+ assignSuperClass(context, info, statement);
20398
20767
  resolveClassHeader(context, info, statement);
20399
20768
  return info;
20400
20769
  }
@@ -20508,7 +20877,7 @@ function breakCycle(context, info) {
20508
20877
  function predeclareModule(context, body) {
20509
20878
  const declared = declareHeaders(context, body);
20510
20879
  for (const entry of declared) {
20511
- entry.info.superClass = resolveSuperClass(context, entry.statement);
20880
+ assignSuperClass(context, entry.info, entry.statement);
20512
20881
  resolveClassHeader(context, entry.info, entry.statement);
20513
20882
  }
20514
20883
  for (const entry of declared) {
@@ -20685,9 +21054,9 @@ function finalizeEmission(code, markers, sourceLineOffset = 0) {
20685
21054
  continue;
20686
21055
  }
20687
21056
  const marker = markers[Number(match[1])];
20688
- const emitted = line2.replace(pattern, "");
21057
+ const emitted2 = line2.replace(pattern, "");
20689
21058
  if (marker === void 0) {
20690
- output.push(emitted);
21059
+ output.push(emitted2);
20691
21060
  continue;
20692
21061
  }
20693
21062
  while (output.length + 1 < marker.sourceLine - sourceLineOffset) {
@@ -20697,7 +21066,7 @@ function finalizeEmission(code, markers, sourceLineOffset = 0) {
20697
21066
  if (marker.symbol !== void 0) {
20698
21067
  mapping.symbol = marker.symbol;
20699
21068
  }
20700
- output.push(emitted);
21069
+ output.push(emitted2);
20701
21070
  const sameSegment = previous !== null && previous.symbol === mapping.symbol && previous.sourceLine - previous.generatedLine === mapping.sourceLine - mapping.generatedLine;
20702
21071
  if (!sameSegment) {
20703
21072
  mappings.push(mapping);
@@ -20838,10 +21207,10 @@ function emitFunctionBody(state, parameters, body, header) {
20838
21207
  const lines = emitBlock(state, body);
20839
21208
  state.loopWrap = previous;
20840
21209
  state.indent -= 1;
20841
- const signature = `${header}(${emitParameters(parameters)})`;
21210
+ const signature2 = `${header}(${emitParameters(parameters)})`;
20842
21211
  const closing = indentLine(state, "end");
20843
- return lines.length === 0 ? `${signature}
20844
- ${closing}` : `${signature}
21212
+ return lines.length === 0 ? `${signature2}
21213
+ ${closing}` : `${signature2}
20845
21214
  ${lines.join("\n")}
20846
21215
  ${closing}`;
20847
21216
  }
@@ -21535,8 +21904,8 @@ function erasedEdit(source, span) {
21535
21904
  }
21536
21905
  function canonicalEdit(input, statement, span) {
21537
21906
  const { source } = input;
21538
- const emitted = emit2({ ...input.program, body: [statement] }, input.types, input.references, input.generatedMembers, statement.position.line - 1, input.staticAccess);
21539
- const trimmed = span.end < source.length && emitted.code.endsWith("\n") ? emitted.code.slice(0, -1) : emitted.code;
21907
+ const emitted2 = emit2({ ...input.program, body: [statement] }, input.types, input.references, input.generatedMembers, statement.position.line - 1, input.staticAccess);
21908
+ const trimmed = span.end < source.length && emitted2.code.endsWith("\n") ? emitted2.code.slice(0, -1) : emitted2.code;
21540
21909
  if (trimmed.length === 0) {
21541
21910
  return erasedEdit(source, span);
21542
21911
  }
@@ -21547,7 +21916,7 @@ function canonicalEdit(input, statement, span) {
21547
21916
  return null;
21548
21917
  }
21549
21918
  const padded = missing2 > 0 && source.slice(span.end).trim().length > 0 ? `${replacement}${"\n".repeat(missing2)}` : replacement;
21550
- return { start: span.start, end: span.end, replacement: padded, lines: emitted.lines };
21919
+ return { start: span.start, end: span.end, replacement: padded, lines: emitted2.lines };
21551
21920
  }
21552
21921
 
21553
21922
  // ../compiler/src/emitter/preserve-decorators.ts
@@ -22020,7 +22389,7 @@ function compile(source, options = {}) {
22020
22389
  const settings = options.compilerOptions ?? DEFAULT_COMPILER_OPTIONS;
22021
22390
  const parsed = parse(source);
22022
22391
  const mode = resolveStrictMode(parsed.directives, settings.strict);
22023
- const resolved2 = resolveEnvironment(options.filePath ?? null, parsed.directives, options.environment ?? null);
22392
+ const resolved2 = resolveEnvironment(options.filePath ?? null, parsed.directives, options.environment ?? null, options.environmentLocked === true);
22024
22393
  const environment = resolved2.environment;
22025
22394
  const isDeclarationFile = options.filePath !== void 0 && isDeclarationPath(options.filePath);
22026
22395
  const checked = check(parsed.program, mode, environment, {
@@ -22050,7 +22419,7 @@ function compile(source, options = {}) {
22050
22419
  return { ...shared, code: null, requiredHelpers: [], lines: [] };
22051
22420
  }
22052
22421
  const references = reachedNames(checked.references, options.projectReferences);
22053
- const emitted = emit2(parsed.program, checked.types, references, checked.generatedMembers, 0, checked.staticAccess);
22422
+ const emitted2 = emit2(parsed.program, checked.types, references, checked.generatedMembers, 0, checked.staticAccess);
22054
22423
  const preserved = emitPreservingSource({
22055
22424
  source,
22056
22425
  program: parsed.program,
@@ -22063,7 +22432,7 @@ function compile(source, options = {}) {
22063
22432
  staticAccess: checked.staticAccess,
22064
22433
  development: options.development === true
22065
22434
  });
22066
- const required = new Set(emitted.requiredHelpers);
22435
+ const required = new Set(emitted2.requiredHelpers);
22067
22436
  for (const name of checked.references) {
22068
22437
  const helper = checked.declaredGlobals.has(name) ? null : helperForGlobal(name);
22069
22438
  if (helper !== null) {
@@ -22072,9 +22441,9 @@ function compile(source, options = {}) {
22072
22441
  }
22073
22442
  return {
22074
22443
  ...shared,
22075
- code: preserved?.code ?? emitted.code,
22444
+ code: preserved?.code ?? emitted2.code,
22076
22445
  requiredHelpers: expandHelpers(required).sort(),
22077
- lines: preserved?.lines ?? emitted.lines
22446
+ lines: preserved?.lines ?? emitted2.lines
22078
22447
  };
22079
22448
  }
22080
22449
 
@@ -22291,11 +22660,20 @@ function baseKey(context) {
22291
22660
  const contracts = context.contracts.map((contract) => serializeAbi(contract)).join(";");
22292
22661
  return `${optionsKey(context.options)}|${mode}|${JSON.stringify(context.project.globals)}|${[...context.references].sort().join(",")}|${hashString(contracts)}`;
22293
22662
  }
22663
+ function isVisible(environment, isLibrary2, provider) {
22664
+ return canReference(environment, provider.environment) && (!isLibrary2 || provider.isLibrary);
22665
+ }
22666
+ function scopeKey(environment, isLibrary2) {
22667
+ return `${environment}|${isLibrary2 ? "library" : "project"}`;
22668
+ }
22669
+ var SCOPES = [false, true];
22294
22670
  function environmentKeys(collected) {
22295
- const keys = {};
22671
+ const keys = /* @__PURE__ */ new Map();
22296
22672
  for (const environment of ALL_ENVIRONMENTS) {
22297
- const visible = collected.filter((entry) => canReference(environment, entry.environment));
22298
- keys[environment] = hashString(visible.map((entry) => `${entry.path}=${entry.fingerprint}`).join(";"));
22673
+ for (const isLibrary2 of SCOPES) {
22674
+ const visible = collected.filter((entry) => isVisible(environment, isLibrary2, entry));
22675
+ keys.set(scopeKey(environment, isLibrary2), hashString(visible.map((entry) => `${entry.path}=${entry.fingerprint}`).join(";")));
22676
+ }
22299
22677
  }
22300
22678
  return keys;
22301
22679
  }
@@ -22303,15 +22681,19 @@ function createResolver(collected) {
22303
22681
  const visible = /* @__PURE__ */ new Map();
22304
22682
  const merged = /* @__PURE__ */ new Map();
22305
22683
  for (const environment of ALL_ENVIRONMENTS) {
22306
- const entries3 = collected.filter((entry) => canReference(environment, entry.environment) && !declaresNothing(entry.declarations));
22307
- visible.set(environment, entries3);
22308
- merged.set(environment, mergeAmbient(entries3.map((entry) => entry.declarations)));
22684
+ for (const isLibrary2 of SCOPES) {
22685
+ const key = scopeKey(environment, isLibrary2);
22686
+ const entries3 = collected.filter((entry) => isVisible(environment, isLibrary2, entry) && !declaresNothing(entry.declarations));
22687
+ visible.set(key, entries3);
22688
+ merged.set(key, mergeAmbient(entries3.map((entry) => entry.declarations)));
22689
+ }
22309
22690
  }
22310
22691
  return (entry) => {
22692
+ const key = scopeKey(entry.environment, entry.isLibrary);
22311
22693
  if (declaresNothing(entry.declarations)) {
22312
- return merged.get(entry.environment) ?? EMPTY_AMBIENT;
22694
+ return merged.get(key) ?? EMPTY_AMBIENT;
22313
22695
  }
22314
- const entries3 = visible.get(entry.environment) ?? [];
22696
+ const entries3 = visible.get(key) ?? [];
22315
22697
  return mergeAmbient(entries3.filter((candidate) => candidate.path !== entry.path).map((candidate) => candidate.declarations));
22316
22698
  };
22317
22699
  }
@@ -22321,7 +22703,7 @@ function createAmbientScope(collected, context) {
22321
22703
  const fallback = environmentKeys(collected);
22322
22704
  return {
22323
22705
  graph,
22324
- keyFor: (entry) => hashString(`${base}|${graph.keyOf(entry.path) ?? fallback[entry.environment]}`),
22706
+ keyFor: (entry) => hashString(`${base}|${graph.keyOf(entry.path) ?? fallback.get(scopeKey(entry.environment, entry.isLibrary)) ?? ""}`),
22325
22707
  resolve: createResolver(collected)
22326
22708
  };
22327
22709
  }
@@ -22366,13 +22748,96 @@ function validateContributions(modules) {
22366
22748
  }
22367
22749
  return diagnostics;
22368
22750
  }
22751
+ var LIBRARY_COLLISION = "project-library-collision";
22752
+ var LIBRARY_SHADOWS_API = "project-library-shadows-api";
22753
+ function describeOwner(owner) {
22754
+ return owner.module.origin === null ? `the project file "${owner.module.path}"` : `the library "${owner.module.origin.package}"`;
22755
+ }
22756
+ function describeSides(sides) {
22757
+ return sides.length > 1 ? '"shared"' : `"${sides[0] ?? "shared"}"`;
22758
+ }
22759
+ function collisionDiagnostic(collision) {
22760
+ const { name, intruder, other } = collision;
22761
+ const subject = `"${name}" is declared by ${describeOwner(intruder)} and by ${describeOwner(other)} on the ${describeSides(collision.sides)} side`;
22762
+ const message = `${subject}. MTA keeps one global per name, so one silently replaces the other. Stop using one of them.`;
22763
+ return { path: intruder.module.path, diagnostic: createDiagnostic("project", LIBRARY_COLLISION, message, intruder.position) };
22764
+ }
22765
+ function recordCollision(collisions, name, side2, first, next) {
22766
+ if (first.module.origin === null && next.module.origin === null) {
22767
+ return;
22768
+ }
22769
+ const intruder = next.module.origin === null ? first : next;
22770
+ const other = intruder === next ? first : next;
22771
+ const key = `${name}:${intruder.module.path}:${other.module.path}`;
22772
+ const found = collisions.get(key);
22773
+ if (found === void 0) {
22774
+ collisions.set(key, { name, intruder, other, sides: [side2] });
22775
+ return;
22776
+ }
22777
+ found.sides.push(side2);
22778
+ }
22779
+ function validateLibraryGlobals(modules) {
22780
+ const owners = /* @__PURE__ */ new Map();
22781
+ const collisions = /* @__PURE__ */ new Map();
22782
+ if (!modules.some((module) => module.origin !== null)) {
22783
+ return [];
22784
+ }
22785
+ for (const module of modules) {
22786
+ for (const [name, position2] of module.declaredGlobals) {
22787
+ for (const side2 of sidesOf(module.environment)) {
22788
+ const key = `${side2}:${name}`;
22789
+ const first = owners.get(key);
22790
+ if (first === void 0) {
22791
+ owners.set(key, { module, position: position2 });
22792
+ continue;
22793
+ }
22794
+ recordCollision(collisions, name, side2, first, { module, position: position2 });
22795
+ }
22796
+ }
22797
+ }
22798
+ return [...collisions.values()].map(collisionDiagnostic);
22799
+ }
22800
+ function shadowsApi(name, sides) {
22801
+ return sides.find((side2) => builtinSymbols(side2).has(name)) ?? null;
22802
+ }
22803
+ function validateLibraryApiShadowing(modules) {
22804
+ const diagnostics = [];
22805
+ for (const module of modules) {
22806
+ if (module.origin === null) {
22807
+ continue;
22808
+ }
22809
+ for (const [name, position2] of module.declaredGlobals) {
22810
+ const side2 = shadowsApi(name, sidesOf(module.environment));
22811
+ if (side2 === null) {
22812
+ continue;
22813
+ }
22814
+ const subject = `The library "${module.origin.package}" declares "${name}", which the MTA API defines for the "${side2}" side`;
22815
+ const message = `${subject}. Every file in the resource sees the library's version. Keep it only if the library means to wrap it.`;
22816
+ diagnostics.push({ path: module.path, diagnostic: createDiagnostic("project", LIBRARY_SHADOWS_API, message, position2, "warning") });
22817
+ }
22818
+ }
22819
+ return diagnostics;
22820
+ }
22821
+ function projectReferenceDiagnostic(module, symbol, position2) {
22822
+ const subject = `"${symbol.name}" is declared by the project file "${symbol.path}" and cannot be used from the library "${module.origin?.package ?? ""}"`;
22823
+ const message = `${subject}. A library sees only its own files and the libraries it requires.`;
22824
+ return { path: module.path, diagnostic: createDiagnostic("project", "project-library-project-reference", message, position2) };
22825
+ }
22369
22826
  function validateModuleReferences(modules) {
22370
22827
  const symbols = buildSymbolTable(modules);
22828
+ const libraries = new Set(modules.filter((module) => module.origin !== null).map((module) => module.path));
22371
22829
  const diagnostics = [];
22372
22830
  for (const module of modules) {
22373
22831
  for (const [name, position2] of module.externalReferences) {
22374
22832
  const symbol = symbols.get(name);
22375
- if (symbol === void 0 || symbol.path === module.path || canReference(module.environment, symbol.environment)) {
22833
+ if (symbol === void 0 || symbol.path === module.path) {
22834
+ continue;
22835
+ }
22836
+ if (module.origin !== null && !libraries.has(symbol.path)) {
22837
+ diagnostics.push(projectReferenceDiagnostic(module, symbol, position2));
22838
+ continue;
22839
+ }
22840
+ if (canReference(module.environment, symbol.environment)) {
22376
22841
  continue;
22377
22842
  }
22378
22843
  diagnostics.push(referenceDiagnostic(module, symbol, position2));
@@ -22389,6 +22854,7 @@ function compileModule(file, ambient, context) {
22389
22854
  const result = compile(file.source, {
22390
22855
  filePath: file.path,
22391
22856
  ...file.environment === void 0 ? {} : { environment: file.environment },
22857
+ environmentLocked: file.origin !== void 0,
22392
22858
  ambient,
22393
22859
  contracts: context.contracts,
22394
22860
  project: isTestPath(file.path) ? context.testProject : context.project,
@@ -22398,6 +22864,7 @@ function compileModule(file, ambient, context) {
22398
22864
  });
22399
22865
  return {
22400
22866
  path: file.path,
22867
+ origin: file.origin ?? null,
22401
22868
  environment: result.environment,
22402
22869
  isDeclaration: isDeclarationPath(file.path),
22403
22870
  code: result.code,
@@ -22427,7 +22894,7 @@ function createProjectCache() {
22427
22894
  const declarationCache = /* @__PURE__ */ new Map();
22428
22895
  const moduleCache = /* @__PURE__ */ new Map();
22429
22896
  function declarationsFor(file, options, reused) {
22430
- const hash = hashString(`${file.environment ?? ""}|${optionsKey(options)}|${file.source}`);
22897
+ const hash = hashString(`${file.environment ?? ""}|${file.origin?.root ?? ""}|${optionsKey(options)}|${file.source}`);
22431
22898
  const cached = declarationCache.get(file.path);
22432
22899
  if (cached !== void 0 && cached.hash === hash) {
22433
22900
  reused.count += 1;
@@ -22436,11 +22903,13 @@ function createProjectCache() {
22436
22903
  const result = compile(file.source, {
22437
22904
  filePath: file.path,
22438
22905
  ...file.environment === void 0 ? {} : { environment: file.environment },
22906
+ environmentLocked: file.origin !== void 0,
22439
22907
  compilerOptions: options,
22440
22908
  emitCode: false
22441
22909
  });
22442
22910
  const entry = {
22443
22911
  path: file.path,
22912
+ isLibrary: file.origin !== void 0,
22444
22913
  hash,
22445
22914
  environment: result.environment,
22446
22915
  declarations: result.declarations,
@@ -22491,7 +22960,8 @@ function createProjectCache() {
22491
22960
  });
22492
22961
  const references = validateModuleReferences(modules);
22493
22962
  const contributions = validateContributions(modules);
22494
- const collectedDiagnostics = sortFileDiagnostics([...flattenDiagnostics(modules), ...references, ...contributions]);
22963
+ const libraries = [...validateLibraryGlobals(modules), ...validateLibraryApiShadowing(modules)];
22964
+ const collectedDiagnostics = sortFileDiagnostics([...flattenDiagnostics(modules), ...references, ...contributions, ...libraries]);
22495
22965
  const diagnostics = compilerOptions.warningsAsErrors ? promoteWarnings(collectedDiagnostics) : collectedDiagnostics;
22496
22966
  const paths = new Set(files.map((file) => file.path));
22497
22967
  prune(declarationCache, paths);
@@ -22519,7 +22989,7 @@ function helperList(config, inputs) {
22519
22989
  const helpers = [...config.helpers, "env"];
22520
22990
  return helpers.sort();
22521
22991
  }
22522
- function resourceOptions(config, inputs, minMtaVersion, developmentLogs, layout) {
22992
+ function resourceOptions(config, inputs, minMtaVersion, developmentLogs, layout, libraryFiles) {
22523
22993
  const options = {
22524
22994
  oop: config.compilerOptions.oop,
22525
22995
  dependencies: config.dependencies,
@@ -22528,6 +22998,7 @@ function resourceOptions(config, inputs, minMtaVersion, developmentLogs, layout)
22528
22998
  configuration: inputs.configuration,
22529
22999
  environmentFile: config.environment.file,
22530
23000
  loadOrder: config.loadOrder,
23001
+ libraryFiles,
22531
23002
  minMtaVersion,
22532
23003
  developmentLogs,
22533
23004
  layout,
@@ -22570,9 +23041,11 @@ function runCompile(root, config, options = {}) {
22570
23041
  const excluded = [config.outDir, config.contracts];
22571
23042
  const sources = discoverSources(root, config.sources, excluded);
22572
23043
  const inputs = readProjectInputs(root, { assets: config.assets, environment: config.environment, excluded });
22573
- const files = [...sources.files, ...options.additionalFiles ?? []].sort((left, right) => left.path.localeCompare(right.path));
23044
+ const libraries = resolveLibraries(root, config.libraries);
23045
+ const projectFiles = [...sources.files, ...options.additionalFiles ?? []].sort((left, right) => left.path.localeCompare(right.path));
23046
+ const files = [...libraries.files, ...projectFiles];
22574
23047
  const contracts = readDependencyContracts(root, config);
22575
- const diagnostics = [...sources.diagnostics, ...inputs.diagnostics, ...contracts.diagnostics];
23048
+ const diagnostics = [...sources.diagnostics, ...inputs.diagnostics, ...libraries.diagnostics, ...contracts.diagnostics];
22576
23049
  if (hasCliErrors(diagnostics)) {
22577
23050
  tracker.end("failed");
22578
23051
  return {
@@ -22601,7 +23074,7 @@ function runCompile(root, config, options = {}) {
22601
23074
  tracker.begin("assembly");
22602
23075
  const assembly = assembleResource(
22603
23076
  project,
22604
- resourceOptions(config, inputs, options.minMtaVersion ?? null, options.developmentLogs ?? null, options.layout ?? "tree"),
23077
+ resourceOptions(config, inputs, options.minMtaVersion ?? null, options.developmentLogs ?? null, options.layout ?? "tree", libraries.verbatim),
22605
23078
  (step) => {
22606
23079
  if (step === "assembly") {
22607
23080
  tracker.begin("manifest");
@@ -22626,8 +23099,8 @@ function runCompile(root, config, options = {}) {
22626
23099
  }
22627
23100
 
22628
23101
  // src/build/resource-map-file.ts
22629
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
22630
- import { dirname as dirname3, isAbsolute as isAbsolute3, join as join4, posix, relative as relative2, resolve as resolve9 } from "node:path";
23102
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
23103
+ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join4, posix, relative as relative2, resolve as resolve10 } from "node:path";
22631
23104
  var RESOURCE_MAP_SUFFIX = ".luam-map.json";
22632
23105
  function isObject(value) {
22633
23106
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -22692,28 +23165,28 @@ function reason(error) {
22692
23165
  return error instanceof Error ? error.message : String(error);
22693
23166
  }
22694
23167
  function resourceMapPath(root, config) {
22695
- return resolve9(root, config.outDir, `${config.name}${RESOURCE_MAP_SUFFIX}`);
23168
+ return resolve10(root, config.outDir, `${config.name}${RESOURCE_MAP_SUFFIX}`);
22696
23169
  }
22697
23170
  function explicitResourceMapPath(root, path) {
22698
- return isAbsolute3(path) ? path : resolve9(root, path);
23171
+ return isAbsolute3(path) ? path : resolve10(root, path);
22699
23172
  }
22700
23173
  function writeResourceMap(root, config, map) {
22701
23174
  const path = resourceMapPath(root, config);
22702
23175
  if (map === null) {
22703
- if (existsSync5(path)) {
23176
+ if (existsSync6(path)) {
22704
23177
  unlinkSync(path);
22705
23178
  }
22706
23179
  return;
22707
23180
  }
22708
23181
  mkdirSync3(dirname3(path), { recursive: true });
22709
23182
  const content = serializeResourceMap(map);
22710
- if (!existsSync5(path) || readFileSync7(path, "utf8") !== content) {
23183
+ if (!existsSync6(path) || readFileSync8(path, "utf8") !== content) {
22711
23184
  writeFileSync4(path, content, "utf8");
22712
23185
  }
22713
23186
  }
22714
23187
  function readResourceMap(path) {
22715
23188
  try {
22716
- const value = JSON.parse(readFileSync7(path, "utf8"));
23189
+ const value = JSON.parse(readFileSync8(path, "utf8"));
22717
23190
  if (isObject(value) && Number.isInteger(value.version) && value.version !== RESOURCE_MAP_VERSION) {
22718
23191
  return { map: null, error: `Resource map version ${String(value.version)} is not supported.` };
22719
23192
  }
@@ -22747,7 +23220,7 @@ function collectResourceMaps(directory, found) {
22747
23220
  }
22748
23221
  }
22749
23222
  function discoverResourceMap(root) {
22750
- if (!existsSync5(root)) {
23223
+ if (!existsSync6(root)) {
22751
23224
  return { path: null, candidates: [] };
22752
23225
  }
22753
23226
  const found = [];
@@ -22757,8 +23230,8 @@ function discoverResourceMap(root) {
22757
23230
  }
22758
23231
 
22759
23232
  // src/build/resource-writer.ts
22760
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
22761
- import { dirname as dirname4, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve11 } from "node:path";
23233
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
23234
+ import { dirname as dirname4, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve12 } from "node:path";
22762
23235
 
22763
23236
  // src/build/lua-tokens.ts
22764
23237
  var WHITESPACE = /* @__PURE__ */ new Set([" ", " ", "\n", "\r", "\v", "\f"]);
@@ -22961,8 +23434,8 @@ function minifyLuaFiles(files) {
22961
23434
  }
22962
23435
 
22963
23436
  // src/build/resource-prune.ts
22964
- import { existsSync as existsSync6, readdirSync as readdirSync3, rmdirSync, unlinkSync as unlinkSync2 } from "node:fs";
22965
- import { join as join5, relative as relative3, resolve as resolve10 } from "node:path";
23437
+ import { existsSync as existsSync7, readdirSync as readdirSync3, rmdirSync, unlinkSync as unlinkSync2 } from "node:fs";
23438
+ import { join as join5, relative as relative3, resolve as resolve11 } from "node:path";
22966
23439
  var GENERATED_MANIFEST = "meta.xml";
22967
23440
  var GENERATED_EXTENSION = ".lua";
22968
23441
  var PROTECTED = /* @__PURE__ */ new Set([ENVIRONMENT_FILE, ".env.local"]);
@@ -22979,17 +23452,17 @@ function isGenerated(relativePath2, options) {
22979
23452
  return options.generatedRoots.some((root) => isUnderRoot(relativePath2, root));
22980
23453
  }
22981
23454
  function removeEmptyDirectories(targetDir, directory) {
22982
- if (resolve10(directory) === resolve10(targetDir)) {
23455
+ if (resolve11(directory) === resolve11(targetDir)) {
22983
23456
  return;
22984
23457
  }
22985
23458
  if (readdirSync3(directory).length > 0) {
22986
23459
  return;
22987
23460
  }
22988
23461
  rmdirSync(directory);
22989
- removeEmptyDirectories(targetDir, resolve10(directory, ".."));
23462
+ removeEmptyDirectories(targetDir, resolve11(directory, ".."));
22990
23463
  }
22991
23464
  function pruneResource(targetDir, keep, options) {
22992
- if (!existsSync6(targetDir)) {
23465
+ if (!existsSync7(targetDir)) {
22993
23466
  return [];
22994
23467
  }
22995
23468
  const removed = [];
@@ -23039,7 +23512,7 @@ function resourceFiles(build) {
23039
23512
  return files;
23040
23513
  }
23041
23514
  function resolveInside(targetDir, path) {
23042
- const absolute = resolve11(targetDir, path);
23515
+ const absolute = resolve12(targetDir, path);
23043
23516
  const inside = relative4(targetDir, absolute);
23044
23517
  if (inside.startsWith("..") || isAbsolute4(inside)) {
23045
23518
  throw new Error(`The generated file "${path}" resolves outside the resource directory "${targetDir}".`);
@@ -23047,7 +23520,7 @@ function resolveInside(targetDir, path) {
23047
23520
  return absolute;
23048
23521
  }
23049
23522
  function writeIfChanged(absolute, content) {
23050
- if (existsSync7(absolute) && readFileSync8(absolute).equals(content)) {
23523
+ if (existsSync8(absolute) && readFileSync9(absolute).equals(content)) {
23051
23524
  return false;
23052
23525
  }
23053
23526
  mkdirSync4(dirname4(absolute), { recursive: true });
@@ -23059,7 +23532,7 @@ function writeEnvironmentFile(targetDir, template) {
23059
23532
  return false;
23060
23533
  }
23061
23534
  const absolute = resolveInside(targetDir, ENVIRONMENT_FILE);
23062
- if (existsSync7(absolute)) {
23535
+ if (existsSync8(absolute)) {
23063
23536
  return false;
23064
23537
  }
23065
23538
  mkdirSync4(dirname4(absolute), { recursive: true });
@@ -23087,7 +23560,7 @@ function writeResource(targetDir, build, options) {
23087
23560
  advance(path);
23088
23561
  }
23089
23562
  for (const asset of build.assets) {
23090
- if (writeIfChanged(resolveInside(targetDir, asset.path), readFileSync8(resolve11(options.root, asset.source)))) {
23563
+ if (writeIfChanged(resolveInside(targetDir, asset.path), readFileSync9(resolve12(options.root, asset.source)))) {
23091
23564
  written.push(asset.path);
23092
23565
  advance(asset.path);
23093
23566
  continue;
@@ -23117,7 +23590,7 @@ function destinationRoot(mapping) {
23117
23590
  }
23118
23591
  function generatedRoots(config) {
23119
23592
  const roots = config.assets.map(destinationRoot).filter((root) => root.length > 0);
23120
- return [.../* @__PURE__ */ new Set([...roots, LIBRARY_DIRECTORY])];
23593
+ return [.../* @__PURE__ */ new Set([...roots, LIBRARY_DIRECTORY, LIBRARIES_DIRECTORY])];
23121
23594
  }
23122
23595
  function trackedWriteOptions(root, config, environmentTemplate, tracker) {
23123
23596
  return {
@@ -23204,16 +23677,16 @@ function reportBuildOutcome(context, outcome, action) {
23204
23677
  }
23205
23678
 
23206
23679
  // src/commands/resource-targets.ts
23207
- import { isAbsolute as isAbsolute5, resolve as resolve12 } from "node:path";
23680
+ import { isAbsolute as isAbsolute5, resolve as resolve13 } from "node:path";
23208
23681
  function resolveBuildTarget(root, config) {
23209
- return resolve12(root, config.outDir, config.name);
23682
+ return resolve13(root, config.outDir, config.name);
23210
23683
  }
23211
23684
  function resolveServerTarget(root, config) {
23212
23685
  if (config.serverPath === null) {
23213
23686
  return null;
23214
23687
  }
23215
- const serverRoot = isAbsolute5(config.serverPath) ? config.serverPath : resolve12(root, config.serverPath);
23216
- return resolve12(serverRoot, config.resourcesDir, config.name);
23688
+ const serverRoot = isAbsolute5(config.serverPath) ? config.serverPath : resolve13(root, config.serverPath);
23689
+ return resolve13(serverRoot, config.resourcesDir, config.name);
23217
23690
  }
23218
23691
 
23219
23692
  // src/reporting/progress-renderer.ts
@@ -23258,7 +23731,7 @@ function createProgressRenderer(reporter, options = {}) {
23258
23731
  paint(reporter.style.eraseLine());
23259
23732
  dirty = false;
23260
23733
  }
23261
- function render2(event) {
23734
+ function render3(event) {
23262
23735
  const columns = Math.max(20, reporter.capability.columns - 1);
23263
23736
  const line2 = activeLine(reporter, event);
23264
23737
  paint(`${reporter.style.eraseLine()}${line2.slice(0, columns)}`);
@@ -23278,7 +23751,7 @@ function createProgressRenderer(reporter, options = {}) {
23278
23751
  if (frames > 0 && now() - lastPaintAt < throttleMs) {
23279
23752
  return;
23280
23753
  }
23281
- render2(event);
23754
+ render3(event);
23282
23755
  }
23283
23756
  return {
23284
23757
  listen: (event) => {
@@ -23321,6 +23794,7 @@ async function runBuildCommand(context, options = {}) {
23321
23794
  if (outcome.build === null) {
23322
23795
  reportBuildOutcome(context, outcome, "Build");
23323
23796
  reportPhaseTimings(reporter, tracker.durations(), totalDuration(tracker.durations()));
23797
+ options.onOutcome?.(outcome);
23324
23798
  return EXIT_DIAGNOSTICS;
23325
23799
  }
23326
23800
  const target = resolveBuildTarget(context.root, context.config);
@@ -23333,6 +23807,7 @@ async function runBuildCommand(context, options = {}) {
23333
23807
  tracker.end("failed");
23334
23808
  renderer.clear();
23335
23809
  reporter.error(`Build wrote nothing to "${target}". ${error instanceof Error ? error.message : String(error)}`);
23810
+ options.onOutcome?.(outcome);
23336
23811
  return EXIT_DIAGNOSTICS;
23337
23812
  }
23338
23813
  writeResourceMap(context.root, context.config, writtenMap(outcome.map, minify));
@@ -23345,13 +23820,93 @@ async function runBuildCommand(context, options = {}) {
23345
23820
  const counts = `${result.unchanged} unchanged, ${result.removed.length} removed`;
23346
23821
  reporter.info(`Wrote ${pluralize(result.written.length, "file")} to "${target}" (${counts}).`);
23347
23822
  reportPhaseTimings(reporter, tracker.durations(), totalDuration(tracker.durations()));
23823
+ options.onOutcome?.(outcome);
23348
23824
  return EXIT_OK;
23349
23825
  }
23350
23826
 
23827
+ // src/reporting/json-report.ts
23828
+ var REPORT_SCHEMA_VERSION = 1;
23829
+ function fromCli(diagnostic) {
23830
+ return { path: null, line: null, column: null, endLine: null, endColumn: null, ...diagnostic };
23831
+ }
23832
+ function fromFile(entry) {
23833
+ const { position: position2, end, severity, code, message } = entry.diagnostic;
23834
+ return {
23835
+ path: entry.path,
23836
+ line: position2.line,
23837
+ column: position2.column,
23838
+ endLine: end === null ? null : end.line,
23839
+ endColumn: end === null ? null : end.column,
23840
+ severity,
23841
+ code,
23842
+ message
23843
+ };
23844
+ }
23845
+ function buildReport(command, outcome, success) {
23846
+ const cli = countCliDiagnostics(outcome.diagnostics);
23847
+ const files = countFileDiagnostics(outcome.fileDiagnostics);
23848
+ return {
23849
+ version: REPORT_SCHEMA_VERSION,
23850
+ luam: VERSION,
23851
+ command,
23852
+ success,
23853
+ diagnostics: [...outcome.diagnostics.map(fromCli), ...outcome.fileDiagnostics.map(fromFile)],
23854
+ summary: {
23855
+ errors: cli.errors + files.errors,
23856
+ warnings: cli.warnings + files.warnings,
23857
+ files: outcome.fileCount,
23858
+ durationMs: Math.round(outcome.durationMs)
23859
+ }
23860
+ };
23861
+ }
23862
+ function writeReport(logger, report2) {
23863
+ logger.info(JSON.stringify(report2));
23864
+ }
23865
+
23866
+ // src/reporting/silent.ts
23867
+ function createSilentLogger() {
23868
+ return {
23869
+ info: () => void 0,
23870
+ warn: () => void 0,
23871
+ error: () => void 0
23872
+ };
23873
+ }
23874
+ function createSilentReporter() {
23875
+ return createReporter(createSilentLogger(), PLAIN_CAPABILITY, () => void 0);
23876
+ }
23877
+
23878
+ // src/commands/json-command.ts
23879
+ function emptyReport(command, success) {
23880
+ return {
23881
+ version: REPORT_SCHEMA_VERSION,
23882
+ luam: VERSION,
23883
+ command,
23884
+ success,
23885
+ diagnostics: [],
23886
+ summary: { errors: success ? 0 : 1, warnings: 0, files: 0, durationMs: 0 }
23887
+ };
23888
+ }
23889
+ function captureJson(context) {
23890
+ let captured = null;
23891
+ return {
23892
+ context: { ...context, logger: createSilentLogger(), reporter: createSilentReporter() },
23893
+ outcome: () => captured,
23894
+ record: (outcome) => {
23895
+ captured = outcome;
23896
+ }
23897
+ };
23898
+ }
23899
+ function emitJson(logger, command, capture, code) {
23900
+ const outcome = capture.outcome();
23901
+ const success = code === EXIT_OK;
23902
+ writeReport(logger, outcome === null ? emptyReport(command, success) : buildReport(command, outcome, success));
23903
+ return code;
23904
+ }
23905
+
23351
23906
  // src/cli/registry/build-registration.ts
23352
23907
  function registerBuildCommand(program2, runtime) {
23353
23908
  const command = program2.command("build").description("Compile the project and write the resource to the output directory.");
23354
- addMinifyOptions(addLayoutOptions(addProjectOptions(command))).addOption(noMapOption()).addOption(offlineOption());
23909
+ addMinifyOptions(addLayoutOptions(addProjectOptions(command))).addOption(noMapOption()).addOption(offlineOption()).addOption(jsonOption());
23355
23910
  command.action(async (options) => {
23356
23911
  const project = createProjectContext(runtime, "build", options);
23357
23912
  if (project.context === null) {
@@ -23359,39 +23914,236 @@ function registerBuildCommand(program2, runtime) {
23359
23914
  return;
23360
23915
  }
23361
23916
  const bundled = options.bundle ?? project.context.config.output.bundle;
23362
- runtime.exitCode = await runBuildCommand(project.context, {
23917
+ const build = {
23363
23918
  layout: bundled ? "bundle" : "tree",
23364
23919
  map: options.map && project.context.config.output.map,
23365
23920
  minify: options.minify ?? project.context.config.output.minify
23366
- });
23921
+ };
23922
+ if (options.json !== true) {
23923
+ runtime.exitCode = await runBuildCommand(project.context, build);
23924
+ return;
23925
+ }
23926
+ const capture = captureJson(project.context);
23927
+ runtime.exitCode = emitJson(runtime.logger, "build", capture, await runBuildCommand(capture.context, { ...build, onOutcome: capture.record }));
23928
+ });
23929
+ }
23930
+
23931
+ // src/reporting/rebuild-separator.ts
23932
+ var RULE_WIDTH = 40;
23933
+ function formatTimestamp(at) {
23934
+ return at.toISOString().slice(11, 19);
23935
+ }
23936
+ function reportRebuildSeparator(reporter, at = /* @__PURE__ */ new Date()) {
23937
+ const rule = (reporter.capability.unicode ? "\u2500" : "-").repeat(RULE_WIDTH);
23938
+ reporter.raw("");
23939
+ reporter.detail(`${rule} rebuild at ${formatTimestamp(at)}`);
23940
+ }
23941
+
23942
+ // src/watch/abort-wait.ts
23943
+ function untilAborted(signal) {
23944
+ return new Promise((resolveLoop) => {
23945
+ if (signal?.aborted === true) {
23946
+ resolveLoop();
23947
+ return;
23948
+ }
23949
+ const stop = () => {
23950
+ process.off("SIGINT", stop);
23951
+ signal?.removeEventListener("abort", stop);
23952
+ resolveLoop();
23953
+ };
23954
+ if (signal !== null) {
23955
+ signal.addEventListener("abort", stop, { once: true });
23956
+ }
23957
+ process.on("SIGINT", stop);
23367
23958
  });
23368
23959
  }
23369
23960
 
23961
+ // src/watch/manifest-watcher.ts
23962
+ import { existsSync as existsSync10, watch as watch2 } from "node:fs";
23963
+
23964
+ // src/watch/source-watcher.ts
23965
+ import { existsSync as existsSync9, watch } from "node:fs";
23966
+ import { relative as relative5, resolve as resolve14 } from "node:path";
23967
+ var DEFAULT_DEBOUNCE_MS = 120;
23968
+ function watchSources(root, sources, onChange, debounceMs = DEFAULT_DEBOUNCE_MS) {
23969
+ const resolver = createSourceResolver(sources);
23970
+ const watchers = [];
23971
+ let timer = null;
23972
+ const schedule = () => {
23973
+ if (timer !== null) {
23974
+ clearTimeout(timer);
23975
+ }
23976
+ timer = setTimeout(() => {
23977
+ timer = null;
23978
+ onChange();
23979
+ }, debounceMs);
23980
+ };
23981
+ const isWatched = (directory, filename) => {
23982
+ const path = normalizePattern(relative5(root, resolve14(directory, filename)));
23983
+ return path.endsWith(SOURCE_EXTENSION) && resolver.resolve(path).matches.length > 0;
23984
+ };
23985
+ for (const entry of resolver.roots) {
23986
+ const absolute = resolve14(root, entry);
23987
+ if (!existsSync9(absolute)) {
23988
+ continue;
23989
+ }
23990
+ watchers.push(
23991
+ watch(absolute, { recursive: true }, (_event, filename) => {
23992
+ if (filename !== null && isWatched(absolute, filename.toString())) {
23993
+ schedule();
23994
+ }
23995
+ })
23996
+ );
23997
+ }
23998
+ return {
23999
+ close: () => {
24000
+ if (timer !== null) {
24001
+ clearTimeout(timer);
24002
+ timer = null;
24003
+ }
24004
+ for (const watcher of watchers) {
24005
+ watcher.close();
24006
+ }
24007
+ }
24008
+ };
24009
+ }
24010
+ function watchedRoots(sources) {
24011
+ return createSourceResolver(sources).roots;
24012
+ }
24013
+
24014
+ // src/watch/manifest-watcher.ts
24015
+ function watchManifest(path, onChange, debounceMs = DEFAULT_DEBOUNCE_MS) {
24016
+ let timer = null;
24017
+ let watcher = null;
24018
+ const clear = () => {
24019
+ if (timer !== null) {
24020
+ clearTimeout(timer);
24021
+ timer = null;
24022
+ }
24023
+ };
24024
+ if (existsSync10(path)) {
24025
+ watcher = watch2(path, () => {
24026
+ clear();
24027
+ timer = setTimeout(() => {
24028
+ timer = null;
24029
+ onChange();
24030
+ }, debounceMs);
24031
+ });
24032
+ }
24033
+ return {
24034
+ close: () => {
24035
+ clear();
24036
+ watcher?.close();
24037
+ }
24038
+ };
24039
+ }
24040
+
23370
24041
  // src/commands/check-command.ts
23371
- function runCheckCommand(context) {
24042
+ function runCheckCommand(context, options = {}) {
23372
24043
  const reporter = commandReporter(context);
23373
24044
  const renderer = createProgressRenderer(reporter);
23374
24045
  const tracker = createPhaseTracker(renderer.listen);
23375
- const outcome = runCompile(context.root, context.config, { tracker });
24046
+ const cache3 = options.cache;
24047
+ const outcome = runCompile(context.root, context.config, cache3 === void 0 ? { tracker } : { tracker, cache: cache3 });
23376
24048
  renderer.clear();
23377
24049
  const passed = reportBuildOutcome(context, outcome, "Check");
23378
24050
  reportPhaseTimings(reporter, tracker.durations(), totalDuration(tracker.durations()));
24051
+ options.onOutcome?.(outcome);
23379
24052
  return passed ? EXIT_OK : EXIT_DIAGNOSTICS;
23380
24053
  }
24054
+ async function runCheckWatch(context, options) {
24055
+ const reporter = commandReporter(context);
24056
+ let config = context.config;
24057
+ let cache3 = createProjectCache();
24058
+ const sources = [];
24059
+ let running = false;
24060
+ let queued = false;
24061
+ const announce = () => {
24062
+ const roots = watchedRoots(config.sources).map((entry) => `"${entry.length === 0 ? "." : entry}"`);
24063
+ reporter.info(`Watching ${roots.join(", ")} for changes. Press Ctrl+C to stop.`);
24064
+ };
24065
+ const recheck = () => {
24066
+ if (running) {
24067
+ queued = true;
24068
+ return;
24069
+ }
24070
+ running = true;
24071
+ do {
24072
+ queued = false;
24073
+ reportRebuildSeparator(reporter);
24074
+ runCheckCommand({ ...context, config }, { cache: cache3 });
24075
+ } while (queued);
24076
+ running = false;
24077
+ };
24078
+ const listen = () => {
24079
+ for (const watcher of sources.splice(0)) {
24080
+ watcher.close();
24081
+ }
24082
+ sources.push(watchSources(context.root, config.sources, recheck));
24083
+ };
24084
+ const reconfigure = () => {
24085
+ const reloaded = options.reload();
24086
+ if (reloaded !== null) {
24087
+ config = reloaded;
24088
+ }
24089
+ cache3 = createProjectCache();
24090
+ listen();
24091
+ recheck();
24092
+ announce();
24093
+ };
24094
+ runCheckCommand(context, { cache: cache3 });
24095
+ listen();
24096
+ const manifest = watchManifest(options.manifestPath, reconfigure);
24097
+ announce();
24098
+ await untilAborted(options.signal);
24099
+ manifest.close();
24100
+ for (const watcher of sources.splice(0)) {
24101
+ watcher.close();
24102
+ }
24103
+ return EXIT_OK;
24104
+ }
23381
24105
 
23382
24106
  // src/cli/registry/check-registration.ts
23383
24107
  function registerCheckCommand(program2, runtime) {
23384
24108
  const command = program2.command("check").description("Compile the project and report diagnostics without writing files.");
23385
- addProjectOptions(command);
23386
- command.action((options) => {
24109
+ addProjectOptions(command).addOption(new Option("--watch", "Keep the command running and re-check on source changes.")).addOption(new Option("--no-watch", "Run the command once and exit.")).addOption(jsonOption());
24110
+ command.action(async (options) => {
24111
+ if (options.json === true && options.watch === true) {
24112
+ runtime.reporter.error('"--json" writes one document and "--watch" never stops. Run "luam check --json" without "--watch".');
24113
+ runtime.exitCode = EXIT_USAGE;
24114
+ return;
24115
+ }
23387
24116
  const project = createProjectContext(runtime, "check", options);
23388
- runtime.exitCode = project.context === null ? project.error : runCheckCommand(project.context);
24117
+ if (project.context === null) {
24118
+ runtime.exitCode = project.error;
24119
+ return;
24120
+ }
24121
+ if (options.json === true) {
24122
+ const capture = captureJson(project.context);
24123
+ runtime.exitCode = emitJson(runtime.logger, "check", capture, runCheckCommand(capture.context, { onOutcome: capture.record }));
24124
+ return;
24125
+ }
24126
+ if (options.watch !== true) {
24127
+ runtime.exitCode = runCheckCommand(project.context);
24128
+ return;
24129
+ }
24130
+ const root = project.context.root;
24131
+ const manifestPath = resolveManifestPath(root, options.manifest ?? null);
24132
+ const reload = () => {
24133
+ const loaded = loadManifest(root, { path: options.manifest ?? null, mode: manifestMode("check"), env: runtime.env });
24134
+ reportManifestDiagnostics(runtime.reporter, loaded.path, loaded.source, loaded.diagnostics);
24135
+ if (loaded.config === null) {
24136
+ runtime.reporter.error(`Manifest "${loaded.path}" is invalid. Keeping the previous configuration.`);
24137
+ }
24138
+ return loaded.config;
24139
+ };
24140
+ runtime.exitCode = await runCheckWatch(project.context, { signal: runtime.overrides.signal ?? null, manifestPath, reload });
23389
24141
  });
23390
24142
  }
23391
24143
 
23392
24144
  // src/commands/config-command.ts
23393
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
23394
- import { dirname as dirname5, relative as relative5, resolve as resolve13 } from "node:path";
24145
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
24146
+ import { dirname as dirname5, relative as relative6, resolve as resolve15 } from "node:path";
23395
24147
 
23396
24148
  // ../compiler/src/project/config-reader.ts
23397
24149
  var Reader = class {
@@ -23605,33 +24357,33 @@ function isGeneratedDeclaration(text) {
23605
24357
  var DEFAULT_CONFIG_SOURCE = "config.lua";
23606
24358
  var DEFAULT_CONFIG_OUTPUT = "config.d.luam";
23607
24359
  function escapes(root, path) {
23608
- const inside = relative5(root, path);
23609
- return inside.startsWith("..") || resolve13(root, inside) !== path;
24360
+ const inside = relative6(root, path);
24361
+ return inside.startsWith("..") || resolve15(root, inside) !== path;
23610
24362
  }
23611
24363
  function readSource2(root, source, reporter) {
23612
- const path = resolve13(root, source);
24364
+ const path = resolve15(root, source);
23613
24365
  if (escapes(root, path)) {
23614
24366
  reporter.error(`Config declarations read only inside the project, but "${source}" points outside it.`);
23615
24367
  return null;
23616
24368
  }
23617
- if (!existsSync8(path)) {
24369
+ if (!existsSync11(path)) {
23618
24370
  reporter.error(`Config declarations found no file at "${source}".`);
23619
24371
  return null;
23620
24372
  }
23621
24373
  try {
23622
- return readFileSync9(path, "utf8");
24374
+ return readFileSync10(path, "utf8");
23623
24375
  } catch {
23624
24376
  reporter.error(`Config declarations could not read "${source}".`);
23625
24377
  return null;
23626
24378
  }
23627
24379
  }
23628
24380
  function writable(root, out, reporter) {
23629
- const path = resolve13(root, out);
24381
+ const path = resolve15(root, out);
23630
24382
  if (escapes(root, path)) {
23631
24383
  reporter.error(`Config declarations write only inside the project, but "${out}" points outside it.`);
23632
24384
  return null;
23633
24385
  }
23634
- if (existsSync8(path) && !isGeneratedDeclaration(readFileSync9(path, "utf8"))) {
24386
+ if (existsSync11(path) && !isGeneratedDeclaration(readFileSync10(path, "utf8"))) {
23635
24387
  reporter.error(`"${out}" was not generated by this command and was left alone. Choose another path with --out.`);
23636
24388
  return null;
23637
24389
  }
@@ -23764,85 +24516,7 @@ function createEnsureRunner(context, options = {}) {
23764
24516
  };
23765
24517
  }
23766
24518
 
23767
- // src/reporting/rebuild-separator.ts
23768
- var RULE_WIDTH = 40;
23769
- function formatTimestamp(at) {
23770
- return at.toISOString().slice(11, 19);
23771
- }
23772
- function reportRebuildSeparator(reporter, at = /* @__PURE__ */ new Date()) {
23773
- const rule = (reporter.capability.unicode ? "\u2500" : "-").repeat(RULE_WIDTH);
23774
- reporter.raw("");
23775
- reporter.detail(`${rule} rebuild at ${formatTimestamp(at)}`);
23776
- }
23777
-
23778
- // src/watch/source-watcher.ts
23779
- import { existsSync as existsSync9, watch } from "node:fs";
23780
- import { relative as relative6, resolve as resolve14 } from "node:path";
23781
- var DEFAULT_DEBOUNCE_MS = 120;
23782
- function watchSources(root, sources, onChange, debounceMs = DEFAULT_DEBOUNCE_MS) {
23783
- const resolver = createSourceResolver(sources);
23784
- const watchers = [];
23785
- let timer = null;
23786
- const schedule = () => {
23787
- if (timer !== null) {
23788
- clearTimeout(timer);
23789
- }
23790
- timer = setTimeout(() => {
23791
- timer = null;
23792
- onChange();
23793
- }, debounceMs);
23794
- };
23795
- const isWatched = (directory, filename) => {
23796
- const path = normalizePattern(relative6(root, resolve14(directory, filename)));
23797
- return path.endsWith(SOURCE_EXTENSION) && resolver.resolve(path).matches.length > 0;
23798
- };
23799
- for (const entry of resolver.roots) {
23800
- const absolute = resolve14(root, entry);
23801
- if (!existsSync9(absolute)) {
23802
- continue;
23803
- }
23804
- watchers.push(
23805
- watch(absolute, { recursive: true }, (_event, filename) => {
23806
- if (filename !== null && isWatched(absolute, filename.toString())) {
23807
- schedule();
23808
- }
23809
- })
23810
- );
23811
- }
23812
- return {
23813
- close: () => {
23814
- if (timer !== null) {
23815
- clearTimeout(timer);
23816
- timer = null;
23817
- }
23818
- for (const watcher of watchers) {
23819
- watcher.close();
23820
- }
23821
- }
23822
- };
23823
- }
23824
- function watchedRoots(sources) {
23825
- return createSourceResolver(sources).roots;
23826
- }
23827
-
23828
24519
  // src/commands/ensure-command.ts
23829
- function untilAborted(signal) {
23830
- return new Promise((resolveLoop) => {
23831
- if (signal?.aborted === true) {
23832
- resolveLoop();
23833
- return;
23834
- }
23835
- const stop = () => {
23836
- process.off("SIGINT", stop);
23837
- signal?.removeEventListener("abort", stop);
23838
- resolveLoop();
23839
- };
23840
- if (signal !== null) {
23841
- signal.addEventListener("abort", stop, { once: true });
23842
- }
23843
- process.on("SIGINT", stop);
23844
- });
23845
- }
23846
24520
  async function watchLoop(context, runner, options) {
23847
24521
  const reporter = commandReporter(context);
23848
24522
  let running = false;
@@ -24005,12 +24679,12 @@ function parseMtaLogLine(line2, activeResource, fallback = /* @__PURE__ */ new D
24005
24679
  }
24006
24680
 
24007
24681
  // src/logging/server-log-follower.ts
24008
- import { closeSync, existsSync as existsSync10, openSync, readSync, statSync as statSync4 } from "node:fs";
24009
- import { isAbsolute as isAbsolute6, resolve as resolve15 } from "node:path";
24682
+ import { closeSync, existsSync as existsSync12, openSync, readSync, statSync as statSync4 } from "node:fs";
24683
+ import { isAbsolute as isAbsolute6, resolve as resolve16 } from "node:path";
24010
24684
  var DEFAULT_POLL_INTERVAL_MS = 100;
24011
24685
  function resolveServerLogPath(root, serverPath) {
24012
- const server = isAbsolute6(serverPath) ? serverPath : resolve15(root, serverPath);
24013
- return resolve15(server, "mods/deathmatch/logs/server.log");
24686
+ const server = isAbsolute6(serverPath) ? serverPath : resolve16(root, serverPath);
24687
+ return resolve16(server, "mods/deathmatch/logs/server.log");
24014
24688
  }
24015
24689
  function identity(path) {
24016
24690
  const stats = statSync4(path);
@@ -24035,7 +24709,7 @@ function followServerLog(path, onLine, options = {}) {
24035
24709
  let initialized = false;
24036
24710
  let closed = false;
24037
24711
  const poll = () => {
24038
- if (closed || !existsSync10(path)) {
24712
+ if (closed || !existsSync12(path)) {
24039
24713
  return;
24040
24714
  }
24041
24715
  try {
@@ -24151,7 +24825,7 @@ function connectServerConsoleInput(input, output, interrupt) {
24151
24825
 
24152
24826
  // src/server/server-executable-resolver.ts
24153
24827
  import { realpathSync, statSync as statSync5 } from "node:fs";
24154
- import { isAbsolute as isAbsolute7, relative as relative7, resolve as resolve16 } from "node:path";
24828
+ import { isAbsolute as isAbsolute7, relative as relative7, resolve as resolve17 } from "node:path";
24155
24829
  function isInside(root, path) {
24156
24830
  const pathFromRoot = relative7(root, path);
24157
24831
  return pathFromRoot.length === 0 || !pathFromRoot.startsWith("..") && !isAbsolute7(pathFromRoot);
@@ -24166,14 +24840,14 @@ function candidates2(platform) {
24166
24840
  throw new Error(`Local MTA server startup is not supported on platform "${platform}". Use Windows or Linux.`);
24167
24841
  }
24168
24842
  function resolveServerExecutable(options) {
24169
- const serverRoot = resolve16(options.root, options.serverPath);
24843
+ const serverRoot = resolve17(options.root, options.serverPath);
24170
24844
  const names = options.configured === null ? candidates2(options.platform ?? process.platform) : [options.configured];
24171
24845
  const attempted = [];
24172
- if (options.configured !== null && (isAbsolute7(options.configured) || !isInside(serverRoot, resolve16(serverRoot, options.configured)))) {
24846
+ if (options.configured !== null && (isAbsolute7(options.configured) || !isInside(serverRoot, resolve17(serverRoot, options.configured)))) {
24173
24847
  throw new Error(`Configured development.server.executable must stay inside serverPath but received "${options.configured}".`);
24174
24848
  }
24175
24849
  for (const name of names) {
24176
- const path = resolve16(serverRoot, name);
24850
+ const path = resolve17(serverRoot, name);
24177
24851
  attempted.push(path);
24178
24852
  try {
24179
24853
  if (!statSync5(path).isFile()) {
@@ -24533,8 +25207,520 @@ function registerEnsureCommand(program2, runtime) {
24533
25207
  });
24534
25208
  }
24535
25209
 
25210
+ // src/commands/format-command.ts
25211
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
25212
+ import { resolve as resolve19 } from "node:path";
25213
+
25214
+ // src/format/format-selection.ts
25215
+ import { statSync as statSync6 } from "node:fs";
25216
+ import { relative as relative8, resolve as resolve18 } from "node:path";
25217
+ var UNREADABLE_PATH = "format-path-unreadable";
25218
+ function entryKind(absolute) {
25219
+ try {
25220
+ const stats = statSync6(absolute);
25221
+ return stats.isDirectory() ? "directory" : "file";
25222
+ } catch {
25223
+ return null;
25224
+ }
25225
+ }
25226
+ function sorted(files) {
25227
+ return [...new Set(files)].sort((left, right) => left.localeCompare(right));
25228
+ }
25229
+ function selectProjectFiles(root, config) {
25230
+ const resolver = createSourceResolver(config.sources);
25231
+ const tree = listProjectFiles(root, ["."], [config.outDir]);
25232
+ const files = tree.files.filter((path) => isSourcePath(path) && (isDeclarationPath(path) || resolver.resolve(path).matches.length > 0));
25233
+ return { files: sorted(files), diagnostics: tree.errors.map((message) => cliError(UNREADABLE_PATH, message)) };
25234
+ }
25235
+ function selectPathFiles(root, paths) {
25236
+ const files = [];
25237
+ const diagnostics = [];
25238
+ for (const entry of paths) {
25239
+ const absolute = resolve18(root, entry);
25240
+ const path = normalizePattern(relative8(root, absolute));
25241
+ const kind = path.startsWith("..") ? null : entryKind(absolute);
25242
+ if (kind === null) {
25243
+ diagnostics.push(cliError(UNREADABLE_PATH, `"${normalizePattern(entry)}" does not exist in "${normalizePattern(root)}".`));
25244
+ continue;
25245
+ }
25246
+ if (kind === "file") {
25247
+ if (isSourcePath(path)) {
25248
+ files.push(path);
25249
+ } else {
25250
+ diagnostics.push(cliError(UNREADABLE_PATH, `"${path}" is not a Luam source file.`));
25251
+ }
25252
+ continue;
25253
+ }
25254
+ const tree = listProjectFiles(root, [path]);
25255
+ files.push(...tree.files.filter(isSourcePath));
25256
+ diagnostics.push(...tree.errors.map((message) => cliError(UNREADABLE_PATH, message)));
25257
+ }
25258
+ return { files: sorted(files), diagnostics };
25259
+ }
25260
+
25261
+ // src/format/formatter-file-system.ts
25262
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
25263
+ import { dirname as dirname6, join as join6 } from "node:path";
25264
+ var NODE_FORMATTER_FILES = {
25265
+ exists: (path) => existsSync13(path),
25266
+ read: (path) => readFileSync11(path, "utf8"),
25267
+ join: (directory, name) => join6(directory, name),
25268
+ parent: (directory) => dirname6(directory)
25269
+ };
25270
+
25271
+ // ../compiler/src/format/format-indent.ts
25272
+ var OPENERS = /* @__PURE__ */ new Set(["(", "[", "{", "then", "do", "repeat", "function"]);
25273
+ var CLOSERS = /* @__PURE__ */ new Set([")", "]", "}", "end", "until"]);
25274
+ function opensOf(piece) {
25275
+ if (piece.kind === "comment") {
25276
+ return 0;
25277
+ }
25278
+ return piece.value === "else" || OPENERS.has(piece.value) ? 1 : 0;
25279
+ }
25280
+ function closesOf(piece) {
25281
+ if (piece.kind === "comment") {
25282
+ return 0;
25283
+ }
25284
+ return piece.value === "else" || piece.value === "elseif" || CLOSERS.has(piece.value) ? 1 : 0;
25285
+ }
25286
+ function leadingRun(pieces) {
25287
+ let length = 0;
25288
+ while (length < pieces.length) {
25289
+ const piece = pieces[length];
25290
+ if (piece === void 0 || closesOf(piece) === 0) {
25291
+ break;
25292
+ }
25293
+ length += 1;
25294
+ }
25295
+ return length;
25296
+ }
25297
+ function indentOf2(depth, pieces) {
25298
+ const leading = leadingRun(pieces);
25299
+ const indent = Math.max(0, leading === 0 ? depth : depth - 1);
25300
+ const opened = pieces.reduce((total, piece, index) => total + opensOf(piece) - (index < leading ? 0 : closesOf(piece)), 0);
25301
+ return { indent, next: opened > 0 ? indent + 1 : indent };
25302
+ }
25303
+
25304
+ // ../compiler/src/format/format-options.ts
25305
+ var DEFAULT_FORMAT_OPTIONS = {
25306
+ indent: "space",
25307
+ indentWidth: 4,
25308
+ keywordParenSpace: true,
25309
+ maxBlankLines: 1,
25310
+ lineEnding: "infer"
25311
+ };
25312
+ var MAX_INDENT_WIDTH = 8;
25313
+ var MAX_BLANK_LINES = 4;
25314
+ function resolveFormatOptions(options = {}) {
25315
+ return { ...DEFAULT_FORMAT_OPTIONS, ...options };
25316
+ }
25317
+ function indentUnit(options) {
25318
+ return options.indent === "tab" ? " " : " ".repeat(options.indentWidth);
25319
+ }
25320
+ function newlineOf(source, options) {
25321
+ if (options.lineEnding === "lf") {
25322
+ return "\n";
25323
+ }
25324
+ if (options.lineEnding === "crlf") {
25325
+ return "\r\n";
25326
+ }
25327
+ return source.includes("\r\n") ? "\r\n" : "\n";
25328
+ }
25329
+
25330
+ // ../compiler/src/format/format-pieces.ts
25331
+ function isTypeOffset(erasures, offset) {
25332
+ return erasures.some((span) => offset >= span.start && offset < span.end);
25333
+ }
25334
+ function tokenPiece(source, token, erasures) {
25335
+ return {
25336
+ kind: token.kind,
25337
+ value: source.slice(token.position.offset, token.end.offset),
25338
+ offset: token.position.offset,
25339
+ line: token.position.line,
25340
+ endLine: token.end.line,
25341
+ isType: isTypeOffset(erasures, token.position.offset)
25342
+ };
25343
+ }
25344
+ function commentPiece(source, comment) {
25345
+ return {
25346
+ kind: "comment",
25347
+ value: source.slice(comment.position.offset, comment.end.offset).trimEnd(),
25348
+ offset: comment.position.offset,
25349
+ line: comment.position.line,
25350
+ endLine: comment.end.line,
25351
+ isType: false
25352
+ };
25353
+ }
25354
+ function piecesOf(source, tokens, comments, erasures) {
25355
+ const pieces = [
25356
+ ...tokens.filter((token) => token.kind !== "eof").map((token) => tokenPiece(source, token, erasures)),
25357
+ ...comments.map((comment) => commentPiece(source, comment))
25358
+ ];
25359
+ return pieces.sort((left, right) => left.offset - right.offset);
25360
+ }
25361
+ function linesOf(pieces) {
25362
+ const lines = [];
25363
+ let previous = null;
25364
+ for (const piece of pieces) {
25365
+ const current = lines[lines.length - 1];
25366
+ if (previous !== null && current !== void 0 && piece.line === previous.endLine) {
25367
+ current.pieces.push(piece);
25368
+ } else {
25369
+ lines.push({ pieces: [piece], blankLines: previous === null ? 0 : Math.max(0, piece.line - previous.endLine - 1) });
25370
+ }
25371
+ previous = piece;
25372
+ }
25373
+ return lines;
25374
+ }
25375
+
25376
+ // ../compiler/src/format/format-spacing.ts
25377
+ var FUNCTION_TYPE2 = "fun";
25378
+ var VALUE_KINDS = /* @__PURE__ */ new Set(["identifier", "number", "string", "template"]);
25379
+ var VALUE_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "nil", "end"]);
25380
+ var TIGHT_BEFORE = /* @__PURE__ */ new Set([",", ";", ")", "]", ".", "?", ":", "++", "--"]);
25381
+ var TIGHT_AFTER = /* @__PURE__ */ new Set(["(", "[", ".", "@", "#", "..."]);
25382
+ var CALL_PREFIX = /* @__PURE__ */ new Set([")", "]"]);
25383
+ var TYPE_BRACKETS = /* @__PURE__ */ new Set(["<", ">"]);
25384
+ function endsValue(piece) {
25385
+ if (piece.kind === "keyword") {
25386
+ return VALUE_KEYWORDS.has(piece.value);
25387
+ }
25388
+ return VALUE_KINDS.has(piece.kind) || piece.value === ")" || piece.value === "]" || piece.value === "}";
25389
+ }
25390
+ function isName(piece) {
25391
+ return piece.kind === "identifier" || piece.kind === "keyword" && isLuamKeyword(piece.value);
25392
+ }
25393
+ function bindsTight(piece) {
25394
+ return isName(piece) || CALL_PREFIX.has(piece.value) || piece.isType && piece.value === ">";
25395
+ }
25396
+ function isUnary(pieces, index) {
25397
+ const piece = pieces[index];
25398
+ if (piece === void 0) {
25399
+ return false;
25400
+ }
25401
+ if (piece.value === "#") {
25402
+ return true;
25403
+ }
25404
+ if (piece.value !== "-") {
25405
+ return false;
25406
+ }
25407
+ const previous = index === 0 ? void 0 : pieces[index - 1];
25408
+ return previous === void 0 || !endsValue(previous);
25409
+ }
25410
+ function isMethodColon(pieces, index) {
25411
+ const next = pieces[index + 1];
25412
+ const after = pieces[index + 2];
25413
+ if (next === void 0 || !isName(next) || next.value === FUNCTION_TYPE2) {
25414
+ return false;
25415
+ }
25416
+ return after !== void 0 && after.value === "(";
25417
+ }
25418
+ function spaceBefore(pieces, index, options) {
25419
+ const piece = pieces[index];
25420
+ const previous = pieces[index - 1];
25421
+ if (piece === void 0 || previous === void 0) {
25422
+ return false;
25423
+ }
25424
+ if (piece.kind === "comment" || previous.kind === "comment") {
25425
+ return true;
25426
+ }
25427
+ if (piece.value === "}") {
25428
+ return previous.value !== "{";
25429
+ }
25430
+ if (piece.isType && piece.value === "<" && previous.kind === "keyword" && previous.value === "function") {
25431
+ return true;
25432
+ }
25433
+ if (TIGHT_BEFORE.has(piece.value) || piece.isType && TYPE_BRACKETS.has(piece.value)) {
25434
+ return false;
25435
+ }
25436
+ if (previous.isType && previous.value === "<") {
25437
+ return false;
25438
+ }
25439
+ if (previous.value === ":") {
25440
+ return !isMethodColon(pieces, index - 1);
25441
+ }
25442
+ if (TIGHT_AFTER.has(previous.value) || isUnary(pieces, index - 1)) {
25443
+ return false;
25444
+ }
25445
+ if (piece.value === "(") {
25446
+ return !bindsTight(previous) && (options.keywordParenSpace || previous.kind !== "keyword");
25447
+ }
25448
+ if (piece.value === "[") {
25449
+ return !bindsTight(previous);
25450
+ }
25451
+ return true;
25452
+ }
25453
+ function renderLine(pieces, options) {
25454
+ let text = "";
25455
+ for (const [index, piece] of pieces.entries()) {
25456
+ text += spaceBefore(pieces, index, options) ? ` ${piece.value}` : piece.value;
25457
+ }
25458
+ return text;
25459
+ }
25460
+
25461
+ // ../compiler/src/format/format.ts
25462
+ function signature(pieces) {
25463
+ return JSON.stringify(pieces.map((piece) => [piece.kind, piece.value]));
25464
+ }
25465
+ function render2(source, options) {
25466
+ const parsed = parse(source);
25467
+ if (parsed.diagnostics.length > 0) {
25468
+ return null;
25469
+ }
25470
+ const pieces = piecesOf(source, parsed.tokens, parsed.comments, parsed.erasures);
25471
+ const rendered = [];
25472
+ const unit = indentUnit(options);
25473
+ let depth = 0;
25474
+ for (const line2 of linesOf(pieces)) {
25475
+ const { indent, next } = indentOf2(depth, line2.pieces);
25476
+ const first = line2.pieces[0];
25477
+ const last = line2.pieces[line2.pieces.length - 1];
25478
+ const blanks = rendered.length === 0 ? 0 : Math.min(line2.blankLines, options.maxBlankLines);
25479
+ for (let count = 0; count < blanks; count += 1) {
25480
+ rendered.push({ text: "", line: 0, endLine: 0 });
25481
+ }
25482
+ rendered.push({ text: `${unit.repeat(indent)}${renderLine(line2.pieces, options)}`, line: first?.line ?? 0, endLine: last?.endLine ?? 0 });
25483
+ depth = next;
25484
+ }
25485
+ return { pieces, rendered, newline: newlineOf(source, options) };
25486
+ }
25487
+ function assemble(rendered, newline) {
25488
+ return rendered.length === 0 ? "" : `${rendered.map((entry) => entry.text).join(newline)}${newline}`;
25489
+ }
25490
+ function verified(formatted, options) {
25491
+ const text = assemble(formatted.rendered, formatted.newline);
25492
+ const round = render2(text, options);
25493
+ if (round === null || signature(round.pieces) !== signature(formatted.pieces)) {
25494
+ return null;
25495
+ }
25496
+ return text;
25497
+ }
25498
+ function formatSource(source, options = {}) {
25499
+ const resolved2 = resolveFormatOptions(options);
25500
+ const formatted = render2(source, resolved2);
25501
+ return formatted === null ? null : verified(formatted, resolved2);
25502
+ }
25503
+
25504
+ // ../compiler/src/format/formatter-fields.ts
25505
+ var FORMATTER_FILE_NAME = ".luam.formatter";
25506
+ var INDENT_STYLES = ["space", "tab"];
25507
+ var LINE_ENDINGS = ["infer", "lf", "crlf"];
25508
+ var FORMATTER_FIELDS = [
25509
+ field("indent", STRING_TYPE, 'The indent character. "space" indents with spaces, "tab" with tabs.', {
25510
+ defaultValue: DEFAULT_FORMAT_OPTIONS.indent,
25511
+ values: INDENT_STYLES
25512
+ }),
25513
+ field("indentWidth", NUMBER_TYPE, `Spaces per indent level, from 1 to ${MAX_INDENT_WIDTH}. Ignored when "indent" is "tab".`, {
25514
+ defaultValue: DEFAULT_FORMAT_OPTIONS.indentWidth
25515
+ }),
25516
+ field("keywordParenSpace", BOOLEAN_TYPE, 'Whether a "(" that follows a keyword is preceded by a space, as in "function (".', {
25517
+ defaultValue: DEFAULT_FORMAT_OPTIONS.keywordParenSpace
25518
+ }),
25519
+ field("maxBlankLines", NUMBER_TYPE, `Consecutive blank lines kept, from 0 to ${MAX_BLANK_LINES}.`, {
25520
+ defaultValue: DEFAULT_FORMAT_OPTIONS.maxBlankLines
25521
+ }),
25522
+ field("lineEnding", STRING_TYPE, 'The line ending. "infer" follows the file, "lf" and "crlf" pin it.', {
25523
+ defaultValue: DEFAULT_FORMAT_OPTIONS.lineEnding,
25524
+ values: LINE_ENDINGS
25525
+ })
25526
+ ];
25527
+ var FORMATTER_FIELD_NAMES = FORMATTER_FIELDS.map((entry) => entry.name);
25528
+
25529
+ // ../compiler/src/format/formatter-file.ts
25530
+ var FORMATTER_UNKNOWN_FIELD = "formatter-unknown-field";
25531
+ var FORMATTER_INVALID_VALUE = "formatter-invalid-value";
25532
+ var FORMATTER_PARSE_ERROR = "formatter-parse-error";
25533
+ var START4 = createPosition(1, 1, 0);
25534
+ function boundedInteger(value, name, low, high, positions, diagnostics) {
25535
+ if (value === void 0) {
25536
+ return null;
25537
+ }
25538
+ if (typeof value !== "number" || !Number.isInteger(value) || value < low || value > high) {
25539
+ diagnostics.push(manifestError(FORMATTER_INVALID_VALUE, `"${name}" must be a whole number from ${low} to ${high}.`, positionAt(positions, name)));
25540
+ return null;
25541
+ }
25542
+ return value;
25543
+ }
25544
+ function choice(value, allowed) {
25545
+ return typeof value === "string" && allowed.includes(value) ? value : null;
25546
+ }
25547
+ function retag(diagnostic) {
25548
+ if (diagnostic.stage === "lexer" || diagnostic.stage === "parser") {
25549
+ return { ...diagnostic, stage: "manifest", code: FORMATTER_PARSE_ERROR };
25550
+ }
25551
+ return { ...diagnostic, code: diagnostic.code === UNKNOWN_FIELD ? FORMATTER_UNKNOWN_FIELD : FORMATTER_INVALID_VALUE };
25552
+ }
25553
+ function unknownFieldMessage(name) {
25554
+ const known = FORMATTER_FIELD_NAMES.map((entry) => `"${entry}"`).join(", ");
25555
+ return `"${name}" is not a "${FORMATTER_FILE_NAME}" field. The fields are ${known}.`;
25556
+ }
25557
+ var FORMATTER_SCHEMA = {
25558
+ fields: FORMATTER_FIELDS,
25559
+ removed: {},
25560
+ normalize: (value) => ({ value, diagnostics: [] }),
25561
+ retag,
25562
+ unknownName: unknownFieldMessage
25563
+ };
25564
+ function analyzeFormatterFile(source, root) {
25565
+ const analysis = analyzeManifest(source, { mode: "format", root, env: {} }, FORMATTER_SCHEMA);
25566
+ const diagnostics = [...analysis.diagnostics];
25567
+ const raw = analysis.value;
25568
+ const width = boundedInteger(raw["indentWidth"], "indentWidth", 1, MAX_INDENT_WIDTH, analysis.positions, diagnostics);
25569
+ const blanks = boundedInteger(raw["maxBlankLines"], "maxBlankLines", 0, MAX_BLANK_LINES, analysis.positions, diagnostics);
25570
+ const keywordParenSpace = raw["keywordParenSpace"];
25571
+ return {
25572
+ options: {
25573
+ indent: choice(raw["indent"], INDENT_STYLES) ?? DEFAULT_FORMAT_OPTIONS.indent,
25574
+ indentWidth: width ?? DEFAULT_FORMAT_OPTIONS.indentWidth,
25575
+ keywordParenSpace: typeof keywordParenSpace === "boolean" ? keywordParenSpace : DEFAULT_FORMAT_OPTIONS.keywordParenSpace,
25576
+ maxBlankLines: blanks ?? DEFAULT_FORMAT_OPTIONS.maxBlankLines,
25577
+ lineEnding: choice(raw["lineEnding"], LINE_ENDINGS) ?? DEFAULT_FORMAT_OPTIONS.lineEnding
25578
+ },
25579
+ diagnostics
25580
+ };
25581
+ }
25582
+ function formatterFileError(message) {
25583
+ return manifestError(FORMATTER_PARSE_ERROR, message, START4);
25584
+ }
25585
+
25586
+ // ../compiler/src/format/formatter-discovery.ts
25587
+ var LIBRARY_DIRECTORY2 = "node_modules";
25588
+ var DEFAULT_FORMATTER_OPTIONS = { path: null, options: DEFAULT_FORMAT_OPTIONS, diagnostics: [], valid: true };
25589
+ function insideLibrary(directory) {
25590
+ return directory.split(/[\\/]/).includes(LIBRARY_DIRECTORY2);
25591
+ }
25592
+ function findFormatterFile(files, start) {
25593
+ let directory = start;
25594
+ for (; ; ) {
25595
+ if (insideLibrary(directory)) {
25596
+ return null;
25597
+ }
25598
+ const candidate = files.join(directory, FORMATTER_FILE_NAME);
25599
+ if (files.exists(candidate)) {
25600
+ return candidate;
25601
+ }
25602
+ const parent = files.parent(directory);
25603
+ if (parent === directory) {
25604
+ return null;
25605
+ }
25606
+ directory = parent;
25607
+ }
25608
+ }
25609
+ function readFormatterFile(files, path, root) {
25610
+ let source;
25611
+ try {
25612
+ source = files.read(path);
25613
+ } catch (error) {
25614
+ const message = `"${path}" could not be read: ${error instanceof Error ? error.message : String(error)}`;
25615
+ return { path, options: DEFAULT_FORMAT_OPTIONS, diagnostics: [formatterFileError(message)], valid: false };
25616
+ }
25617
+ const analysis = analyzeFormatterFile(source, root);
25618
+ return { path, options: analysis.options, diagnostics: analysis.diagnostics, valid: !hasErrors(analysis.diagnostics) };
25619
+ }
25620
+ function resolveFormatterOptions(files, start) {
25621
+ const path = findFormatterFile(files, start);
25622
+ return path === null ? DEFAULT_FORMATTER_OPTIONS : readFormatterFile(files, path, files.parent(path));
25623
+ }
25624
+
25625
+ // src/commands/format-command.ts
25626
+ var UNPARSEABLE_SOURCE = "format-source-unparseable";
25627
+ var UNREADABLE_SOURCE2 = "format-source-unreadable";
25628
+ var UNWRITABLE_SOURCE = "format-source-unwritable";
25629
+ function describe2(error) {
25630
+ return error instanceof Error ? error.message : String(error);
25631
+ }
25632
+ function select(context, options) {
25633
+ if (options.paths.length > 0) {
25634
+ return selectPathFiles(context.root, options.paths);
25635
+ }
25636
+ if (context.config === null) {
25637
+ return { files: [], diagnostics: [] };
25638
+ }
25639
+ return selectProjectFiles(context.root, context.config);
25640
+ }
25641
+ function formatFile(context, path, check2, style, diagnostics) {
25642
+ const absolute = resolve19(context.root, path);
25643
+ let source;
25644
+ try {
25645
+ source = readFileSync12(absolute, "utf8");
25646
+ } catch (error) {
25647
+ diagnostics.push(cliError(UNREADABLE_SOURCE2, `The source file "${path}" could not be read: ${describe2(error)}`));
25648
+ return false;
25649
+ }
25650
+ const formatted = formatSource(source, style);
25651
+ if (formatted === null) {
25652
+ diagnostics.push(cliWarning(UNPARSEABLE_SOURCE, `"${path}" could not be parsed and was left unchanged. Run "luam check" to see why.`));
25653
+ return false;
25654
+ }
25655
+ if (formatted === source) {
25656
+ return false;
25657
+ }
25658
+ if (check2) {
25659
+ return true;
25660
+ }
25661
+ try {
25662
+ writeFileSync7(absolute, formatted, "utf8");
25663
+ } catch (error) {
25664
+ diagnostics.push(cliError(UNWRITABLE_SOURCE, `The source file "${path}" could not be written: ${describe2(error)}`));
25665
+ return false;
25666
+ }
25667
+ return true;
25668
+ }
25669
+ function runFormatCommand(context, options) {
25670
+ const reporter = context.reporter ?? createReporter(context.logger);
25671
+ const started = Date.now();
25672
+ const style = resolveFormatterOptions(NODE_FORMATTER_FILES, context.root);
25673
+ if (!style.valid) {
25674
+ reportManifestDiagnostics(reporter, style.path ?? "", "", style.diagnostics);
25675
+ reporter.error(`The formatter configuration "${style.path ?? ""}" is invalid. Nothing was formatted.`);
25676
+ return EXIT_USAGE;
25677
+ }
25678
+ const selection = select(context, options);
25679
+ const diagnostics = [...selection.diagnostics];
25680
+ const touched = [];
25681
+ for (const path of selection.files) {
25682
+ if (formatFile(context, path, options.check, style.options, diagnostics)) {
25683
+ touched.push(path);
25684
+ }
25685
+ }
25686
+ if (options.check) {
25687
+ for (const path of touched) {
25688
+ reporter.raw(path);
25689
+ }
25690
+ }
25691
+ reportCliDiagnostics(reporter, diagnostics);
25692
+ const failed3 = hasCliErrors(diagnostics) || options.check && touched.length > 0;
25693
+ const label2 = options.check ? "differing" : "reformatted";
25694
+ const summary = `${pluralize(selection.files.length, "file")} scanned, ${touched.length} ${label2} in ${formatDuration(Date.now() - started)}.`;
25695
+ if (failed3) {
25696
+ reporter.error(`Format failed: ${summary}`);
25697
+ return EXIT_DIAGNOSTICS;
25698
+ }
25699
+ reporter.success(`Format passed: ${summary}`);
25700
+ return EXIT_OK;
25701
+ }
25702
+
25703
+ // src/cli/registry/format-registration.ts
25704
+ function registerFormatCommand(program2, runtime) {
25705
+ const command = program2.command("format").description("Rewrite the project sources in the recorded formatting style.");
25706
+ command.argument("[paths...]", "Files or directories to format instead of the manifest sources.");
25707
+ addProjectOptions(command).addOption(new Option("--check", "Write nothing and report the files that differ."));
25708
+ command.action((paths, options) => {
25709
+ const check2 = options.check === true;
25710
+ if (paths.length > 0) {
25711
+ runtime.exitCode = runFormatCommand(
25712
+ { root: commandRoot(runtime, options), config: null, logger: runtime.logger, reporter: runtime.reporter },
25713
+ { check: check2, paths }
25714
+ );
25715
+ return;
25716
+ }
25717
+ const project = createProjectContext(runtime, "format", options);
25718
+ runtime.exitCode = project.context === null ? project.error : runFormatCommand({ ...project.context, config: project.context.config }, { check: check2, paths: [] });
25719
+ });
25720
+ }
25721
+
24536
25722
  // src/cli/registry/init-registration.ts
24537
- import { resolve as resolve18 } from "node:path";
25723
+ import { resolve as resolve21 } from "node:path";
24538
25724
 
24539
25725
  // src/commands/init-command.ts
24540
25726
  import { basename as basename2 } from "node:path";
@@ -24596,7 +25782,7 @@ function renderManifest(source, details) {
24596
25782
  }
24597
25783
 
24598
25784
  // src/scaffold/template-files.ts
24599
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
25785
+ import { existsSync as existsSync14, readFileSync as readFileSync13 } from "node:fs";
24600
25786
  import { fileURLToPath as fileURLToPath2 } from "node:url";
24601
25787
 
24602
25788
  // ../template/src/template.ts
@@ -24612,10 +25798,10 @@ function bundledTemplatePath(source) {
24612
25798
  }
24613
25799
  function resolveTemplatePath(source) {
24614
25800
  const packaged = fileURLToPath2(resolveTemplateUrl(source));
24615
- return existsSync11(packaged) ? packaged : bundledTemplatePath(source);
25801
+ return existsSync14(packaged) ? packaged : bundledTemplatePath(source);
24616
25802
  }
24617
25803
  function readTemplateSource(source) {
24618
- return readFileSync10(resolveTemplatePath(source), "utf8");
25804
+ return readFileSync13(resolveTemplatePath(source), "utf8");
24619
25805
  }
24620
25806
 
24621
25807
  // src/scaffold/scaffold-plan.ts
@@ -24631,19 +25817,19 @@ function buildScaffoldPlan(details) {
24631
25817
  }
24632
25818
 
24633
25819
  // src/scaffold/scaffold-writer.ts
24634
- import { existsSync as existsSync12, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "node:fs";
24635
- import { dirname as dirname6, resolve as resolve17 } from "node:path";
25820
+ import { existsSync as existsSync15, mkdirSync as mkdirSync6, writeFileSync as writeFileSync8 } from "node:fs";
25821
+ import { dirname as dirname7, resolve as resolve20 } from "node:path";
24636
25822
  function writeScaffold(targetDir, plan, force) {
24637
25823
  const written = [];
24638
25824
  const skipped = [];
24639
25825
  for (const file of plan.files) {
24640
- const absolute = resolve17(targetDir, file.path);
24641
- if (!force && existsSync12(absolute)) {
25826
+ const absolute = resolve20(targetDir, file.path);
25827
+ if (!force && existsSync15(absolute)) {
24642
25828
  skipped.push(file.path);
24643
25829
  continue;
24644
25830
  }
24645
- mkdirSync6(dirname6(absolute), { recursive: true });
24646
- writeFileSync7(absolute, file.content, "utf8");
25831
+ mkdirSync6(dirname7(absolute), { recursive: true });
25832
+ writeFileSync8(absolute, file.content, "utf8");
24647
25833
  written.push(file.path);
24648
25834
  }
24649
25835
  return { written: written.sort((left, right) => left.localeCompare(right)), skipped: skipped.sort((left, right) => left.localeCompare(right)) };
@@ -24693,7 +25879,7 @@ function registerInitCommand(program2, runtime) {
24693
25879
  command.addOption(cwdOption()).addOption(valueOption("--name <name>", "Resource name. Defaults to the destination directory name.")).addOption(new Option("--force", "Overwrite files that already exist.")).addOption(new Option("-y, --yes", "Accept the defaults without prompting.")).addOption(colorOption());
24694
25880
  command.action(async (path, options) => {
24695
25881
  const root = commandRoot(runtime, options);
24696
- const target = path === void 0 ? root : resolve18(root, path);
25882
+ const target = path === void 0 ? root : resolve21(root, path);
24697
25883
  runtime.exitCode = await runInitCommand(target, runtime.logger, {
24698
25884
  name: options.name ?? null,
24699
25885
  force: options.force === true,
@@ -24817,13 +26003,13 @@ function registerServerCommand(program2, runtime) {
24817
26003
  }
24818
26004
 
24819
26005
  // src/testing/test-report.ts
24820
- import { readFileSync as readFileSync11 } from "node:fs";
24821
- import { resolve as resolve19 } from "node:path";
26006
+ import { readFileSync as readFileSync14 } from "node:fs";
26007
+ import { resolve as resolve22 } from "node:path";
24822
26008
  function sourceLine(root, path, line2, cache3) {
24823
26009
  let lines = cache3.get(path);
24824
26010
  if (lines === void 0) {
24825
26011
  try {
24826
- lines = readFileSync11(resolve19(root, path), "utf8").split(/\r?\n/);
26012
+ lines = readFileSync14(resolve22(root, path), "utf8").split(/\r?\n/);
24827
26013
  } catch {
24828
26014
  lines = [];
24829
26015
  }
@@ -24896,14 +26082,14 @@ function reportTestResults(reporter, runs, root, map, durationMs) {
24896
26082
  }
24897
26083
 
24898
26084
  // src/testing/test-discovery.ts
24899
- import { readFileSync as readFileSync12 } from "node:fs";
24900
- import { resolve as resolve20 } from "node:path";
26085
+ import { readFileSync as readFileSync15 } from "node:fs";
26086
+ import { resolve as resolve23 } from "node:path";
24901
26087
  var DEFAULT_TEST_ENVIRONMENT = "shared";
24902
26088
  var SIDE_CONFLICT2 = "test-source-side-conflict";
24903
26089
  var UNREADABLE_TEST = "test-source-unreadable";
24904
26090
  function readTest(root, path, diagnostics) {
24905
26091
  try {
24906
- return readFileSync12(resolve20(root, path), "utf8");
26092
+ return readFileSync15(resolve23(root, path), "utf8");
24907
26093
  } catch (error) {
24908
26094
  diagnostics.push(cliError(UNREADABLE_TEST, `The test file "${path}" could not be read: ${error instanceof Error ? error.message : String(error)}`));
24909
26095
  return null;
@@ -24930,9 +26116,9 @@ function discoverTests(root, sources, excluded = []) {
24930
26116
 
24931
26117
  // src/testing/test-runner.ts
24932
26118
  import { spawnSync as spawnSync3 } from "node:child_process";
24933
- import { mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync8 } from "node:fs";
26119
+ import { mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync9 } from "node:fs";
24934
26120
  import { tmpdir as tmpdir2 } from "node:os";
24935
- import { dirname as dirname7, join as join6 } from "node:path";
26121
+ import { dirname as dirname8, join as join7 } from "node:path";
24936
26122
 
24937
26123
  // src/testing/harness-assertions.ts
24938
26124
  var ASSERTIONS_SOURCE = String.raw`local SENTINEL = '##luam:test'
@@ -25347,11 +26533,11 @@ function parseRunOutput(environment, stdout) {
25347
26533
  return { results, output };
25348
26534
  }
25349
26535
  function writeWorkspace(directory, scripts) {
25350
- writeFileSync8(join6(directory, HARNESS_FILE), HARNESS_SOURCE, "utf8");
26536
+ writeFileSync9(join7(directory, HARNESS_FILE), HARNESS_SOURCE, "utf8");
25351
26537
  for (const script of scripts) {
25352
- const target = join6(directory, script.path);
25353
- mkdirSync7(dirname7(target), { recursive: true });
25354
- writeFileSync8(target, script.content, "utf8");
26538
+ const target = join7(directory, script.path);
26539
+ mkdirSync7(dirname8(target), { recursive: true });
26540
+ writeFileSync9(target, script.content, "utf8");
25355
26541
  }
25356
26542
  }
25357
26543
  function loadOrder(available, environment) {
@@ -25365,14 +26551,14 @@ function loadOrder(available, environment) {
25365
26551
  }
25366
26552
  function runTests(request) {
25367
26553
  const spawn2 = request.spawn ?? runLua;
25368
- const directory = mkdtempSync2(join6(tmpdir2(), "luam-test-"));
26554
+ const directory = mkdtempSync2(join7(tmpdir2(), "luam-test-"));
25369
26555
  const available = new Set(request.scripts.map((script) => script.path));
25370
26556
  try {
25371
26557
  writeWorkspace(directory, request.scripts);
25372
26558
  return ALL_ENVIRONMENTS.filter((environment) => request.environments.includes(environment)).map((environment) => {
25373
26559
  const { preload, target } = loadOrder(available, environment);
25374
26560
  const entry = entryFile(environment);
25375
- writeFileSync8(join6(directory, entry), entrySource(environment, preload, target), "utf8");
26561
+ writeFileSync9(join7(directory, entry), entrySource(environment, preload, target), "utf8");
25376
26562
  const execution = spawn2(request.executable, [entry], directory);
25377
26563
  const parsed = parseRunOutput(environment, execution.stdout);
25378
26564
  const failure3 = execution.failure ?? (parsed.results.length === 0 && execution.stderr.length > 0 ? execution.stderr.trim() : null);
@@ -25444,12 +26630,12 @@ function registerTestCommand(program2, runtime) {
25444
26630
  }
25445
26631
 
25446
26632
  // src/commands/trace-command.ts
25447
- import { existsSync as existsSync13, readFileSync as readFileSync13 } from "node:fs";
26633
+ import { existsSync as existsSync16, readFileSync as readFileSync16 } from "node:fs";
25448
26634
  function defaultMapPath(root, options, reporter) {
25449
26635
  const loaded = loadManifest(root, { path: options.manifestPath, mode: manifestMode("trace"), env: options.env });
25450
26636
  if (loaded.config !== null) {
25451
26637
  const configured = resourceMapPath(root, loaded.config);
25452
- if (existsSync13(configured)) {
26638
+ if (existsSync16(configured)) {
25453
26639
  return configured;
25454
26640
  }
25455
26641
  }
@@ -25469,7 +26655,7 @@ function readStandardInput() {
25469
26655
  return "";
25470
26656
  }
25471
26657
  try {
25472
- return readFileSync13(0, "utf8");
26658
+ return readFileSync16(0, "utf8");
25473
26659
  } catch {
25474
26660
  return "";
25475
26661
  }
@@ -25551,6 +26737,7 @@ var COMMAND_REGISTRARS = [
25551
26737
  registerDevCommand,
25552
26738
  registerDoctorCommand,
25553
26739
  registerEnsureCommand,
26740
+ registerFormatCommand,
25554
26741
  registerInitCommand,
25555
26742
  registerServerCommand,
25556
26743
  registerSetupCommand,