@webjsdev/server 0.8.26 → 0.8.27
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/package.json +1 -1
- package/src/check.js +74 -0
package/package.json
CHANGED
package/src/check.js
CHANGED
|
@@ -93,6 +93,11 @@ export const RULES = [
|
|
|
93
93
|
description:
|
|
94
94
|
'Files that declare the `\'use server\'` directive at the top must also have the `.server.{js,ts,mts,mjs}` extension. The two markers are complementary, not interchangeable: `.server.ts` is the path-level boundary that triggers source protection by the file router; `\'use server\'` is the semantic opt-in that registers exports as RPC-callable from client code. A `\'use server\'` directive without the extension is silently ignored: the file is served to the browser as plain source, exports are NOT registered as RPC, and code the developer expects to run on the server actually runs in the browser. Rename the file to add the `.server.` infix.',
|
|
95
95
|
},
|
|
96
|
+
{
|
|
97
|
+
name: 'use-server-exports-callable',
|
|
98
|
+
description:
|
|
99
|
+
'A `.server.{js,ts}` file that declares the `\'use server\'` directive registers its function exports as RPC-callable server actions, but only its FUNCTION exports: `buildActionIndex` / the stub generator register an export only when `typeof export === \'function\'`, so a `\'use server\'` file that exports zero functions (or only a non-function `const` / a type / only verb config like `method` / `cache`) registers NOTHING and gives no signal. The developer believes they exposed an action; nothing did, and the failure only surfaces as a 404 / undefined at the first call site. This is the complement of `use-server-needs-extension` (the directive without the extension) and of `one-action-per-configured-file` (more than one action in a configured file); this rule catches the directive-present-but-nothing-callable case. The rule asserts only that the file exports at least one callable, NOT that the action returns a value (a server action may be a void side-effect or throw `redirect()` / `notFound()` and never return). Conservative: a re-export (`export ... from`, `export *`) or an `export const NAME = <identifier-or-call>` (a possible factory-produced function such as `export const get = cache(fetch)`) is treated as a possible callable and NOT flagged, so the rule fires only when every export is provably non-callable. Fix: export an `async function` action, or drop the `\'use server\'` directive if the file is a plain server-only utility.',
|
|
100
|
+
},
|
|
96
101
|
{
|
|
97
102
|
name: 'no-non-erasable-typescript',
|
|
98
103
|
description:
|
|
@@ -733,6 +738,75 @@ export async function checkConventions(appDir) {
|
|
|
733
738
|
}
|
|
734
739
|
}
|
|
735
740
|
|
|
741
|
+
// --- Rule: use-server-exports-callable (#464) ---
|
|
742
|
+
// A `.server.{js,ts}` file with the `'use server'` directive registers only
|
|
743
|
+
// its FUNCTION exports as RPC actions (the registrar checks `typeof === 'function'`).
|
|
744
|
+
// A file that declares the directive but exports no callable registers nothing,
|
|
745
|
+
// silently: the developer thinks they exposed an action, and the only signal is
|
|
746
|
+
// a 404 / undefined at the first call site. Flag it. The complement of
|
|
747
|
+
// use-server-needs-extension (the directive without the extension) and of
|
|
748
|
+
// one-action-per-configured-file (more than one action).
|
|
749
|
+
{
|
|
750
|
+
for (const { rel, content, scan } of files) {
|
|
751
|
+
// Only properly-marked action files (the extension boundary). A `'use
|
|
752
|
+
// server'` file WITHOUT the .server. extension is the use-server-needs-
|
|
753
|
+
// extension rule's job; do not double-flag it here.
|
|
754
|
+
if (!/\.server\.m?[jt]s$/.test(rel)) continue;
|
|
755
|
+
if (!hasUseServerDirective(content)) continue;
|
|
756
|
+
// Count function-shaped EXPORTED callables, the SAME way the action
|
|
757
|
+
// registrar sees them: function declarations + arrow / function-expression
|
|
758
|
+
// consts (with an optional `: Type` annotation, #495). Reserved verb-config
|
|
759
|
+
// names (method/cache/...) are config, never a callable action.
|
|
760
|
+
const names = new Set();
|
|
761
|
+
let m;
|
|
762
|
+
const reFn = /\bexport\s+(?:async\s+)?function\s*\*?\s+([A-Za-z_$][\w$]*)/g;
|
|
763
|
+
while ((m = reFn.exec(scan))) names.add(m[1]);
|
|
764
|
+
const reArrow = /\bexport\s+const\s+([A-Za-z_$][\w$]*)\s*(?::(?:[^=]|=>)*?)?=(?!>)\s*(?:async\s+)?(?:function\b|(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>)/g;
|
|
765
|
+
while ((m = reArrow.exec(scan))) names.add(m[1]);
|
|
766
|
+
const callables = [...names].filter((n) => !RESERVED_CONFIG.has(n));
|
|
767
|
+
if (callables.length > 0) continue; // exports at least one callable -> fine
|
|
768
|
+
// A default export is assumed callable (an action commonly default-exports).
|
|
769
|
+
if (/\bexport\s+default\b/.test(scan)) continue;
|
|
770
|
+
// Conservative, avoid a FALSE POSITIVE: the runtime registrar
|
|
771
|
+
// (`actionFunctionNames`) keeps EVERY export whose value is a function at
|
|
772
|
+
// load time, regardless of the export syntax, so any export shape these
|
|
773
|
+
// patterns cannot prove is non-callable must NOT be flagged. Skip when the
|
|
774
|
+
// file has:
|
|
775
|
+
// - a named-export clause `export { a, b as c }` (with or WITHOUT `from`):
|
|
776
|
+
// it can surface a local function (`function getX(){}; export { getX }`)
|
|
777
|
+
// or a re-exported / imported function, neither matched above;
|
|
778
|
+
// - a star re-export `export * from ...`;
|
|
779
|
+
// - a destructuring export `export const { x } = obj` / `export const [x] = arr`,
|
|
780
|
+
// which may bind a function;
|
|
781
|
+
// - an `export const NAME = <identifier-or-call>`, a factory-produced
|
|
782
|
+
// function (`export const get = cache(fetch)`).
|
|
783
|
+
// A sole `export { aConst }` of a non-function is then a tolerated FALSE
|
|
784
|
+
// NEGATIVE, which is the right bias for a non-overridable correctness rule.
|
|
785
|
+
if (/\bexport\s*\{/.test(scan)) continue;
|
|
786
|
+
if (/\bexport\s*\*/.test(scan)) continue;
|
|
787
|
+
if (/\bexport\s+(?:const|let|var)\s*[{[]/.test(scan)) continue;
|
|
788
|
+
// A factory / identifier-valued const could be a callable ACTION, so skip,
|
|
789
|
+
// BUT only when its name is NOT a reserved config key: the registrar
|
|
790
|
+
// excludes reserved names from actions (`actionFunctionNames`), so a file
|
|
791
|
+
// whose only ambiguous const is `validate` / `tags` / ... (config produced
|
|
792
|
+
// by an arrow or a factory) still has zero actions and must flag. Skip only
|
|
793
|
+
// for a NON-reserved ambiguous const (a real possible action).
|
|
794
|
+
const reFactory = /\bexport\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::(?:[^=]|=>)*?)?=\s*[A-Za-z_$(]/g;
|
|
795
|
+
let ambiguousAction = false;
|
|
796
|
+
while ((m = reFactory.exec(scan))) { if (!RESERVED_CONFIG.has(m[1])) { ambiguousAction = true; break; } }
|
|
797
|
+
if (ambiguousAction) continue;
|
|
798
|
+
// Every export is provably non-callable (a literal const, a type) or there
|
|
799
|
+
// are none: the directive exposes nothing.
|
|
800
|
+
violations.push({
|
|
801
|
+
rule: 'use-server-exports-callable',
|
|
802
|
+
file: rel,
|
|
803
|
+
message:
|
|
804
|
+
"File declares `'use server'` but exports no callable action. The `'use server'` directive registers FUNCTION exports as RPC-callable; a file exporting only a non-function (a `const` / a type / only verb config) registers nothing, so a client import resolves to nothing and the call 404s.",
|
|
805
|
+
fix: "Export an `async function` action from this file, or remove the `'use server'` directive if it is a plain server-only utility.",
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
736
810
|
// --- Rule: one-action-per-configured-file (#488) ---
|
|
737
811
|
// A `'use server'` file that declares HTTP-verb config (method/cache/tags/
|
|
738
812
|
// invalidates/validate) must export exactly one callable action; the config
|