@clerk/eslint-plugin 0.0.1 → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Clerk, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,263 @@
1
+ <p align="center">
2
+ <a href="https://clerk.com?utm_source=github&utm_medium=clerk_eslint_plugin" target="_blank" rel="noopener noreferrer">
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://images.clerk.com/static/logo-dark-mode-400x400.png">
5
+ <img src="https://images.clerk.com/static/logo-light-mode-400x400.png" height="64">
6
+ </picture>
7
+ </a>
8
+ <br />
9
+ </p>
10
+
11
+ # @clerk/eslint-plugin
12
+
13
+ <div align="center">
14
+
15
+ [![Chat on Discord](https://img.shields.io/discord/856971667393609759.svg?logo=discord)](https://clerk.com/discord)
16
+ [![Clerk documentation](https://img.shields.io/badge/documentation-clerk-green.svg)](https://clerk.com/docs?utm_source=github&utm_medium=clerk_eslint_plugin)
17
+ [![Follow on X](https://img.shields.io/twitter/follow/clerk?style=social)](https://x.com/intent/follow?screen_name=clerk)
18
+
19
+ [Changelog](https://github.com/clerk/javascript/blob/main/packages/eslint-plugin/CHANGELOG.md)
20
+ ·
21
+ [Report a Bug](https://github.com/clerk/javascript/issues/new?assignees=&labels=needs-triage&projects=&template=BUG_REPORT.yml)
22
+ ·
23
+ [Request a Feature](https://feedback.clerk.com/roadmap)
24
+ ·
25
+ [Get help](https://clerk.com/contact/support?utm_source=github&utm_medium=clerk_eslint_plugin)
26
+
27
+ </div>
28
+
29
+ ## Overview
30
+
31
+ > [!NOTE]
32
+ > This lint rule is experimental, but should already be working well.
33
+ >
34
+ > We encourage trying it out and getting in touch with us about your experience.
35
+ >
36
+ > If you try it out, pin your version as we might do breaking changes in minors before v1.
37
+
38
+ ESLint rules to help with Clerk patterns across JavaScript frameworks.
39
+
40
+ Currently includes a Next.js App Router rule to help enforce protecting resources where they are used. Instead of relying on a proxy matcher, you declare which folders are protected and the `require-auth-protection` rule flags any `page`, `layout`, `template`, `default`, `route`, or Server Function under those folders that doesn't guard itself.
41
+
42
+ Client components are not checked by the rule. These can only get privileged access through other protected resources, or via external API calls which are assumed to be separately protected.
43
+
44
+ The rule only detects protected or not, which corresponds to signed in/signed out. You are still responsible for making sure the checks are _correct_ and that the user has the correct permissions to access the resource.
45
+
46
+ > The config **declares intent for tooling — it does not guard anything at runtime.** Protection only happens when each resource calls `await auth.protect()` (or an equivalent check). This rule verifies that it does.
47
+
48
+ ## Installation
49
+
50
+ ```sh
51
+ npm install --save-dev @clerk/eslint-plugin
52
+ ```
53
+
54
+ Requires ESLint `>=9` (flat config), and also works with Oxlint when configured as a `jsPlugin`.
55
+
56
+ ## Usage
57
+
58
+ Register the plugin and declare your protected/public folder globs in `eslint.config.mjs`:
59
+
60
+ ```js
61
+ import clerkNext from '@clerk/eslint-plugin/next';
62
+
63
+ export default [
64
+ {
65
+ plugins: { '@clerk/next': clerkNext },
66
+ rules: {
67
+ '@clerk/next/require-auth-protection': [
68
+ 'error',
69
+ {
70
+ protected: ['app/**'],
71
+ public: ['app/sign-in/**', 'app/sign-up/**'],
72
+ },
73
+ ],
74
+ },
75
+ },
76
+ ];
77
+ ```
78
+
79
+ This rule also works with Oxlint, you can configure the `rules` just like above after adding the plugin as a `jsPlugin` in `.oxlintrc.json`:
80
+
81
+ ```json
82
+ "jsPlugins": [
83
+ {
84
+ "name": "@clerk/next",
85
+ "specifier": "@clerk/eslint-plugin/next"
86
+ }
87
+ ]
88
+ ```
89
+
90
+ Note that the bulk auto-fixer described further down does require `eslint` to be installed.
91
+
92
+ ## Options
93
+
94
+ | Option | Type | Default | Description |
95
+ | ------------------- | --------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
96
+ | `protected` | `string[]` (required) | — | Folder globs whose resources must be guarded. |
97
+ | `public` | `string[]` | `[]` | Folder globs that are exempt. |
98
+ | `resources` | `object` | all true | Resource groups to check. Supports `routeHandlers`, `serverFunctions`, and `serverComponentEntrypoints`, each as an optional boolean. |
99
+ | `mixedScopeLayouts` | `'auto' \| string[]` | `'auto'` | Layouts/templates that intentionally wrap both protected and public descendants. `'auto'` allows them silently; a list requires each such folder to be acknowledged explicitly. |
100
+ | `rootDir` | `string` | _(auto)_ | Directory folder globs are resolved against. Defaults to the nearest ancestor `eslint.config.*`, then ESLint `cwd`. Set to `import.meta.dirname` in your config file when auto-discovery is unavailable. |
101
+
102
+ Globs use a minimal dialect — only `*` (single segment) and `**` (any depth). When a folder matches both `protected` and `public`, the most specific pattern wins, and `protected` wins ties.
103
+
104
+ Use `resources` to disable whole resource groups when a project only wants this rule to enforce protection for some App Router resources:
105
+
106
+ ```js
107
+ {
108
+ protected: ['app/**'],
109
+ resources: {
110
+ routeHandlers: true,
111
+ serverFunctions: true,
112
+ serverComponentEntrypoints: false,
113
+ },
114
+ }
115
+ ```
116
+
117
+ We recommend leaving all as true, but switching some off can be useful during incremental migrations. This configuration also scopes suggestions and bulk-fix tooling: disabled resource groups are not reported by the rule, so they will not receive editor quick-fixes or bulk-applied fixes.
118
+
119
+ ## What counts as protected
120
+
121
+ The rule is satisfied when the relevant function guards itself at the top, either by calling `auth.protect()`:
122
+
123
+ ```ts
124
+ import { auth } from '@clerk/nextjs/server';
125
+
126
+ export default async function Page() {
127
+ await auth.protect();
128
+ // A protect call with a more narrow role or permissions check also works:
129
+ await auth.protect({ role: 'org:admin' });
130
+
131
+ // ...
132
+ }
133
+ ```
134
+
135
+ …or by an early-exit check derived from `auth()` that returns, throws, or redirects signed-out users (via Next.js navigation helpers or Clerk's `redirectToSignIn()` / `redirectToSignUp()` from `auth()`):
136
+
137
+ ```ts
138
+ import { auth } from '@clerk/nextjs/server';
139
+
140
+ export default async function Page() {
141
+ const { isAuthenticated, redirectToSignIn } = await auth();
142
+ if (!isAuthenticated) redirectToSignIn();
143
+ // ...
144
+ }
145
+ ```
146
+
147
+ General protection must happen at the top of the function, but additional narrowing auth checks can happen further down.
148
+
149
+ ## Suggestions
150
+
151
+ For each unprotected resource it flags, the rule offers an editor quick-fix suggestion that inserts `await auth.protect()` at the top of the function (making it `async` and adding the `import { auth } from '@clerk/nextjs/server'` import if needed). Suggestions are opt-in: they appear in your editor's quick-fix menu and are not applied by `eslint --fix`, since adding a protection check changes runtime behavior.
152
+
153
+ ## Bulk auto-fixing
154
+
155
+ > [!WARNING]
156
+ > Applying these fixes changes the runtime behavior of your application — `await auth.protect()` enforces authentication where there potentially was none, or might override custom auth checks that were already in place. Always review the changes and test your application afterwards.
157
+
158
+ Because the protection insertion is a suggestion rather than an autofix, `eslint --fix` deliberately won't apply it. To apply it across many files at once, use the bundled command, which lints with your existing ESLint config (so your protected/public globs are honored) and applies the suggestion to every resource it can safely fix:
159
+
160
+ ```sh
161
+ # Fix everything under the current directory
162
+ npx clerk-next-fix-auth-protection
163
+
164
+ # Preview without writing
165
+ npx clerk-next-fix-auth-protection --dry-run
166
+
167
+ # Scope fixes to a specific pattern, this will still
168
+ # use protected/public from your ESLint config, but
169
+ # can be useful to only fix a subset of your application
170
+ npx clerk-next-fix-auth-protection "app/**"
171
+ ```
172
+
173
+ Resources the rule can't safely fix on its own (imported/wrapped exports, unacknowledged mixed-scope layouts) are listed as needing manual attention, and the command exits non-zero when any remain (or when `--dry-run` would make changes).
174
+
175
+ The same logic is available programmatically:
176
+
177
+ ```ts
178
+ import { fixAuthProtection } from '@clerk/eslint-plugin/next/fix-auth-protection';
179
+
180
+ const { fixed, unresolved } = await fixAuthProtection({
181
+ patterns: ['app/**'],
182
+ dryRun: false,
183
+ });
184
+ ```
185
+
186
+ ## Implementation details
187
+
188
+ This section describes the exact details of how the lint rule works. You normally do not need to read or understand this if you only want to use the rule.
189
+
190
+ Within folders that are configured as protected (and that eslint covers), this rule checks these resources when their resource group is enabled:
191
+
192
+ - No files with `'use client'` at the top - Early bailout for these
193
+ - `serverComponentEntrypoints`: the default export of `page`, `layout`, `template`, and `default` files
194
+ - `routeHandlers`: all http verb exports of `route` files (`GET`, `POST` etc - API endpoints)
195
+ - `serverFunctions`: all exports of files with `'use server'` at the top, and all inline functions that have `'use server'` at the top of the function
196
+
197
+ Notably, it does not:
198
+
199
+ - Check `loading` or `error` files
200
+ - These normally don't use privileged resources, but if yours do, make sure you protect them
201
+ - Check arbitrary Server Components
202
+ - Only the different page entrypoints listed above are checked
203
+ - If you are importing a Server Component doing privileged access into a non-protected page, like an admin panel on an otherwise public page, it should be guarded but the lint rule does not detect this
204
+
205
+ At the top of the relevant async function, after any directives or TypeScript-only declarations, to count as protected the rule accepts these patterns:
206
+
207
+ ```tsx
208
+ // -- Using the default .protect() behavior --
209
+ await auth.protect()
210
+ await (await auth()).protect()
211
+ // Any kind of variable declaration is okay
212
+ const { userId } = await auth.protect();
213
+ // More narrow checks are also fine
214
+ await auth.protect({ role: 'org:admin' })
215
+
216
+ // -- Custom handling --
217
+ const { isAuthenticated, userId, sessionId, redirectToSignIn, redirectToSignUp } = await auth();
218
+ // Any of these checks are okay
219
+ // Note: For useAuth() on the client !userId can also mean
220
+ // "loading", but here it's fine
221
+ if (
222
+ !userId || userId == null || userId === null ||
223
+ !sessionId || sessionId == null || sessionId === null ||
224
+ !isAuthenticated
225
+ ) {
226
+ // It is fine to have arbitrary code here:
227
+ console.log('Unauthenticated');
228
+ // To count as protected, the function needs to have an
229
+ // unconditional "exit" at the top level, these count:
230
+ return;
231
+ throw;
232
+ // Matched by callee identifier name (imports are not traced):
233
+ redirect();
234
+ permanentRedirect();
235
+ notFound();
236
+ unauthorized();
237
+ forbidden();
238
+ redirectToSignIn();
239
+ redirectToSignUp();
240
+ }
241
+ ```
242
+
243
+ ## Support
244
+
245
+ For help, visit our [support page](https://clerk.com/contact/support?utm_source=github&utm_medium=clerk_eslint_plugin).
246
+
247
+ ## Contributing
248
+
249
+ We're open to all community contributions! Please read [our contribution guidelines](https://github.com/clerk/javascript/blob/main/docs/CONTRIBUTING.md) and [code of conduct](https://github.com/clerk/javascript/blob/main/docs/CODE_OF_CONDUCT.md).
250
+
251
+ ## Security
252
+
253
+ `@clerk/eslint-plugin` is a static analysis aid, not a runtime guard. It's provided to _help_ you catch missing protections and it does error on the side of caution, but there are no guarantees it will catch everything, there might be edge cases it does not catch.
254
+
255
+ We aim to fix bugs leading to false negatives promptly, but they are not considered vulnerabilities and will not lead to us posting advisories. You are free to file lint rule bugs via normal [GitHub issues](https://github.com/clerk/javascript/issues/new?assignees=&labels=needs-triage&projects=&template=BUG_REPORT.yml).
256
+
257
+ _For more information and to report what you think is a security issue, please refer to our [security documentation](https://github.com/clerk/javascript/blob/main/docs/SECURITY.md)._
258
+
259
+ ## License
260
+
261
+ This project is licensed under the **MIT license**.
262
+
263
+ See [LICENSE](https://github.com/clerk/javascript/blob/main/packages/eslint-plugin/LICENSE) for more information.
@@ -0,0 +1,34 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+
29
+ Object.defineProperty(exports, '__toESM', {
30
+ enumerable: true,
31
+ get: function () {
32
+ return __toESM;
33
+ }
34
+ });
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ const require_chunk = require('../chunk-C_NdSu1c.js');
3
+ const require_next_fix_auth_protection = require('./fix-auth-protection.js');
4
+ let node_path = require("node:path");
5
+ node_path = require_chunk.__toESM(node_path);
6
+ let node_util = require("node:util");
7
+
8
+ //#region src/next/fix-auth-protection-cli.ts
9
+ function relative(filePath) {
10
+ return node_path.default.relative(process.cwd(), filePath) || filePath;
11
+ }
12
+ const HELP = `clerk-next-fix-auth-protection
13
+
14
+ Apply the @clerk/eslint-plugin \`require-auth-protection\` rule's
15
+ \`await auth.protect()\` suggestions across your project. Uses your existing
16
+ ESLint config (so the protected/public folder globs are honored).
17
+
18
+ Usage
19
+ clerk-next-fix-auth-protection [patterns...] [options]
20
+
21
+ Arguments
22
+ patterns Files, directories, or globs to scan (default: ".")
23
+
24
+ Options
25
+ --dry-run Report what would change without writing any files
26
+ -h, --help Show this help
27
+
28
+ Examples
29
+ clerk-next-fix-auth-protection
30
+ clerk-next-fix-auth-protection "app/**" --dry-run
31
+ `;
32
+ function pluralize(count, noun) {
33
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
34
+ }
35
+ async function main() {
36
+ const { values, positionals } = (0, node_util.parseArgs)({
37
+ allowPositionals: true,
38
+ options: {
39
+ "dry-run": { type: "boolean" },
40
+ help: {
41
+ type: "boolean",
42
+ short: "h"
43
+ }
44
+ }
45
+ });
46
+ if (values.help) {
47
+ console.log(HELP);
48
+ return 0;
49
+ }
50
+ const patterns = positionals.length > 0 ? positionals : ["."];
51
+ const dryRun = Boolean(values["dry-run"]);
52
+ const verb = dryRun ? "Would protect" : "Protected";
53
+ console.log(`Scanning: ${patterns.join(", ")}`);
54
+ const { fixed, unresolved } = await require_next_fix_auth_protection.fixAuthProtection({
55
+ patterns,
56
+ dryRun,
57
+ onConfigResolved(configFile) {
58
+ console.log(`Config: ${configFile ? relative(configFile) : "(resolved by ESLint from the working directory)"}`);
59
+ console.log("");
60
+ console.log("Scanning for unprotected resources…");
61
+ console.log("This lints your whole project, so it can take a while on large codebases.");
62
+ },
63
+ onScanComplete(fileCount) {
64
+ if (fileCount === 0) return;
65
+ console.log("");
66
+ console.log(`Found ${pluralize(fileCount, "file")} to update. ${dryRun ? "Previewing" : "Applying"} fixes…`);
67
+ },
68
+ onFileFixed(file) {
69
+ console.log(` ${verb} ${relative(file.filePath)} (${pluralize(file.protections, "resource")})`);
70
+ }
71
+ });
72
+ if (fixed.length === 0 && unresolved.length === 0) {
73
+ console.log("");
74
+ console.log("No unprotected resources found. Nothing to do.");
75
+ return 0;
76
+ }
77
+ if (unresolved.length > 0) {
78
+ console.log("");
79
+ console.log("Needs manual attention (no safe automatic fix):");
80
+ for (const file of unresolved) for (const issue of file.issues) console.log(` ${relative(file.filePath)}:${issue.line}:${issue.column} ${issue.message}`);
81
+ }
82
+ const totalProtections = fixed.reduce((sum, file) => sum + file.protections, 0);
83
+ console.log("");
84
+ console.log(`${verb} ${pluralize(totalProtections, "resource")} across ${pluralize(fixed.length, "file")}.` + (dryRun ? " Run without --dry-run to apply." : ""));
85
+ if (fixed.length > 0) {
86
+ console.log("");
87
+ console.log("Warning: Adding `await auth.protect()` changes your application’s runtime behavior — it enforces");
88
+ console.log("authentication where there potentially was none, or might override custom auth checks that were");
89
+ console.log("already in place. Always review the changes and test your application.");
90
+ }
91
+ return unresolved.length > 0 || dryRun && fixed.length > 0 ? 1 : 0;
92
+ }
93
+ main().then((code) => {
94
+ process.exitCode = code;
95
+ }).catch((error) => {
96
+ console.error(error instanceof Error ? error.message : error);
97
+ process.exitCode = 1;
98
+ });
99
+
100
+ //#endregion
101
+ //# sourceMappingURL=fix-auth-protection-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix-auth-protection-cli.js","names":["path","fixAuthProtection"],"sources":["../../src/next/fix-auth-protection-cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport path from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport { fixAuthProtection } from './fix-auth-protection';\n\nfunction relative(filePath: string): string {\n return path.relative(process.cwd(), filePath) || filePath;\n}\n\nconst HELP = `clerk-next-fix-auth-protection\n\nApply the @clerk/eslint-plugin \\`require-auth-protection\\` rule's\n\\`await auth.protect()\\` suggestions across your project. Uses your existing\nESLint config (so the protected/public folder globs are honored).\n\nUsage\n clerk-next-fix-auth-protection [patterns...] [options]\n\nArguments\n patterns Files, directories, or globs to scan (default: \".\")\n\nOptions\n --dry-run Report what would change without writing any files\n -h, --help Show this help\n\nExamples\n clerk-next-fix-auth-protection\n clerk-next-fix-auth-protection \"app/**\" --dry-run\n`;\n\nfunction pluralize(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? '' : 's'}`;\n}\n\nasync function main(): Promise<number> {\n const { values, positionals } = parseArgs({\n allowPositionals: true,\n options: {\n 'dry-run': { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n\n if (values.help) {\n console.log(HELP);\n return 0;\n }\n\n const patterns = positionals.length > 0 ? positionals : ['.'];\n const dryRun = Boolean(values['dry-run']);\n const verb = dryRun ? 'Would protect' : 'Protected';\n\n console.log(`Scanning: ${patterns.join(', ')}`);\n\n const { fixed, unresolved } = await fixAuthProtection({\n patterns,\n dryRun,\n onConfigResolved(configFile) {\n console.log(`Config: ${configFile ? relative(configFile) : '(resolved by ESLint from the working directory)'}`);\n console.log('');\n console.log('Scanning for unprotected resources…');\n console.log('This lints your whole project, so it can take a while on large codebases.');\n },\n onScanComplete(fileCount) {\n if (fileCount === 0) {\n return;\n }\n console.log('');\n console.log(`Found ${pluralize(fileCount, 'file')} to update. ${dryRun ? 'Previewing' : 'Applying'} fixes…`);\n },\n onFileFixed(file) {\n console.log(` ${verb} ${relative(file.filePath)} (${pluralize(file.protections, 'resource')})`);\n },\n });\n\n if (fixed.length === 0 && unresolved.length === 0) {\n console.log('');\n console.log('No unprotected resources found. Nothing to do.');\n return 0;\n }\n\n if (unresolved.length > 0) {\n console.log('');\n console.log('Needs manual attention (no safe automatic fix):');\n for (const file of unresolved) {\n for (const issue of file.issues) {\n console.log(` ${relative(file.filePath)}:${issue.line}:${issue.column} ${issue.message}`);\n }\n }\n }\n\n const totalProtections = fixed.reduce((sum, file) => sum + file.protections, 0);\n console.log('');\n console.log(\n `${verb} ${pluralize(totalProtections, 'resource')} across ${pluralize(fixed.length, 'file')}.` +\n (dryRun ? ' Run without --dry-run to apply.' : ''),\n );\n\n if (fixed.length > 0) {\n console.log('');\n console.log(\n 'Warning: Adding `await auth.protect()` changes your application\\u2019s runtime behavior \\u2014 it enforces',\n );\n console.log('authentication where there potentially was none, or might override custom auth checks that were');\n console.log('already in place. Always review the changes and test your application.');\n }\n\n // Non-zero when there is still work to do (manual fixes, or pending changes in\n // a dry run) so CI can gate on it.\n return unresolved.length > 0 || (dryRun && fixed.length > 0) ? 1 : 0;\n}\n\nmain()\n .then(code => {\n process.exitCode = code;\n })\n .catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : error);\n process.exitCode = 1;\n });\n"],"mappings":";;;;;;;;AAMA,SAAS,SAAS,UAA0B;CAC1C,OAAOA,kBAAK,SAAS,QAAQ,IAAI,GAAG,QAAQ,KAAK;AACnD;AAEA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;AAqBb,SAAS,UAAU,OAAe,MAAsB;CACtD,OAAO,GAAG,MAAM,GAAG,OAAO,UAAU,IAAI,KAAK;AAC/C;AAEA,eAAe,OAAwB;CACrC,MAAM,EAAE,QAAQ,yCAA0B;EACxC,kBAAkB;EAClB,SAAS;GACP,WAAW,EAAE,MAAM,UAAU;GAC7B,MAAM;IAAE,MAAM;IAAW,OAAO;GAAI;EACtC;CACF,CAAC;CAED,IAAI,OAAO,MAAM;EACf,QAAQ,IAAI,IAAI;EAChB,OAAO;CACT;CAEA,MAAM,WAAW,YAAY,SAAS,IAAI,cAAc,CAAC,GAAG;CAC5D,MAAM,SAAS,QAAQ,OAAO,UAAU;CACxC,MAAM,OAAO,SAAS,kBAAkB;CAExC,QAAQ,IAAI,aAAa,SAAS,KAAK,IAAI,GAAG;CAE9C,MAAM,EAAE,OAAO,eAAe,MAAMC,mDAAkB;EACpD;EACA;EACA,iBAAiB,YAAY;GAC3B,QAAQ,IAAI,aAAa,aAAa,SAAS,UAAU,IAAI,mDAAmD;GAChH,QAAQ,IAAI,EAAE;GACd,QAAQ,IAAI,qCAAqC;GACjD,QAAQ,IAAI,2EAA2E;EACzF;EACA,eAAe,WAAW;GACxB,IAAI,cAAc,GAChB;GAEF,QAAQ,IAAI,EAAE;GACd,QAAQ,IAAI,SAAS,UAAU,WAAW,MAAM,EAAE,cAAc,SAAS,eAAe,WAAW,QAAQ;EAC7G;EACA,YAAY,MAAM;GAChB,QAAQ,IAAI,KAAK,KAAK,GAAG,SAAS,KAAK,QAAQ,EAAE,IAAI,UAAU,KAAK,aAAa,UAAU,EAAE,EAAE;EACjG;CACF,CAAC;CAED,IAAI,MAAM,WAAW,KAAK,WAAW,WAAW,GAAG;EACjD,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,gDAAgD;EAC5D,OAAO;CACT;CAEA,IAAI,WAAW,SAAS,GAAG;EACzB,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,iDAAiD;EAC7D,KAAK,MAAM,QAAQ,YACjB,KAAK,MAAM,SAAS,KAAK,QACvB,QAAQ,IAAI,KAAK,SAAS,KAAK,QAAQ,EAAE,GAAG,MAAM,KAAK,GAAG,MAAM,OAAO,GAAG,MAAM,SAAS;CAG/F;CAEA,MAAM,mBAAmB,MAAM,QAAQ,KAAK,SAAS,MAAM,KAAK,aAAa,CAAC;CAC9E,QAAQ,IAAI,EAAE;CACd,QAAQ,IACN,GAAG,KAAK,GAAG,UAAU,kBAAkB,UAAU,EAAE,UAAU,UAAU,MAAM,QAAQ,MAAM,EAAE,MAC1F,SAAS,qCAAqC,GACnD;CAEA,IAAI,MAAM,SAAS,GAAG;EACpB,QAAQ,IAAI,EAAE;EACd,QAAQ,IACN,kGACF;EACA,QAAQ,IAAI,iGAAiG;EAC7G,QAAQ,IAAI,wEAAwE;CACtF;CAIA,OAAO,WAAW,SAAS,KAAM,UAAU,MAAM,SAAS,IAAK,IAAI;AACrE;AAEA,KAAK,CAAC,CACH,MAAK,SAAQ;CACZ,QAAQ,WAAW;AACrB,CAAC,CAAC,CACD,OAAO,UAAmB;CACzB,QAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,KAAK;CAC5D,QAAQ,WAAW;AACrB,CAAC"}
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ import { fixAuthProtection } from "./fix-auth-protection.mjs";
3
+ import path from "node:path";
4
+ import { parseArgs } from "node:util";
5
+
6
+ //#region src/next/fix-auth-protection-cli.ts
7
+ function relative(filePath) {
8
+ return path.relative(process.cwd(), filePath) || filePath;
9
+ }
10
+ const HELP = `clerk-next-fix-auth-protection
11
+
12
+ Apply the @clerk/eslint-plugin \`require-auth-protection\` rule's
13
+ \`await auth.protect()\` suggestions across your project. Uses your existing
14
+ ESLint config (so the protected/public folder globs are honored).
15
+
16
+ Usage
17
+ clerk-next-fix-auth-protection [patterns...] [options]
18
+
19
+ Arguments
20
+ patterns Files, directories, or globs to scan (default: ".")
21
+
22
+ Options
23
+ --dry-run Report what would change without writing any files
24
+ -h, --help Show this help
25
+
26
+ Examples
27
+ clerk-next-fix-auth-protection
28
+ clerk-next-fix-auth-protection "app/**" --dry-run
29
+ `;
30
+ function pluralize(count, noun) {
31
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
32
+ }
33
+ async function main() {
34
+ const { values, positionals } = parseArgs({
35
+ allowPositionals: true,
36
+ options: {
37
+ "dry-run": { type: "boolean" },
38
+ help: {
39
+ type: "boolean",
40
+ short: "h"
41
+ }
42
+ }
43
+ });
44
+ if (values.help) {
45
+ console.log(HELP);
46
+ return 0;
47
+ }
48
+ const patterns = positionals.length > 0 ? positionals : ["."];
49
+ const dryRun = Boolean(values["dry-run"]);
50
+ const verb = dryRun ? "Would protect" : "Protected";
51
+ console.log(`Scanning: ${patterns.join(", ")}`);
52
+ const { fixed, unresolved } = await fixAuthProtection({
53
+ patterns,
54
+ dryRun,
55
+ onConfigResolved(configFile) {
56
+ console.log(`Config: ${configFile ? relative(configFile) : "(resolved by ESLint from the working directory)"}`);
57
+ console.log("");
58
+ console.log("Scanning for unprotected resources…");
59
+ console.log("This lints your whole project, so it can take a while on large codebases.");
60
+ },
61
+ onScanComplete(fileCount) {
62
+ if (fileCount === 0) return;
63
+ console.log("");
64
+ console.log(`Found ${pluralize(fileCount, "file")} to update. ${dryRun ? "Previewing" : "Applying"} fixes…`);
65
+ },
66
+ onFileFixed(file) {
67
+ console.log(` ${verb} ${relative(file.filePath)} (${pluralize(file.protections, "resource")})`);
68
+ }
69
+ });
70
+ if (fixed.length === 0 && unresolved.length === 0) {
71
+ console.log("");
72
+ console.log("No unprotected resources found. Nothing to do.");
73
+ return 0;
74
+ }
75
+ if (unresolved.length > 0) {
76
+ console.log("");
77
+ console.log("Needs manual attention (no safe automatic fix):");
78
+ for (const file of unresolved) for (const issue of file.issues) console.log(` ${relative(file.filePath)}:${issue.line}:${issue.column} ${issue.message}`);
79
+ }
80
+ const totalProtections = fixed.reduce((sum, file) => sum + file.protections, 0);
81
+ console.log("");
82
+ console.log(`${verb} ${pluralize(totalProtections, "resource")} across ${pluralize(fixed.length, "file")}.` + (dryRun ? " Run without --dry-run to apply." : ""));
83
+ if (fixed.length > 0) {
84
+ console.log("");
85
+ console.log("Warning: Adding `await auth.protect()` changes your application’s runtime behavior — it enforces");
86
+ console.log("authentication where there potentially was none, or might override custom auth checks that were");
87
+ console.log("already in place. Always review the changes and test your application.");
88
+ }
89
+ return unresolved.length > 0 || dryRun && fixed.length > 0 ? 1 : 0;
90
+ }
91
+ main().then((code) => {
92
+ process.exitCode = code;
93
+ }).catch((error) => {
94
+ console.error(error instanceof Error ? error.message : error);
95
+ process.exitCode = 1;
96
+ });
97
+
98
+ //#endregion
99
+ export { };
100
+ //# sourceMappingURL=fix-auth-protection-cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix-auth-protection-cli.mjs","names":[],"sources":["../../src/next/fix-auth-protection-cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport path from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport { fixAuthProtection } from './fix-auth-protection';\n\nfunction relative(filePath: string): string {\n return path.relative(process.cwd(), filePath) || filePath;\n}\n\nconst HELP = `clerk-next-fix-auth-protection\n\nApply the @clerk/eslint-plugin \\`require-auth-protection\\` rule's\n\\`await auth.protect()\\` suggestions across your project. Uses your existing\nESLint config (so the protected/public folder globs are honored).\n\nUsage\n clerk-next-fix-auth-protection [patterns...] [options]\n\nArguments\n patterns Files, directories, or globs to scan (default: \".\")\n\nOptions\n --dry-run Report what would change without writing any files\n -h, --help Show this help\n\nExamples\n clerk-next-fix-auth-protection\n clerk-next-fix-auth-protection \"app/**\" --dry-run\n`;\n\nfunction pluralize(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? '' : 's'}`;\n}\n\nasync function main(): Promise<number> {\n const { values, positionals } = parseArgs({\n allowPositionals: true,\n options: {\n 'dry-run': { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n\n if (values.help) {\n console.log(HELP);\n return 0;\n }\n\n const patterns = positionals.length > 0 ? positionals : ['.'];\n const dryRun = Boolean(values['dry-run']);\n const verb = dryRun ? 'Would protect' : 'Protected';\n\n console.log(`Scanning: ${patterns.join(', ')}`);\n\n const { fixed, unresolved } = await fixAuthProtection({\n patterns,\n dryRun,\n onConfigResolved(configFile) {\n console.log(`Config: ${configFile ? relative(configFile) : '(resolved by ESLint from the working directory)'}`);\n console.log('');\n console.log('Scanning for unprotected resources…');\n console.log('This lints your whole project, so it can take a while on large codebases.');\n },\n onScanComplete(fileCount) {\n if (fileCount === 0) {\n return;\n }\n console.log('');\n console.log(`Found ${pluralize(fileCount, 'file')} to update. ${dryRun ? 'Previewing' : 'Applying'} fixes…`);\n },\n onFileFixed(file) {\n console.log(` ${verb} ${relative(file.filePath)} (${pluralize(file.protections, 'resource')})`);\n },\n });\n\n if (fixed.length === 0 && unresolved.length === 0) {\n console.log('');\n console.log('No unprotected resources found. Nothing to do.');\n return 0;\n }\n\n if (unresolved.length > 0) {\n console.log('');\n console.log('Needs manual attention (no safe automatic fix):');\n for (const file of unresolved) {\n for (const issue of file.issues) {\n console.log(` ${relative(file.filePath)}:${issue.line}:${issue.column} ${issue.message}`);\n }\n }\n }\n\n const totalProtections = fixed.reduce((sum, file) => sum + file.protections, 0);\n console.log('');\n console.log(\n `${verb} ${pluralize(totalProtections, 'resource')} across ${pluralize(fixed.length, 'file')}.` +\n (dryRun ? ' Run without --dry-run to apply.' : ''),\n );\n\n if (fixed.length > 0) {\n console.log('');\n console.log(\n 'Warning: Adding `await auth.protect()` changes your application\\u2019s runtime behavior \\u2014 it enforces',\n );\n console.log('authentication where there potentially was none, or might override custom auth checks that were');\n console.log('already in place. Always review the changes and test your application.');\n }\n\n // Non-zero when there is still work to do (manual fixes, or pending changes in\n // a dry run) so CI can gate on it.\n return unresolved.length > 0 || (dryRun && fixed.length > 0) ? 1 : 0;\n}\n\nmain()\n .then(code => {\n process.exitCode = code;\n })\n .catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : error);\n process.exitCode = 1;\n });\n"],"mappings":";;;;;;AAMA,SAAS,SAAS,UAA0B;CAC1C,OAAO,KAAK,SAAS,QAAQ,IAAI,GAAG,QAAQ,KAAK;AACnD;AAEA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;AAqBb,SAAS,UAAU,OAAe,MAAsB;CACtD,OAAO,GAAG,MAAM,GAAG,OAAO,UAAU,IAAI,KAAK;AAC/C;AAEA,eAAe,OAAwB;CACrC,MAAM,EAAE,QAAQ,gBAAgB,UAAU;EACxC,kBAAkB;EAClB,SAAS;GACP,WAAW,EAAE,MAAM,UAAU;GAC7B,MAAM;IAAE,MAAM;IAAW,OAAO;GAAI;EACtC;CACF,CAAC;CAED,IAAI,OAAO,MAAM;EACf,QAAQ,IAAI,IAAI;EAChB,OAAO;CACT;CAEA,MAAM,WAAW,YAAY,SAAS,IAAI,cAAc,CAAC,GAAG;CAC5D,MAAM,SAAS,QAAQ,OAAO,UAAU;CACxC,MAAM,OAAO,SAAS,kBAAkB;CAExC,QAAQ,IAAI,aAAa,SAAS,KAAK,IAAI,GAAG;CAE9C,MAAM,EAAE,OAAO,eAAe,MAAM,kBAAkB;EACpD;EACA;EACA,iBAAiB,YAAY;GAC3B,QAAQ,IAAI,aAAa,aAAa,SAAS,UAAU,IAAI,mDAAmD;GAChH,QAAQ,IAAI,EAAE;GACd,QAAQ,IAAI,qCAAqC;GACjD,QAAQ,IAAI,2EAA2E;EACzF;EACA,eAAe,WAAW;GACxB,IAAI,cAAc,GAChB;GAEF,QAAQ,IAAI,EAAE;GACd,QAAQ,IAAI,SAAS,UAAU,WAAW,MAAM,EAAE,cAAc,SAAS,eAAe,WAAW,QAAQ;EAC7G;EACA,YAAY,MAAM;GAChB,QAAQ,IAAI,KAAK,KAAK,GAAG,SAAS,KAAK,QAAQ,EAAE,IAAI,UAAU,KAAK,aAAa,UAAU,EAAE,EAAE;EACjG;CACF,CAAC;CAED,IAAI,MAAM,WAAW,KAAK,WAAW,WAAW,GAAG;EACjD,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,gDAAgD;EAC5D,OAAO;CACT;CAEA,IAAI,WAAW,SAAS,GAAG;EACzB,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,iDAAiD;EAC7D,KAAK,MAAM,QAAQ,YACjB,KAAK,MAAM,SAAS,KAAK,QACvB,QAAQ,IAAI,KAAK,SAAS,KAAK,QAAQ,EAAE,GAAG,MAAM,KAAK,GAAG,MAAM,OAAO,GAAG,MAAM,SAAS;CAG/F;CAEA,MAAM,mBAAmB,MAAM,QAAQ,KAAK,SAAS,MAAM,KAAK,aAAa,CAAC;CAC9E,QAAQ,IAAI,EAAE;CACd,QAAQ,IACN,GAAG,KAAK,GAAG,UAAU,kBAAkB,UAAU,EAAE,UAAU,UAAU,MAAM,QAAQ,MAAM,EAAE,MAC1F,SAAS,qCAAqC,GACnD;CAEA,IAAI,MAAM,SAAS,GAAG;EACpB,QAAQ,IAAI,EAAE;EACd,QAAQ,IACN,kGACF;EACA,QAAQ,IAAI,iGAAiG;EAC7G,QAAQ,IAAI,wEAAwE;CACtF;CAIA,OAAO,WAAW,SAAS,KAAM,UAAU,MAAM,SAAS,IAAK,IAAI;AACrE;AAEA,KAAK,CAAC,CACH,MAAK,SAAQ;CACZ,QAAQ,WAAW;AACrB,CAAC,CAAC,CACD,OAAO,UAAmB;CACzB,QAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,KAAK;CAC5D,QAAQ,WAAW;AACrB,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { ESLint } from "eslint";
2
+
3
+ //#region src/next/fix-auth-protection.d.ts
4
+ interface FixAuthProtectionOptions {
5
+ /** File, directory, or glob patterns to scan. Defaults to `['.']`. */
6
+ patterns?: string[];
7
+ /** Working directory ESLint resolves config and files against. Defaults to `process.cwd()`. */
8
+ cwd?: string;
9
+ /** Compute the changes without writing them to disk. */
10
+ dryRun?: boolean;
11
+ /**
12
+ * Advanced/escape hatch: a pre-configured ESLint instance to lint with. When
13
+ * omitted, a default `new ESLint({ cwd })` is used, which discovers the
14
+ * consumer's flat config. Mainly useful for tests.
15
+ */
16
+ eslint?: ESLint;
17
+ /**
18
+ * Called before scanning with the path to the ESLint config file that will be
19
+ * used (or `undefined` when none is found / an instance was injected).
20
+ */
21
+ onConfigResolved?: (configFilePath: string | undefined) => void;
22
+ /**
23
+ * Called once linting finishes and per-file fixing begins, with the number of
24
+ * files that have flagged resources. Useful for reporting progress, since the
25
+ * initial lint can be slow on large projects.
26
+ */
27
+ onScanComplete?: (fileCount: number) => void;
28
+ /** Called after each file is fixed (or, in `dryRun`, would be fixed). */
29
+ onFileFixed?: (file: FixedFile) => void;
30
+ }
31
+ interface FixedFile {
32
+ filePath: string;
33
+ /** Number of resources that had `await auth.protect()` applied. */
34
+ protections: number;
35
+ }
36
+ interface UnresolvedIssue {
37
+ line: number;
38
+ column: number;
39
+ message: string;
40
+ }
41
+ interface UnresolvedFile {
42
+ filePath: string;
43
+ issues: UnresolvedIssue[];
44
+ }
45
+ interface FixAuthProtectionResult {
46
+ /** Files that were (or, in `dryRun`, would be) modified. */
47
+ fixed: FixedFile[];
48
+ /** Files with flagged resources that have no safe automatic fix. */
49
+ unresolved: UnresolvedFile[];
50
+ }
51
+ /**
52
+ * Lint the given patterns with the consumer's ESLint config and apply the
53
+ * `require-auth-protection` rule's `await auth.protect()` suggestions to every
54
+ * resource it can safely fix.
55
+ */
56
+ declare function fixAuthProtection(options?: FixAuthProtectionOptions): Promise<FixAuthProtectionResult>;
57
+ //#endregion
58
+ export { FixAuthProtectionOptions, FixAuthProtectionResult, FixedFile, UnresolvedFile, UnresolvedIssue, fixAuthProtection };
59
+ //# sourceMappingURL=fix-auth-protection.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix-auth-protection.d.mts","names":[],"sources":["../../src/next/fix-auth-protection.ts"],"mappings":";;;UAyBiB,wBAAA;EAyBA;EAvBf,QAAA;EAuB8B;EArB9B,GAAA;EAwBwB;EAtBxB,MAAA;EAuBA;AAEW;AAGb;;;EAtBE,MAAA,GAAS,MAAA;EAuBT;;;;EAlBA,gBAAA,IAAoB,cAAA;EAuBL;;;;;EAjBf,cAAA,IAAkB,SAAA;EAmBV;EAjBR,WAAA,IAAe,IAAA,EAAM,SAAS;AAAA;AAAA,UAGf,SAAA;EACf,QAAA;;EAEA,WAAW;AAAA;AAAA,UAGI,eAAA;EACf,IAAA;EACA,MAAA;EACA,OAAA;AAAA;AAAA,UAGe,cAAA;EACf,QAAA;EACA,MAAA,EAAQ,eAAe;AAAA;AAAA,UAGR,uBAAA;EAmGgE;EAjG/E,KAAA,EAAO,SAAA;EAiG+E;EA/FtF,UAAA,EAAY,cAAc;AAAA;;;;AA+FoF;;iBAA1F,iBAAA,CAAkB,OAAA,GAAS,wBAAA,GAAgC,OAAA,CAAQ,uBAAA"}