nanos-lint 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -134,7 +134,8 @@ nanos-lint [command] [options] [paths...]
134
134
 
135
135
  COMMANDS:
136
136
  check [paths...] Check workspace files or directories (default)
137
- init [path] Scaffold a .luarc.json configuration in the workspace (copies definitions to .nanos-lint/; supports --annotations <path>)
137
+ init [path] Scaffold a .luarc.json configuration in the workspace (supports --vendor, --force)
138
+ copy-annotations [dest] Copy cached annotations.lua definitions into a target file or directory (alias: export-annotations; targets not ending in .lua are treated as directories)
138
139
  warmup, download Pre-fetch and cache both LuaLS binary and annotations for offline execution
139
140
  cache status, cache info Show cache status, installed versions, and disk usage (supports --json)
140
141
  cache clean Clear the nanos-lint cache directory
@@ -153,14 +154,15 @@ OPTIONS:
153
154
  debug adds diagnostic tracing
154
155
  --checklevel=<level> Minimum diagnostic level: Error, Warning, Information, Hint (default: Warning)
155
156
  --config=<path> Path to custom .luarc.json configuration file
156
- --annotations=<path> Path to custom annotations.lua file (applies to both check and init)
157
+ --annotations=<path> Path to custom annotations.lua file (applies to check, init --vendor, and copy-annotations)
157
158
  --format=<format> Output format: pretty, json, github (default: pretty)
158
159
  --github Output in GitHub Actions format (shortcut for --format=github)
159
160
  --luals-version=<ver> Version of LuaLS to use (default: latest, falling back to 3.19.1 when offline)
160
161
  --no-fail Do not exit with code 1 if diagnostics are found
161
162
  --realm <realm> Execution realm to check: all, client, server, shared (default: all)
162
163
  --no-progress Disable the interactive download, extraction and realm derivation progress display
163
- -f, --force (init command only) Overwrite existing .luarc.json
164
+ --vendor (init command only) Vendor annotations.lua into .nanos-lint/ inside workspace
165
+ -f, --force (init and copy-annotations only) Overwrite existing files
164
166
  ```
165
167
 
166
168
  ### Progress Display
@@ -223,7 +225,7 @@ If your project already has a `.luarc.json`, `nanos-lint` automatically merges i
223
225
  }
224
226
  ```
225
227
 
226
- To scaffold a portable `.luarc.json` with vendored type annotations in your project, run:
228
+ To scaffold a `.luarc.json` configuration in your project:
227
229
 
228
230
  ```bash
229
231
  npx nanos-lint init
@@ -231,6 +233,24 @@ npx nanos-lint init
231
233
  npx nanos-lint init --force
232
234
  ```
233
235
 
236
+ By default `init` writes no annotations reference at all: `workspace.library` stays empty and
237
+ `nanos-lint check` injects the managed cache path into its own temporary merged configuration at
238
+ runtime, so a later cache update is picked up without touching repository files. Editors that read
239
+ `.luarc.json` directly need a pinned copy, which is always an explicit opt-in:
240
+
241
+ ```bash
242
+ # Initialize with annotations vendored into .nanos-lint/ and pinned in workspace.library
243
+ npx nanos-lint init --vendor
244
+
245
+ # Or copy the cached annotations to a custom file, or to any target that does not end in
246
+ # ".lua" (treated as a directory). Copying alone does not pin anything: add the resulting
247
+ # path to workspace.library yourself.
248
+ npx nanos-lint copy-annotations
249
+ npx nanos-lint copy-annotations ./types
250
+ ```
251
+
252
+ `copy-annotations` refuses to overwrite an existing file unless `--force` is passed.
253
+
234
254
  ### Realm Mapping (`nanos.realms`)
235
255
 
236
256
  nanos world runs two isolated Lua VMs: the server executes `Server/**` + `Shared/**`, the client executes `Client/**` + `Shared/**`. `nanos-lint` mirrors that split and lets you describe a non-standard layout in `.luarc.json`:
@@ -369,7 +389,7 @@ npm run check:all
369
389
  npm run format:check # Prettier code formatting (auto-fix via npm run format:fix)
370
390
  npm run lint # ESLint
371
391
  npm run lint:deps # Dependency architecture & license checks (dependency-cruiser)
372
- npm run lint:comments # Comment density limit (<= 15%)
392
+ npm run lint:comments # Comment density limit (<= 10%)
373
393
  npm run lint:docstrings # Docstring coverage (>= 90% top-level/exported function coverage per file in src/)
374
394
  npm run typecheck # TypeScript typecheck
375
395
  npm run build # Build distribution bundle
package/action.yml CHANGED
@@ -172,5 +172,5 @@ runs:
172
172
  else
173
173
  # Exact version: a range could run a different release than the pinned
174
174
  # tag. Bump together with "version" in package.json.
175
- npx --yes nanos-lint@3.2.0 "${ARGS[@]}"
175
+ npx --yes nanos-lint@3.3.0 "${ARGS[@]}"
176
176
  fi
@@ -1464,20 +1464,23 @@ function resolveRealmMappings(userConfig) {
1464
1464
  function initWorkspace(workspacePath, options) {
1465
1465
  const targetFile = path.join(workspacePath, ".luarc.json");
1466
1466
  if (fs.existsSync(targetFile) && !options?.force) throw new ConfigError(`.luarc.json already exists at ${targetFile}. Use --force to overwrite.`, "ERR_CONFIG_EXISTS", "Pass --force to overwrite the existing .luarc.json file.");
1467
+ if (options?.annotationsPath && !options.vendor) throw new ConfigError("--annotations requires --vendor: nanos-lint init does not pin or vendor annotations.lua by default.", "ERR_ANNOTATIONS_WITHOUT_VENDOR", "Run 'nanos-lint init --vendor --annotations <path>' (or pass vendor: true), or omit the custom path to use the managed annotations cache.");
1467
1468
  const template = loadConfigFile(getDefaultTemplatePath());
1468
- const sourceAnnotations = options?.annotationsPath || getDefaultAnnotationsPath();
1469
- if (!fs.existsSync(sourceAnnotations)) throw new ConfigError(`Definitions file not found at ${sourceAnnotations}. Make sure annotations have been downloaded or pass a valid file with --annotations.`, "ERR_ANNOTATIONS_NOT_FOUND", "Run 'nanos-lint warmup' to download annotations or provide --annotations <path>.");
1470
- const targetNanosDir = path.join(workspacePath, ".nanos-lint");
1471
- fs.mkdirSync(targetNanosDir, { recursive: true });
1472
- const targetAnnotations = path.join(targetNanosDir, "annotations.lua");
1473
- fs.copyFileSync(sourceAnnotations, targetAnnotations);
1474
- template.workspace = template.workspace ?? {};
1475
- template.workspace.library = [".nanos-lint/annotations.lua"];
1476
- const existingIgnore = template.workspace.ignoreDir ?? [];
1477
- if (!existingIgnore.includes(".nanos-lint")) template.workspace.ignoreDir = [".nanos-lint", ...existingIgnore];
1478
- template.files = template.files ?? {};
1479
- const existingExclude = template.files.exclude ?? [];
1480
- if (!existingExclude.includes(".nanos-lint/**")) template.files.exclude = [".nanos-lint/**", ...existingExclude];
1469
+ if (options?.vendor) {
1470
+ const sourceAnnotations = options.annotationsPath || getDefaultAnnotationsPath();
1471
+ if (!fs.existsSync(sourceAnnotations)) throw new ConfigError(`Definitions file not found at ${sourceAnnotations}. Make sure annotations have been downloaded or pass a valid file with --annotations.`, "ERR_ANNOTATIONS_NOT_FOUND", "Run 'nanos-lint warmup' to download annotations or provide --annotations <path>.");
1472
+ const targetNanosDir = path.join(workspacePath, ".nanos-lint");
1473
+ fs.mkdirSync(targetNanosDir, { recursive: true });
1474
+ const targetAnnotations = path.join(targetNanosDir, "annotations.lua");
1475
+ fs.copyFileSync(sourceAnnotations, targetAnnotations);
1476
+ template.workspace = template.workspace ?? {};
1477
+ template.workspace.library = [".nanos-lint/annotations.lua"];
1478
+ const existingIgnore = template.workspace.ignoreDir ?? [];
1479
+ if (!existingIgnore.includes(".nanos-lint")) template.workspace.ignoreDir = [".nanos-lint", ...existingIgnore];
1480
+ template.files = template.files ?? {};
1481
+ const existingExclude = template.files.exclude ?? [];
1482
+ if (!existingExclude.includes(".nanos-lint/**")) template.files.exclude = [".nanos-lint/**", ...existingExclude];
1483
+ }
1481
1484
  fs.writeFileSync(targetFile, JSON.stringify(template, null, 2), "utf-8");
1482
1485
  return targetFile;
1483
1486
  }
@@ -6368,6 +6371,13 @@ async function validateArchiveMembers(archivePath) {
6368
6371
  * user-supplied `LUALS_BIN` may legitimately be a small wrapper script.
6369
6372
  */
6370
6373
  const MIN_DOWNLOADED_BINARY_SIZE_BYTES = 1e5;
6374
+ /** Propagates blocked LuaLS execution or cache access without triggering repair. */
6375
+ function rethrowLuaLSPermissionError(err, targetPath, operation = "access") {
6376
+ if (err instanceof LuaLSError && (err.code === "ERR_LUALS_EXECUTION_DENIED" || err.code === "ERR_LUALS_CACHE_PERMISSION")) throw err;
6377
+ const code = err?.code;
6378
+ if (code !== "EPERM" && code !== "EACCES") return;
6379
+ throw new LuaLSError(`Cannot ${operation} LuaLS ${operation === "execute" ? "binary" : "cache path"} '${targetPath}': ${code}: ${err instanceof Error ? err.message : String(err)}`, operation === "execute" ? "ERR_LUALS_EXECUTION_DENIED" : "ERR_LUALS_CACHE_PERMISSION", operation === "execute" ? "Run nanos-lint in an environment that permits LuaLS execution and check the binary's execute permissions and sandbox policy. The existing cache has been preserved." : "Allow access to the LuaLS cache directory or use a writable cache directory.", { cause: err });
6380
+ }
6371
6381
  /**
6372
6382
  * Runs `binaryPath --version` and checks that it reports a LuaLS version.
6373
6383
  * Shared by every binary validation path; the size heuristic stays separate
@@ -6382,6 +6392,7 @@ function reportsLuaLSVersion(binaryPath) {
6382
6392
  });
6383
6393
  return /^\d+\.\d+\.\d+/.test(output.trim());
6384
6394
  } catch (err) {
6395
+ rethrowLuaLSPermissionError(err, binaryPath, "execute");
6385
6396
  logger.debug(`[luals] Binary validation check failed for ${binaryPath}: ${err instanceof Error ? err.message : String(err)}`);
6386
6397
  return false;
6387
6398
  }
@@ -6397,6 +6408,7 @@ function isBinaryValid(binaryPath) {
6397
6408
  if (!stats.isFile() || stats.size < 1e5) return false;
6398
6409
  return reportsLuaLSVersion(binaryPath);
6399
6410
  } catch (err) {
6411
+ rethrowLuaLSPermissionError(err, binaryPath);
6400
6412
  logger.debug(`[luals] Binary validation check failed for ${binaryPath}: ${err instanceof Error ? err.message : String(err)}`);
6401
6413
  return false;
6402
6414
  }
@@ -6410,6 +6422,7 @@ function isBinaryRunnable(binaryPath) {
6410
6422
  try {
6411
6423
  if (!fs.statSync(binaryPath).isFile()) return false;
6412
6424
  } catch (err) {
6425
+ rethrowLuaLSPermissionError(err, binaryPath);
6413
6426
  logger.debug(`[luals] Binary validation check failed for ${binaryPath}: ${err instanceof Error ? err.message : String(err)}`);
6414
6427
  return false;
6415
6428
  }
@@ -6434,6 +6447,7 @@ function assertValidLuaLSBinary(binaryPath, source) {
6434
6447
  try {
6435
6448
  stats = fs.statSync(binaryPath);
6436
6449
  } catch (err) {
6450
+ rethrowLuaLSPermissionError(err, binaryPath);
6437
6451
  throw new LuaLSError(`${source} points to '${binaryPath}', which does not exist or cannot be read: ${err instanceof Error ? err.message : String(err)}`, "ERR_LUALS_BIN_INVALID", `Set ${source} to the full path of a lua-language-server executable, or leave it unset so nanos-lint resolves LuaLS automatically.`, { cause: err });
6438
6452
  }
6439
6453
  if (!stats.isFile()) throw new LuaLSError(`${source} points to '${binaryPath}', which is not a regular file (a directory or special file was found).`, "ERR_LUALS_BIN_INVALID", `Point ${source} at the lua-language-server executable itself, or leave it unset so nanos-lint resolves LuaLS automatically.`);
@@ -6550,11 +6564,13 @@ function listCachedLuaLSVersions(baseCacheDir = getBaseLuaLSCacheDir()) {
6550
6564
  if (fs.existsSync(marker) && fs.existsSync(bin)) try {
6551
6565
  if (fs.readFileSync(marker, "utf-8").trim() === version && isBinaryValid(bin)) versions.push(version);
6552
6566
  } catch (err) {
6567
+ rethrowLuaLSPermissionError(err, bin);
6553
6568
  logger.debug(`[luals] Error validating cached version ${version}: ${err instanceof Error ? err.message : String(err)}`);
6554
6569
  }
6555
6570
  }
6556
6571
  return versions;
6557
6572
  } catch (err) {
6573
+ rethrowLuaLSPermissionError(err, baseCacheDir);
6558
6574
  logger.debug(`[luals] Failed to list cached LuaLS versions: ${err instanceof Error ? err.message : String(err)}`);
6559
6575
  return [];
6560
6576
  }
@@ -6604,6 +6620,7 @@ function findExistingLuaLSDir(version, baseCacheDir = getBaseLuaLSCacheDir()) {
6604
6620
  if (fs.existsSync(primaryMarker)) try {
6605
6621
  if (fs.readFileSync(primaryMarker, "utf-8").trim() === version && isBinaryValid(primaryBin)) return primaryCache;
6606
6622
  } catch (err) {
6623
+ rethrowLuaLSPermissionError(err, primaryMarker);
6607
6624
  logger.debug(`[luals] Error checking primary LuaLS cache marker at ${primaryMarker}: ${err instanceof Error ? err.message : String(err)}`);
6608
6625
  }
6609
6626
  const pkgRoot = getPackageRoot();
@@ -6664,6 +6681,7 @@ function isCompleteLuaLSInstall(binaryPath, completeMarker, version) {
6664
6681
  try {
6665
6682
  return fs.readFileSync(completeMarker, "utf-8").trim() === version && isBinaryValid(binaryPath);
6666
6683
  } catch (err) {
6684
+ rethrowLuaLSPermissionError(err, completeMarker);
6667
6685
  logger.debug(`[luals] Failed to read complete marker at ${completeMarker}: ${err instanceof Error ? err.message : String(err)}`);
6668
6686
  return false;
6669
6687
  }
@@ -6694,6 +6712,7 @@ async function downloadAndPromoteLuaLS(resolvedVersion, info, destDir, binaryPat
6694
6712
  if (options?.reuseExisting !== false && fs.existsSync(binaryPath) && fs.existsSync(completeMarker)) try {
6695
6713
  if (fs.readFileSync(completeMarker, "utf-8").trim() === resolvedVersion && isBinaryValid(binaryPath)) return binaryPath;
6696
6714
  } catch (err) {
6715
+ rethrowLuaLSPermissionError(err, completeMarker);
6697
6716
  logger.debug(`[luals] Failed to read complete marker at ${completeMarker}: ${err instanceof Error ? err.message : String(err)}`);
6698
6717
  }
6699
6718
  try {
@@ -6702,6 +6721,7 @@ async function downloadAndPromoteLuaLS(resolvedVersion, info, destDir, binaryPat
6702
6721
  force: true
6703
6722
  });
6704
6723
  } catch (err) {
6724
+ rethrowLuaLSPermissionError(err, destDir);
6705
6725
  logger.warn(`[luals] Failed to remove stale or invalid cache dir ${destDir}: ${err instanceof Error ? err.message : String(err)}`);
6706
6726
  }
6707
6727
  }
@@ -7310,6 +7330,7 @@ async function resolveLuaLSBinary(version = DEFAULT_LUALS_VERSION, options) {
7310
7330
  if (options?.reuseExisting !== false) return cachedPath;
7311
7331
  }
7312
7332
  } catch (err) {
7333
+ rethrowLuaLSPermissionError(err, completeMarker);
7313
7334
  logger.debug(`[luals] Failed to read complete marker at ${completeMarker}: ${err instanceof Error ? err.message : String(err)}`);
7314
7335
  }
7315
7336
  if (!isValid) {
@@ -7322,6 +7343,7 @@ async function resolveLuaLSBinary(version = DEFAULT_LUALS_VERSION, options) {
7322
7343
  force: true
7323
7344
  });
7324
7345
  } catch (err) {
7346
+ rethrowLuaLSPermissionError(err, cachedDir);
7325
7347
  logger.warn(`[luals] Failed to remove corrupted cache directory ${cachedDir}: ${err instanceof Error ? err.message : String(err)}`);
7326
7348
  }
7327
7349
  }
@@ -7331,11 +7353,13 @@ async function resolveLuaLSBinary(version = DEFAULT_LUALS_VERSION, options) {
7331
7353
  const found = stdout.trim().split(/\r?\n/)[0];
7332
7354
  if (found && fs.existsSync(found) && isBinaryValid(found)) return found;
7333
7355
  } catch (err) {
7356
+ rethrowLuaLSPermissionError(err, "lua-language-server", "execute");
7334
7357
  logger.debug(`[luals] LuaLS binary not found in PATH: ${err instanceof Error ? err.message : String(err)}`);
7335
7358
  }
7336
7359
  try {
7337
7360
  return await downloadAndExtractLuaLS(resolvedVersion, getCacheDir(resolvedVersion, baseCacheDir), options);
7338
7361
  } catch (err) {
7362
+ rethrowLuaLSPermissionError(err, cachedDir);
7339
7363
  if (wasCorrupted) {
7340
7364
  const isOffline = isOfflineError(err);
7341
7365
  const reason = isOffline ? "while offline" : err instanceof Error ? err.message : String(err);
@@ -7397,12 +7421,14 @@ async function resolveLuaLSBinary(version = DEFAULT_LUALS_VERSION, options) {
7397
7421
  const found = stdout.trim().split(/\r?\n/)[0];
7398
7422
  if (found && fs.existsSync(found) && isBinaryValid(found)) return found;
7399
7423
  } catch (err) {
7424
+ rethrowLuaLSPermissionError(err, "lua-language-server", "execute");
7400
7425
  logger.debug(`[luals] LuaLS binary not found in PATH: ${err instanceof Error ? err.message : String(err)}`);
7401
7426
  }
7402
7427
  let downloadedBinary;
7403
7428
  try {
7404
7429
  downloadedBinary = await downloadAndExtractLuaLS(targetVersion, targetCacheDir, options);
7405
7430
  } catch (err) {
7431
+ rethrowLuaLSPermissionError(err, targetCacheDir);
7406
7432
  if (wasCorrupted) {
7407
7433
  const isOffline = isOfflineError(err);
7408
7434
  const reason = isOffline ? "while offline" : err instanceof Error ? err.message : String(err);
@@ -11338,6 +11364,22 @@ async function resolveAnnotations(options = {}) {
11338
11364
  throw new AnnotationsError(`Failed to resolve nanos world API annotations. Please check your network connection or pass a custom file with --annotations. (${err instanceof Error ? err.message : String(err)})`, "ERR_ANNOTATIONS_NETWORK", "Check your network connection, run 'nanos-lint warmup' when online, or specify --annotations <path>.", { cause: err });
11339
11365
  }
11340
11366
  }
11367
+ const DEFAULT_COPY_TARGET = `.nanos-lint/${ANNOTATIONS_FILENAME}`;
11368
+ /**
11369
+ * Copies nanos world annotations.lua definitions into a target file or directory.
11370
+ * A target counts as a directory when it ends with a separator, already is a directory,
11371
+ * or does not end in `.lua` (case-insensitively).
11372
+ */
11373
+ async function copyAnnotations(targetPath = DEFAULT_COPY_TARGET, options = {}) {
11374
+ let resolvedTarget = path.resolve(targetPath);
11375
+ if (targetPath.endsWith("/") || targetPath.endsWith("\\") || fs.existsSync(resolvedTarget) && fs.statSync(resolvedTarget).isDirectory() || path.extname(resolvedTarget).toLowerCase() !== ".lua") resolvedTarget = path.join(resolvedTarget, ANNOTATIONS_FILENAME);
11376
+ if (fs.existsSync(resolvedTarget) && !options.force) throw new AnnotationsError(`Target annotations file already exists: ${resolvedTarget}. Use --force to overwrite.`, "ERR_ANNOTATIONS_EXISTS", "Pass --force to overwrite the existing annotations file.");
11377
+ const sourcePath = options.annotationsPath ? validateCustomAnnotationsPath(options.annotationsPath, "custom") : await resolveAnnotations({ cacheDir: options.cacheDir });
11378
+ const targetDir = path.dirname(resolvedTarget);
11379
+ fs.mkdirSync(targetDir, { recursive: true });
11380
+ fs.copyFileSync(sourcePath, resolvedTarget);
11381
+ return resolvedTarget;
11382
+ }
11341
11383
  //#endregion
11342
11384
  //#region src/cache-status.ts
11343
11385
  /** Collects detailed status and disk usage information for LuaLS and annotations caches. */
@@ -11432,6 +11474,80 @@ function formatCacheStatusPretty(report) {
11432
11474
  return lines.join("\n");
11433
11475
  }
11434
11476
  //#endregion
11477
+ //#region src/output.ts
11478
+ /** Writes command results to stdout unless the log level is `silent`. */
11479
+ function writeOutput(message) {
11480
+ if (logger.isOutputEnabled()) console.log(message);
11481
+ }
11482
+ //#endregion
11483
+ //#region src/cli-check.ts
11484
+ /** Executes the check command on target paths with specified options. */
11485
+ async function executeCheckCommand(targetPaths = ["."], opts, setExitCode) {
11486
+ if (opts.logLevel && isValidLogLevel(opts.logLevel)) logger.setLevel(opts.logLevel);
11487
+ const { rootPath, targetPaths: canonicalTargets } = resolveCheckTargets(targetPaths && targetPaths.length > 0 ? targetPaths : ["."]);
11488
+ const format = opts.github ? "github" : opts.format || (process.env.GITHUB_ACTIONS ? "github" : "pretty");
11489
+ if (format === "json") {
11490
+ setProgressMode("off");
11491
+ logger.setDiagnosticStream("stderr");
11492
+ }
11493
+ const checkOptions = {
11494
+ path: rootPath,
11495
+ paths: canonicalTargets,
11496
+ configpath: opts.config,
11497
+ checklevel: opts.checklevel,
11498
+ format,
11499
+ lualsVersion: opts.lualsVersion,
11500
+ failOnError: opts.fail !== false,
11501
+ ignore: opts.ignore,
11502
+ deps: opts.dep
11503
+ };
11504
+ if (opts.config) {
11505
+ const resolvedConfig = path.resolve(opts.config);
11506
+ if (!fs.existsSync(resolvedConfig)) throw new ConfigError(`Configuration file not found: ${resolvedConfig}`, "ERR_CONFIG_NOT_FOUND", "Verify the path passed to --config exists and is readable.");
11507
+ }
11508
+ const annotationsPath = await resolveAnnotations({ customPath: opts.annotations });
11509
+ const userConfig = loadUserConfig(rootPath, checkOptions.configpath);
11510
+ const realmPlan = planRealmCheck({
11511
+ targetPath: rootPath,
11512
+ userConfig,
11513
+ selection: opts.realm ?? "all",
11514
+ annotationsPath,
11515
+ customConfigPath: checkOptions.configpath,
11516
+ ignore: checkOptions.ignore,
11517
+ targetPaths: canonicalTargets,
11518
+ cliDeps: checkOptions.deps
11519
+ });
11520
+ let result;
11521
+ if (realmPlan) try {
11522
+ result = await runRealmAwareCheck(realmPlan, rootPath, checkOptions);
11523
+ } finally {
11524
+ realmPlan.cleanup();
11525
+ }
11526
+ else {
11527
+ const resolvedDeps = resolvePackageDependencies(rootPath, userConfig, checkOptions.deps);
11528
+ const unrequestedExclusions = computeUnrequestedExclusions(rootPath, canonicalTargets);
11529
+ const resolved = resolveWorkspaceConfig(rootPath, checkOptions.configpath, {
11530
+ ignore: checkOptions.ignore,
11531
+ annotationsPath,
11532
+ dependencyLibraries: resolvedDeps.all,
11533
+ unrequestedExclusions
11534
+ });
11535
+ try {
11536
+ result = await runLuaLSCheck(rootPath, resolved.configPath, checkOptions);
11537
+ } finally {
11538
+ if (resolved.isTemp && fs.existsSync(resolved.configPath)) try {
11539
+ fs.unlinkSync(resolved.configPath);
11540
+ } catch (err) {
11541
+ logger.warn(`Failed to clean up temporary config file ${resolved.configPath}: ${err instanceof Error ? err.message : String(err)}`);
11542
+ }
11543
+ }
11544
+ }
11545
+ const output = formatReport(result, checkOptions.format, process.cwd());
11546
+ if (output) writeOutput(output);
11547
+ if (!result.passed && checkOptions.failOnError) setExitCode(1);
11548
+ else setExitCode(0);
11549
+ }
11550
+ //#endregion
11435
11551
  //#region src/cli.ts
11436
11552
  /** Retrieves the formatted package name and version string from package.json. */
11437
11553
  function getVersionString() {
@@ -11443,10 +11559,6 @@ function getVersionString() {
11443
11559
  return "nanos-lint v1.0.0";
11444
11560
  }
11445
11561
  }
11446
- /** Writes command results to stdout unless the level is `silent`. */
11447
- function writeOutput(message) {
11448
- if (logger.isOutputEnabled()) console.log(message);
11449
- }
11450
11562
  /** Accumulates repeatable --ignore pattern arguments, splitting by comma and newline. */
11451
11563
  function collectIgnorePatterns(val, prev) {
11452
11564
  const parts = val.split(/[\r\n,]+/).map((s) => s.trim()).filter(Boolean);
@@ -11492,75 +11604,22 @@ function createProgram(options) {
11492
11604
  "server",
11493
11605
  "shared"
11494
11606
  ]).default("all")).action(async (targetPaths = ["."], opts) => {
11495
- if (opts.logLevel && isValidLogLevel(opts.logLevel)) logger.setLevel(opts.logLevel);
11496
- const { rootPath, targetPaths: canonicalTargets } = resolveCheckTargets(targetPaths && targetPaths.length > 0 ? targetPaths : ["."]);
11497
- const format = opts.github ? "github" : opts.format || (process.env.GITHUB_ACTIONS ? "github" : "pretty");
11498
- if (format === "json") {
11499
- setProgressMode("off");
11500
- logger.setDiagnosticStream("stderr");
11501
- }
11502
- const checkOptions = {
11503
- path: rootPath,
11504
- paths: canonicalTargets,
11505
- configpath: opts.config,
11506
- checklevel: opts.checklevel,
11507
- format,
11508
- lualsVersion: opts.lualsVersion,
11509
- failOnError: opts.fail !== false,
11510
- ignore: opts.ignore,
11511
- deps: opts.dep
11512
- };
11513
- if (opts.config) {
11514
- const resolvedConfig = path.resolve(opts.config);
11515
- if (!fs.existsSync(resolvedConfig)) throw new ConfigError(`Configuration file not found: ${resolvedConfig}`, "ERR_CONFIG_NOT_FOUND", "Verify the path passed to --config exists and is readable.");
11516
- }
11517
- const annotationsPath = await resolveAnnotations({ customPath: opts.annotations });
11518
- const userConfig = loadUserConfig(rootPath, checkOptions.configpath);
11519
- const realmPlan = planRealmCheck({
11520
- targetPath: rootPath,
11521
- userConfig,
11522
- selection: opts.realm ?? "all",
11523
- annotationsPath,
11524
- customConfigPath: checkOptions.configpath,
11525
- ignore: checkOptions.ignore,
11526
- targetPaths: canonicalTargets,
11527
- cliDeps: checkOptions.deps
11528
- });
11529
- let result;
11530
- if (realmPlan) try {
11531
- result = await runRealmAwareCheck(realmPlan, rootPath, checkOptions);
11532
- } finally {
11533
- realmPlan.cleanup();
11534
- }
11535
- else {
11536
- const resolvedDeps = resolvePackageDependencies(rootPath, userConfig, checkOptions.deps);
11537
- const unrequestedExclusions = computeUnrequestedExclusions(rootPath, canonicalTargets);
11538
- const resolved = resolveWorkspaceConfig(rootPath, checkOptions.configpath, {
11539
- ignore: checkOptions.ignore,
11540
- annotationsPath,
11541
- dependencyLibraries: resolvedDeps.all,
11542
- unrequestedExclusions
11543
- });
11544
- try {
11545
- result = await runLuaLSCheck(rootPath, resolved.configPath, checkOptions);
11546
- } finally {
11547
- if (resolved.isTemp && fs.existsSync(resolved.configPath)) try {
11548
- fs.unlinkSync(resolved.configPath);
11549
- } catch (err) {
11550
- logger.warn(`Failed to clean up temporary config file ${resolved.configPath}: ${err instanceof Error ? err.message : String(err)}`);
11551
- }
11552
- }
11553
- }
11554
- const output = formatReport(result, checkOptions.format, process.cwd());
11555
- if (output) writeOutput(output);
11556
- if (!result.passed && checkOptions.failOnError) setExitCode(1);
11557
- else setExitCode(0);
11607
+ await executeCheckCommand(targetPaths, opts, setExitCode);
11558
11608
  });
11559
- program.command("init [path]").description("Scaffold a .luarc.json configuration in the workspace").option("-f, --force", "Overwrite existing .luarc.json configuration").option("--annotations <path>", "Path to custom annotations.lua file").action(async (targetPath = ".", opts) => {
11560
- const annotationsPath = await resolveAnnotations({ customPath: opts.annotations });
11609
+ program.command("init [path]").description("Scaffold a .luarc.json configuration in the workspace").option("-f, --force", "Overwrite existing .luarc.json configuration").option("--vendor", "Vendor annotations.lua into .nanos-lint/ inside workspace for portable standalone editor setup").option("--annotations <path>", "Path to custom annotations.lua file (requires --vendor)").action(async (targetPath = ".", opts) => {
11610
+ const shouldVendor = Boolean(opts.vendor);
11611
+ const annotationsPath = shouldVendor ? await resolveAnnotations({ customPath: opts.annotations }) : opts.annotations;
11561
11612
  writeOutput(`[init] Initialized nanos world LuaLS configuration: ${initWorkspace(path.resolve(targetPath), {
11562
11613
  force: opts.force,
11563
- annotationsPath
11614
+ annotationsPath,
11615
+ vendor: shouldVendor
11616
+ })}`);
11617
+ setExitCode(0);
11618
+ });
11619
+ program.command("copy-annotations [destination]").alias("export-annotations").description("Copy cached nanos world annotations.lua definitions into a target file or directory (targets not ending in .lua are treated as directories)").option("-f, --force", "Overwrite existing annotations file").option("--annotations <path>", "Path to custom annotations.lua source file").action(async (destination, opts) => {
11620
+ writeOutput(`[copy-annotations] Copied nanos world annotations to: ${await copyAnnotations(destination, {
11621
+ force: opts.force,
11622
+ annotationsPath: opts.annotations
11564
11623
  })}`);
11565
11624
  setExitCode(0);
11566
11625
  });
@@ -11626,6 +11685,7 @@ Examples:
11626
11685
  $ npx nanos-lint check . --realm server
11627
11686
  $ npx nanos-lint check Shared/ Server/ --realm server
11628
11687
  $ npx nanos-lint init
11688
+ $ npx nanos-lint copy-annotations
11629
11689
  $ npx nanos-lint clean-cache
11630
11690
  `);
11631
11691
  return program;
@@ -11706,6 +11766,6 @@ if (isDirectExecution()) runCLI().then((code) => {
11706
11766
  process.exit(1);
11707
11767
  });
11708
11768
  //#endregion
11709
- export { DOWNLOAD_TIMEOUT_MS as $, getDefaultAnnotationsPath as $t, formatProblemSummary as A, DEFAULT_LUALS_VERSION as At, resolvePackageDependencies as B, PROGRESS_MILESTONES as Bt, getCachedAnnotationsFilePath as C, DEFAULT_LOG_LEVEL as Cn, isBinaryRunnable as Ct, updateLastCheckedDate as D, isValidLogLevel as Dn, parseTarTvSize as Dt, readAnnotationsMetadata as E, Logger as En, isZipArchive as Et, shouldEnableColor as F, sanitizeLuaLSVersion as Ft, computeUnrequestedExclusions as G, formatTransferSummary as Gt, runLuaLSCheck as H, SPINNER_FRAMES as Ht, collectRealmFiles as I, ALLOWED_DOWNLOAD_DOMAINS as It, findProjectRoot as J, restoreTerminal as Jt, filterReportByTargetPaths as K, getProgressMode as Kt, planRealmCheck as L, isAllowedDownloadUrl as Lt, formatSeverityBadge as M, fetchLatestLuaLSVersionFromGitHub as Mt, getColors as N, resolveLatestLuaLSVersion as Nt, formatGitHubAnnotations as O, logger as On, validateArchiveMembers as Ot, pluralize as P, resolveLuaLSVersion as Pt, resolveCheckTargets as Q, buildWorkspaceConfig as Qt, runRealmAwareCheck as R, deriveRealmAnnotationFiles as Rt, getAnnotationsMetadataFilePath as S, fileUriToPath as Sn, inspectZipMembers as St, isAnnotationsValid as T, LOG_LEVEL_PRIORITY as Tn, isGzipArchive as Tt, countCheckedFiles as U, createProgressBar as Ut, resolveLuaLSBinary as V, PROGRESS_THROTTLE_MS as Vt, listCheckedFiles as W, createSpinner as Wt, isFilesystemRoot as X, DEFAULT_REALM_MAPPINGS as Xt, getCanonicalPath as Y, setProgressMode as Yt, matchesTargetPaths as Z, VALID_LUALS_DIAGNOSTIC_CODES as Zt, MAX_ANNOTATIONS_SIZE_BYTES as _, NanosLintError as _n, MAX_DECOMPRESSED_SIZE_BYTES as _t, formatCacheStatusPretty as a, loadUserConfig as an, LUALS_METADATA_FILENAME as at, MIN_ANNOTATIONS_SIZE_BYTES as b, getDirectorySize as bn, escapePowerShellSingleQuote as bt, DOCGEN_BRANCH as c, resolveRealmMappings as cn, getBaseLuaLSCacheDir as ct, RAW_ANNOTATIONS_URL as d, stripTrailingSlashes as dn, getLuaLSMetadataPath as dt, getDefaultTemplatePath as en, MAX_ARCHIVE_SIZE_BYTES as et, downloadAndCacheAnnotations as f, writeTempConfig as fn, listCachedLuaLSVersionDirs as ft, ANNOTATIONS_FILENAME as g, LuaLSError as gn, MAX_ARCHIVE_MEMBER_COUNT as gt, getRawAnnotationsUrl as h, ConfigError as hn, writeLuaLSMetadata as ht, runCLI as i, loadConfigFile as in, trackDownloadProgress as it, formatReport as j, FALLBACK_LUALS_VERSION as jt, formatPretty as k, parseLogLevel as kn, getPlatformInfo as kt, DOCGEN_REPO as l, resolveWorkspaceConfig as ln, getCacheDir as lt, fetchRawAnnotationsContent as m, CacheError as mn, readLuaLSMetadata as mt, createProgram as n, getPackageRoot as nn, downloadAndExtractLuaLS as nt, getCacheStatus as o, mergeConfigs as on, cleanupOldCachedLuaLSVersions as ot, fetchLatestCommitId as p, AnnotationsError as pn, listCachedLuaLSVersions as pt, findCommonAncestorDirectory as q, isProgressInteractive as qt, isDirectExecution as r, initWorkspace as rn, limitDownloadStream as rt, resolveAnnotations as s, parseJsonc as sn, findExistingLuaLSDir as st, collectIgnorePatterns as t, getDefinitionsDir as tn, computeFileSha256 as tt, GITHUB_COMMITS_API as u, stripJsonComments as un, getIsoWeek as ut, MAX_COMMIT_JSON_SIZE_BYTES as v, cleanCache as vn, MIN_DOWNLOADED_BINARY_SIZE_BYTES as vt, getTodayDateString as w, LOG_LEVELS as wn, isBinaryValid as wt, getAnnotationsCacheDir as x, systemPaths as xn, getTarBinary as xt, METADATA_FILENAME as y, formatBytes as yn, assertValidLuaLSBinary as yt, collectDeps as z, splitAnnotationsByRealm as zt };
11769
+ export { resolveCheckTargets as $, VALID_LUALS_DIAGNOSTIC_CODES as $t, formatPretty as A, logger as An, validateArchiveMembers as At, collectDeps as B, deriveRealmAnnotationFiles as Bt, getAnnotationsMetadataFilePath as C, systemPaths as Cn, inspectZipMembers as Ct, readAnnotationsMetadata as D, LOG_LEVEL_PRIORITY as Dn, isZipArchive as Dt, isAnnotationsValid as E, LOG_LEVELS as En, isGzipArchive as Et, pluralize as F, resolveLatestLuaLSVersion as Ft, listCheckedFiles as G, createProgressBar as Gt, resolveLuaLSBinary as H, PROGRESS_MILESTONES as Ht, shouldEnableColor as I, resolveLuaLSVersion as It, findCommonAncestorDirectory as J, getProgressMode as Jt, computeUnrequestedExclusions as K, createSpinner as Kt, collectRealmFiles as L, sanitizeLuaLSVersion as Lt, formatReport as M, DEFAULT_LUALS_VERSION as Mt, formatSeverityBadge as N, FALLBACK_LUALS_VERSION as Nt, updateLastCheckedDate as O, Logger as On, parseTarTvSize as Ot, getColors as P, fetchLatestLuaLSVersionFromGitHub as Pt, matchesTargetPaths as Q, DEFAULT_REALM_MAPPINGS as Qt, planRealmCheck as R, ALLOWED_DOWNLOAD_DOMAINS as Rt, getAnnotationsCacheDir as S, getDirectorySize as Sn, getTarBinary as St, getTodayDateString as T, DEFAULT_LOG_LEVEL as Tn, isBinaryValid as Tt, runLuaLSCheck as U, PROGRESS_THROTTLE_MS as Ut, resolvePackageDependencies as V, splitAnnotationsByRealm as Vt, countCheckedFiles as W, SPINNER_FRAMES as Wt, getCanonicalPath as X, restoreTerminal as Xt, findProjectRoot as Y, isProgressInteractive as Yt, isFilesystemRoot as Z, setProgressMode as Zt, ANNOTATIONS_FILENAME as _, ConfigError as _n, MAX_ARCHIVE_MEMBER_COUNT as _t, formatCacheStatusPretty as a, initWorkspace as an, trackDownloadProgress as at, METADATA_FILENAME as b, cleanCache as bn, assertValidLuaLSBinary as bt, resolveAnnotations as c, mergeConfigs as cn, findExistingLuaLSDir as ct, GITHUB_COMMITS_API as d, resolveWorkspaceConfig as dn, getIsoWeek as dt, buildWorkspaceConfig as en, DOWNLOAD_TIMEOUT_MS as et, RAW_ANNOTATIONS_URL as f, stripJsonComments as fn, getLuaLSMetadataPath as ft, getRawAnnotationsUrl as g, CacheError as gn, writeLuaLSMetadata as gt, fetchRawAnnotationsContent as h, AnnotationsError as hn, readLuaLSMetadata as ht, runCLI as i, getPackageRoot as in, limitDownloadStream as it, formatProblemSummary as j, parseLogLevel as jn, getPlatformInfo as jt, formatGitHubAnnotations as k, isValidLogLevel as kn, rethrowLuaLSPermissionError as kt, DOCGEN_BRANCH as l, parseJsonc as ln, getBaseLuaLSCacheDir as lt, fetchLatestCommitId as m, writeTempConfig as mn, listCachedLuaLSVersions as mt, createProgram as n, getDefaultTemplatePath as nn, computeFileSha256 as nt, getCacheStatus as o, loadConfigFile as on, LUALS_METADATA_FILENAME as ot, downloadAndCacheAnnotations as p, stripTrailingSlashes as pn, listCachedLuaLSVersionDirs as pt, filterReportByTargetPaths as q, formatTransferSummary as qt, isDirectExecution as r, getDefinitionsDir as rn, downloadAndExtractLuaLS as rt, copyAnnotations as s, loadUserConfig as sn, cleanupOldCachedLuaLSVersions as st, collectIgnorePatterns as t, getDefaultAnnotationsPath as tn, MAX_ARCHIVE_SIZE_BYTES as tt, DOCGEN_REPO as u, resolveRealmMappings as un, getCacheDir as ut, MAX_ANNOTATIONS_SIZE_BYTES as v, LuaLSError as vn, MAX_DECOMPRESSED_SIZE_BYTES as vt, getCachedAnnotationsFilePath as w, fileUriToPath as wn, isBinaryRunnable as wt, MIN_ANNOTATIONS_SIZE_BYTES as x, formatBytes as xn, escapePowerShellSingleQuote as xt, MAX_COMMIT_JSON_SIZE_BYTES as y, NanosLintError as yn, MIN_DOWNLOADED_BINARY_SIZE_BYTES as yt, runRealmAwareCheck as z, isAllowedDownloadUrl as zt };
11710
11770
 
11711
- //# sourceMappingURL=cli-CeNxOiST.js.map
11771
+ //# sourceMappingURL=cli-CsIAHyRZ.js.map