@cmssy/eslint-plugin 12.13.0 → 12.14.1

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 CHANGED
@@ -96,6 +96,11 @@ var editRouteProviderParity = {
96
96
  };
97
97
  var SERVER_MODULES = [/^@cmssy\/next\/server$/];
98
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
+ );
99
104
  var EXTENSIONS = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs"];
100
105
  var aliasCache = /* @__PURE__ */ new Map();
101
106
  function stripJsonComments(text) {
@@ -223,7 +228,25 @@ function valueImports(code) {
223
228
  ...withoutTypes.matchAll(/(?:from|import)\s*["']([^"']+)["']/g)
224
229
  ].map(([, specifier]) => specifier ?? "");
225
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
+ }
226
248
  function importsServerConfig(code) {
249
+ if (readsServerEnv(code)) return true;
227
250
  if (SERVER_MODULES.some(
228
251
  (pattern) => valueImports(code).some((s) => pattern.test(s))
229
252
  )) {
@@ -290,7 +313,7 @@ var noServerConfigInClient = {
290
313
  meta: {
291
314
  type: "problem",
292
315
  docs: {
293
- 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"
294
317
  },
295
318
  schema: [
296
319
  {
@@ -305,8 +328,9 @@ var noServerConfigInClient = {
305
328
  }
306
329
  ],
307
330
  messages: {
308
- reachesConfig: 'This client component pulls the cmssy config into the browser bundle, where server env does not exist - the page will fail at runtime with "missing required configuration".\n {{chain}}\nImport the type instead (types are erased), or move the value into a module that does not touch the config.',
309
- 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."
310
334
  }
311
335
  },
312
336
  create(context) {
@@ -318,6 +342,11 @@ var noServerConfigInClient = {
318
342
  if (!client && !entry) return {};
319
343
  const messageId = client ? "reachesConfig" : "reachesConfigFromEntry";
320
344
  return {
345
+ MemberExpression(node) {
346
+ const name = serverEnvName(node);
347
+ if (!name) return;
348
+ context.report({ node, messageId: "readsServerEnv", data: { name } });
349
+ },
321
350
  ImportDeclaration(node) {
322
351
  const typed = node;
323
352
  const onlyTypes = typed.importKind === "type" || node.specifiers.length > 0 && node.specifiers.every(
package/dist/index.js CHANGED
@@ -88,6 +88,11 @@ var editRouteProviderParity = {
88
88
  };
89
89
  var SERVER_MODULES = [/^@cmssy\/next\/server$/];
90
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
+ );
91
96
  var EXTENSIONS = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs"];
92
97
  var aliasCache = /* @__PURE__ */ new Map();
93
98
  function stripJsonComments(text) {
@@ -215,7 +220,25 @@ function valueImports(code) {
215
220
  ...withoutTypes.matchAll(/(?:from|import)\s*["']([^"']+)["']/g)
216
221
  ].map(([, specifier]) => specifier ?? "");
217
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
+ }
218
240
  function importsServerConfig(code) {
241
+ if (readsServerEnv(code)) return true;
219
242
  if (SERVER_MODULES.some(
220
243
  (pattern) => valueImports(code).some((s) => pattern.test(s))
221
244
  )) {
@@ -282,7 +305,7 @@ var noServerConfigInClient = {
282
305
  meta: {
283
306
  type: "problem",
284
307
  docs: {
285
- 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"
286
309
  },
287
310
  schema: [
288
311
  {
@@ -297,8 +320,9 @@ var noServerConfigInClient = {
297
320
  }
298
321
  ],
299
322
  messages: {
300
- reachesConfig: 'This client component pulls the cmssy config into the browser bundle, where server env does not exist - the page will fail at runtime with "missing required configuration".\n {{chain}}\nImport the type instead (types are erased), or move the value into a module that does not touch the config.',
301
- 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."
302
326
  }
303
327
  },
304
328
  create(context) {
@@ -310,6 +334,11 @@ var noServerConfigInClient = {
310
334
  if (!client && !entry) return {};
311
335
  const messageId = client ? "reachesConfig" : "reachesConfigFromEntry";
312
336
  return {
337
+ MemberExpression(node) {
338
+ const name = serverEnvName(node);
339
+ if (!name) return;
340
+ context.report({ node, messageId: "readsServerEnv", data: { name } });
341
+ },
313
342
  ImportDeclaration(node) {
314
343
  const typed = node;
315
344
  const onlyTypes = typed.importKind === "type" || node.specifiers.length > 0 && node.specifiers.every(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmssy/eslint-plugin",
3
- "version": "12.13.0",
3
+ "version": "12.14.1",
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",