@cmssy/eslint-plugin 12.12.0 → 12.14.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/dist/index.cjs +61 -4
- package/dist/index.js +57 -4
- package/package.json +4 -2
package/dist/index.cjs
CHANGED
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var tsParser = require('@typescript-eslint/parser');
|
|
5
6
|
var fs = require('fs');
|
|
6
7
|
var path = require('path');
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
|
|
11
|
+
var tsParser__default = /*#__PURE__*/_interopDefault(tsParser);
|
|
12
|
+
|
|
13
|
+
// src/index.ts
|
|
9
14
|
var LAYOUT = /\/layout\.(tsx|jsx|js|ts)$/;
|
|
10
15
|
var EDIT_SEGMENT = "cmssy-edit";
|
|
11
16
|
var WRAPPERS = /* @__PURE__ */ new Set([
|
|
@@ -91,6 +96,11 @@ var editRouteProviderParity = {
|
|
|
91
96
|
};
|
|
92
97
|
var SERVER_MODULES = [/^@cmssy\/next\/server$/];
|
|
93
98
|
var SERVER_SYMBOLS = /* @__PURE__ */ new Set(["defineCmssyConfig"]);
|
|
99
|
+
var SERVER_ENV_NAME = "CMSSY_[A-Z0-9_]+";
|
|
100
|
+
var SERVER_ENV = new RegExp(`^${SERVER_ENV_NAME}$`);
|
|
101
|
+
var SERVER_ENV_READ = new RegExp(
|
|
102
|
+
`\\bprocess\\s*\\.\\s*env\\s*(?:\\.\\s*${SERVER_ENV_NAME}|\\[\\s*["'\`]${SERVER_ENV_NAME}["'\`]\\s*\\])`
|
|
103
|
+
);
|
|
94
104
|
var EXTENSIONS = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
95
105
|
var aliasCache = /* @__PURE__ */ new Map();
|
|
96
106
|
function stripJsonComments(text) {
|
|
@@ -218,7 +228,25 @@ function valueImports(code) {
|
|
|
218
228
|
...withoutTypes.matchAll(/(?:from|import)\s*["']([^"']+)["']/g)
|
|
219
229
|
].map(([, specifier]) => specifier ?? "");
|
|
220
230
|
}
|
|
231
|
+
function readsServerEnv(code) {
|
|
232
|
+
return SERVER_ENV_READ.test(code);
|
|
233
|
+
}
|
|
234
|
+
function serverEnvName(node) {
|
|
235
|
+
const member = node;
|
|
236
|
+
const env = member.object;
|
|
237
|
+
if (env?.type !== "MemberExpression" || env.computed) return null;
|
|
238
|
+
if (env.object?.type !== "Identifier" || env.object.name !== "process") {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
if (env.property?.type !== "Identifier" || env.property.name !== "env") {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const property = member.property;
|
|
245
|
+
const name = member.computed ? typeof property?.value === "string" ? property.value : null : property?.type === "Identifier" ? property.name ?? null : null;
|
|
246
|
+
return name && SERVER_ENV.test(name) ? name : null;
|
|
247
|
+
}
|
|
221
248
|
function importsServerConfig(code) {
|
|
249
|
+
if (readsServerEnv(code)) return true;
|
|
222
250
|
if (SERVER_MODULES.some(
|
|
223
251
|
(pattern) => valueImports(code).some((s) => pattern.test(s))
|
|
224
252
|
)) {
|
|
@@ -285,7 +313,7 @@ var noServerConfigInClient = {
|
|
|
285
313
|
meta: {
|
|
286
314
|
type: "problem",
|
|
287
315
|
docs: {
|
|
288
|
-
description: "Disallow a client component from importing values that read the cmssy server config"
|
|
316
|
+
description: "Disallow a client component from importing values that read the cmssy server config, or reading a CMSSY_* variable itself"
|
|
289
317
|
},
|
|
290
318
|
schema: [
|
|
291
319
|
{
|
|
@@ -300,8 +328,9 @@ var noServerConfigInClient = {
|
|
|
300
328
|
}
|
|
301
329
|
],
|
|
302
330
|
messages: {
|
|
303
|
-
reachesConfig: 'This client component pulls the cmssy config into the browser bundle, where server env does not exist - the page
|
|
304
|
-
reachesConfigFromEntry: "This module is loaded in the browser, so it pulls the cmssy config there, where server env does not exist.\n {{chain}}\nImport the type instead (types are erased), or load the value with a dynamic import inside the function that needs it."
|
|
331
|
+
reachesConfig: 'This client component pulls the cmssy config into the browser bundle, where server env does not exist - the page fails at runtime with "missing required configuration", or reads whatever the config falls back to, which is usually "".\n {{chain}}\nImport the type instead (types are erased), or move the value into a module that does not touch the config.',
|
|
332
|
+
reachesConfigFromEntry: "This module is loaded in the browser, so it pulls the cmssy config there, where server env does not exist.\n {{chain}}\nImport the type instead (types are erased), or load the value with a dynamic import inside the function that needs it.",
|
|
333
|
+
readsServerEnv: "{{name}} is read here and this module runs in the browser, where it is undefined - the bundler only inlines NEXT_PUBLIC_* names, and a draft secret must never be one.\nRead it in a server component and pass the value down as a prop."
|
|
305
334
|
}
|
|
306
335
|
},
|
|
307
336
|
create(context) {
|
|
@@ -313,6 +342,11 @@ var noServerConfigInClient = {
|
|
|
313
342
|
if (!client && !entry) return {};
|
|
314
343
|
const messageId = client ? "reachesConfig" : "reachesConfigFromEntry";
|
|
315
344
|
return {
|
|
345
|
+
MemberExpression(node) {
|
|
346
|
+
const name = serverEnvName(node);
|
|
347
|
+
if (!name) return;
|
|
348
|
+
context.report({ node, messageId: "readsServerEnv", data: { name } });
|
|
349
|
+
},
|
|
316
350
|
ImportDeclaration(node) {
|
|
317
351
|
const typed = node;
|
|
318
352
|
const onlyTypes = typed.importKind === "type" || node.specifiers.length > 0 && node.specifiers.every(
|
|
@@ -363,6 +397,29 @@ plugin.configs.recommended = [
|
|
|
363
397
|
}
|
|
364
398
|
}
|
|
365
399
|
];
|
|
400
|
+
plugin.configs.standalone = [
|
|
401
|
+
{
|
|
402
|
+
ignores: [
|
|
403
|
+
"**/.next/**",
|
|
404
|
+
"**/.astro/**",
|
|
405
|
+
"**/.react-router/**",
|
|
406
|
+
"**/.vercel/**",
|
|
407
|
+
"**/build/**",
|
|
408
|
+
"**/coverage/**",
|
|
409
|
+
"**/dist/**",
|
|
410
|
+
"**/out/**"
|
|
411
|
+
]
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
files: ["**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"],
|
|
415
|
+
languageOptions: {
|
|
416
|
+
parser: tsParser__default.default,
|
|
417
|
+
sourceType: "module",
|
|
418
|
+
parserOptions: { ecmaFeatures: { jsx: true } }
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
...plugin.configs.recommended
|
|
422
|
+
];
|
|
366
423
|
var index_default = plugin;
|
|
367
424
|
|
|
368
425
|
exports.default = index_default;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import tsParser from '@typescript-eslint/parser';
|
|
1
2
|
import { readFileSync, existsSync } from 'fs';
|
|
2
3
|
import { isAbsolute, resolve, dirname, sep } from 'path';
|
|
3
4
|
|
|
4
|
-
// src/
|
|
5
|
+
// src/index.ts
|
|
5
6
|
var LAYOUT = /\/layout\.(tsx|jsx|js|ts)$/;
|
|
6
7
|
var EDIT_SEGMENT = "cmssy-edit";
|
|
7
8
|
var WRAPPERS = /* @__PURE__ */ new Set([
|
|
@@ -87,6 +88,11 @@ var editRouteProviderParity = {
|
|
|
87
88
|
};
|
|
88
89
|
var SERVER_MODULES = [/^@cmssy\/next\/server$/];
|
|
89
90
|
var SERVER_SYMBOLS = /* @__PURE__ */ new Set(["defineCmssyConfig"]);
|
|
91
|
+
var SERVER_ENV_NAME = "CMSSY_[A-Z0-9_]+";
|
|
92
|
+
var SERVER_ENV = new RegExp(`^${SERVER_ENV_NAME}$`);
|
|
93
|
+
var SERVER_ENV_READ = new RegExp(
|
|
94
|
+
`\\bprocess\\s*\\.\\s*env\\s*(?:\\.\\s*${SERVER_ENV_NAME}|\\[\\s*["'\`]${SERVER_ENV_NAME}["'\`]\\s*\\])`
|
|
95
|
+
);
|
|
90
96
|
var EXTENSIONS = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
91
97
|
var aliasCache = /* @__PURE__ */ new Map();
|
|
92
98
|
function stripJsonComments(text) {
|
|
@@ -214,7 +220,25 @@ function valueImports(code) {
|
|
|
214
220
|
...withoutTypes.matchAll(/(?:from|import)\s*["']([^"']+)["']/g)
|
|
215
221
|
].map(([, specifier]) => specifier ?? "");
|
|
216
222
|
}
|
|
223
|
+
function readsServerEnv(code) {
|
|
224
|
+
return SERVER_ENV_READ.test(code);
|
|
225
|
+
}
|
|
226
|
+
function serverEnvName(node) {
|
|
227
|
+
const member = node;
|
|
228
|
+
const env = member.object;
|
|
229
|
+
if (env?.type !== "MemberExpression" || env.computed) return null;
|
|
230
|
+
if (env.object?.type !== "Identifier" || env.object.name !== "process") {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
if (env.property?.type !== "Identifier" || env.property.name !== "env") {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const property = member.property;
|
|
237
|
+
const name = member.computed ? typeof property?.value === "string" ? property.value : null : property?.type === "Identifier" ? property.name ?? null : null;
|
|
238
|
+
return name && SERVER_ENV.test(name) ? name : null;
|
|
239
|
+
}
|
|
217
240
|
function importsServerConfig(code) {
|
|
241
|
+
if (readsServerEnv(code)) return true;
|
|
218
242
|
if (SERVER_MODULES.some(
|
|
219
243
|
(pattern) => valueImports(code).some((s) => pattern.test(s))
|
|
220
244
|
)) {
|
|
@@ -281,7 +305,7 @@ var noServerConfigInClient = {
|
|
|
281
305
|
meta: {
|
|
282
306
|
type: "problem",
|
|
283
307
|
docs: {
|
|
284
|
-
description: "Disallow a client component from importing values that read the cmssy server config"
|
|
308
|
+
description: "Disallow a client component from importing values that read the cmssy server config, or reading a CMSSY_* variable itself"
|
|
285
309
|
},
|
|
286
310
|
schema: [
|
|
287
311
|
{
|
|
@@ -296,8 +320,9 @@ var noServerConfigInClient = {
|
|
|
296
320
|
}
|
|
297
321
|
],
|
|
298
322
|
messages: {
|
|
299
|
-
reachesConfig: 'This client component pulls the cmssy config into the browser bundle, where server env does not exist - the page
|
|
300
|
-
reachesConfigFromEntry: "This module is loaded in the browser, so it pulls the cmssy config there, where server env does not exist.\n {{chain}}\nImport the type instead (types are erased), or load the value with a dynamic import inside the function that needs it."
|
|
323
|
+
reachesConfig: 'This client component pulls the cmssy config into the browser bundle, where server env does not exist - the page fails at runtime with "missing required configuration", or reads whatever the config falls back to, which is usually "".\n {{chain}}\nImport the type instead (types are erased), or move the value into a module that does not touch the config.',
|
|
324
|
+
reachesConfigFromEntry: "This module is loaded in the browser, so it pulls the cmssy config there, where server env does not exist.\n {{chain}}\nImport the type instead (types are erased), or load the value with a dynamic import inside the function that needs it.",
|
|
325
|
+
readsServerEnv: "{{name}} is read here and this module runs in the browser, where it is undefined - the bundler only inlines NEXT_PUBLIC_* names, and a draft secret must never be one.\nRead it in a server component and pass the value down as a prop."
|
|
301
326
|
}
|
|
302
327
|
},
|
|
303
328
|
create(context) {
|
|
@@ -309,6 +334,11 @@ var noServerConfigInClient = {
|
|
|
309
334
|
if (!client && !entry) return {};
|
|
310
335
|
const messageId = client ? "reachesConfig" : "reachesConfigFromEntry";
|
|
311
336
|
return {
|
|
337
|
+
MemberExpression(node) {
|
|
338
|
+
const name = serverEnvName(node);
|
|
339
|
+
if (!name) return;
|
|
340
|
+
context.report({ node, messageId: "readsServerEnv", data: { name } });
|
|
341
|
+
},
|
|
312
342
|
ImportDeclaration(node) {
|
|
313
343
|
const typed = node;
|
|
314
344
|
const onlyTypes = typed.importKind === "type" || node.specifiers.length > 0 && node.specifiers.every(
|
|
@@ -359,6 +389,29 @@ plugin.configs.recommended = [
|
|
|
359
389
|
}
|
|
360
390
|
}
|
|
361
391
|
];
|
|
392
|
+
plugin.configs.standalone = [
|
|
393
|
+
{
|
|
394
|
+
ignores: [
|
|
395
|
+
"**/.next/**",
|
|
396
|
+
"**/.astro/**",
|
|
397
|
+
"**/.react-router/**",
|
|
398
|
+
"**/.vercel/**",
|
|
399
|
+
"**/build/**",
|
|
400
|
+
"**/coverage/**",
|
|
401
|
+
"**/dist/**",
|
|
402
|
+
"**/out/**"
|
|
403
|
+
]
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
files: ["**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"],
|
|
407
|
+
languageOptions: {
|
|
408
|
+
parser: tsParser,
|
|
409
|
+
sourceType: "module",
|
|
410
|
+
parserOptions: { ecmaFeatures: { jsx: true } }
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
...plugin.configs.recommended
|
|
414
|
+
];
|
|
362
415
|
var index_default = plugin;
|
|
363
416
|
|
|
364
417
|
export { index_default as default, editRouteProviderParity, noServerConfigInClient, rules };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmssy/eslint-plugin",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.14.0",
|
|
4
4
|
"description": "Catches the cmssy wiring mistakes that a build cannot: server config dragged into a client bundle, a provider the editor route never gets.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cmssy",
|
|
@@ -37,9 +37,11 @@
|
|
|
37
37
|
"files": [
|
|
38
38
|
"dist"
|
|
39
39
|
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@typescript-eslint/parser": "^8.0.0"
|
|
42
|
+
},
|
|
40
43
|
"devDependencies": {
|
|
41
44
|
"@types/node": "^20.0.0",
|
|
42
|
-
"@typescript-eslint/parser": "^8.0.0",
|
|
43
45
|
"eslint": "^9.0.0",
|
|
44
46
|
"tsup": "^8.3.0",
|
|
45
47
|
"typescript": "^5.6.0",
|