nanos-lint 2.8.1 → 3.0.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
@@ -13,6 +13,7 @@ A dedicated, fast linter and type-checker for **[nanos world](https://nanos-worl
13
13
 
14
14
  ## Features
15
15
 
16
+ - **Realm-aware diagnostics**: `Server/`, `Client/` and `Shared/` are checked against their real execution context, so cross-realm API misuse and client/server global leakage fail the build instead of crashing at runtime.
16
17
  - **Accurate nanos world Type Checking**: Bundles verified nanos world API annotations (Lua 5.4.9).
17
18
  - **Zero-Install Local CLI**: Run directly via `npx nanos-lint [path]` without installing anything.
18
19
  - **Native GitHub Action**: Use `vugi99/nanos-lint` directly in CI workflows with inline GitHub PR annotations.
@@ -33,11 +34,13 @@ A dedicated, fast linter and type-checker for **[nanos world](https://nanos-worl
33
34
  ### 1. Run via `npx` (No installation needed)
34
35
 
35
36
  Check the current directory:
37
+
36
38
  ```bash
37
39
  npx nanos-lint
38
40
  ```
39
41
 
40
42
  Check a specific directory or file:
43
+
41
44
  ```bash
42
45
  npx nanos-lint check ./my-package
43
46
  npx nanos-lint check ./my-package/Server/Index.lua
@@ -55,14 +58,17 @@ nanos-lint check .
55
58
  ### 3. Setup VS Code / Neovim IntelliSense
56
59
 
57
60
  To configure autocompletion, type annotations, and diagnostics in your local editor:
61
+
58
62
  ```bash
59
63
  npx nanos-lint init
60
64
  ```
65
+
61
66
  This generates a `.luarc.json` file in your workspace pointing to the nanos world definitions and schemas.
62
67
 
63
68
  ### 4. Pre-warm Cache for Offline / Docker Environments
64
69
 
65
70
  To pre-populate all required assets (both LuaLS binary and nanos world annotations) for air-gapped CI or container builds:
71
+
66
72
  ```bash
67
73
  npx nanos-lint warmup
68
74
  ```
@@ -78,9 +84,9 @@ name: Lint Lua Scripts
78
84
 
79
85
  on:
80
86
  push:
81
- branches: [ main, master ]
87
+ branches: [main, master]
82
88
  pull_request:
83
- branches: [ main, master ]
89
+ branches: [main, master]
84
90
 
85
91
  jobs:
86
92
  lint:
@@ -90,25 +96,26 @@ jobs:
90
96
  uses: actions/checkout@v7
91
97
 
92
98
  - name: Lint nanos world Lua scripts
93
- uses: vugi99/nanos-lint@v2
99
+ uses: vugi99/nanos-lint@v3
94
100
  with:
95
- path: '.'
96
- checklevel: 'Warning'
101
+ path: "."
102
+ checklevel: "Warning"
97
103
  ```
98
104
 
99
105
  ### Action Inputs
100
106
 
101
- | Input | Description | Default |
102
- | :--- | :--- | :--- |
103
- | `path` | Path to workspace directory or Lua file to check | `.` |
104
- | `checklevel` | Minimum severity to report (`Error`, `Warning`, `Information`, `Hint`) | `Warning` |
105
- | `config` | Path to a custom `.luarc.json` configuration file | `""` |
106
- | `annotations` | Path to a custom `annotations.lua` file | `""` |
107
- | `ignore` | Files or directories to ignore (supports glob patterns, newline or comma separated) | `""` |
108
- | `luals-version` | Version of `lua-language-server` to use | `latest` |
109
- | `fail-on-error` | Fail the workflow step if diagnostics are found | `true` |
110
- | `quiet` | Suppress progress messages | `false` |
111
- | `cache` | Whether to cache the LuaLS binary and annotations across workflow runs | `true` |
107
+ | Input | Description | Default |
108
+ | :-------------- | :---------------------------------------------------------------------------------- | :-------- |
109
+ | `path` | Path to workspace directory or Lua file to check | `.` |
110
+ | `checklevel` | Minimum severity to report (`Error`, `Warning`, `Information`, `Hint`) | `Warning` |
111
+ | `config` | Path to a custom `.luarc.json` configuration file | `""` |
112
+ | `annotations` | Path to a custom `annotations.lua` file | `""` |
113
+ | `ignore` | Files or directories to ignore (supports glob patterns, newline or comma separated) | `""` |
114
+ | `luals-version` | Version of `lua-language-server` to use | `latest` |
115
+ | `fail-on-error` | Fail the workflow step if diagnostics are found | `true` |
116
+ | `log-level` | Logging level (`error`, `warn`, `info`, `debug`, `silent`) | `warn` |
117
+ | `realm` | Execution realm to check (`all`, `client`, `server`, `shared`) | `all` |
118
+ | `cache` | Whether to cache the LuaLS binary and annotations across workflow runs | `true` |
112
119
 
113
120
  When caching is enabled, the cache key rolls over each ISO week, so a freshly downloaded LuaLS binary is actually persisted under the new week's key; in the meantime the previous week's entry is restored from the cache.
114
121
 
@@ -146,20 +153,20 @@ OPTIONS:
146
153
  --github Output in GitHub Actions format (shortcut for --format=github)
147
154
  --luals-version=<ver> Version of LuaLS to use (default: latest, falling back to 3.19.1 when offline)
148
155
  --no-fail Do not exit with code 1 if diagnostics are found
149
- --quiet Suppress progress output only (equivalent to --log-level=error; the report is still printed)
156
+ --realm <realm> Execution realm to check: all, client, server, shared (default: all)
150
157
  -f, --force (init command only) Overwrite existing .luarc.json
151
158
  ```
152
159
 
153
160
  ### Environment Variables
154
161
 
155
- | Variable | Description |
156
- | :--- | :--- |
157
- | `LUALS_BIN` | Explicit path to a pre-installed `lua-language-server` binary; it must be a regular file that reports its version via `--version` (thin wrapper scripts are accepted), otherwise nanos-lint fails with `ERR_LUALS_BIN_INVALID` |
158
- | `NANOS_ANNOTATIONS_PATH`, `NANOS_ANNOTATIONS` | Explicit path to a custom `annotations.lua` file |
159
- | `NANOS_LOG_LEVEL` | Default logging level: `error`, `warn`, `info`, `debug`, `silent` (default: `warn`) |
160
- | `GITHUB_TOKEN` | GitHub personal access token used for authenticated GitHub API requests (avoids unauthenticated rate limits) |
161
- | `NO_COLOR` | Disables ANSI color output when set to any non-empty value |
162
- | `FORCE_COLOR` | Forces ANSI color output even in non-TTY environments |
162
+ | Variable | Description |
163
+ | :-------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
164
+ | `LUALS_BIN` | Explicit path to a pre-installed `lua-language-server` binary; it must be a regular file that reports its version via `--version` (thin wrapper scripts are accepted), otherwise nanos-lint fails with `ERR_LUALS_BIN_INVALID` |
165
+ | `NANOS_ANNOTATIONS_PATH`, `NANOS_ANNOTATIONS` | Explicit path to a custom `annotations.lua` file |
166
+ | `NANOS_LOG_LEVEL` | Default logging level: `error`, `warn`, `info`, `debug`, `silent` (default: `warn`) |
167
+ | `GITHUB_TOKEN` | GitHub personal access token used for authenticated GitHub API requests (avoids unauthenticated rate limits) |
168
+ | `NO_COLOR` | Disables ANSI color output when set to any non-empty value |
169
+ | `FORCE_COLOR` | Forces ANSI color output even in non-TTY environments |
163
170
 
164
171
  Only `NANOS_LOG_LEVEL` controls the logging level; a generic `LOG_LEVEL` environment variable is intentionally not read, because CI images commonly set it.
165
172
 
@@ -175,23 +182,90 @@ If your project already has a `.luarc.json`, `nanos-lint` automatically merges i
175
182
  {
176
183
  "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
177
184
  "diagnostics": {
178
- "disable": [
179
- "lowercase-global"
180
- ],
181
- "globals": [
182
- "MyCustomGlobal"
183
- ]
185
+ "disable": ["lowercase-global"],
186
+ "globals": ["MyCustomGlobal"]
184
187
  }
185
188
  }
186
189
  ```
187
190
 
188
191
  To scaffold a portable `.luarc.json` with vendored type annotations in your project, run:
192
+
189
193
  ```bash
190
194
  npx nanos-lint init
191
195
  # Or overwrite an existing configuration:
192
196
  npx nanos-lint init --force
193
197
  ```
194
198
 
199
+ ### Realm Mapping (`nanos.realms`)
200
+
201
+ 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`:
202
+
203
+ ```json
204
+ {
205
+ "nanos": {
206
+ "realms": {
207
+ "src/server/**": "server",
208
+ "src/client/**": "client",
209
+ "common/**": "shared"
210
+ }
211
+ }
212
+ }
213
+ ```
214
+
215
+ | Realm value | Meaning |
216
+ | :---------------------- | :-------------------------------------------------------------------------------------------------------------- |
217
+ | `"server"` | Checked with the server context: client-only APIs and `Client/**` globals do not exist. |
218
+ | `"client"` | Checked with the client context: server-only APIs and `Server/**` globals do not exist. |
219
+ | `"shared"` / `"global"` | Checked with the complete context, because a shared script may guard side-specific calls behind runtime checks. |
220
+
221
+ - Omitting `nanos.realms` falls back to the conventional `Server/**`, `Client/**`, `Shared/**` layout, and realm passes only run when at least one of those patterns matches a checked Lua file.
222
+ - `"nanos": { "realms": {} }` disables realm checking and restores a single standard pass.
223
+ - When several entries match the same file, the last matching entry wins.
224
+ - Files matched by no entry (a root `main.lua`, for example) are checked with the complete context.
225
+
226
+ The `nanos` key is nanos-lint specific and ignored by LuaLS, so the same `.luarc.json` keeps working in the editor.
227
+
228
+ ### File Counting and Glob Semantics
229
+
230
+ The `N files checked` figure is produced by matching `workspace.ignoreDir` and `files.exclude` with the bundled `glob` engine. The v3.0.0 contract is:
231
+
232
+ - **Trailing slashes and `./` prefixes** are normalized (`vendor/` behaves like `vendor`), and `\` separators are converted to `/`.
233
+ - **Wildcards** follow standard glob semantics (`*`, `**`, `?`, `[0-9]`, `{a,b}`), including inside `workspace.ignoreDir`.
234
+ - **Absolute entries are rejected** (with a warning). LuaLS matches patterns relative to the workspace root, and `glob` anchors absolute patterns inconsistently across platforms, so one `.luarc.json` stays portable by not supporting them.
235
+ - **Negation prefixes (`!pattern`) are rejected** (with a warning). LuaLS uses a gitignore-style matcher without negation support, so honoring `!` here would count files LuaLS never checks.
236
+ - **Over-budget patterns** (excessive wildcards or brace alternatives) are skipped with a warning instead of risking a slow, backtracking-heavy match. Skipping can only over-count, never hide a file.
237
+ - **Symlinks are never followed**: symlinked `.lua` files are not counted and symlinked directories are not traversed.
238
+ - A `workspace.ignoreDir` list in your `.luarc.json` **replaces** the built-in defaults for that file, mirroring LuaLS. The configuration handed to LuaLS is always merged with nanos-lint's defaults, so `.git`, `.vscode`, `.nanos-lint` and `node_modules` stay ignored during a check.
239
+
240
+ ---
241
+
242
+ ## Realm-Aware Checking
243
+
244
+ nanos world loads a package into two isolated Lua VMs: the **server** executes `Server/**` plus `Shared/**`, and the **client** executes `Client/**` plus `Shared/**`. `nanos-lint` mirrors that split so a `Client/` script can no longer call `Server.ChangeMap()` unnoticed, and a global defined in `Client/` no longer satisfies a `Server/` script.
245
+
246
+ `nanos-lint check <path>` detects the conventional layout automatically and runs one LuaLS pass per realm (see [Realm Mapping](#realm-mapping-nanosrealms) to describe a custom layout):
247
+
248
+ | Pass | Files reported | Context |
249
+ | :--------- | :------------------------- | :--------------------------------------------------------------------------------------------------------------------- |
250
+ | **server** | `Server/**` | Server annotations only; `Client/**` is excluded from the analysis. |
251
+ | **client** | `Client/**` | Client annotations only; `Server/**` is excluded from the analysis. |
252
+ | **shared** | `Shared/**` and root files | The complete annotation set, because a shared script may legitimately call a side-specific API behind a runtime check. |
253
+
254
+ Only the annotations that belong to a realm are loaded, so misuse is reported as `undefined-global` (for side-only classes such as `Client`, `Server`, `Database` or `WebUI`) or as `undefined-field` (for realm-specific members of classes that exist on both sides, such as `Character:SetTeam()` on the client).
255
+
256
+ ```bash
257
+ nanos-lint check . # every detected realm
258
+ nanos-lint check . --realm server # server files + shared files
259
+ nanos-lint check . --realm client # client files + shared files
260
+ nanos-lint check . --realm shared # shared files only, with the complete context
261
+ ```
262
+
263
+ Deliberate limits:
264
+
265
+ - **Runtime guards are not analyzed.** `Shared/**` is checked with the complete context, so a guarded `Server.ChangeMap()` inside a `Package.IsUnloading()`-style branch is accepted, and so is an unguarded one. Real nanos world packages gate calls in too many ways for static detection to be reliable.
266
+ - **`Package.Require` keeps resolving across realms**, because `runtime.path` keeps `?.lua`, `Shared/?.lua`, `Client/?.lua` and `Server/?.lua` in every pass. A shared file may require a realm-specific module behind a guard.
267
+ - **Realm checking only activates when a configured pattern matches at least one checked Lua file**, so non-package repositories keep running a single standard pass. `"nanos": { "realms": {} }` disables it explicitly.
268
+
195
269
  ---
196
270
 
197
271
  ## Pre-packaged Release Distributions (Offline / CD)
@@ -218,17 +292,20 @@ cd nanos-lint
218
292
  # Install dependencies
219
293
  npm install
220
294
 
221
- # Run ESLint
222
- npm run lint
223
-
224
- # Run type check
225
- npm run typecheck
226
-
227
- # Build distribution bundle
228
- npm run build
229
-
230
- # Run Vitest test suite with coverage (unit + live LuaLS integration tests)
231
- npm run test:coverage
295
+ # Run all quality gates (recommended before commit/PR)
296
+ npm run gates
297
+ # Or alias
298
+ npm run check:all
299
+
300
+ # Run individual quality gates
301
+ npm run format:check # Prettier code formatting (auto-fix via npm run format:fix)
302
+ npm run lint # ESLint
303
+ npm run lint:deps # Dependency architecture & license checks (dependency-cruiser)
304
+ npm run lint:comments # Comment density limit (<= 15%)
305
+ npm run lint:docstrings # Docstring coverage (>= 90% top-level/exported function coverage per file in src/)
306
+ npm run typecheck # TypeScript typecheck
307
+ npm run build # Build distribution bundle
308
+ npm run test:coverage # Vitest unit + live LuaLS integration tests with coverage
232
309
 
233
310
  # Offline: skip live LuaLS tests, perform no network access, disable coverage thresholds
234
311
  NANOS_LIVE_TESTS=0 npm run test:coverage
@@ -238,10 +315,10 @@ NANOS_LIVE_TESTS=0 npm run test:coverage
238
315
 
239
316
  `tests/global-setup.ts` downloads the LuaLS binary and annotations **once per run** into an isolated temporary cache, so the real `~/.cache/nanos-lint` is never touched; the run fails if anything downloads LuaLS a second time.
240
317
 
241
- | Variable | Description |
242
- | :--- | :--- |
243
- | `NANOS_LIVE_TESTS` | `0` (or `false`/`no`/`off`) skips the live LuaLS/annotations tests, performs no network access, and does not enforce the coverage thresholds. Any other value or unset requires the shared fixtures; if they cannot be resolved the run fails. |
244
- | `NANOS_TEST_CACHE_ROOT` | Reuses the given directory as the isolated test cache instead of a fresh one, avoiding a re-download on repeat runs. |
318
+ | Variable | Description |
319
+ | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
320
+ | `NANOS_LIVE_TESTS` | `0` (or `false`/`no`/`off`) skips the live LuaLS/annotations tests, performs no network access, and does not enforce the coverage thresholds. Any other value or unset requires the shared fixtures; if they cannot be resolved the run fails. |
321
+ | `NANOS_TEST_CACHE_ROOT` | Reuses the given directory as the isolated test cache instead of a fresh one, avoiding a re-download on repeat runs. |
245
322
 
246
323
  ```bash
247
324
  NANOS_LIVE_TESTS=0 npm run test:coverage # fully offline
package/action.yml CHANGED
@@ -1,55 +1,59 @@
1
- name: 'nanos-world Lua Linter'
2
- description: 'Fast, accurate linter and type-checker for nanos world Lua scripts powered by LuaLS'
3
- author: 'nanos-world'
1
+ name: "nanos-world Lua Linter"
2
+ description: "Fast, accurate linter and type-checker for nanos world Lua scripts powered by LuaLS"
3
+ author: "nanos-world"
4
4
  branding:
5
- icon: 'check-circle'
6
- color: 'yellow'
5
+ icon: "check-circle"
6
+ color: "yellow"
7
7
 
8
8
  inputs:
9
9
  path:
10
- description: 'Path to workspace directory or Lua file to check'
10
+ description: "Path to workspace directory or Lua file to check"
11
11
  required: false
12
- default: '.'
12
+ default: "."
13
13
  checklevel:
14
- description: 'Minimum diagnostic severity level to report (Error, Warning, Information, Hint)'
14
+ description: "Minimum diagnostic severity level to report (Error, Warning, Information, Hint)"
15
15
  required: false
16
- default: 'Warning'
16
+ default: "Warning"
17
17
  config:
18
- description: 'Path to a custom .luarc.json configuration file'
18
+ description: "Path to a custom .luarc.json configuration file"
19
19
  required: false
20
- default: ''
20
+ default: ""
21
21
  annotations:
22
- description: 'Path to a custom annotations.lua file'
22
+ description: "Path to a custom annotations.lua file"
23
23
  required: false
24
- default: ''
24
+ default: ""
25
25
  luals-version:
26
26
  description: 'Version of LuaLS to use (or "latest")'
27
27
  required: false
28
- default: 'latest'
28
+ default: "latest"
29
29
  fail-on-error:
30
- description: 'Whether to fail the action step if diagnostic errors/warnings are found'
30
+ description: "Whether to fail the action step if diagnostic errors/warnings are found"
31
31
  required: false
32
- default: 'true'
33
- quiet:
34
- description: 'Suppress progress output'
32
+ default: "true"
33
+ log-level:
34
+ description: "Logging level: error, warn, info, debug, silent"
35
35
  required: false
36
- default: 'false'
36
+ default: "warn"
37
+ realm:
38
+ description: "Execution realm to check: all, client, server, shared"
39
+ required: false
40
+ default: "all"
37
41
  ignore:
38
- description: 'Files or directories to ignore (supports glob patterns, multiple entries can be newline or comma separated)'
42
+ description: "Files or directories to ignore (supports glob patterns, multiple entries can be newline or comma separated)"
39
43
  required: false
40
- default: ''
44
+ default: ""
41
45
  cache:
42
- description: 'Whether to cache the LuaLS binary and annotations across workflow runs'
46
+ description: "Whether to cache the LuaLS binary and annotations across workflow runs"
43
47
  required: false
44
- default: 'true'
48
+ default: "true"
45
49
 
46
50
  runs:
47
- using: 'composite'
51
+ using: "composite"
48
52
  steps:
49
53
  - name: Setup Node.js
50
54
  uses: actions/setup-node@v7
51
55
  with:
52
- node-version: '24'
56
+ node-version: "24"
53
57
 
54
58
  # The ISO week refreshes the cache entry (a static key would never be saved
55
59
  # again); restore-keys fall back to previous weeks. Cheap: one `date` call.
@@ -80,7 +84,8 @@ runs:
80
84
  INPUT_ANNOTATIONS: ${{ inputs.annotations }}
81
85
  INPUT_LUALS_VERSION: ${{ inputs.luals-version }}
82
86
  INPUT_FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
83
- INPUT_QUIET: ${{ inputs.quiet }}
87
+ INPUT_LOG_LEVEL: ${{ inputs.log-level }}
88
+ INPUT_REALM: ${{ inputs.realm }}
84
89
  INPUT_IGNORE: ${{ inputs.ignore }}
85
90
  ACTION_PATH: ${{ github.action_path }}
86
91
  run: |
@@ -107,8 +112,12 @@ runs:
107
112
  ARGS+=("--no-fail")
108
113
  fi
109
114
 
110
- if [ "$INPUT_QUIET" = "true" ]; then
111
- ARGS+=("--quiet")
115
+ if [ -n "$INPUT_LOG_LEVEL" ]; then
116
+ ARGS+=("--log-level=$INPUT_LOG_LEVEL")
117
+ fi
118
+
119
+ if [ -n "$INPUT_REALM" ]; then
120
+ ARGS+=("--realm=$INPUT_REALM")
112
121
  fi
113
122
 
114
123
  # dist/ is gitignored, so `uses: vugi99/nanos-lint@<ref>` consumers always
@@ -118,5 +127,5 @@ runs:
118
127
  else
119
128
  # Exact version: a range could run a different release than the pinned
120
129
  # tag. Bump together with "version" in package.json.
121
- npx --yes nanos-lint@2.8.1 "${ARGS[@]}"
130
+ npx --yes nanos-lint@3.0.0 "${ARGS[@]}"
122
131
  fi
package/bin/nanos-lint.js CHANGED
@@ -13,5 +13,3 @@ runCLI()
13
13
  }
14
14
  process.exit(1);
15
15
  });
16
-
17
-