@thigasdevelopment/luam 0.19.5 → 0.19.8

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 +1968 -617
  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
  }
@@ -4008,6 +4019,188 @@ function parseNamedAnnotation(stream, declaration) {
4008
4019
  return { kind: "type-optional", element, position: optional.position };
4009
4020
  }
4010
4021
 
4022
+ // ../compiler/src/parser/token-stream.ts
4023
+ var ParserError = class extends Error {
4024
+ diagnostic;
4025
+ constructor(diagnostic) {
4026
+ super(diagnostic.message);
4027
+ this.name = "ParserError";
4028
+ this.diagnostic = diagnostic;
4029
+ }
4030
+ };
4031
+ var TokenStream = class {
4032
+ diagnostics = [];
4033
+ tokens;
4034
+ index = 0;
4035
+ erased = [];
4036
+ spans = /* @__PURE__ */ new Map();
4037
+ constructor(tokens) {
4038
+ this.tokens = tokens;
4039
+ }
4040
+ peek(offset = 0) {
4041
+ const token = this.tokens[Math.min(this.index + offset, this.tokens.length - 1)];
4042
+ return token;
4043
+ }
4044
+ current() {
4045
+ return this.peek();
4046
+ }
4047
+ isEof() {
4048
+ return this.current().kind === "eof";
4049
+ }
4050
+ next() {
4051
+ const token = this.current();
4052
+ if (!this.isEof()) {
4053
+ this.index += 1;
4054
+ }
4055
+ return token;
4056
+ }
4057
+ checkpoint() {
4058
+ return this.index;
4059
+ }
4060
+ speculate() {
4061
+ return { index: this.index, erasures: this.erased.length, diagnostics: this.diagnostics.length };
4062
+ }
4063
+ rewind(point) {
4064
+ this.index = point.index;
4065
+ this.erased.length = point.erasures;
4066
+ this.diagnostics.length = point.diagnostics;
4067
+ }
4068
+ eraseFrom(checkpoint, kind = "annotation") {
4069
+ const first = this.tokens[checkpoint];
4070
+ const last = this.tokens[this.index - 1];
4071
+ if (first !== void 0 && last !== void 0 && first.kind !== "eof") {
4072
+ this.erased.push({ start: first.position.offset, end: last.end.offset, kind });
4073
+ }
4074
+ }
4075
+ eraseToCurrent(checkpoint) {
4076
+ const first = this.tokens[checkpoint];
4077
+ if (first !== void 0 && first.kind !== "eof") {
4078
+ this.erased.push({ start: first.position.offset, end: this.current().position.offset, kind: "annotation" });
4079
+ }
4080
+ }
4081
+ extendDeclarationErasure(end) {
4082
+ const last = this.erased[this.erased.length - 1];
4083
+ if (last !== void 0 && last.kind === "declaration" && last.end <= end) {
4084
+ last.end = end;
4085
+ }
4086
+ }
4087
+ erasures() {
4088
+ return [...this.erased];
4089
+ }
4090
+ recordSpan(node, checkpoint) {
4091
+ const span = this.sourceSpanFrom(checkpoint);
4092
+ if (span !== null) {
4093
+ this.spans.set(node, span);
4094
+ }
4095
+ }
4096
+ nodeSpans() {
4097
+ return this.spans;
4098
+ }
4099
+ sourceSpanFrom(checkpoint) {
4100
+ const first = this.tokens[checkpoint];
4101
+ const last = this.tokens[this.index - 1];
4102
+ return first === void 0 || last === void 0 || first.kind === "eof" ? null : { start: first.position.offset, end: last.end.offset };
4103
+ }
4104
+ check(kind, value) {
4105
+ const token = this.current();
4106
+ return token.kind === kind && (value === void 0 || token.value === value);
4107
+ }
4108
+ checkAhead(offset, kind, value) {
4109
+ const token = this.peek(offset);
4110
+ return token.kind === kind && (value === void 0 || token.value === value);
4111
+ }
4112
+ match(kind, value) {
4113
+ if (!this.check(kind, value)) {
4114
+ return false;
4115
+ }
4116
+ this.next();
4117
+ return true;
4118
+ }
4119
+ expect(kind, value) {
4120
+ if (this.check(kind, value)) {
4121
+ return this.next();
4122
+ }
4123
+ const expected = value === void 0 ? kind : `"${value}"`;
4124
+ throw this.error(`Expected ${expected} but found "${this.describeCurrent()}".`, "parse-unexpected-token");
4125
+ }
4126
+ checkName(offset = 0) {
4127
+ const token = this.peek(offset);
4128
+ return token.kind === "identifier" || token.kind === "keyword" && isLuamKeyword(token.value);
4129
+ }
4130
+ expectName() {
4131
+ if (this.checkName()) {
4132
+ return this.next();
4133
+ }
4134
+ throw this.error(`Expected a name but found "${this.describeCurrent()}".`, "parse-unexpected-token");
4135
+ }
4136
+ describeCurrent() {
4137
+ const token = this.current();
4138
+ return token.kind === "eof" ? "<end of file>" : token.value;
4139
+ }
4140
+ error(message, code = "parse-error") {
4141
+ const token = this.current();
4142
+ return new ParserError(createDiagnostic("parser", code, message, token.position, "error", token.end));
4143
+ }
4144
+ errorAt(message, code, position2, end) {
4145
+ return new ParserError(createDiagnostic("parser", code, message, position2, "error", end));
4146
+ }
4147
+ report(code, message, position2) {
4148
+ this.diagnostics.push(createDiagnostic("parser", code, message, position2));
4149
+ }
4150
+ };
4151
+
4152
+ // ../compiler/src/parser/type-parameters.ts
4153
+ function emptyTypeParameters() {
4154
+ return { names: [], constraints: [] };
4155
+ }
4156
+ function parseTypeParameters(stream) {
4157
+ const list = emptyTypeParameters();
4158
+ if (!stream.check("operator", "<")) {
4159
+ return list;
4160
+ }
4161
+ const checkpoint = stream.checkpoint();
4162
+ stream.next();
4163
+ do {
4164
+ list.names.push(stream.expect("identifier").value);
4165
+ list.constraints.push(stream.match("keyword", "extends") ? parseTypeAnnotation(stream) : null);
4166
+ } while (stream.match("punctuation", ","));
4167
+ stream.expect("operator", ">");
4168
+ stream.eraseFrom(checkpoint);
4169
+ return list;
4170
+ }
4171
+ function parseTypeArguments(stream) {
4172
+ const args = [];
4173
+ if (!stream.check("operator", "<")) {
4174
+ return args;
4175
+ }
4176
+ const checkpoint = stream.checkpoint();
4177
+ stream.next();
4178
+ do {
4179
+ args.push(parseTypeAnnotation(stream));
4180
+ } while (stream.match("punctuation", ","));
4181
+ stream.expect("operator", ">");
4182
+ stream.eraseFrom(checkpoint);
4183
+ return args;
4184
+ }
4185
+ function parseCallTypeArguments(stream) {
4186
+ if (!stream.check("operator", "<")) {
4187
+ return [];
4188
+ }
4189
+ const point = stream.speculate();
4190
+ try {
4191
+ const args = parseTypeArguments(stream);
4192
+ if (args.length > 0 && stream.check("punctuation", "(")) {
4193
+ return args;
4194
+ }
4195
+ } catch (error) {
4196
+ if (!(error instanceof ParserError)) {
4197
+ throw error;
4198
+ }
4199
+ }
4200
+ stream.rewind(point);
4201
+ return [];
4202
+ }
4203
+
4011
4204
  // ../compiler/src/parser/function-expression.ts
4012
4205
  function parseParameter(stream) {
4013
4206
  const token = stream.current();
@@ -4038,11 +4231,20 @@ function parseParameters(stream) {
4038
4231
  }
4039
4232
  function parseFunctionExpression(stream) {
4040
4233
  const position2 = stream.expect("keyword", "function").position;
4234
+ const typeParameters = parseTypeParameters(stream);
4041
4235
  const parameters = parseParameters(stream);
4042
4236
  const returnAnnotation = parseOptionalAnnotation(stream);
4043
4237
  const body = parseBlock(stream, ["end"]);
4044
4238
  stream.expect("keyword", "end");
4045
- return { kind: "function-expression", parameters, returnAnnotation, body, position: position2 };
4239
+ return {
4240
+ kind: "function-expression",
4241
+ typeParameters: typeParameters.names,
4242
+ typeConstraints: typeParameters.constraints,
4243
+ parameters,
4244
+ returnAnnotation,
4245
+ body,
4246
+ position: position2
4247
+ };
4046
4248
  }
4047
4249
  function parseFunctionName(stream) {
4048
4250
  const token = stream.expect("identifier");
@@ -4060,11 +4262,25 @@ function parseFunctionName(stream) {
4060
4262
  function parseFunctionDeclaration(stream, isLocal, isExported = false, isHttpExport = false) {
4061
4263
  const position2 = stream.expect("keyword", "function").position;
4062
4264
  const { name, isMethod } = parseFunctionName(stream);
4265
+ const typeParameters = parseTypeParameters(stream);
4063
4266
  const parameters = parseParameters(stream);
4064
4267
  const returnAnnotation = parseOptionalAnnotation(stream);
4065
4268
  const body = parseBlock(stream, ["end"]);
4066
4269
  stream.expect("keyword", "end");
4067
- return { kind: "function-declaration", name, isLocal, isExported, isHttpExport, isMethod, parameters, returnAnnotation, body, position: position2 };
4270
+ return {
4271
+ kind: "function-declaration",
4272
+ name,
4273
+ isLocal,
4274
+ isExported,
4275
+ isHttpExport,
4276
+ isMethod,
4277
+ typeParameters: typeParameters.names,
4278
+ typeConstraints: typeParameters.constraints,
4279
+ parameters,
4280
+ returnAnnotation,
4281
+ body,
4282
+ position: position2
4283
+ };
4068
4284
  }
4069
4285
 
4070
4286
  // ../compiler/src/parser/precedence.ts
@@ -4159,7 +4375,7 @@ function parsePrimary(stream) {
4159
4375
  if (token.kind === "keyword" && token.value === "new" && stream.checkAhead(1, "identifier")) {
4160
4376
  stream.next();
4161
4377
  const className = stream.next().value;
4162
- const typeArguments = parseNewTypeArguments(stream);
4378
+ const typeArguments = parseTypeArguments(stream);
4163
4379
  return { kind: "new-expression", className, typeArguments, args: parseArguments(stream), position: token.position };
4164
4380
  }
4165
4381
  if (token.kind === "keyword" && CALLABLE_KEYWORDS.has(token.value) && stream.checkAhead(1, "punctuation", "(")) {
@@ -4192,20 +4408,6 @@ function parsePrimary(stream) {
4192
4408
  }
4193
4409
  throw stream.error(`Unexpected "${stream.describeCurrent()}" in expression.`, "parse-unexpected-token");
4194
4410
  }
4195
- function parseNewTypeArguments(stream) {
4196
- const args = [];
4197
- if (!stream.check("operator", "<")) {
4198
- return args;
4199
- }
4200
- const checkpoint = stream.checkpoint();
4201
- stream.next();
4202
- do {
4203
- args.push(parseTypeAnnotation(stream));
4204
- } while (stream.match("punctuation", ","));
4205
- stream.expect("operator", ">");
4206
- stream.eraseFrom(checkpoint);
4207
- return args;
4208
- }
4209
4411
  function parseArguments(stream) {
4210
4412
  if (stream.check("string") || stream.check("template") || stream.check("punctuation", "{")) {
4211
4413
  return [parsePrimary(stream)];
@@ -4244,11 +4446,13 @@ function parseSuffixed(stream) {
4244
4446
  if (token.kind === "punctuation" && token.value === ":" && stream.checkName(1)) {
4245
4447
  stream.next();
4246
4448
  const method = stream.next().value;
4247
- expression = { kind: "call-expression", callee: expression, method, args: parseArguments(stream), position: token.position };
4449
+ const typeArguments2 = parseCallTypeArguments(stream);
4450
+ expression = { kind: "call-expression", callee: expression, method, typeArguments: typeArguments2, args: parseArguments(stream), position: token.position };
4248
4451
  continue;
4249
4452
  }
4250
- if (isCallStart(stream)) {
4251
- expression = { kind: "call-expression", callee: expression, method: null, args: parseArguments(stream), position: token.position };
4453
+ const typeArguments = parseCallTypeArguments(stream);
4454
+ if (typeArguments.length > 0 || isCallStart(stream)) {
4455
+ expression = { kind: "call-expression", callee: expression, method: null, typeArguments, args: parseArguments(stream), position: token.position };
4252
4456
  continue;
4253
4457
  }
4254
4458
  return expression;
@@ -4256,8 +4460,8 @@ function parseSuffixed(stream) {
4256
4460
  }
4257
4461
  function parseUnary(stream) {
4258
4462
  const token = stream.current();
4259
- const isUnary = (token.kind === "operator" || token.kind === "keyword") && UNARY_OPERATORS.has(token.value);
4260
- if (!isUnary) {
4463
+ const isUnary2 = (token.kind === "operator" || token.kind === "keyword") && UNARY_OPERATORS.has(token.value);
4464
+ if (!isUnary2) {
4261
4465
  return parseSuffixed(stream);
4262
4466
  }
4263
4467
  stream.next();
@@ -4367,166 +4571,44 @@ function recoverInBlock(stream, error, terminators = BRACE_TERMINATORS) {
4367
4571
  }
4368
4572
  }
4369
4573
 
4370
- // ../compiler/src/parser/token-stream.ts
4371
- var ParserError = class extends Error {
4372
- diagnostic;
4373
- constructor(diagnostic) {
4374
- super(diagnostic.message);
4375
- this.name = "ParserError";
4376
- this.diagnostic = diagnostic;
4574
+ // ../compiler/src/parser/declarations.ts
4575
+ var DECLARATION_NAMES = /* @__PURE__ */ new Set(["class", "interface", "enum"]);
4576
+ var CLASS_MODIFIERS = /* @__PURE__ */ new Set(["extends", "implements"]);
4577
+ function skipTypeParameters(stream, offset) {
4578
+ if (!stream.checkAhead(offset, "operator", "<")) {
4579
+ return offset;
4377
4580
  }
4378
- };
4379
- var TokenStream = class {
4380
- diagnostics = [];
4381
- tokens;
4382
- index = 0;
4383
- erased = [];
4384
- spans = /* @__PURE__ */ new Map();
4385
- constructor(tokens) {
4386
- this.tokens = tokens;
4581
+ let depth = 0;
4582
+ let index = offset;
4583
+ do {
4584
+ const token = stream.peek(index);
4585
+ depth += token.kind === "operator" && token.value === "<" ? 1 : 0;
4586
+ depth -= token.kind === "operator" && token.value === ">" ? 1 : 0;
4587
+ index += 1;
4588
+ } while (depth > 0 && stream.peek(index).kind !== "eof");
4589
+ return index;
4590
+ }
4591
+ function isClassHeader(stream, offset = 0) {
4592
+ const after = skipTypeParameters(stream, offset + 2);
4593
+ return stream.checkAhead(after, "punctuation", "{") || stream.checkAhead(after, "keyword", "extends") || stream.checkAhead(after, "keyword", "implements");
4594
+ }
4595
+ function isDeclarationStart(stream) {
4596
+ let offset = 0;
4597
+ while (stream.checkAhead(offset, "punctuation", "@") && stream.checkAhead(offset + 1, "identifier")) {
4598
+ offset += 2;
4599
+ if (stream.checkAhead(offset, "punctuation", "(")) {
4600
+ let depth = 0;
4601
+ do {
4602
+ const token2 = stream.peek(offset);
4603
+ depth += token2.kind === "punctuation" && token2.value === "(" ? 1 : 0;
4604
+ depth -= token2.kind === "punctuation" && token2.value === ")" ? 1 : 0;
4605
+ offset += 1;
4606
+ } while (depth > 0 && stream.peek(offset).kind !== "eof");
4607
+ }
4387
4608
  }
4388
- peek(offset = 0) {
4389
- const token = this.tokens[Math.min(this.index + offset, this.tokens.length - 1)];
4390
- return token;
4391
- }
4392
- current() {
4393
- return this.peek();
4394
- }
4395
- isEof() {
4396
- return this.current().kind === "eof";
4397
- }
4398
- next() {
4399
- const token = this.current();
4400
- if (!this.isEof()) {
4401
- this.index += 1;
4402
- }
4403
- return token;
4404
- }
4405
- checkpoint() {
4406
- return this.index;
4407
- }
4408
- eraseFrom(checkpoint, kind = "annotation") {
4409
- const first = this.tokens[checkpoint];
4410
- const last = this.tokens[this.index - 1];
4411
- if (first !== void 0 && last !== void 0 && first.kind !== "eof") {
4412
- this.erased.push({ start: first.position.offset, end: last.end.offset, kind });
4413
- }
4414
- }
4415
- eraseToCurrent(checkpoint) {
4416
- const first = this.tokens[checkpoint];
4417
- if (first !== void 0 && first.kind !== "eof") {
4418
- this.erased.push({ start: first.position.offset, end: this.current().position.offset, kind: "annotation" });
4419
- }
4420
- }
4421
- extendDeclarationErasure(end) {
4422
- const last = this.erased[this.erased.length - 1];
4423
- if (last !== void 0 && last.kind === "declaration" && last.end <= end) {
4424
- last.end = end;
4425
- }
4426
- }
4427
- erasures() {
4428
- return [...this.erased];
4429
- }
4430
- recordSpan(node, checkpoint) {
4431
- const span = this.sourceSpanFrom(checkpoint);
4432
- if (span !== null) {
4433
- this.spans.set(node, span);
4434
- }
4435
- }
4436
- nodeSpans() {
4437
- return this.spans;
4438
- }
4439
- sourceSpanFrom(checkpoint) {
4440
- const first = this.tokens[checkpoint];
4441
- const last = this.tokens[this.index - 1];
4442
- return first === void 0 || last === void 0 || first.kind === "eof" ? null : { start: first.position.offset, end: last.end.offset };
4443
- }
4444
- check(kind, value) {
4445
- const token = this.current();
4446
- return token.kind === kind && (value === void 0 || token.value === value);
4447
- }
4448
- checkAhead(offset, kind, value) {
4449
- const token = this.peek(offset);
4450
- return token.kind === kind && (value === void 0 || token.value === value);
4451
- }
4452
- match(kind, value) {
4453
- if (!this.check(kind, value)) {
4454
- return false;
4455
- }
4456
- this.next();
4457
- return true;
4458
- }
4459
- expect(kind, value) {
4460
- if (this.check(kind, value)) {
4461
- return this.next();
4462
- }
4463
- const expected = value === void 0 ? kind : `"${value}"`;
4464
- throw this.error(`Expected ${expected} but found "${this.describeCurrent()}".`, "parse-unexpected-token");
4465
- }
4466
- checkName(offset = 0) {
4467
- const token = this.peek(offset);
4468
- return token.kind === "identifier" || token.kind === "keyword" && isLuamKeyword(token.value);
4469
- }
4470
- expectName() {
4471
- if (this.checkName()) {
4472
- return this.next();
4473
- }
4474
- throw this.error(`Expected a name but found "${this.describeCurrent()}".`, "parse-unexpected-token");
4475
- }
4476
- describeCurrent() {
4477
- const token = this.current();
4478
- return token.kind === "eof" ? "<end of file>" : token.value;
4479
- }
4480
- error(message, code = "parse-error") {
4481
- const token = this.current();
4482
- return new ParserError(createDiagnostic("parser", code, message, token.position, "error", token.end));
4483
- }
4484
- errorAt(message, code, position2, end) {
4485
- return new ParserError(createDiagnostic("parser", code, message, position2, "error", end));
4486
- }
4487
- report(code, message, position2) {
4488
- this.diagnostics.push(createDiagnostic("parser", code, message, position2));
4489
- }
4490
- };
4491
-
4492
- // ../compiler/src/parser/declarations.ts
4493
- var DECLARATION_NAMES = /* @__PURE__ */ new Set(["class", "interface", "enum"]);
4494
- var CLASS_MODIFIERS = /* @__PURE__ */ new Set(["extends", "implements"]);
4495
- function skipTypeParameters(stream, offset) {
4496
- if (!stream.checkAhead(offset, "operator", "<")) {
4497
- return offset;
4498
- }
4499
- let depth = 0;
4500
- let index = offset;
4501
- do {
4502
- const token = stream.peek(index);
4503
- depth += token.kind === "operator" && token.value === "<" ? 1 : 0;
4504
- depth -= token.kind === "operator" && token.value === ">" ? 1 : 0;
4505
- index += 1;
4506
- } while (depth > 0 && stream.peek(index).kind !== "eof");
4507
- return index;
4508
- }
4509
- function isClassHeader(stream, offset = 0) {
4510
- const after = skipTypeParameters(stream, offset + 2);
4511
- return stream.checkAhead(after, "punctuation", "{") || stream.checkAhead(after, "keyword", "extends") || stream.checkAhead(after, "keyword", "implements");
4512
- }
4513
- function isDeclarationStart(stream) {
4514
- let offset = 0;
4515
- while (stream.checkAhead(offset, "punctuation", "@") && stream.checkAhead(offset + 1, "identifier")) {
4516
- offset += 2;
4517
- if (stream.checkAhead(offset, "punctuation", "(")) {
4518
- let depth = 0;
4519
- do {
4520
- const token2 = stream.peek(offset);
4521
- depth += token2.kind === "punctuation" && token2.value === "(" ? 1 : 0;
4522
- depth -= token2.kind === "punctuation" && token2.value === ")" ? 1 : 0;
4523
- offset += 1;
4524
- } while (depth > 0 && stream.peek(offset).kind !== "eof");
4525
- }
4526
- }
4527
- const token = stream.peek(offset);
4528
- if (token.kind !== "keyword" || !DECLARATION_NAMES.has(token.value) || !stream.checkAhead(offset + 1, "identifier")) {
4529
- return false;
4609
+ const token = stream.peek(offset);
4610
+ if (token.kind !== "keyword" || !DECLARATION_NAMES.has(token.value) || !stream.checkAhead(offset + 1, "identifier")) {
4611
+ return false;
4530
4612
  }
4531
4613
  if (offset > 0 && token.value !== "class") {
4532
4614
  return false;
@@ -4573,6 +4655,8 @@ function parseClassMethod(stream, token, decorators, isStatic) {
4573
4655
  isConstructor: token.value === "constructor",
4574
4656
  isSynthetic: false,
4575
4657
  isStatic,
4658
+ typeParameters: expression.typeParameters,
4659
+ typeConstraints: expression.typeConstraints,
4576
4660
  parameters: expression.parameters,
4577
4661
  returnAnnotation: expression.returnAnnotation,
4578
4662
  body: expression.body,
@@ -4590,6 +4674,8 @@ function parseBraceClassMethod(stream, token, decorators, isStatic) {
4590
4674
  isConstructor: token.value === "constructor",
4591
4675
  isSynthetic: false,
4592
4676
  isStatic,
4677
+ typeParameters: [],
4678
+ typeConstraints: [],
4593
4679
  parameters,
4594
4680
  returnAnnotation,
4595
4681
  body,
@@ -4638,35 +4724,6 @@ function parseClassMember(stream) {
4638
4724
  expectClassFieldBoundary(stream, token);
4639
4725
  return { kind: "class-field", name: token.value, annotation, value, decorators, isStatic, position: token.position };
4640
4726
  }
4641
- function parseTypeParameters(stream) {
4642
- const list = { names: [], constraints: [] };
4643
- if (!stream.check("operator", "<")) {
4644
- return list;
4645
- }
4646
- const checkpoint = stream.checkpoint();
4647
- stream.next();
4648
- do {
4649
- list.names.push(stream.expect("identifier").value);
4650
- list.constraints.push(stream.match("keyword", "extends") ? parseTypeAnnotation(stream) : null);
4651
- } while (stream.match("punctuation", ","));
4652
- stream.expect("operator", ">");
4653
- stream.eraseFrom(checkpoint);
4654
- return list;
4655
- }
4656
- function parseTypeArguments(stream) {
4657
- const args = [];
4658
- if (!stream.check("operator", "<")) {
4659
- return args;
4660
- }
4661
- const checkpoint = stream.checkpoint();
4662
- stream.next();
4663
- do {
4664
- args.push(parseTypeAnnotation(stream));
4665
- } while (stream.match("punctuation", ","));
4666
- stream.expect("operator", ">");
4667
- stream.eraseFrom(checkpoint);
4668
- return args;
4669
- }
4670
4727
  function parseClassModifiers(stream, declaration) {
4671
4728
  while (stream.check("keyword") && CLASS_MODIFIERS.has(stream.current().value)) {
4672
4729
  if (stream.next().value === "extends") {
@@ -5129,6 +5186,8 @@ var ESCAPING_PATH = "config-escaping-path";
5129
5186
  var UNKNOWN_HELPER = "config-unknown-helper";
5130
5187
  var INVALID_PATTERN = "config-invalid-pattern";
5131
5188
  var INVALID_DEPENDENCY = "config-invalid-dependency";
5189
+ var INVALID_LIBRARY = "config-library-invalid";
5190
+ var DUPLICATE_LIBRARY = "config-library-duplicate";
5132
5191
  var INVALID_ENGINE_VERSION = "config-invalid-engine-version";
5133
5192
  var REMOVED_FIELD = "config-removed-field";
5134
5193
  var ALLOWED_STATEMENTS = 'A manifest holds only "local" declarations and assignments to configuration fields.';
@@ -5305,10 +5364,12 @@ function typeToString(type) {
5305
5364
  }
5306
5365
  if (type.kind === "function") {
5307
5366
  const parameters = type.parameters.map(typeToString);
5367
+ const names = type.typeParameters ?? [];
5308
5368
  if (type.isVariadic) {
5309
5369
  parameters.push(type.variadicType === void 0 ? "..." : `...: ${typeToString(type.variadicType)}`);
5310
5370
  }
5311
- return `fun(${parameters.join(", ")}): ${typeToString(type.returnType)}`;
5371
+ const generic = names.length === 0 ? "" : `<${names.join(", ")}>`;
5372
+ return `fun${generic}(${parameters.join(", ")}): ${typeToString(type.returnType)}`;
5312
5373
  }
5313
5374
  return type.kind;
5314
5375
  }
@@ -5754,6 +5815,12 @@ var MANIFEST_FIELDS = [
5754
5815
  owner: "dependencies",
5755
5816
  allowEmpty: true
5756
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
+ }),
5757
5824
  field("contracts", STRING_TYPE, "Directory the export contract is written to and dependency contracts are read from.", {
5758
5825
  defaultValue: DEFAULT_CONTRACTS_DIR,
5759
5826
  rule: "contained-path",
@@ -5918,8 +5985,8 @@ function isNumber(type) {
5918
5985
  function isText(type) {
5919
5986
  return type.kind === "string" || type.kind === "string-literal" || type.kind === "any";
5920
5987
  }
5921
- function fail(operator, expected, left, right, describe2) {
5922
- 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);
5923
5990
  return { evaluated: null, error: `"${operator}" expects ${expected} on both sides but received ${culprit}.` };
5924
5991
  }
5925
5992
  function arithmetic(operator, left, right) {
@@ -5958,22 +6025,22 @@ function applyOr(left, right) {
5958
6025
  const value = isTruthy(left.value) ? left.value : right.value;
5959
6026
  return { value, type: unionOf([left.truthy, right.type]), truthy: unionOf([left.truthy, right.truthy]), falsy: right.falsy };
5960
6027
  }
5961
- function applyOrdering(operator, left, right, describe2) {
6028
+ function applyOrdering(operator, left, right, describe3) {
5962
6029
  const numeric = isNumber(left.type) && isNumber(right.type);
5963
6030
  const textual = isText(left.type) && isText(right.type);
5964
6031
  if (!numeric && !textual) {
5965
- 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);
5966
6033
  }
5967
6034
  return { evaluated: booleanValue(compare(operator, left.value, right.value)), error: null };
5968
6035
  }
5969
- function applyConcat(left, right, describe2) {
6036
+ function applyConcat(left, right, describe3) {
5970
6037
  const usable = (type) => isText(type) || isNumber(type);
5971
6038
  if (!usable(left.type) || !usable(right.type)) {
5972
- 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);
5973
6040
  }
5974
6041
  return { evaluated: stringValue(`${left.value}${right.value}`), error: null };
5975
6042
  }
5976
- function applyBinary(operator, left, right, describe2) {
6043
+ function applyBinary(operator, left, right, describe3) {
5977
6044
  if (operator === "and") {
5978
6045
  return { evaluated: applyAnd(left, right), error: null };
5979
6046
  }
@@ -5985,23 +6052,23 @@ function applyBinary(operator, left, right, describe2) {
5985
6052
  return { evaluated: booleanValue(operator === "==" ? same : !same), error: null };
5986
6053
  }
5987
6054
  if (ORDERING.includes(operator)) {
5988
- return applyOrdering(operator, left, right, describe2);
6055
+ return applyOrdering(operator, left, right, describe3);
5989
6056
  }
5990
6057
  if (operator === "..") {
5991
- return applyConcat(left, right, describe2);
6058
+ return applyConcat(left, right, describe3);
5992
6059
  }
5993
6060
  if (!isNumber(left.type) || !isNumber(right.type)) {
5994
- return fail(operator, "a number", left.type, right.type, describe2);
6061
+ return fail(operator, "a number", left.type, right.type, describe3);
5995
6062
  }
5996
6063
  return { evaluated: numberValue(arithmetic(operator, left.value, right.value)), error: null };
5997
6064
  }
5998
- function applyUnary(operator, operand, describe2) {
6065
+ function applyUnary(operator, operand, describe3) {
5999
6066
  if (operator === "not") {
6000
6067
  return { evaluated: booleanValue(!isTruthy(operand.value)), error: null };
6001
6068
  }
6002
6069
  if (operator === "-") {
6003
6070
  if (!isNumber(operand.type)) {
6004
- 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)}.` };
6005
6072
  }
6006
6073
  return { evaluated: numberValue(-operand.value), error: null };
6007
6074
  }
@@ -6337,6 +6404,130 @@ function watchRoots(patterns) {
6337
6404
  return roots.filter((root) => !roots.some((other) => other.length < root.length && root.startsWith(`${other}/`)));
6338
6405
  }
6339
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
+
6340
6531
  // ../compiler/src/manifest/manifest-rules.ts
6341
6532
  var START = createPosition(1, 1, 0);
6342
6533
  var RESOURCE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
@@ -6429,6 +6620,9 @@ var RuleWalk = class {
6429
6620
  if (entry.rule === "dependency-name" && !isValidResourceName(value)) {
6430
6621
  this.report(INVALID_DEPENDENCY, `"${path}" must be a valid MTA resource name but received "${value}".`, key);
6431
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
+ }
6432
6626
  }
6433
6627
  version(entry, value, path, key) {
6434
6628
  if (entry.rule !== "engine-version" || value === LATEST_ENGINE_VERSION || ENGINE_VERSION.test(value)) {
@@ -6465,6 +6659,7 @@ function normalizeManifest(value, positions) {
6465
6659
  }
6466
6660
 
6467
6661
  // ../compiler/src/manifest/manifest-analysis.ts
6662
+ var MANIFEST_SCHEMA = { fields: MANIFEST_FIELDS, removed: REMOVED_FIELDS, normalize: normalizeManifest };
6468
6663
  var START2 = createPosition(1, 1, 0);
6469
6664
  function statementError(kind) {
6470
6665
  return `A manifest cannot contain ${kind}. ${ALLOWED_STATEMENTS}`;
@@ -6494,7 +6689,7 @@ function readLocal(pass, statement) {
6494
6689
  pass.declareLocal(declaration.name, value === void 0 ? nilValue() : pass.evaluate(value, null), declaration.position);
6495
6690
  }
6496
6691
  }
6497
- function readAssignment(pass, statement, assignments, value) {
6692
+ function readAssignment(pass, statement, assignments, value, schema) {
6498
6693
  const [target] = statement.targets;
6499
6694
  const [expression] = statement.values;
6500
6695
  if (statement.operator !== "=" || statement.targets.length !== 1 || statement.values.length !== 1 || target === void 0 || expression === void 0) {
@@ -6505,11 +6700,11 @@ function readAssignment(pass, statement, assignments, value) {
6505
6700
  pass.report(INVALID_STATEMENT, statementError("an assignment to anything but a configuration field"), target.position);
6506
6701
  return;
6507
6702
  }
6508
- const field2 = findField(MANIFEST_FIELDS, target.name);
6703
+ const field2 = findField(schema.fields, target.name);
6509
6704
  if (field2 === null) {
6510
- const replacement = REMOVED_FIELDS[target.name];
6705
+ const replacement = schema.removed[target.name];
6511
6706
  if (replacement === void 0) {
6512
- pass.report(UNKNOWN_FIELD, unknownNameMessage(target.name), target.position);
6707
+ pass.report(UNKNOWN_FIELD, (schema.unknownName ?? unknownNameMessage)(target.name), target.position);
6513
6708
  } else {
6514
6709
  pass.report(REMOVED_FIELD, `"${target.name}" is no longer a manifest field. ${replacement}`, target.position);
6515
6710
  }
@@ -6518,25 +6713,25 @@ function readAssignment(pass, statement, assignments, value) {
6518
6713
  assignments.push({ name: target.name, position: target.position, value: expression });
6519
6714
  value[target.name] = pass.expression(expression, { field: field2, path: target.name, key: target.name }).value;
6520
6715
  }
6521
- function readStatement(pass, statement, assignments, value) {
6716
+ function readStatement(pass, statement, assignments, value, schema) {
6522
6717
  if (statement.kind === "local-statement") {
6523
6718
  readLocal(pass, statement);
6524
6719
  return;
6525
6720
  }
6526
6721
  if (statement.kind === "assignment-statement") {
6527
- readAssignment(pass, statement, assignments, value);
6722
+ readAssignment(pass, statement, assignments, value, schema);
6528
6723
  return;
6529
6724
  }
6530
6725
  pass.report(INVALID_STATEMENT, statementError(STATEMENT_NAMES[statement.kind] ?? "this statement"), statement.position);
6531
6726
  }
6532
- function reportMissing(diagnostics, value) {
6533
- for (const field2 of requiredFields(MANIFEST_FIELDS)) {
6727
+ function reportMissing(diagnostics, value, schema) {
6728
+ for (const field2 of requiredFields(schema.fields)) {
6534
6729
  if (value[field2.name] === void 0) {
6535
6730
  diagnostics.push(manifestError(MISSING_FIELD, `The manifest requires a "${field2.name}" field. ${field2.summary}`, START2));
6536
6731
  }
6537
6732
  }
6538
6733
  }
6539
- function analyzeManifest(source, context) {
6734
+ function analyzeManifest(source, context, schema = MANIFEST_SCHEMA) {
6540
6735
  const parsed = parse(source);
6541
6736
  const pass = new ManifestPass(context);
6542
6737
  const assignments = [];
@@ -6545,15 +6740,15 @@ function analyzeManifest(source, context) {
6545
6740
  pass.report(INVALID_STATEMENT, statementError(`the "#!${directive}" directive`), START2);
6546
6741
  }
6547
6742
  for (const statement of parsed.program.body) {
6548
- readStatement(pass, statement, assignments, raw);
6743
+ readStatement(pass, statement, assignments, raw, schema);
6549
6744
  }
6550
- reportMissing(pass.diagnostics, raw);
6745
+ reportMissing(pass.diagnostics, raw, schema);
6551
6746
  pass.diagnostics.push(...pass.locals.unused());
6552
- const normalized = normalizeManifest(raw, pass.positions);
6747
+ const normalized = schema.normalize(raw, pass.positions);
6553
6748
  return {
6554
6749
  program: parsed.program,
6555
6750
  tokens: parsed.tokens,
6556
- diagnostics: sortDiagnostics([...parsed.diagnostics, ...pass.diagnostics, ...normalized.diagnostics]),
6751
+ diagnostics: sortDiagnostics([...parsed.diagnostics, ...pass.diagnostics, ...normalized.diagnostics]).map(schema.retag ?? ((entry) => entry)),
6557
6752
  value: normalized.value,
6558
6753
  raw,
6559
6754
  positions: pass.positions,
@@ -6625,6 +6820,9 @@ function readAssetMappings(value) {
6625
6820
  function readDependencies(value) {
6626
6821
  return [...new Set(readStrings(value, "dependencies"))].sort();
6627
6822
  }
6823
+ function readLibraries(value) {
6824
+ return readStrings(value, "libraries");
6825
+ }
6628
6826
  function readEngine(value) {
6629
6827
  const source = readTable(value, "engine") ?? EMPTY;
6630
6828
  return { minVersion: readString(source, "minVersion") ?? DEFAULT_ENGINE.minVersion };
@@ -6684,10 +6882,20 @@ function checkDependencies(name, dependencies, context) {
6684
6882
  }
6685
6883
  }
6686
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
+ }
6687
6894
  function validateConfig(value, positions) {
6688
6895
  const context = new ValidationContext(positions);
6689
6896
  const name = readString(value, "name") ?? "";
6690
6897
  const dependencies = readDependencies(value);
6898
+ const libraries = readLibraries(value);
6691
6899
  const config = {
6692
6900
  name,
6693
6901
  author: readString(value, "author"),
@@ -6697,6 +6905,7 @@ function validateConfig(value, positions) {
6697
6905
  sources: readSourceMapping(value),
6698
6906
  assets: readAssetMappings(value),
6699
6907
  dependencies,
6908
+ libraries,
6700
6909
  contracts: readString(value, "contracts") ?? "",
6701
6910
  engine: readEngine(value),
6702
6911
  environment: readEnvironmentFiles(value),
@@ -6709,6 +6918,7 @@ function validateConfig(value, positions) {
6709
6918
  development: readDevelopment(readTable(value, "development"))
6710
6919
  };
6711
6920
  checkDependencies(name, dependencies, context);
6921
+ checkLibraries(libraries, context);
6712
6922
  return { config, diagnostics: context.diagnostics };
6713
6923
  }
6714
6924
 
@@ -6762,7 +6972,7 @@ import { tmpdir } from "node:os";
6762
6972
  import { join as join2 } from "node:path";
6763
6973
 
6764
6974
  // src/cli/version.ts
6765
- var VERSION = true ? "0.19.5" : "0.0.0-dev";
6975
+ var VERSION = true ? "0.19.8" : "0.0.0-dev";
6766
6976
  var PROGRAM_NAME = "luam";
6767
6977
  var PROGRAM_DESCRIPTION = "luam \u2014 the Luam compiler for Multi Theft Auto resources.";
6768
6978
 
@@ -7296,6 +7506,9 @@ function manifestOption() {
7296
7506
  function colorOption() {
7297
7507
  return new Option("--no-color", "Print plain output with no colour and no emoji.");
7298
7508
  }
7509
+ function jsonOption() {
7510
+ return new Option("--json", "Write one machine-readable document to stdout instead of the human report.");
7511
+ }
7299
7512
  function offlineOption() {
7300
7513
  return new Option("--offline", "Skip the MTA release lookup and use the cached version, if there is one.");
7301
7514
  }
@@ -7411,15 +7624,15 @@ function knowsResource(contracts, resource) {
7411
7624
  }
7412
7625
  function buildResourceAbi(resource, contributions) {
7413
7626
  const exports = contributions.map((contribution) => {
7414
- const signature = contribution.signature;
7627
+ const signature2 = contribution.signature;
7415
7628
  return {
7416
7629
  name: contribution.name,
7417
7630
  side: contribution.side,
7418
7631
  http: contribution.http,
7419
- parameters: signature?.parameters ?? [],
7420
- minimumArguments: signature?.minimumArguments ?? 0,
7421
- variadic: signature?.variadic ?? true,
7422
- returns: signature?.returns ?? "any"
7632
+ parameters: signature2?.parameters ?? [],
7633
+ minimumArguments: signature2?.minimumArguments ?? 0,
7634
+ variadic: signature2?.variadic ?? true,
7635
+ returns: signature2?.returns ?? "any"
7423
7636
  };
7424
7637
  });
7425
7638
  return { abi: ABI_VERSION, resource, exports };
@@ -7476,78 +7689,9 @@ function readHelperSource(helper, file) {
7476
7689
  return readFileSync4(resolveHelperPath(helper, file), "utf8");
7477
7690
  }
7478
7691
 
7479
- // src/build/build-phase.ts
7480
- var COUNTED_PHASES = /* @__PURE__ */ new Set(["compile", "write"]);
7481
- var PHASE_LABELS = {
7482
- version: "Version",
7483
- discovery: "Discovery",
7484
- compile: "Compile",
7485
- assembly: "Assembly",
7486
- manifest: "Manifest",
7487
- write: "Write",
7488
- sync: "Sync",
7489
- restart: "Restart"
7490
- };
7491
- var PHASE_UNITS = {
7492
- version: "step",
7493
- discovery: "file",
7494
- compile: "file",
7495
- assembly: "step",
7496
- manifest: "step",
7497
- write: "file",
7498
- sync: "file",
7499
- restart: "step"
7500
- };
7501
- function isCountedPhase(phase) {
7502
- return COUNTED_PHASES.has(phase);
7503
- }
7504
-
7505
- // src/build/phase-tracker.ts
7506
- function createPhaseTracker(listener = null) {
7507
- const closed = [];
7508
- let open = null;
7509
- function emit3(phase, state, item, durationMs) {
7510
- listener?.({ phase: phase.phase, state, completed: phase.completed, total: phase.total, item, durationMs });
7511
- }
7512
- function close(state) {
7513
- if (open === null) {
7514
- return;
7515
- }
7516
- const durationMs = performance.now() - open.startedAt;
7517
- const completed = state === "done" && isCountedPhase(open.phase) ? open.total : open.completed;
7518
- const finished = { ...open, completed };
7519
- closed.push({ phase: open.phase, state, completed, total: open.total, durationMs });
7520
- open = null;
7521
- emit3(finished, state, null, durationMs);
7522
- }
7523
- return {
7524
- begin: (phase, total = 0) => {
7525
- close("done");
7526
- open = { phase, startedAt: performance.now(), completed: 0, total };
7527
- emit3(open, "active", null, 0);
7528
- },
7529
- advance: (item, completed, total) => {
7530
- if (open === null) {
7531
- return;
7532
- }
7533
- open.completed = completed;
7534
- open.total = total;
7535
- emit3(open, "active", item, performance.now() - open.startedAt);
7536
- },
7537
- end: (state = "done") => {
7538
- close(state);
7539
- },
7540
- durations: () => [...closed]
7541
- };
7542
- }
7543
-
7544
- // src/build/project-inputs.ts
7545
- import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
7546
- import { resolve as resolve7 } from "node:path";
7547
-
7548
- // src/build/asset-resolution.ts
7549
- import { statSync as statSync3 } from "node:fs";
7550
- import { resolve as resolve6 } from "node:path";
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";
7551
7695
 
7552
7696
  // src/build/project-files.ts
7553
7697
  import { readdirSync, statSync as statSync2 } from "node:fs";
@@ -7597,6 +7741,196 @@ function listProjectFiles(root, roots, excluded = []) {
7597
7741
  return { files: [...new Set(tree.files)].sort(), errors: tree.errors };
7598
7742
  }
7599
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
+
7861
+ // src/build/build-phase.ts
7862
+ var COUNTED_PHASES = /* @__PURE__ */ new Set(["compile", "write"]);
7863
+ var PHASE_LABELS = {
7864
+ version: "Version",
7865
+ discovery: "Discovery",
7866
+ compile: "Compile",
7867
+ assembly: "Assembly",
7868
+ manifest: "Manifest",
7869
+ write: "Write",
7870
+ sync: "Sync",
7871
+ restart: "Restart"
7872
+ };
7873
+ var PHASE_UNITS = {
7874
+ version: "step",
7875
+ discovery: "file",
7876
+ compile: "file",
7877
+ assembly: "step",
7878
+ manifest: "step",
7879
+ write: "file",
7880
+ sync: "file",
7881
+ restart: "step"
7882
+ };
7883
+ function isCountedPhase(phase) {
7884
+ return COUNTED_PHASES.has(phase);
7885
+ }
7886
+
7887
+ // src/build/phase-tracker.ts
7888
+ function createPhaseTracker(listener = null) {
7889
+ const closed = [];
7890
+ let open = null;
7891
+ function emit3(phase, state, item, durationMs) {
7892
+ listener?.({ phase: phase.phase, state, completed: phase.completed, total: phase.total, item, durationMs });
7893
+ }
7894
+ function close(state) {
7895
+ if (open === null) {
7896
+ return;
7897
+ }
7898
+ const durationMs = performance.now() - open.startedAt;
7899
+ const completed = state === "done" && isCountedPhase(open.phase) ? open.total : open.completed;
7900
+ const finished = { ...open, completed };
7901
+ closed.push({ phase: open.phase, state, completed, total: open.total, durationMs });
7902
+ open = null;
7903
+ emit3(finished, state, null, durationMs);
7904
+ }
7905
+ return {
7906
+ begin: (phase, total = 0) => {
7907
+ close("done");
7908
+ open = { phase, startedAt: performance.now(), completed: 0, total };
7909
+ emit3(open, "active", null, 0);
7910
+ },
7911
+ advance: (item, completed, total) => {
7912
+ if (open === null) {
7913
+ return;
7914
+ }
7915
+ open.completed = completed;
7916
+ open.total = total;
7917
+ emit3(open, "active", item, performance.now() - open.startedAt);
7918
+ },
7919
+ end: (state = "done") => {
7920
+ close(state);
7921
+ },
7922
+ durations: () => [...closed]
7923
+ };
7924
+ }
7925
+
7926
+ // src/build/project-inputs.ts
7927
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
7928
+ import { resolve as resolve8 } from "node:path";
7929
+
7930
+ // src/build/asset-resolution.ts
7931
+ import { statSync as statSync3 } from "node:fs";
7932
+ import { resolve as resolve7 } from "node:path";
7933
+
7600
7934
  // ../compiler/src/project/resource-map.ts
7601
7935
  var RESOURCE_MAP_VERSION = 1;
7602
7936
  function findSegment(segments, line2) {
@@ -7669,11 +8003,12 @@ var ENVIRONMENTS2 = ["shared", "server", "client"];
7669
8003
  function bundlePath(environment) {
7670
8004
  return `${BUNDLE_DIRECTORY}/${environment}.lua`;
7671
8005
  }
7672
- function collectBundles(helpers, scripts, pinned) {
8006
+ function collectBundles(helpers, scripts, pinned, libraries = []) {
7673
8007
  const pinnedPaths = new Set(pinned.map((script) => script.path));
7674
8008
  return ENVIRONMENTS2.flatMap((environment) => {
7675
8009
  const environmentHelpers = helpers.filter((helper) => helper.environment === environment).map((helper) => ({ kind: "helper", helper }));
7676
8010
  const orderedModules = [
8011
+ ...libraries.filter((script) => script.environment === environment),
7677
8012
  ...pinned.filter((script) => script.environment === environment),
7678
8013
  ...scripts.filter((script) => script.environment === environment && !pinnedPaths.has(script.path))
7679
8014
  ].map((module) => ({ kind: "module", module }));
@@ -7731,6 +8066,9 @@ var OCCUPIED_SIDES = {
7731
8066
  function occupiedSides(contribution) {
7732
8067
  return OCCUPIED_SIDES[contribution.side];
7733
8068
  }
8069
+ function sidesOf(environment) {
8070
+ return OCCUPIED_SIDES[environment];
8071
+ }
7734
8072
  function exportSignature(type) {
7735
8073
  if (type === void 0 || type.kind !== "function") {
7736
8074
  return null;
@@ -7753,10 +8091,11 @@ function toContributions(directives, environment, globals) {
7753
8091
  var INDENT = " ";
7754
8092
  var RESOURCE_TYPE = "script";
7755
8093
  var DEFAULT_SIDE = "server";
7756
- var SIDE_ORDER = { shared: 0, server: 1, client: 2 };
7757
- var SCRIPT_GROUPS = ["library", "configuration", "source"];
8094
+ var SIDE_ORDER2 = { shared: 0, server: 1, client: 2 };
8095
+ var SCRIPT_GROUPS = ["library", "libraries", "configuration", "source"];
7758
8096
  var GROUP_COMMENTS = {
7759
8097
  library: "Runtime library",
8098
+ libraries: "Libraries",
7760
8099
  configuration: "Configuration",
7761
8100
  source: "Source scripts"
7762
8101
  };
@@ -7819,7 +8158,7 @@ function versionElement(version) {
7819
8158
  return `${INDENT}<min_mta_version ${attribute("server", version)} ${attribute("client", version)} />`;
7820
8159
  }
7821
8160
  function exportElements(contributions) {
7822
- 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);
7823
8162
  }
7824
8163
  function scriptSections(scripts) {
7825
8164
  return SCRIPT_GROUPS.flatMap((group) => section(GROUP_COMMENTS[group], scripts.filter((script) => script.group === group).map(scriptElement)));
@@ -7903,16 +8242,39 @@ function collectDevelopmentLogHelpers(options) {
7903
8242
  replacements
7904
8243
  }));
7905
8244
  }
8245
+ function emitted(modules) {
8246
+ return modules.filter((module) => module.code !== null);
8247
+ }
7906
8248
  function collectScripts(modules) {
7907
- 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);
7908
8265
  }
7909
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.';
7910
8268
  function missingEntry(entry) {
7911
8269
  return { path: entry, diagnostic: createDiagnostic("project", "project-load-order-missing", `"${entry}" ${MISSING_LOAD_ORDER}`, FILE_START) };
7912
8270
  }
7913
- 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 = []) {
7914
8275
  const byScript = new Map(scripts.map((script) => [normalizePath(script.source), script]));
7915
8276
  const byAsset = new Map(assets.map((asset) => [normalizePath(asset.source), asset]));
8277
+ const byLibrary = new Set(libraries.map((script) => normalizePath(script.source)));
7916
8278
  const resolved2 = { scripts: [], assets: [], diagnostics: [] };
7917
8279
  const seen = /* @__PURE__ */ new Set();
7918
8280
  for (const entry of entries3) {
@@ -7924,7 +8286,7 @@ function resolveLoadOrder(entries3, scripts, assets) {
7924
8286
  }
7925
8287
  seen.add(path);
7926
8288
  if (script === void 0 && asset === void 0) {
7927
- resolved2.diagnostics.push(missingEntry(entry));
8289
+ resolved2.diagnostics.push(byLibrary.has(path) ? libraryEntry(entry) : missingEntry(entry));
7928
8290
  continue;
7929
8291
  }
7930
8292
  if (script !== void 0) {
@@ -8085,9 +8447,9 @@ function withEnvironmentFile(helpers, file) {
8085
8447
  function helperEntry(helper) {
8086
8448
  return { src: helper.path, environment: helper.environment, group: "library" };
8087
8449
  }
8088
- function manifestScripts(helpers, configuration, sources) {
8450
+ function manifestScripts(helpers, libraries, configuration, sources) {
8089
8451
  const settings = configuration === null ? [] : [{ src: configuration.path, environment: "shared", group: "configuration" }];
8090
- return [...helpers.map(helperEntry), ...settings, ...sources];
8452
+ return [...helpers.map(helperEntry), ...libraries, ...settings, ...sources];
8091
8453
  }
8092
8454
  function collectContributions(project) {
8093
8455
  return project.modules.flatMap((module) => module.contributions);
@@ -8160,32 +8522,35 @@ function assembleResource(project, options, onStep) {
8160
8522
  const collected = [...collectHelpers(project.modules, options.helpers ?? []), ...collectDevelopmentLogHelpers(options.developmentLogs)];
8161
8523
  const helpers = withEnvironmentFile(collected, options.environmentFile);
8162
8524
  const scripts = collectScripts(project.modules);
8525
+ const libraries = collectLibraryScripts(project.modules, options.libraryFiles ?? []);
8163
8526
  const configuration = configurationScript(options.configuration);
8164
8527
  const deployment = configuration === null ? [] : [configuration];
8165
- const sorted = [...options.assets ?? []].sort((left, right) => left.path.localeCompare(right.path));
8166
- 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);
8167
8530
  const layout = options.layout ?? "tree";
8168
- const bundles = layout === "bundle" ? collectBundles(helpers, scripts, order.scripts) : [];
8169
- const duplicates = layout === "tree" ? findDuplicateOutputs([...scripts, ...deployment], sorted) : [...findDuplicateOutputs(scripts, []), ...findDuplicateOutputs(deployment, sorted)];
8170
- 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) : [];
8171
8534
  const diagnostics = sortFileDiagnostics([...project.diagnostics, ...duplicates, ...collisions, ...order.diagnostics]);
8172
8535
  if (duplicates.length > 0 || collisions.length > 0 || order.diagnostics.length > 0) {
8173
8536
  return { build: null, diagnostics };
8174
8537
  }
8175
8538
  onStep?.("assembly");
8176
- const assets = orderAssets(sorted, order.assets);
8539
+ const assets = orderAssets(sorted2, order.assets);
8177
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" }));
8178
8541
  const manifestHelpers = layout === "tree" ? helpers : [];
8542
+ const vendored = layout === "tree" ? libraries.map((script) => ({ src: script.path, environment: script.environment, group: "libraries" })) : [];
8179
8543
  const manifest = generateManifest(
8180
8544
  manifestInfo(options),
8181
- manifestScripts(manifestHelpers, configuration, sources),
8545
+ manifestScripts(manifestHelpers, vendored, configuration, sources),
8182
8546
  manifestFiles(assets),
8183
8547
  collectContributions(project),
8184
8548
  { oop: options.oop === true, minMtaVersion: options.minMtaVersion ?? null, dependencies: options.dependencies ?? [] }
8185
8549
  );
8186
8550
  onStep?.("manifest");
8187
- const map = layout === "tree" ? treeResourceMap(options.resourceName ?? "", scripts) : null;
8188
- 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 };
8189
8554
  }
8190
8555
 
8191
8556
  // src/build/asset-resolution.ts
@@ -8195,14 +8560,14 @@ var MANIFEST_OUTPUT = "meta.xml";
8195
8560
  var HERE = ".";
8196
8561
  function isDirectory2(root, path) {
8197
8562
  try {
8198
- return statSync3(resolve6(root, path)).isDirectory();
8563
+ return statSync3(resolve7(root, path)).isDirectory();
8199
8564
  } catch {
8200
8565
  return false;
8201
8566
  }
8202
8567
  }
8203
8568
  function exists(root, path) {
8204
8569
  try {
8205
- statSync3(resolve6(root, path));
8570
+ statSync3(resolve7(root, path));
8206
8571
  return true;
8207
8572
  } catch {
8208
8573
  return false;
@@ -8270,10 +8635,10 @@ var CONFIGURATION_FILE = "config.lua";
8270
8635
  var MALFORMED_ENV = "build-env-malformed";
8271
8636
  var MISSING_ENV = "config-missing-env-file";
8272
8637
  function readTextFile(path) {
8273
- return existsSync4(path) ? readFileSync5(path, "utf8") : null;
8638
+ return existsSync5(path) ? readFileSync6(path, "utf8") : null;
8274
8639
  }
8275
8640
  function readEnvironment(root, file, required, diagnostics) {
8276
- const source = readTextFile(resolve7(root, file));
8641
+ const source = readTextFile(resolve8(root, file));
8277
8642
  if (source === null) {
8278
8643
  if (required) {
8279
8644
  diagnostics.push(cliError(MISSING_ENV, `"${file}" is configured under "environment" but does not exist. Create it or remove the setting.`));
@@ -8287,7 +8652,7 @@ function readEnvironment(root, file, required, diagnostics) {
8287
8652
  return parsed;
8288
8653
  }
8289
8654
  function readConfiguration(root, diagnostics) {
8290
- const content = readTextFile(resolve7(root, CONFIGURATION_FILE));
8655
+ const content = readTextFile(resolve8(root, CONFIGURATION_FILE));
8291
8656
  if (content === null) {
8292
8657
  return null;
8293
8658
  }
@@ -8306,14 +8671,14 @@ function readProjectInputs(root, options) {
8306
8671
  }
8307
8672
 
8308
8673
  // src/build/source-discovery.ts
8309
- import { readFileSync as readFileSync6 } from "node:fs";
8310
- import { resolve as resolve8 } from "node:path";
8674
+ import { readFileSync as readFileSync7 } from "node:fs";
8675
+ import { resolve as resolve9 } from "node:path";
8311
8676
 
8312
8677
  // ../compiler/src/project/source-mapping.ts
8313
8678
  function createSourceResolver(mapping) {
8314
8679
  const matchers = new Map(SOURCE_SIDES.map((environment) => [environment, createPatternMatcher(mapping[environment])]));
8315
8680
  const patterns = SOURCE_SIDES.flatMap((environment) => [...matchers.get(environment)?.patterns ?? []]);
8316
- function resolve21(path) {
8681
+ function resolve24(path) {
8317
8682
  const normalized = normalizePattern(path);
8318
8683
  const matches = [];
8319
8684
  for (const environment of SOURCE_SIDES) {
@@ -8327,25 +8692,14 @@ function createSourceResolver(mapping) {
8327
8692
  return {
8328
8693
  patterns,
8329
8694
  roots: watchRoots(patterns),
8330
- resolve: resolve21,
8331
- side: (path) => resolve21(path).environment
8695
+ resolve: resolve24,
8696
+ side: (path) => resolve24(path).environment
8332
8697
  };
8333
8698
  }
8334
8699
  function describeMatches(matches) {
8335
8700
  return matches.map((match) => `"${match.environment}" through "${match.pattern}"`).join(" and ");
8336
8701
  }
8337
8702
 
8338
- // ../compiler/src/project/source-kind.ts
8339
- var SOURCE_EXTENSION = ".luam";
8340
- var DECLARATION_EXTENSION = ".d.luam";
8341
- var TEST_EXTENSION = ".test.luam";
8342
- function isDeclarationPath(path) {
8343
- return normalizePath(path).endsWith(DECLARATION_EXTENSION);
8344
- }
8345
- function isTestPath(path) {
8346
- return normalizePath(path).endsWith(TEST_EXTENSION);
8347
- }
8348
-
8349
8703
  // src/build/source-discovery.ts
8350
8704
  var MISSING_SOURCE = "config-missing-source";
8351
8705
  var NO_SOURCES = "config-no-sources";
@@ -8371,7 +8725,7 @@ function checkLiterals(root, resolver, found, diagnostics) {
8371
8725
  }
8372
8726
  function readSource(root, path, diagnostics) {
8373
8727
  try {
8374
- return readFileSync6(resolve8(root, path), "utf8");
8728
+ return readFileSync7(resolve9(root, path), "utf8");
8375
8729
  } catch (error) {
8376
8730
  diagnostics.push(cliError(UNREADABLE_SOURCE, `The source file "${path}" could not be read: ${error instanceof Error ? error.message : String(error)}`));
8377
8731
  return null;
@@ -8780,6 +9134,13 @@ function assignmentFact(narrowed, declared) {
8780
9134
  }
8781
9135
 
8782
9136
  // ../compiler/src/checker/type-substitution.ts
9137
+ function shadowed(substitutions, names) {
9138
+ const remaining = new Map(substitutions);
9139
+ for (const name of names) {
9140
+ remaining.delete(name);
9141
+ }
9142
+ return remaining;
9143
+ }
8783
9144
  function substituteType(type, substitutions) {
8784
9145
  if (type.kind === "named") {
8785
9146
  const args = type.typeArguments ?? [];
@@ -8801,10 +9162,17 @@ function substituteType(type, substitutions) {
8801
9162
  return createUnion(type.options.map((option) => substituteType(option, substitutions)));
8802
9163
  }
8803
9164
  if (type.kind === "function") {
8804
- const parameters = type.parameters.map((parameter) => substituteType(parameter, substitutions));
8805
- const returnType = substituteType(type.returnType, substitutions);
8806
- const variadicType = type.variadicType === void 0 ? void 0 : substituteType(type.variadicType, substitutions);
8807
- return createFunction(parameters, returnType, type.minimumArguments, type.isVariadic, type.parameterNames, variadicType);
9165
+ const own = type.typeParameters ?? [];
9166
+ const outer = own.length === 0 ? substitutions : shadowed(substitutions, own);
9167
+ const parameters = type.parameters.map((parameter) => substituteType(parameter, outer));
9168
+ const returnType = substituteType(type.returnType, outer);
9169
+ const variadicType = type.variadicType === void 0 ? void 0 : substituteType(type.variadicType, outer);
9170
+ const substituted = createFunction(parameters, returnType, type.minimumArguments, type.isVariadic, type.parameterNames, variadicType);
9171
+ if (own.length > 0) {
9172
+ substituted.typeParameters = [...own];
9173
+ substituted.typeConstraints = (type.typeConstraints ?? []).map((constraint) => constraint === null ? null : substituteType(constraint, outer));
9174
+ }
9175
+ return substituted;
8808
9176
  }
8809
9177
  if (type.kind === "tuple") {
8810
9178
  return createTuple(type.elements.map((element) => substituteType(element, substitutions)));
@@ -8921,21 +9289,24 @@ function satisfies(context, argument, constraint) {
8921
9289
  }
8922
9290
  return isAssignable(argument, constraint, { allowNil: false });
8923
9291
  }
8924
- function checkTypeConstraints(context, name, typeArguments, position2) {
8925
- const info = context.declarations.lookupClass(name);
8926
- if (info === null) {
8927
- return;
8928
- }
8929
- info.typeConstraints.forEach((constraint, index) => {
9292
+ function reportConstraintViolations(context, owner, typeParameters, typeConstraints, typeArguments, position2) {
9293
+ typeConstraints.forEach((constraint, index) => {
8930
9294
  const argument = typeArguments[index];
8931
9295
  if (constraint === null || argument === void 0 || argument.kind === "any" || satisfies(context, argument, constraint)) {
8932
9296
  return;
8933
9297
  }
8934
- const parameter = info.typeParameters[index] ?? "T";
8935
- const message = `Type argument "${typeToString(argument)}" does not satisfy "${parameter} extends ${typeToString(constraint)}" on class "${name}".`;
9298
+ const parameter = typeParameters[index] ?? "T";
9299
+ const message = `Type argument "${typeToString(argument)}" does not satisfy "${parameter} extends ${typeToString(constraint)}" on ${owner}.`;
8936
9300
  context.report("check-generic-constraint", message, position2);
8937
9301
  });
8938
9302
  }
9303
+ function checkTypeConstraints(context, name, typeArguments, position2) {
9304
+ const info = context.declarations.lookupClass(name);
9305
+ if (info === null) {
9306
+ return;
9307
+ }
9308
+ reportConstraintViolations(context, `class "${name}"`, info.typeParameters, info.typeConstraints, typeArguments, position2);
9309
+ }
8939
9310
 
8940
9311
  // ../mta-types/src/lua-standard.ts
8941
9312
  var LUA_GLOBALS = {
@@ -14925,12 +15296,12 @@ function failed(type) {
14925
15296
  function entries2(parts) {
14926
15297
  return `{ ${parts.join(", ")} }`;
14927
15298
  }
14928
- function combine(parts, render2) {
15299
+ function combine(parts, render3) {
14929
15300
  const broken = parts.find((part) => part.lua === null);
14930
15301
  if (broken !== void 0) {
14931
15302
  return broken;
14932
15303
  }
14933
- return { lua: render2(parts.map((part) => part.lua)), unreifiable: null };
15304
+ return { lua: render3(parts.map((part) => part.lua)), unreifiable: null };
14934
15305
  }
14935
15306
  function namedDescriptor(context, type, name, seen) {
14936
15307
  if (context.declarations.lookupClass(name) !== null) {
@@ -15027,6 +15398,8 @@ function accessorMethod(field2, decorator, name) {
15027
15398
  isConstructor: false,
15028
15399
  isSynthetic: true,
15029
15400
  isStatic: false,
15401
+ typeParameters: [],
15402
+ typeConstraints: [],
15030
15403
  parameters: decorator === "Getter" ? [] : [value],
15031
15404
  returnAnnotation: decorator === "Getter" ? field2.annotation : voidAnnotation,
15032
15405
  body,
@@ -15035,7 +15408,7 @@ function accessorMethod(field2, decorator, name) {
15035
15408
  };
15036
15409
  }
15037
15410
  function generatedMethod(field2, name, parameters, returnAnnotation, kind, fields) {
15038
- return { kind: "class-method", name, isConstructor: false, isSynthetic: true, isStatic: false, parameters, returnAnnotation, body: [], decorators: [], generated: { kind, fields }, position: field2.position };
15411
+ return { kind: "class-method", name, isConstructor: false, isSynthetic: true, isStatic: false, typeParameters: [], typeConstraints: [], parameters, returnAnnotation, body: [], decorators: [], generated: { kind, fields }, position: field2.position };
15039
15412
  }
15040
15413
  function typeName(name, node) {
15041
15414
  return { kind: "type-name", name, typeArguments: [], position: node.position };
@@ -15073,6 +15446,8 @@ function staticMethod(statement, name, returnAnnotation, kind, descriptor) {
15073
15446
  isConstructor: false,
15074
15447
  isSynthetic: true,
15075
15448
  isStatic: true,
15449
+ typeParameters: [],
15450
+ typeConstraints: [],
15076
15451
  parameters: [value],
15077
15452
  returnAnnotation,
15078
15453
  body: [],
@@ -18541,33 +18916,60 @@ function selectCandidate(context, expression, forms) {
18541
18916
  });
18542
18917
  return found[0] ?? null;
18543
18918
  }
18544
- function specialize(signature, candidate) {
18919
+ function specialize(signature2, candidate) {
18545
18920
  const callback = candidate.form.callbackArgument;
18546
18921
  if (callback !== void 0) {
18547
- const parameters = [...signature.parameters];
18922
+ const parameters = [...signature2.parameters];
18548
18923
  parameters[callback] = candidate.handler;
18549
- return { ...signature, parameters };
18924
+ return { ...signature2, parameters };
18550
18925
  }
18551
18926
  const payload = candidate.form.payloadArgument;
18552
18927
  if (payload === void 0) {
18553
18928
  return null;
18554
18929
  }
18555
18930
  return createFunction(
18556
- [...signature.parameters.slice(0, payload), ...candidate.handler.parameters],
18557
- signature.returnType,
18931
+ [...signature2.parameters.slice(0, payload), ...candidate.handler.parameters],
18932
+ signature2.returnType,
18558
18933
  payload + candidate.handler.minimumArguments,
18559
18934
  candidate.handler.isVariadic,
18560
18935
  void 0,
18561
18936
  candidate.handler.variadicType
18562
18937
  );
18563
18938
  }
18564
- function specializeEventCall(context, expression, signature) {
18565
- 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") {
18566
18941
  return null;
18567
18942
  }
18568
18943
  const forms = eventCallForms(expression.callee.name);
18569
18944
  const candidate = selectCandidate(context, expression, forms);
18570
- return candidate === null ? null : specialize(signature, candidate);
18945
+ return candidate === null ? null : specialize(signature2, candidate);
18946
+ }
18947
+
18948
+ // ../compiler/src/checker/generic-call.ts
18949
+ function countArguments(total) {
18950
+ return total === 1 ? "1 type argument" : `${total} type arguments`;
18951
+ }
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);
18957
+ }
18958
+ function specializeCall(context, owner, signature2, argumentTypes, typeArguments, position2) {
18959
+ const names = signature2.typeParameters ?? [];
18960
+ if (names.length === 0) {
18961
+ if (typeArguments.length > 0) {
18962
+ context.report("check-generic-arity", `${owner} does not accept type arguments.`, position2);
18963
+ }
18964
+ return signature2;
18965
+ }
18966
+ const explicit = typeArguments.map((argument) => context.resolveAnnotation(argument));
18967
+ if (typeArguments.length > 0 && explicit.length !== names.length) {
18968
+ context.report("check-generic-arity", `${owner} expects ${countArguments(names.length)} but received ${explicit.length}.`, position2);
18969
+ }
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])));
18571
18973
  }
18572
18974
 
18573
18975
  // ../compiler/src/checker/method-receiver.ts
@@ -18579,13 +18981,13 @@ function resolveNonNominalMethod(receiver, method) {
18579
18981
  const member = receiver.members.get(method);
18580
18982
  return member !== void 0 && member.kind === "function" ? member : null;
18581
18983
  }
18582
- function withoutSelfParameter(signature) {
18583
- const names = signature.parameterNames;
18984
+ function withoutSelfParameter(signature2) {
18985
+ const names = signature2.parameterNames;
18584
18986
  if (names === void 0 || names[0] !== SELF_PARAMETER) {
18585
- return signature;
18987
+ return signature2;
18586
18988
  }
18587
- const minimum = Math.max(0, signature.minimumArguments - 1);
18588
- 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);
18589
18991
  }
18590
18992
 
18591
18993
  // ../compiler/src/checker/resource-exports.ts
@@ -18883,6 +19285,7 @@ function resolveMtaMember(context, className, name, position2) {
18883
19285
  }
18884
19286
 
18885
19287
  // ../compiler/src/checker/members.ts
19288
+ var SELF_RECEIVER = "self";
18886
19289
  function checkEnumMember(context, name, members, expression) {
18887
19290
  if (members.includes(expression.property)) {
18888
19291
  return NUMBER_TYPE;
@@ -18943,6 +19346,35 @@ function resolveStaticMember(context, className, expression) {
18943
19346
  context.report("check-unknown-member", `Class "${className}" has no static member "${expression.property}".${hint}`, expression.position);
18944
19347
  return ANY_TYPE;
18945
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
+ }
18946
19378
  function resolveNamedMember(context, receiver, expression) {
18947
19379
  const name = receiver.name;
18948
19380
  const enumeration = context.declarations.lookupEnum(name);
@@ -18968,13 +19400,39 @@ function resolveNamedMember(context, receiver, expression) {
18968
19400
  if (context.awaitsDeclaration(name)) {
18969
19401
  return ANY_TYPE;
18970
19402
  }
18971
- 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);
18972
19407
  if (contract === null) {
18973
19408
  return null;
18974
19409
  }
18975
19410
  const members = new Map(context.declarations.collectMembers(name).map((member2) => [member2.name, member2]));
18976
19411
  return checkInterfaceMember(context, name, members, expression);
18977
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
+ }
18978
19436
  var NATIVE_CONSTRUCTOR = "new";
18979
19437
  function nativeConstructor(context, name) {
18980
19438
  const symbol = context.binder.lookup(name);
@@ -19031,9 +19489,9 @@ function checkNewExpression(context, expression) {
19031
19489
  const constructor = context.declarations.lookupMember(expression.className, "constructor");
19032
19490
  if (constructor !== null && constructor.type.kind === "function") {
19033
19491
  const substitutions = info === null ? /* @__PURE__ */ new Map() : classSubstitutions(info, typeArguments);
19034
- const signature = substituteType(constructor.type, substitutions);
19035
- if (signature.kind === "function") {
19036
- 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);
19037
19495
  }
19038
19496
  } else {
19039
19497
  expression.args.forEach((argument) => checkExpression(context, argument));
@@ -19383,15 +19841,48 @@ function checkLoopBody(context, body, facts) {
19383
19841
  checkBlock(context, body);
19384
19842
  context.setFlow(entry);
19385
19843
  }
19386
- function checkWhile(context, statement) {
19387
- forgetAssignedPaths(context, statement.body);
19388
- checkExpression(context, statement.condition);
19389
- checkLoopBody(context, statement.body, conditionFacts(context, statement.condition));
19390
- context.applyFlowFacts(negatedFacts(context, statement.condition));
19844
+ function isTruthyLiteral(condition) {
19845
+ if (condition.kind === "group-expression") {
19846
+ return isTruthyLiteral(condition.expression);
19847
+ }
19848
+ const literal2 = condition.kind === "number-literal" || condition.kind === "string-literal" || condition.kind === "template-literal";
19849
+ return literal2 || condition.kind === "boolean-literal" && condition.value;
19391
19850
  }
19392
- function checkRepeat(context, statement) {
19393
- checkLoopBody(context, statement.body, /* @__PURE__ */ new Map());
19394
- checkExpression(context, statement.condition);
19851
+ function isFalsyLiteral(condition) {
19852
+ if (condition.kind === "group-expression") {
19853
+ return isFalsyLiteral(condition.expression);
19854
+ }
19855
+ return condition.kind === "nil-literal" || condition.kind === "boolean-literal" && !condition.value;
19856
+ }
19857
+ function hasBreak(body) {
19858
+ return body.some((statement) => {
19859
+ if (statement.kind === "break-statement") {
19860
+ return true;
19861
+ }
19862
+ if (statement.kind === "do-statement") {
19863
+ return hasBreak(statement.body);
19864
+ }
19865
+ if (statement.kind === "if-statement") {
19866
+ return statement.clauses.some((clause) => hasBreak(clause.body)) || statement.alternate !== null && hasBreak(statement.alternate);
19867
+ }
19868
+ return false;
19869
+ });
19870
+ }
19871
+ function checkWhile(context, statement) {
19872
+ forgetAssignedPaths(context, statement.body);
19873
+ checkExpression(context, statement.condition);
19874
+ checkLoopBody(context, statement.body, conditionFacts(context, statement.condition));
19875
+ context.applyFlowFacts(negatedFacts(context, statement.condition));
19876
+ if (isTruthyLiteral(statement.condition) && !hasBreak(statement.body)) {
19877
+ context.markUnreachable();
19878
+ }
19879
+ }
19880
+ function checkRepeat(context, statement) {
19881
+ checkLoopBody(context, statement.body, /* @__PURE__ */ new Map());
19882
+ checkExpression(context, statement.condition);
19883
+ if (isFalsyLiteral(statement.condition) && !hasBreak(statement.body)) {
19884
+ context.markUnreachable();
19885
+ }
19395
19886
  }
19396
19887
  function checkNumericFor(context, statement) {
19397
19888
  const bounds = [statement.start, statement.limit, statement.step].filter((value) => value !== null);
@@ -19505,7 +19996,31 @@ function buildFunctionType(context, parameters, returnAnnotation, contextual = n
19505
19996
  const names = named2.map((parameter) => parameter.name);
19506
19997
  return createFunction(types, context.resolveAnnotation(returnAnnotation), minimumArguments(context, parameters), isVariadic, names);
19507
19998
  }
19508
- function checkFunctionBody(context, parameters, returnAnnotation, body, signature, selfType2) {
19999
+ function applyTypeParameters(context, type, names, constraints) {
20000
+ if (names.length === 0) {
20001
+ return type;
20002
+ }
20003
+ context.noteTypeParameters(names);
20004
+ type.typeParameters = [...names];
20005
+ type.typeConstraints = names.map((unused, index) => {
20006
+ const constraint = constraints[index];
20007
+ return constraint === void 0 || constraint === null ? null : context.resolveAnnotation(constraint);
20008
+ });
20009
+ return type;
20010
+ }
20011
+ var FALLTHROUGH_KINDS = /* @__PURE__ */ new Set(["any", "void", "nil", "optional", "unknown"]);
20012
+ function toleratesFallthrough(declared) {
20013
+ if (FALLTHROUGH_KINDS.has(declared.kind)) {
20014
+ return true;
20015
+ }
20016
+ return declared.kind === "union" && declared.options.some((option) => option.kind === "nil");
20017
+ }
20018
+ function reportMissingReturn(context, declared, position2) {
20019
+ const name = typeToString(declared);
20020
+ const repair = declared.kind === "tuple" ? "Add a return on every path." : `Add a return on every path, or declare "${name}?".`;
20021
+ context.report("check-missing-return", `This function declares "${name}" but can end without returning a value. ${repair}`, position2);
20022
+ }
20023
+ function checkFunctionBody(context, parameters, returnAnnotation, body, signature2, selfType2, position2 = ORIGIN) {
19509
20024
  forgetAssignedPaths(context, body);
19510
20025
  const entry = context.flowState;
19511
20026
  context.setFlow(cloneFlow(entry));
@@ -19516,16 +20031,20 @@ function checkFunctionBody(context, parameters, returnAnnotation, body, signatur
19516
20031
  }
19517
20032
  let parameterIndex = 0;
19518
20033
  for (const parameter of parameters) {
19519
- const type = parameter.isVararg ? ANY_TYPE : signature.parameters[parameterIndex] ?? ANY_TYPE;
20034
+ const type = parameter.isVararg ? ANY_TYPE : signature2.parameters[parameterIndex] ?? ANY_TYPE;
19520
20035
  context.binder.declare({ name: parameter.name, type, isLocal: true, position: parameter.position, origin: "parameter" });
19521
20036
  if (!parameter.isVararg) {
19522
20037
  parameterIndex += 1;
19523
20038
  }
19524
20039
  }
19525
20040
  checkStatements(context, body);
20041
+ const declared = context.currentReturnType();
20042
+ if (declared !== null && context.flowState.reachable && !toleratesFallthrough(declared)) {
20043
+ reportMissingReturn(context, declared, position2);
20044
+ }
19526
20045
  const inferred = context.popReturnType();
19527
20046
  if (returnAnnotation === null) {
19528
- signature.returnType = inferred;
20047
+ signature2.returnType = inferred;
19529
20048
  }
19530
20049
  context.binder.popScope();
19531
20050
  context.setFlow(entry);
@@ -19616,7 +20135,8 @@ function checkAssignment(context, statement) {
19616
20135
  });
19617
20136
  }
19618
20137
  function checkFunctionDeclaration(context, statement) {
19619
- const type = buildFunctionType(context, statement.parameters, statement.returnAnnotation);
20138
+ const built = buildFunctionType(context, statement.parameters, statement.returnAnnotation);
20139
+ const type = applyTypeParameters(context, built, statement.typeParameters, statement.typeConstraints);
19620
20140
  if (statement.name.kind === "identifier") {
19621
20141
  const symbol = { name: statement.name.name, type, isLocal: statement.isLocal, position: statement.position };
19622
20142
  if (statement.isLocal) {
@@ -19626,7 +20146,7 @@ function checkFunctionDeclaration(context, statement) {
19626
20146
  }
19627
20147
  }
19628
20148
  context.record(statement.name, type);
19629
- checkFunctionBody(context, statement.parameters, statement.returnAnnotation, statement.body, type, statement.isMethod ? ANY_TYPE : null);
20149
+ checkFunctionBody(context, statement.parameters, statement.returnAnnotation, statement.body, type, statement.isMethod ? ANY_TYPE : null, statement.position);
19630
20150
  }
19631
20151
  function checkReturn(context, statement) {
19632
20152
  const valueTypes = checkValueList(context, statement.values);
@@ -19819,36 +20339,41 @@ function checkMember(context, expression) {
19819
20339
  }
19820
20340
  return context.record(expression, extension === null ? ANY_TYPE : EXTENSION_RESULTS[extension.result]);
19821
20341
  }
19822
- function countArguments(total) {
20342
+ function countArguments2(total) {
19823
20343
  return total === 1 ? "1 argument" : `${total} arguments`;
19824
20344
  }
19825
- function checkSignature(context, args, signature, position2) {
19826
- const argumentTypes = checkValueList(context, args, signature.parameters);
19827
- if (argumentTypes.length < signature.minimumArguments) {
19828
- const message = `This call expects at least ${countArguments(signature.minimumArguments)} but received ${argumentTypes.length}.`;
20345
+ function checkSignature(context, args, declared, position2, owner = "This call", typeArguments = []) {
20346
+ const argumentTypes = checkValueList(context, args, declared.parameters);
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}.`;
19829
20350
  context.report("check-argument-count", message, position2);
19830
20351
  }
19831
- if (!signature.isVariadic && argumentTypes.length > signature.parameters.length) {
19832
- const message = `This call expects at most ${countArguments(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}.`;
19833
20354
  context.report("check-argument-count", message, position2);
19834
20355
  }
19835
- signature.parameters.forEach((parameter, index) => {
20356
+ signature2.parameters.forEach((parameter, index) => {
19836
20357
  const argumentType = argumentTypes[index];
19837
20358
  const argument = args[index];
19838
20359
  if (argumentType !== void 0 && argument !== void 0) {
19839
20360
  context.expectAssignable(argumentType, parameter, argument.position, `Argument ${index + 1}`);
19840
20361
  }
19841
20362
  });
19842
- if (signature.isVariadic && signature.variadicType !== void 0) {
19843
- 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) {
19844
20365
  const argumentType = argumentTypes[index];
19845
20366
  const argument = args[Math.min(index, args.length - 1)];
19846
20367
  if (argumentType !== void 0 && argument !== void 0) {
19847
- context.expectAssignable(argumentType, signature.variadicType, argument.position, `Argument ${index + 1}`);
20368
+ context.expectAssignable(argumentType, signature2.variadicType, argument.position, `Argument ${index + 1}`);
19848
20369
  }
19849
20370
  }
19850
20371
  }
19851
- return signature.returnType;
20372
+ return signature2.returnType;
20373
+ }
20374
+ function callOwner(expression) {
20375
+ const name = expression.method ?? (expression.callee.kind === "identifier" ? expression.callee.name : null);
20376
+ return name === null ? "This call" : `Function "${name}"`;
19852
20377
  }
19853
20378
  function checkArguments(context, expression, calleeType) {
19854
20379
  if (calleeType.kind !== "function") {
@@ -19856,7 +20381,7 @@ function checkArguments(context, expression, calleeType) {
19856
20381
  reportNotCallable(context, expression, calleeType);
19857
20382
  return ANY_TYPE;
19858
20383
  }
19859
- return checkSignature(context, expression.args, calleeType, expression.position);
20384
+ return checkSignature(context, expression.args, calleeType, expression.position, callOwner(expression), expression.typeArguments);
19860
20385
  }
19861
20386
  function isSuperCall(expression) {
19862
20387
  return expression.method === null && expression.callee.kind === "identifier" && expression.callee.name === "super";
@@ -19866,21 +20391,24 @@ function isLegacySuperCall(expression) {
19866
20391
  }
19867
20392
  function checkMethodCall(context, expression, method, receiver) {
19868
20393
  if (receiver.kind !== "named") {
19869
- const signature = resolveNonNominalMethod(receiver, method);
19870
- if (signature === null) {
20394
+ const signature2 = resolveNonNominalMethod(receiver, method);
20395
+ if (signature2 === null) {
19871
20396
  checkValueList(context, expression.args);
19872
20397
  return ANY_TYPE;
19873
20398
  }
19874
- return checkSignature(context, expression.args, withoutSelfParameter(signature), expression.position);
20399
+ return checkSignature(context, expression.args, withoutSelfParameter(signature2), expression.position, callOwner(expression), expression.typeArguments);
19875
20400
  }
19876
20401
  const declared = memberOf(context, receiver, method);
19877
20402
  if (declared?.type.kind === "function") {
19878
20403
  if (declared.deprecated === true) {
19879
20404
  context.warn("check-deprecated-use", `Member "${method}" is deprecated.`, expression.position);
19880
20405
  }
19881
- return checkSignature(context, expression.args, declared.type, expression.position);
20406
+ return checkSignature(context, expression.args, declared.type, expression.position, callOwner(expression), expression.typeArguments);
19882
20407
  }
19883
20408
  if (!isMtaElement(context, receiver.name)) {
20409
+ if (declared === null) {
20410
+ reportUnknownMethod(context, receiver, expression.callee, method, expression.position);
20411
+ }
19884
20412
  checkValueList(context, expression.args);
19885
20413
  return ANY_TYPE;
19886
20414
  }
@@ -19941,8 +20469,8 @@ function checkTable(context, expression) {
19941
20469
  }
19942
20470
  return checkExpression(context, field2.value);
19943
20471
  });
19944
- const isRecord = expression.fields.every((field2) => field2.name !== null);
19945
- if (isRecord) {
20472
+ const isRecord2 = expression.fields.every((field2) => field2.name !== null);
20473
+ if (isRecord2) {
19946
20474
  const members = /* @__PURE__ */ new Map();
19947
20475
  expression.fields.forEach((field2, index) => {
19948
20476
  if (field2.name !== null) {
@@ -20019,8 +20547,9 @@ function checkMultiValueExpression(context, expression, expected = null) {
20019
20547
  case "table-expression":
20020
20548
  return checkTable(context, expression);
20021
20549
  case "function-expression": {
20022
- const type = buildFunctionType(context, expression.parameters, expression.returnAnnotation, contextualFunction(expected));
20023
- checkFunctionBody(context, expression.parameters, expression.returnAnnotation, expression.body, type, null);
20550
+ const built = buildFunctionType(context, expression.parameters, expression.returnAnnotation, contextualFunction(expected));
20551
+ const type = applyTypeParameters(context, built, expression.typeParameters, expression.typeConstraints);
20552
+ checkFunctionBody(context, expression.parameters, expression.returnAnnotation, expression.body, type, null, expression.position);
20024
20553
  return context.record(expression, type);
20025
20554
  }
20026
20555
  case "binary-expression": {
@@ -20109,7 +20638,7 @@ function registerMembers(context, info, statement) {
20109
20638
  }
20110
20639
  const generated = expandClassDecorators(context, statement, fieldTypes);
20111
20640
  for (const member of [...statement.members, ...generated]) {
20112
- const type = member.kind === "class-field" ? fieldTypes.get(member) ?? ANY_TYPE : member.isSynthetic ? syntheticMethodType(context, member, fieldTypes) : buildFunctionType(context, member.parameters, member.returnAnnotation);
20641
+ const type = member.kind === "class-field" ? fieldTypes.get(member) ?? ANY_TYPE : member.isSynthetic ? syntheticMethodType(context, member, fieldTypes) : applyTypeParameters(context, buildFunctionType(context, member.parameters, member.returnAnnotation), member.typeParameters, member.typeConstraints);
20113
20642
  const decorators = member.decorators.map((decorator) => decorator.name);
20114
20643
  if (member.kind === "class-method" && isMetamethodMember(member)) {
20115
20644
  checkMetamethod(context, info.name, member, type);
@@ -20164,18 +20693,22 @@ function checkMethodBody(context, info, member) {
20164
20693
  if (explicitSelf !== void 0) {
20165
20694
  context.report("check-explicit-self-parameter", 'Class methods receive "self" automatically; remove it from the parameter list.', explicitSelf.position);
20166
20695
  }
20167
- const signature = (member.isStatic ? info.statics : info.members).get(member.name)?.type;
20168
- if (signature?.kind !== "function") {
20696
+ const signature2 = (member.isStatic ? info.statics : info.members).get(member.name)?.type;
20697
+ if (signature2?.kind !== "function") {
20169
20698
  return;
20170
20699
  }
20171
20700
  if (member.isStatic) {
20172
- checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, null);
20701
+ checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature2, null, member.position);
20173
20702
  return;
20174
20703
  }
20175
20704
  context.pushClassMethod({ className: info.name, methodName: member.name });
20176
- checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, selfType(info));
20705
+ checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature2, selfType(info), member.position);
20177
20706
  context.popClassMethod();
20178
20707
  }
20708
+ function assignSuperClass(context, info, statement) {
20709
+ info.superClass = resolveSuperClass(context, statement);
20710
+ info.hasUnresolvedParent = statement.superClass !== null && info.superClass === null;
20711
+ }
20179
20712
  function resolveSuperClass(context, statement) {
20180
20713
  if (statement.superClass === null) {
20181
20714
  return null;
@@ -20230,7 +20763,7 @@ function declareLocalClass(context, statement) {
20230
20763
  if (info === null) {
20231
20764
  return null;
20232
20765
  }
20233
- info.superClass = resolveSuperClass(context, statement);
20766
+ assignSuperClass(context, info, statement);
20234
20767
  resolveClassHeader(context, info, statement);
20235
20768
  return info;
20236
20769
  }
@@ -20344,7 +20877,7 @@ function breakCycle(context, info) {
20344
20877
  function predeclareModule(context, body) {
20345
20878
  const declared = declareHeaders(context, body);
20346
20879
  for (const entry of declared) {
20347
- entry.info.superClass = resolveSuperClass(context, entry.statement);
20880
+ assignSuperClass(context, entry.info, entry.statement);
20348
20881
  resolveClassHeader(context, entry.info, entry.statement);
20349
20882
  }
20350
20883
  for (const entry of declared) {
@@ -20521,9 +21054,9 @@ function finalizeEmission(code, markers, sourceLineOffset = 0) {
20521
21054
  continue;
20522
21055
  }
20523
21056
  const marker = markers[Number(match[1])];
20524
- const emitted = line2.replace(pattern, "");
21057
+ const emitted2 = line2.replace(pattern, "");
20525
21058
  if (marker === void 0) {
20526
- output.push(emitted);
21059
+ output.push(emitted2);
20527
21060
  continue;
20528
21061
  }
20529
21062
  while (output.length + 1 < marker.sourceLine - sourceLineOffset) {
@@ -20533,7 +21066,7 @@ function finalizeEmission(code, markers, sourceLineOffset = 0) {
20533
21066
  if (marker.symbol !== void 0) {
20534
21067
  mapping.symbol = marker.symbol;
20535
21068
  }
20536
- output.push(emitted);
21069
+ output.push(emitted2);
20537
21070
  const sameSegment = previous !== null && previous.symbol === mapping.symbol && previous.sourceLine - previous.generatedLine === mapping.sourceLine - mapping.generatedLine;
20538
21071
  if (!sameSegment) {
20539
21072
  mappings.push(mapping);
@@ -20674,10 +21207,10 @@ function emitFunctionBody(state, parameters, body, header) {
20674
21207
  const lines = emitBlock(state, body);
20675
21208
  state.loopWrap = previous;
20676
21209
  state.indent -= 1;
20677
- const signature = `${header}(${emitParameters(parameters)})`;
21210
+ const signature2 = `${header}(${emitParameters(parameters)})`;
20678
21211
  const closing = indentLine(state, "end");
20679
- return lines.length === 0 ? `${signature}
20680
- ${closing}` : `${signature}
21212
+ return lines.length === 0 ? `${signature2}
21213
+ ${closing}` : `${signature2}
20681
21214
  ${lines.join("\n")}
20682
21215
  ${closing}`;
20683
21216
  }
@@ -21371,8 +21904,8 @@ function erasedEdit(source, span) {
21371
21904
  }
21372
21905
  function canonicalEdit(input, statement, span) {
21373
21906
  const { source } = input;
21374
- const emitted = emit2({ ...input.program, body: [statement] }, input.types, input.references, input.generatedMembers, statement.position.line - 1, input.staticAccess);
21375
- 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;
21376
21909
  if (trimmed.length === 0) {
21377
21910
  return erasedEdit(source, span);
21378
21911
  }
@@ -21383,7 +21916,7 @@ function canonicalEdit(input, statement, span) {
21383
21916
  return null;
21384
21917
  }
21385
21918
  const padded = missing2 > 0 && source.slice(span.end).trim().length > 0 ? `${replacement}${"\n".repeat(missing2)}` : replacement;
21386
- 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 };
21387
21920
  }
21388
21921
 
21389
21922
  // ../compiler/src/emitter/preserve-decorators.ts
@@ -21856,7 +22389,7 @@ function compile(source, options = {}) {
21856
22389
  const settings = options.compilerOptions ?? DEFAULT_COMPILER_OPTIONS;
21857
22390
  const parsed = parse(source);
21858
22391
  const mode = resolveStrictMode(parsed.directives, settings.strict);
21859
- 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);
21860
22393
  const environment = resolved2.environment;
21861
22394
  const isDeclarationFile = options.filePath !== void 0 && isDeclarationPath(options.filePath);
21862
22395
  const checked = check(parsed.program, mode, environment, {
@@ -21886,7 +22419,7 @@ function compile(source, options = {}) {
21886
22419
  return { ...shared, code: null, requiredHelpers: [], lines: [] };
21887
22420
  }
21888
22421
  const references = reachedNames(checked.references, options.projectReferences);
21889
- 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);
21890
22423
  const preserved = emitPreservingSource({
21891
22424
  source,
21892
22425
  program: parsed.program,
@@ -21899,7 +22432,7 @@ function compile(source, options = {}) {
21899
22432
  staticAccess: checked.staticAccess,
21900
22433
  development: options.development === true
21901
22434
  });
21902
- const required = new Set(emitted.requiredHelpers);
22435
+ const required = new Set(emitted2.requiredHelpers);
21903
22436
  for (const name of checked.references) {
21904
22437
  const helper = checked.declaredGlobals.has(name) ? null : helperForGlobal(name);
21905
22438
  if (helper !== null) {
@@ -21908,9 +22441,9 @@ function compile(source, options = {}) {
21908
22441
  }
21909
22442
  return {
21910
22443
  ...shared,
21911
- code: preserved?.code ?? emitted.code,
22444
+ code: preserved?.code ?? emitted2.code,
21912
22445
  requiredHelpers: expandHelpers(required).sort(),
21913
- lines: preserved?.lines ?? emitted.lines
22446
+ lines: preserved?.lines ?? emitted2.lines
21914
22447
  };
21915
22448
  }
21916
22449
 
@@ -22127,11 +22660,20 @@ function baseKey(context) {
22127
22660
  const contracts = context.contracts.map((contract) => serializeAbi(contract)).join(";");
22128
22661
  return `${optionsKey(context.options)}|${mode}|${JSON.stringify(context.project.globals)}|${[...context.references].sort().join(",")}|${hashString(contracts)}`;
22129
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];
22130
22670
  function environmentKeys(collected) {
22131
- const keys = {};
22671
+ const keys = /* @__PURE__ */ new Map();
22132
22672
  for (const environment of ALL_ENVIRONMENTS) {
22133
- const visible = collected.filter((entry) => canReference(environment, entry.environment));
22134
- 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
+ }
22135
22677
  }
22136
22678
  return keys;
22137
22679
  }
@@ -22139,15 +22681,19 @@ function createResolver(collected) {
22139
22681
  const visible = /* @__PURE__ */ new Map();
22140
22682
  const merged = /* @__PURE__ */ new Map();
22141
22683
  for (const environment of ALL_ENVIRONMENTS) {
22142
- const entries3 = collected.filter((entry) => canReference(environment, entry.environment) && !declaresNothing(entry.declarations));
22143
- visible.set(environment, entries3);
22144
- 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
+ }
22145
22690
  }
22146
22691
  return (entry) => {
22692
+ const key = scopeKey(entry.environment, entry.isLibrary);
22147
22693
  if (declaresNothing(entry.declarations)) {
22148
- return merged.get(entry.environment) ?? EMPTY_AMBIENT;
22694
+ return merged.get(key) ?? EMPTY_AMBIENT;
22149
22695
  }
22150
- const entries3 = visible.get(entry.environment) ?? [];
22696
+ const entries3 = visible.get(key) ?? [];
22151
22697
  return mergeAmbient(entries3.filter((candidate) => candidate.path !== entry.path).map((candidate) => candidate.declarations));
22152
22698
  };
22153
22699
  }
@@ -22157,7 +22703,7 @@ function createAmbientScope(collected, context) {
22157
22703
  const fallback = environmentKeys(collected);
22158
22704
  return {
22159
22705
  graph,
22160
- 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)) ?? ""}`),
22161
22707
  resolve: createResolver(collected)
22162
22708
  };
22163
22709
  }
@@ -22202,13 +22748,96 @@ function validateContributions(modules) {
22202
22748
  }
22203
22749
  return diagnostics;
22204
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
+ }
22205
22826
  function validateModuleReferences(modules) {
22206
22827
  const symbols = buildSymbolTable(modules);
22828
+ const libraries = new Set(modules.filter((module) => module.origin !== null).map((module) => module.path));
22207
22829
  const diagnostics = [];
22208
22830
  for (const module of modules) {
22209
22831
  for (const [name, position2] of module.externalReferences) {
22210
22832
  const symbol = symbols.get(name);
22211
- 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)) {
22212
22841
  continue;
22213
22842
  }
22214
22843
  diagnostics.push(referenceDiagnostic(module, symbol, position2));
@@ -22225,6 +22854,7 @@ function compileModule(file, ambient, context) {
22225
22854
  const result = compile(file.source, {
22226
22855
  filePath: file.path,
22227
22856
  ...file.environment === void 0 ? {} : { environment: file.environment },
22857
+ environmentLocked: file.origin !== void 0,
22228
22858
  ambient,
22229
22859
  contracts: context.contracts,
22230
22860
  project: isTestPath(file.path) ? context.testProject : context.project,
@@ -22234,6 +22864,7 @@ function compileModule(file, ambient, context) {
22234
22864
  });
22235
22865
  return {
22236
22866
  path: file.path,
22867
+ origin: file.origin ?? null,
22237
22868
  environment: result.environment,
22238
22869
  isDeclaration: isDeclarationPath(file.path),
22239
22870
  code: result.code,
@@ -22263,7 +22894,7 @@ function createProjectCache() {
22263
22894
  const declarationCache = /* @__PURE__ */ new Map();
22264
22895
  const moduleCache = /* @__PURE__ */ new Map();
22265
22896
  function declarationsFor(file, options, reused) {
22266
- const hash = hashString(`${file.environment ?? ""}|${optionsKey(options)}|${file.source}`);
22897
+ const hash = hashString(`${file.environment ?? ""}|${file.origin?.root ?? ""}|${optionsKey(options)}|${file.source}`);
22267
22898
  const cached = declarationCache.get(file.path);
22268
22899
  if (cached !== void 0 && cached.hash === hash) {
22269
22900
  reused.count += 1;
@@ -22272,11 +22903,13 @@ function createProjectCache() {
22272
22903
  const result = compile(file.source, {
22273
22904
  filePath: file.path,
22274
22905
  ...file.environment === void 0 ? {} : { environment: file.environment },
22906
+ environmentLocked: file.origin !== void 0,
22275
22907
  compilerOptions: options,
22276
22908
  emitCode: false
22277
22909
  });
22278
22910
  const entry = {
22279
22911
  path: file.path,
22912
+ isLibrary: file.origin !== void 0,
22280
22913
  hash,
22281
22914
  environment: result.environment,
22282
22915
  declarations: result.declarations,
@@ -22327,7 +22960,8 @@ function createProjectCache() {
22327
22960
  });
22328
22961
  const references = validateModuleReferences(modules);
22329
22962
  const contributions = validateContributions(modules);
22330
- const collectedDiagnostics = sortFileDiagnostics([...flattenDiagnostics(modules), ...references, ...contributions]);
22963
+ const libraries = [...validateLibraryGlobals(modules), ...validateLibraryApiShadowing(modules)];
22964
+ const collectedDiagnostics = sortFileDiagnostics([...flattenDiagnostics(modules), ...references, ...contributions, ...libraries]);
22331
22965
  const diagnostics = compilerOptions.warningsAsErrors ? promoteWarnings(collectedDiagnostics) : collectedDiagnostics;
22332
22966
  const paths = new Set(files.map((file) => file.path));
22333
22967
  prune(declarationCache, paths);
@@ -22355,7 +22989,7 @@ function helperList(config, inputs) {
22355
22989
  const helpers = [...config.helpers, "env"];
22356
22990
  return helpers.sort();
22357
22991
  }
22358
- function resourceOptions(config, inputs, minMtaVersion, developmentLogs, layout) {
22992
+ function resourceOptions(config, inputs, minMtaVersion, developmentLogs, layout, libraryFiles) {
22359
22993
  const options = {
22360
22994
  oop: config.compilerOptions.oop,
22361
22995
  dependencies: config.dependencies,
@@ -22364,6 +22998,7 @@ function resourceOptions(config, inputs, minMtaVersion, developmentLogs, layout)
22364
22998
  configuration: inputs.configuration,
22365
22999
  environmentFile: config.environment.file,
22366
23000
  loadOrder: config.loadOrder,
23001
+ libraryFiles,
22367
23002
  minMtaVersion,
22368
23003
  developmentLogs,
22369
23004
  layout,
@@ -22406,9 +23041,11 @@ function runCompile(root, config, options = {}) {
22406
23041
  const excluded = [config.outDir, config.contracts];
22407
23042
  const sources = discoverSources(root, config.sources, excluded);
22408
23043
  const inputs = readProjectInputs(root, { assets: config.assets, environment: config.environment, excluded });
22409
- 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];
22410
23047
  const contracts = readDependencyContracts(root, config);
22411
- const diagnostics = [...sources.diagnostics, ...inputs.diagnostics, ...contracts.diagnostics];
23048
+ const diagnostics = [...sources.diagnostics, ...inputs.diagnostics, ...libraries.diagnostics, ...contracts.diagnostics];
22412
23049
  if (hasCliErrors(diagnostics)) {
22413
23050
  tracker.end("failed");
22414
23051
  return {
@@ -22437,7 +23074,7 @@ function runCompile(root, config, options = {}) {
22437
23074
  tracker.begin("assembly");
22438
23075
  const assembly = assembleResource(
22439
23076
  project,
22440
- 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),
22441
23078
  (step) => {
22442
23079
  if (step === "assembly") {
22443
23080
  tracker.begin("manifest");
@@ -22462,8 +23099,8 @@ function runCompile(root, config, options = {}) {
22462
23099
  }
22463
23100
 
22464
23101
  // src/build/resource-map-file.ts
22465
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
22466
- 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";
22467
23104
  var RESOURCE_MAP_SUFFIX = ".luam-map.json";
22468
23105
  function isObject(value) {
22469
23106
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -22528,28 +23165,28 @@ function reason(error) {
22528
23165
  return error instanceof Error ? error.message : String(error);
22529
23166
  }
22530
23167
  function resourceMapPath(root, config) {
22531
- return resolve9(root, config.outDir, `${config.name}${RESOURCE_MAP_SUFFIX}`);
23168
+ return resolve10(root, config.outDir, `${config.name}${RESOURCE_MAP_SUFFIX}`);
22532
23169
  }
22533
23170
  function explicitResourceMapPath(root, path) {
22534
- return isAbsolute3(path) ? path : resolve9(root, path);
23171
+ return isAbsolute3(path) ? path : resolve10(root, path);
22535
23172
  }
22536
23173
  function writeResourceMap(root, config, map) {
22537
23174
  const path = resourceMapPath(root, config);
22538
23175
  if (map === null) {
22539
- if (existsSync5(path)) {
23176
+ if (existsSync6(path)) {
22540
23177
  unlinkSync(path);
22541
23178
  }
22542
23179
  return;
22543
23180
  }
22544
23181
  mkdirSync3(dirname3(path), { recursive: true });
22545
23182
  const content = serializeResourceMap(map);
22546
- if (!existsSync5(path) || readFileSync7(path, "utf8") !== content) {
23183
+ if (!existsSync6(path) || readFileSync8(path, "utf8") !== content) {
22547
23184
  writeFileSync4(path, content, "utf8");
22548
23185
  }
22549
23186
  }
22550
23187
  function readResourceMap(path) {
22551
23188
  try {
22552
- const value = JSON.parse(readFileSync7(path, "utf8"));
23189
+ const value = JSON.parse(readFileSync8(path, "utf8"));
22553
23190
  if (isObject(value) && Number.isInteger(value.version) && value.version !== RESOURCE_MAP_VERSION) {
22554
23191
  return { map: null, error: `Resource map version ${String(value.version)} is not supported.` };
22555
23192
  }
@@ -22583,7 +23220,7 @@ function collectResourceMaps(directory, found) {
22583
23220
  }
22584
23221
  }
22585
23222
  function discoverResourceMap(root) {
22586
- if (!existsSync5(root)) {
23223
+ if (!existsSync6(root)) {
22587
23224
  return { path: null, candidates: [] };
22588
23225
  }
22589
23226
  const found = [];
@@ -22593,8 +23230,8 @@ function discoverResourceMap(root) {
22593
23230
  }
22594
23231
 
22595
23232
  // src/build/resource-writer.ts
22596
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
22597
- 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";
22598
23235
 
22599
23236
  // src/build/lua-tokens.ts
22600
23237
  var WHITESPACE = /* @__PURE__ */ new Set([" ", " ", "\n", "\r", "\v", "\f"]);
@@ -22797,8 +23434,8 @@ function minifyLuaFiles(files) {
22797
23434
  }
22798
23435
 
22799
23436
  // src/build/resource-prune.ts
22800
- import { existsSync as existsSync6, readdirSync as readdirSync3, rmdirSync, unlinkSync as unlinkSync2 } from "node:fs";
22801
- 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";
22802
23439
  var GENERATED_MANIFEST = "meta.xml";
22803
23440
  var GENERATED_EXTENSION = ".lua";
22804
23441
  var PROTECTED = /* @__PURE__ */ new Set([ENVIRONMENT_FILE, ".env.local"]);
@@ -22815,17 +23452,17 @@ function isGenerated(relativePath2, options) {
22815
23452
  return options.generatedRoots.some((root) => isUnderRoot(relativePath2, root));
22816
23453
  }
22817
23454
  function removeEmptyDirectories(targetDir, directory) {
22818
- if (resolve10(directory) === resolve10(targetDir)) {
23455
+ if (resolve11(directory) === resolve11(targetDir)) {
22819
23456
  return;
22820
23457
  }
22821
23458
  if (readdirSync3(directory).length > 0) {
22822
23459
  return;
22823
23460
  }
22824
23461
  rmdirSync(directory);
22825
- removeEmptyDirectories(targetDir, resolve10(directory, ".."));
23462
+ removeEmptyDirectories(targetDir, resolve11(directory, ".."));
22826
23463
  }
22827
23464
  function pruneResource(targetDir, keep, options) {
22828
- if (!existsSync6(targetDir)) {
23465
+ if (!existsSync7(targetDir)) {
22829
23466
  return [];
22830
23467
  }
22831
23468
  const removed = [];
@@ -22875,7 +23512,7 @@ function resourceFiles(build) {
22875
23512
  return files;
22876
23513
  }
22877
23514
  function resolveInside(targetDir, path) {
22878
- const absolute = resolve11(targetDir, path);
23515
+ const absolute = resolve12(targetDir, path);
22879
23516
  const inside = relative4(targetDir, absolute);
22880
23517
  if (inside.startsWith("..") || isAbsolute4(inside)) {
22881
23518
  throw new Error(`The generated file "${path}" resolves outside the resource directory "${targetDir}".`);
@@ -22883,7 +23520,7 @@ function resolveInside(targetDir, path) {
22883
23520
  return absolute;
22884
23521
  }
22885
23522
  function writeIfChanged(absolute, content) {
22886
- if (existsSync7(absolute) && readFileSync8(absolute).equals(content)) {
23523
+ if (existsSync8(absolute) && readFileSync9(absolute).equals(content)) {
22887
23524
  return false;
22888
23525
  }
22889
23526
  mkdirSync4(dirname4(absolute), { recursive: true });
@@ -22895,7 +23532,7 @@ function writeEnvironmentFile(targetDir, template) {
22895
23532
  return false;
22896
23533
  }
22897
23534
  const absolute = resolveInside(targetDir, ENVIRONMENT_FILE);
22898
- if (existsSync7(absolute)) {
23535
+ if (existsSync8(absolute)) {
22899
23536
  return false;
22900
23537
  }
22901
23538
  mkdirSync4(dirname4(absolute), { recursive: true });
@@ -22923,7 +23560,7 @@ function writeResource(targetDir, build, options) {
22923
23560
  advance(path);
22924
23561
  }
22925
23562
  for (const asset of build.assets) {
22926
- 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)))) {
22927
23564
  written.push(asset.path);
22928
23565
  advance(asset.path);
22929
23566
  continue;
@@ -22953,7 +23590,7 @@ function destinationRoot(mapping) {
22953
23590
  }
22954
23591
  function generatedRoots(config) {
22955
23592
  const roots = config.assets.map(destinationRoot).filter((root) => root.length > 0);
22956
- return [.../* @__PURE__ */ new Set([...roots, LIBRARY_DIRECTORY])];
23593
+ return [.../* @__PURE__ */ new Set([...roots, LIBRARY_DIRECTORY, LIBRARIES_DIRECTORY])];
22957
23594
  }
22958
23595
  function trackedWriteOptions(root, config, environmentTemplate, tracker) {
22959
23596
  return {
@@ -23040,16 +23677,16 @@ function reportBuildOutcome(context, outcome, action) {
23040
23677
  }
23041
23678
 
23042
23679
  // src/commands/resource-targets.ts
23043
- import { isAbsolute as isAbsolute5, resolve as resolve12 } from "node:path";
23680
+ import { isAbsolute as isAbsolute5, resolve as resolve13 } from "node:path";
23044
23681
  function resolveBuildTarget(root, config) {
23045
- return resolve12(root, config.outDir, config.name);
23682
+ return resolve13(root, config.outDir, config.name);
23046
23683
  }
23047
23684
  function resolveServerTarget(root, config) {
23048
23685
  if (config.serverPath === null) {
23049
23686
  return null;
23050
23687
  }
23051
- const serverRoot = isAbsolute5(config.serverPath) ? config.serverPath : resolve12(root, config.serverPath);
23052
- 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);
23053
23690
  }
23054
23691
 
23055
23692
  // src/reporting/progress-renderer.ts
@@ -23094,7 +23731,7 @@ function createProgressRenderer(reporter, options = {}) {
23094
23731
  paint(reporter.style.eraseLine());
23095
23732
  dirty = false;
23096
23733
  }
23097
- function render2(event) {
23734
+ function render3(event) {
23098
23735
  const columns = Math.max(20, reporter.capability.columns - 1);
23099
23736
  const line2 = activeLine(reporter, event);
23100
23737
  paint(`${reporter.style.eraseLine()}${line2.slice(0, columns)}`);
@@ -23114,7 +23751,7 @@ function createProgressRenderer(reporter, options = {}) {
23114
23751
  if (frames > 0 && now() - lastPaintAt < throttleMs) {
23115
23752
  return;
23116
23753
  }
23117
- render2(event);
23754
+ render3(event);
23118
23755
  }
23119
23756
  return {
23120
23757
  listen: (event) => {
@@ -23157,6 +23794,7 @@ async function runBuildCommand(context, options = {}) {
23157
23794
  if (outcome.build === null) {
23158
23795
  reportBuildOutcome(context, outcome, "Build");
23159
23796
  reportPhaseTimings(reporter, tracker.durations(), totalDuration(tracker.durations()));
23797
+ options.onOutcome?.(outcome);
23160
23798
  return EXIT_DIAGNOSTICS;
23161
23799
  }
23162
23800
  const target = resolveBuildTarget(context.root, context.config);
@@ -23169,6 +23807,7 @@ async function runBuildCommand(context, options = {}) {
23169
23807
  tracker.end("failed");
23170
23808
  renderer.clear();
23171
23809
  reporter.error(`Build wrote nothing to "${target}". ${error instanceof Error ? error.message : String(error)}`);
23810
+ options.onOutcome?.(outcome);
23172
23811
  return EXIT_DIAGNOSTICS;
23173
23812
  }
23174
23813
  writeResourceMap(context.root, context.config, writtenMap(outcome.map, minify));
@@ -23181,13 +23820,93 @@ async function runBuildCommand(context, options = {}) {
23181
23820
  const counts = `${result.unchanged} unchanged, ${result.removed.length} removed`;
23182
23821
  reporter.info(`Wrote ${pluralize(result.written.length, "file")} to "${target}" (${counts}).`);
23183
23822
  reportPhaseTimings(reporter, tracker.durations(), totalDuration(tracker.durations()));
23823
+ options.onOutcome?.(outcome);
23184
23824
  return EXIT_OK;
23185
23825
  }
23186
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
+
23187
23906
  // src/cli/registry/build-registration.ts
23188
23907
  function registerBuildCommand(program2, runtime) {
23189
23908
  const command = program2.command("build").description("Compile the project and write the resource to the output directory.");
23190
- addMinifyOptions(addLayoutOptions(addProjectOptions(command))).addOption(noMapOption()).addOption(offlineOption());
23909
+ addMinifyOptions(addLayoutOptions(addProjectOptions(command))).addOption(noMapOption()).addOption(offlineOption()).addOption(jsonOption());
23191
23910
  command.action(async (options) => {
23192
23911
  const project = createProjectContext(runtime, "build", options);
23193
23912
  if (project.context === null) {
@@ -23195,39 +23914,236 @@ function registerBuildCommand(program2, runtime) {
23195
23914
  return;
23196
23915
  }
23197
23916
  const bundled = options.bundle ?? project.context.config.output.bundle;
23198
- runtime.exitCode = await runBuildCommand(project.context, {
23917
+ const build = {
23199
23918
  layout: bundled ? "bundle" : "tree",
23200
23919
  map: options.map && project.context.config.output.map,
23201
23920
  minify: options.minify ?? project.context.config.output.minify
23202
- });
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 }));
23203
23928
  });
23204
23929
  }
23205
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);
23958
+ });
23959
+ }
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
+
23206
24041
  // src/commands/check-command.ts
23207
- function runCheckCommand(context) {
24042
+ function runCheckCommand(context, options = {}) {
23208
24043
  const reporter = commandReporter(context);
23209
24044
  const renderer = createProgressRenderer(reporter);
23210
24045
  const tracker = createPhaseTracker(renderer.listen);
23211
- 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 });
23212
24048
  renderer.clear();
23213
24049
  const passed = reportBuildOutcome(context, outcome, "Check");
23214
24050
  reportPhaseTimings(reporter, tracker.durations(), totalDuration(tracker.durations()));
24051
+ options.onOutcome?.(outcome);
23215
24052
  return passed ? EXIT_OK : EXIT_DIAGNOSTICS;
23216
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
+ }
23217
24105
 
23218
24106
  // src/cli/registry/check-registration.ts
23219
24107
  function registerCheckCommand(program2, runtime) {
23220
24108
  const command = program2.command("check").description("Compile the project and report diagnostics without writing files.");
23221
- addProjectOptions(command);
23222
- 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
+ }
23223
24116
  const project = createProjectContext(runtime, "check", options);
23224
- 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 });
23225
24141
  });
23226
24142
  }
23227
24143
 
23228
24144
  // src/commands/config-command.ts
23229
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
23230
- 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";
23231
24147
 
23232
24148
  // ../compiler/src/project/config-reader.ts
23233
24149
  var Reader = class {
@@ -23441,33 +24357,33 @@ function isGeneratedDeclaration(text) {
23441
24357
  var DEFAULT_CONFIG_SOURCE = "config.lua";
23442
24358
  var DEFAULT_CONFIG_OUTPUT = "config.d.luam";
23443
24359
  function escapes(root, path) {
23444
- const inside = relative5(root, path);
23445
- return inside.startsWith("..") || resolve13(root, inside) !== path;
24360
+ const inside = relative6(root, path);
24361
+ return inside.startsWith("..") || resolve15(root, inside) !== path;
23446
24362
  }
23447
24363
  function readSource2(root, source, reporter) {
23448
- const path = resolve13(root, source);
24364
+ const path = resolve15(root, source);
23449
24365
  if (escapes(root, path)) {
23450
24366
  reporter.error(`Config declarations read only inside the project, but "${source}" points outside it.`);
23451
24367
  return null;
23452
24368
  }
23453
- if (!existsSync8(path)) {
24369
+ if (!existsSync11(path)) {
23454
24370
  reporter.error(`Config declarations found no file at "${source}".`);
23455
24371
  return null;
23456
24372
  }
23457
24373
  try {
23458
- return readFileSync9(path, "utf8");
24374
+ return readFileSync10(path, "utf8");
23459
24375
  } catch {
23460
24376
  reporter.error(`Config declarations could not read "${source}".`);
23461
24377
  return null;
23462
24378
  }
23463
24379
  }
23464
24380
  function writable(root, out, reporter) {
23465
- const path = resolve13(root, out);
24381
+ const path = resolve15(root, out);
23466
24382
  if (escapes(root, path)) {
23467
24383
  reporter.error(`Config declarations write only inside the project, but "${out}" points outside it.`);
23468
24384
  return null;
23469
24385
  }
23470
- if (existsSync8(path) && !isGeneratedDeclaration(readFileSync9(path, "utf8"))) {
24386
+ if (existsSync11(path) && !isGeneratedDeclaration(readFileSync10(path, "utf8"))) {
23471
24387
  reporter.error(`"${out}" was not generated by this command and was left alone. Choose another path with --out.`);
23472
24388
  return null;
23473
24389
  }
@@ -23600,85 +24516,7 @@ function createEnsureRunner(context, options = {}) {
23600
24516
  };
23601
24517
  }
23602
24518
 
23603
- // src/reporting/rebuild-separator.ts
23604
- var RULE_WIDTH = 40;
23605
- function formatTimestamp(at) {
23606
- return at.toISOString().slice(11, 19);
23607
- }
23608
- function reportRebuildSeparator(reporter, at = /* @__PURE__ */ new Date()) {
23609
- const rule = (reporter.capability.unicode ? "\u2500" : "-").repeat(RULE_WIDTH);
23610
- reporter.raw("");
23611
- reporter.detail(`${rule} rebuild at ${formatTimestamp(at)}`);
23612
- }
23613
-
23614
- // src/watch/source-watcher.ts
23615
- import { existsSync as existsSync9, watch } from "node:fs";
23616
- import { relative as relative6, resolve as resolve14 } from "node:path";
23617
- var DEFAULT_DEBOUNCE_MS = 120;
23618
- function watchSources(root, sources, onChange, debounceMs = DEFAULT_DEBOUNCE_MS) {
23619
- const resolver = createSourceResolver(sources);
23620
- const watchers = [];
23621
- let timer = null;
23622
- const schedule = () => {
23623
- if (timer !== null) {
23624
- clearTimeout(timer);
23625
- }
23626
- timer = setTimeout(() => {
23627
- timer = null;
23628
- onChange();
23629
- }, debounceMs);
23630
- };
23631
- const isWatched = (directory, filename) => {
23632
- const path = normalizePattern(relative6(root, resolve14(directory, filename)));
23633
- return path.endsWith(SOURCE_EXTENSION) && resolver.resolve(path).matches.length > 0;
23634
- };
23635
- for (const entry of resolver.roots) {
23636
- const absolute = resolve14(root, entry);
23637
- if (!existsSync9(absolute)) {
23638
- continue;
23639
- }
23640
- watchers.push(
23641
- watch(absolute, { recursive: true }, (_event, filename) => {
23642
- if (filename !== null && isWatched(absolute, filename.toString())) {
23643
- schedule();
23644
- }
23645
- })
23646
- );
23647
- }
23648
- return {
23649
- close: () => {
23650
- if (timer !== null) {
23651
- clearTimeout(timer);
23652
- timer = null;
23653
- }
23654
- for (const watcher of watchers) {
23655
- watcher.close();
23656
- }
23657
- }
23658
- };
23659
- }
23660
- function watchedRoots(sources) {
23661
- return createSourceResolver(sources).roots;
23662
- }
23663
-
23664
24519
  // src/commands/ensure-command.ts
23665
- function untilAborted(signal) {
23666
- return new Promise((resolveLoop) => {
23667
- if (signal?.aborted === true) {
23668
- resolveLoop();
23669
- return;
23670
- }
23671
- const stop = () => {
23672
- process.off("SIGINT", stop);
23673
- signal?.removeEventListener("abort", stop);
23674
- resolveLoop();
23675
- };
23676
- if (signal !== null) {
23677
- signal.addEventListener("abort", stop, { once: true });
23678
- }
23679
- process.on("SIGINT", stop);
23680
- });
23681
- }
23682
24520
  async function watchLoop(context, runner, options) {
23683
24521
  const reporter = commandReporter(context);
23684
24522
  let running = false;
@@ -23841,12 +24679,12 @@ function parseMtaLogLine(line2, activeResource, fallback = /* @__PURE__ */ new D
23841
24679
  }
23842
24680
 
23843
24681
  // src/logging/server-log-follower.ts
23844
- import { closeSync, existsSync as existsSync10, openSync, readSync, statSync as statSync4 } from "node:fs";
23845
- 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";
23846
24684
  var DEFAULT_POLL_INTERVAL_MS = 100;
23847
24685
  function resolveServerLogPath(root, serverPath) {
23848
- const server = isAbsolute6(serverPath) ? serverPath : resolve15(root, serverPath);
23849
- 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");
23850
24688
  }
23851
24689
  function identity(path) {
23852
24690
  const stats = statSync4(path);
@@ -23871,7 +24709,7 @@ function followServerLog(path, onLine, options = {}) {
23871
24709
  let initialized = false;
23872
24710
  let closed = false;
23873
24711
  const poll = () => {
23874
- if (closed || !existsSync10(path)) {
24712
+ if (closed || !existsSync12(path)) {
23875
24713
  return;
23876
24714
  }
23877
24715
  try {
@@ -23987,7 +24825,7 @@ function connectServerConsoleInput(input, output, interrupt) {
23987
24825
 
23988
24826
  // src/server/server-executable-resolver.ts
23989
24827
  import { realpathSync, statSync as statSync5 } from "node:fs";
23990
- 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";
23991
24829
  function isInside(root, path) {
23992
24830
  const pathFromRoot = relative7(root, path);
23993
24831
  return pathFromRoot.length === 0 || !pathFromRoot.startsWith("..") && !isAbsolute7(pathFromRoot);
@@ -24002,14 +24840,14 @@ function candidates2(platform) {
24002
24840
  throw new Error(`Local MTA server startup is not supported on platform "${platform}". Use Windows or Linux.`);
24003
24841
  }
24004
24842
  function resolveServerExecutable(options) {
24005
- const serverRoot = resolve16(options.root, options.serverPath);
24843
+ const serverRoot = resolve17(options.root, options.serverPath);
24006
24844
  const names = options.configured === null ? candidates2(options.platform ?? process.platform) : [options.configured];
24007
24845
  const attempted = [];
24008
- 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)))) {
24009
24847
  throw new Error(`Configured development.server.executable must stay inside serverPath but received "${options.configured}".`);
24010
24848
  }
24011
24849
  for (const name of names) {
24012
- const path = resolve16(serverRoot, name);
24850
+ const path = resolve17(serverRoot, name);
24013
24851
  attempted.push(path);
24014
24852
  try {
24015
24853
  if (!statSync5(path).isFile()) {
@@ -24369,8 +25207,520 @@ function registerEnsureCommand(program2, runtime) {
24369
25207
  });
24370
25208
  }
24371
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
+
24372
25722
  // src/cli/registry/init-registration.ts
24373
- import { resolve as resolve18 } from "node:path";
25723
+ import { resolve as resolve21 } from "node:path";
24374
25724
 
24375
25725
  // src/commands/init-command.ts
24376
25726
  import { basename as basename2 } from "node:path";
@@ -24432,7 +25782,7 @@ function renderManifest(source, details) {
24432
25782
  }
24433
25783
 
24434
25784
  // src/scaffold/template-files.ts
24435
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
25785
+ import { existsSync as existsSync14, readFileSync as readFileSync13 } from "node:fs";
24436
25786
  import { fileURLToPath as fileURLToPath2 } from "node:url";
24437
25787
 
24438
25788
  // ../template/src/template.ts
@@ -24448,10 +25798,10 @@ function bundledTemplatePath(source) {
24448
25798
  }
24449
25799
  function resolveTemplatePath(source) {
24450
25800
  const packaged = fileURLToPath2(resolveTemplateUrl(source));
24451
- return existsSync11(packaged) ? packaged : bundledTemplatePath(source);
25801
+ return existsSync14(packaged) ? packaged : bundledTemplatePath(source);
24452
25802
  }
24453
25803
  function readTemplateSource(source) {
24454
- return readFileSync10(resolveTemplatePath(source), "utf8");
25804
+ return readFileSync13(resolveTemplatePath(source), "utf8");
24455
25805
  }
24456
25806
 
24457
25807
  // src/scaffold/scaffold-plan.ts
@@ -24467,19 +25817,19 @@ function buildScaffoldPlan(details) {
24467
25817
  }
24468
25818
 
24469
25819
  // src/scaffold/scaffold-writer.ts
24470
- import { existsSync as existsSync12, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "node:fs";
24471
- 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";
24472
25822
  function writeScaffold(targetDir, plan, force) {
24473
25823
  const written = [];
24474
25824
  const skipped = [];
24475
25825
  for (const file of plan.files) {
24476
- const absolute = resolve17(targetDir, file.path);
24477
- if (!force && existsSync12(absolute)) {
25826
+ const absolute = resolve20(targetDir, file.path);
25827
+ if (!force && existsSync15(absolute)) {
24478
25828
  skipped.push(file.path);
24479
25829
  continue;
24480
25830
  }
24481
- mkdirSync6(dirname6(absolute), { recursive: true });
24482
- writeFileSync7(absolute, file.content, "utf8");
25831
+ mkdirSync6(dirname7(absolute), { recursive: true });
25832
+ writeFileSync8(absolute, file.content, "utf8");
24483
25833
  written.push(file.path);
24484
25834
  }
24485
25835
  return { written: written.sort((left, right) => left.localeCompare(right)), skipped: skipped.sort((left, right) => left.localeCompare(right)) };
@@ -24529,7 +25879,7 @@ function registerInitCommand(program2, runtime) {
24529
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());
24530
25880
  command.action(async (path, options) => {
24531
25881
  const root = commandRoot(runtime, options);
24532
- const target = path === void 0 ? root : resolve18(root, path);
25882
+ const target = path === void 0 ? root : resolve21(root, path);
24533
25883
  runtime.exitCode = await runInitCommand(target, runtime.logger, {
24534
25884
  name: options.name ?? null,
24535
25885
  force: options.force === true,
@@ -24653,13 +26003,13 @@ function registerServerCommand(program2, runtime) {
24653
26003
  }
24654
26004
 
24655
26005
  // src/testing/test-report.ts
24656
- import { readFileSync as readFileSync11 } from "node:fs";
24657
- import { resolve as resolve19 } from "node:path";
26006
+ import { readFileSync as readFileSync14 } from "node:fs";
26007
+ import { resolve as resolve22 } from "node:path";
24658
26008
  function sourceLine(root, path, line2, cache3) {
24659
26009
  let lines = cache3.get(path);
24660
26010
  if (lines === void 0) {
24661
26011
  try {
24662
- lines = readFileSync11(resolve19(root, path), "utf8").split(/\r?\n/);
26012
+ lines = readFileSync14(resolve22(root, path), "utf8").split(/\r?\n/);
24663
26013
  } catch {
24664
26014
  lines = [];
24665
26015
  }
@@ -24732,14 +26082,14 @@ function reportTestResults(reporter, runs, root, map, durationMs) {
24732
26082
  }
24733
26083
 
24734
26084
  // src/testing/test-discovery.ts
24735
- import { readFileSync as readFileSync12 } from "node:fs";
24736
- import { resolve as resolve20 } from "node:path";
26085
+ import { readFileSync as readFileSync15 } from "node:fs";
26086
+ import { resolve as resolve23 } from "node:path";
24737
26087
  var DEFAULT_TEST_ENVIRONMENT = "shared";
24738
26088
  var SIDE_CONFLICT2 = "test-source-side-conflict";
24739
26089
  var UNREADABLE_TEST = "test-source-unreadable";
24740
26090
  function readTest(root, path, diagnostics) {
24741
26091
  try {
24742
- return readFileSync12(resolve20(root, path), "utf8");
26092
+ return readFileSync15(resolve23(root, path), "utf8");
24743
26093
  } catch (error) {
24744
26094
  diagnostics.push(cliError(UNREADABLE_TEST, `The test file "${path}" could not be read: ${error instanceof Error ? error.message : String(error)}`));
24745
26095
  return null;
@@ -24766,9 +26116,9 @@ function discoverTests(root, sources, excluded = []) {
24766
26116
 
24767
26117
  // src/testing/test-runner.ts
24768
26118
  import { spawnSync as spawnSync3 } from "node:child_process";
24769
- 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";
24770
26120
  import { tmpdir as tmpdir2 } from "node:os";
24771
- import { dirname as dirname7, join as join6 } from "node:path";
26121
+ import { dirname as dirname8, join as join7 } from "node:path";
24772
26122
 
24773
26123
  // src/testing/harness-assertions.ts
24774
26124
  var ASSERTIONS_SOURCE = String.raw`local SENTINEL = '##luam:test'
@@ -25183,11 +26533,11 @@ function parseRunOutput(environment, stdout) {
25183
26533
  return { results, output };
25184
26534
  }
25185
26535
  function writeWorkspace(directory, scripts) {
25186
- writeFileSync8(join6(directory, HARNESS_FILE), HARNESS_SOURCE, "utf8");
26536
+ writeFileSync9(join7(directory, HARNESS_FILE), HARNESS_SOURCE, "utf8");
25187
26537
  for (const script of scripts) {
25188
- const target = join6(directory, script.path);
25189
- mkdirSync7(dirname7(target), { recursive: true });
25190
- writeFileSync8(target, script.content, "utf8");
26538
+ const target = join7(directory, script.path);
26539
+ mkdirSync7(dirname8(target), { recursive: true });
26540
+ writeFileSync9(target, script.content, "utf8");
25191
26541
  }
25192
26542
  }
25193
26543
  function loadOrder(available, environment) {
@@ -25201,14 +26551,14 @@ function loadOrder(available, environment) {
25201
26551
  }
25202
26552
  function runTests(request) {
25203
26553
  const spawn2 = request.spawn ?? runLua;
25204
- const directory = mkdtempSync2(join6(tmpdir2(), "luam-test-"));
26554
+ const directory = mkdtempSync2(join7(tmpdir2(), "luam-test-"));
25205
26555
  const available = new Set(request.scripts.map((script) => script.path));
25206
26556
  try {
25207
26557
  writeWorkspace(directory, request.scripts);
25208
26558
  return ALL_ENVIRONMENTS.filter((environment) => request.environments.includes(environment)).map((environment) => {
25209
26559
  const { preload, target } = loadOrder(available, environment);
25210
26560
  const entry = entryFile(environment);
25211
- writeFileSync8(join6(directory, entry), entrySource(environment, preload, target), "utf8");
26561
+ writeFileSync9(join7(directory, entry), entrySource(environment, preload, target), "utf8");
25212
26562
  const execution = spawn2(request.executable, [entry], directory);
25213
26563
  const parsed = parseRunOutput(environment, execution.stdout);
25214
26564
  const failure3 = execution.failure ?? (parsed.results.length === 0 && execution.stderr.length > 0 ? execution.stderr.trim() : null);
@@ -25280,12 +26630,12 @@ function registerTestCommand(program2, runtime) {
25280
26630
  }
25281
26631
 
25282
26632
  // src/commands/trace-command.ts
25283
- import { existsSync as existsSync13, readFileSync as readFileSync13 } from "node:fs";
26633
+ import { existsSync as existsSync16, readFileSync as readFileSync16 } from "node:fs";
25284
26634
  function defaultMapPath(root, options, reporter) {
25285
26635
  const loaded = loadManifest(root, { path: options.manifestPath, mode: manifestMode("trace"), env: options.env });
25286
26636
  if (loaded.config !== null) {
25287
26637
  const configured = resourceMapPath(root, loaded.config);
25288
- if (existsSync13(configured)) {
26638
+ if (existsSync16(configured)) {
25289
26639
  return configured;
25290
26640
  }
25291
26641
  }
@@ -25305,7 +26655,7 @@ function readStandardInput() {
25305
26655
  return "";
25306
26656
  }
25307
26657
  try {
25308
- return readFileSync13(0, "utf8");
26658
+ return readFileSync16(0, "utf8");
25309
26659
  } catch {
25310
26660
  return "";
25311
26661
  }
@@ -25387,6 +26737,7 @@ var COMMAND_REGISTRARS = [
25387
26737
  registerDevCommand,
25388
26738
  registerDoctorCommand,
25389
26739
  registerEnsureCommand,
26740
+ registerFormatCommand,
25390
26741
  registerInitCommand,
25391
26742
  registerServerCommand,
25392
26743
  registerSetupCommand,