nanos-lint 2.8.2 → 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.
@@ -95,7 +96,7 @@ jobs:
95
96
  uses: actions/checkout@v7
96
97
 
97
98
  - name: Lint nanos world Lua scripts
98
- uses: vugi99/nanos-lint@v2
99
+ uses: vugi99/nanos-lint@v3
99
100
  with:
100
101
  path: "."
101
102
  checklevel: "Warning"
@@ -112,7 +113,8 @@ jobs:
112
113
  | `ignore` | Files or directories to ignore (supports glob patterns, newline or comma separated) | `""` |
113
114
  | `luals-version` | Version of `lua-language-server` to use | `latest` |
114
115
  | `fail-on-error` | Fail the workflow step if diagnostics are found | `true` |
115
- | `quiet` | Suppress progress messages | `false` |
116
+ | `log-level` | Logging level (`error`, `warn`, `info`, `debug`, `silent`) | `warn` |
117
+ | `realm` | Execution realm to check (`all`, `client`, `server`, `shared`) | `all` |
116
118
  | `cache` | Whether to cache the LuaLS binary and annotations across workflow runs | `true` |
117
119
 
118
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.
@@ -151,7 +153,7 @@ OPTIONS:
151
153
  --github Output in GitHub Actions format (shortcut for --format=github)
152
154
  --luals-version=<ver> Version of LuaLS to use (default: latest, falling back to 3.19.1 when offline)
153
155
  --no-fail Do not exit with code 1 if diagnostics are found
154
- --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)
155
157
  -f, --force (init command only) Overwrite existing .luarc.json
156
158
  ```
157
159
 
@@ -194,6 +196,76 @@ npx nanos-lint init
194
196
  npx nanos-lint init --force
195
197
  ```
196
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
+
197
269
  ---
198
270
 
199
271
  ## Pre-packaged Release Distributions (Offline / CD)
package/action.yml CHANGED
@@ -30,10 +30,14 @@ inputs:
30
30
  description: "Whether to fail the action step if diagnostic errors/warnings are found"
31
31
  required: false
32
32
  default: "true"
33
- quiet:
34
- description: "Suppress progress output"
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
42
  description: "Files or directories to ignore (supports glob patterns, multiple entries can be newline or comma separated)"
39
43
  required: false
@@ -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.2 "${ARGS[@]}"
130
+ npx --yes nanos-lint@3.0.0 "${ARGS[@]}"
122
131
  fi