@cmssy/eslint-plugin 12.7.1 → 12.8.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 CHANGED
@@ -5,7 +5,90 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var fs = require('fs');
6
6
  var path = require('path');
7
7
 
8
- // src/no-server-config-in-client.ts
8
+ // src/edit-route-provider-parity.ts
9
+ var LAYOUT = /\/layout\.(tsx|jsx|js|ts)$/;
10
+ var EDIT_SEGMENT = "cmssy-edit";
11
+ var WRAPPERS = /* @__PURE__ */ new Set([
12
+ "LazyMotion",
13
+ "MotionConfig",
14
+ "AnimatePresence",
15
+ "MotionProvider"
16
+ ]);
17
+ function normalize(path$1) {
18
+ return path$1.split(path.sep).join("/");
19
+ }
20
+ function isWrapper(name) {
21
+ return name.endsWith("Provider") || WRAPPERS.has(name);
22
+ }
23
+ function elementName(node) {
24
+ const opening = node.openingElement;
25
+ const name = opening?.name ?? {};
26
+ return name.type === "JSXIdentifier" ? name.name ?? null : null;
27
+ }
28
+ function editCounterpart(filename) {
29
+ const file = normalize(filename);
30
+ if (!LAYOUT.test(file)) return null;
31
+ const app = file.lastIndexOf("/app/");
32
+ if (app === -1) return null;
33
+ const rest = file.slice(app + "/app/".length);
34
+ if (!rest.includes("/")) return null;
35
+ if (rest.startsWith(`${EDIT_SEGMENT}/`)) return null;
36
+ return `${file.slice(0, app)}/app/${EDIT_SEGMENT}/${rest}`;
37
+ }
38
+ function rendersElement(code, name) {
39
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
40
+ return new RegExp(`<${escaped}[\\s/>]`).test(code);
41
+ }
42
+ function isChildrenSlot(node) {
43
+ const container = node;
44
+ return container.type === "JSXExpressionContainer" && container.expression?.type === "Identifier" && container.expression.name === "children";
45
+ }
46
+ function wrapsChildren(node) {
47
+ const children = node.children ?? [];
48
+ return children.some(
49
+ (child) => isChildrenSlot(child) || wrapsChildren(child)
50
+ );
51
+ }
52
+ var editRouteProviderParity = {
53
+ meta: {
54
+ type: "problem",
55
+ docs: {
56
+ description: "Require a provider that wraps the public route's children to wrap the cmssy edit route's children too"
57
+ },
58
+ schema: [],
59
+ messages: {
60
+ missingProvider: "<{{name}}> wraps the blocks on the public route but not on {{editRoute}}, so the editor renders them without whatever it provides. A missing animation provider is the loudest case - the reveals never attach and the preview stays blank - but any context the blocks read is gone the same way.\nMove it into cmssy/site-providers.tsx and render that from both roots, or repeat <{{name}}> in the edit layout."
61
+ }
62
+ },
63
+ create(context) {
64
+ const filename = context.filename ?? context.getFilename();
65
+ const counterpart = editCounterpart(filename);
66
+ if (!counterpart) return {};
67
+ let editCode;
68
+ try {
69
+ editCode = fs.readFileSync(counterpart, "utf8");
70
+ } catch {
71
+ return {};
72
+ }
73
+ const editRoute = counterpart.slice(counterpart.lastIndexOf("/app/") + 1);
74
+ const reported = /* @__PURE__ */ new Set();
75
+ return {
76
+ JSXElement(node) {
77
+ const name = elementName(node);
78
+ if (!name || reported.has(name)) return;
79
+ if (!isWrapper(name)) return;
80
+ if (!wrapsChildren(node)) return;
81
+ if (rendersElement(editCode, name)) return;
82
+ reported.add(name);
83
+ context.report({
84
+ node,
85
+ messageId: "missingProvider",
86
+ data: { name, editRoute }
87
+ });
88
+ }
89
+ };
90
+ }
91
+ };
9
92
  var SERVER_MODULES = [/^@cmssy\/next\/server$/];
10
93
  var SERVER_SYMBOLS = /* @__PURE__ */ new Set(["defineCmssyConfig"]);
11
94
  var EXTENSIONS = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs"];
@@ -186,13 +269,13 @@ function chainToServerConfig(file, cache2, seen = /* @__PURE__ */ new Set()) {
186
269
  cache2.set(file, null);
187
270
  return null;
188
271
  }
189
- function normalize(path$1) {
272
+ function normalize2(path$1) {
190
273
  return path$1.split(path.sep).join("/");
191
274
  }
192
275
  function isClientEntry(filename, entries) {
193
- const file = normalize(filename);
276
+ const file = normalize2(filename);
194
277
  return entries.some((entry) => {
195
- const wanted = normalize(entry);
278
+ const wanted = normalize2(entry);
196
279
  if (path.isAbsolute(wanted)) return file === wanted;
197
280
  return file === wanted || file.endsWith(`/${wanted}`);
198
281
  });
@@ -263,6 +346,7 @@ var noServerConfigInClient = {
263
346
 
264
347
  // src/index.ts
265
348
  var rules = {
349
+ "edit-route-provider-parity": editRouteProviderParity,
266
350
  "no-server-config-in-client": noServerConfigInClient
267
351
  };
268
352
  var plugin = {
@@ -273,11 +357,15 @@ var plugin = {
273
357
  plugin.configs.recommended = [
274
358
  {
275
359
  plugins: { cmssy: plugin },
276
- rules: { "cmssy/no-server-config-in-client": "error" }
360
+ rules: {
361
+ "cmssy/edit-route-provider-parity": "error",
362
+ "cmssy/no-server-config-in-client": "error"
363
+ }
277
364
  }
278
365
  ];
279
366
  var index_default = plugin;
280
367
 
281
368
  exports.default = index_default;
369
+ exports.editRouteProviderParity = editRouteProviderParity;
282
370
  exports.noServerConfigInClient = noServerConfigInClient;
283
371
  exports.rules = rules;
package/dist/index.d.cts CHANGED
@@ -1,9 +1,12 @@
1
1
  import * as eslint from 'eslint';
2
2
  import { Rule } from 'eslint';
3
3
 
4
+ declare const editRouteProviderParity: Rule.RuleModule;
5
+
4
6
  declare const noServerConfigInClient: Rule.RuleModule;
5
7
 
6
8
  declare const rules: {
9
+ "edit-route-provider-parity": eslint.Rule.RuleModule;
7
10
  "no-server-config-in-client": eslint.Rule.RuleModule;
8
11
  };
9
12
  declare const plugin: {
@@ -11,9 +14,10 @@ declare const plugin: {
11
14
  name: string;
12
15
  };
13
16
  rules: {
17
+ "edit-route-provider-parity": eslint.Rule.RuleModule;
14
18
  "no-server-config-in-client": eslint.Rule.RuleModule;
15
19
  };
16
20
  configs: Record<string, unknown>;
17
21
  };
18
22
 
19
- export { plugin as default, noServerConfigInClient, rules };
23
+ export { plugin as default, editRouteProviderParity, noServerConfigInClient, rules };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  import * as eslint from 'eslint';
2
2
  import { Rule } from 'eslint';
3
3
 
4
+ declare const editRouteProviderParity: Rule.RuleModule;
5
+
4
6
  declare const noServerConfigInClient: Rule.RuleModule;
5
7
 
6
8
  declare const rules: {
9
+ "edit-route-provider-parity": eslint.Rule.RuleModule;
7
10
  "no-server-config-in-client": eslint.Rule.RuleModule;
8
11
  };
9
12
  declare const plugin: {
@@ -11,9 +14,10 @@ declare const plugin: {
11
14
  name: string;
12
15
  };
13
16
  rules: {
17
+ "edit-route-provider-parity": eslint.Rule.RuleModule;
14
18
  "no-server-config-in-client": eslint.Rule.RuleModule;
15
19
  };
16
20
  configs: Record<string, unknown>;
17
21
  };
18
22
 
19
- export { plugin as default, noServerConfigInClient, rules };
23
+ export { plugin as default, editRouteProviderParity, noServerConfigInClient, rules };
package/dist/index.js CHANGED
@@ -1,7 +1,90 @@
1
1
  import { readFileSync, existsSync } from 'fs';
2
2
  import { isAbsolute, resolve, dirname, sep } from 'path';
3
3
 
4
- // src/no-server-config-in-client.ts
4
+ // src/edit-route-provider-parity.ts
5
+ var LAYOUT = /\/layout\.(tsx|jsx|js|ts)$/;
6
+ var EDIT_SEGMENT = "cmssy-edit";
7
+ var WRAPPERS = /* @__PURE__ */ new Set([
8
+ "LazyMotion",
9
+ "MotionConfig",
10
+ "AnimatePresence",
11
+ "MotionProvider"
12
+ ]);
13
+ function normalize(path) {
14
+ return path.split(sep).join("/");
15
+ }
16
+ function isWrapper(name) {
17
+ return name.endsWith("Provider") || WRAPPERS.has(name);
18
+ }
19
+ function elementName(node) {
20
+ const opening = node.openingElement;
21
+ const name = opening?.name ?? {};
22
+ return name.type === "JSXIdentifier" ? name.name ?? null : null;
23
+ }
24
+ function editCounterpart(filename) {
25
+ const file = normalize(filename);
26
+ if (!LAYOUT.test(file)) return null;
27
+ const app = file.lastIndexOf("/app/");
28
+ if (app === -1) return null;
29
+ const rest = file.slice(app + "/app/".length);
30
+ if (!rest.includes("/")) return null;
31
+ if (rest.startsWith(`${EDIT_SEGMENT}/`)) return null;
32
+ return `${file.slice(0, app)}/app/${EDIT_SEGMENT}/${rest}`;
33
+ }
34
+ function rendersElement(code, name) {
35
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
36
+ return new RegExp(`<${escaped}[\\s/>]`).test(code);
37
+ }
38
+ function isChildrenSlot(node) {
39
+ const container = node;
40
+ return container.type === "JSXExpressionContainer" && container.expression?.type === "Identifier" && container.expression.name === "children";
41
+ }
42
+ function wrapsChildren(node) {
43
+ const children = node.children ?? [];
44
+ return children.some(
45
+ (child) => isChildrenSlot(child) || wrapsChildren(child)
46
+ );
47
+ }
48
+ var editRouteProviderParity = {
49
+ meta: {
50
+ type: "problem",
51
+ docs: {
52
+ description: "Require a provider that wraps the public route's children to wrap the cmssy edit route's children too"
53
+ },
54
+ schema: [],
55
+ messages: {
56
+ missingProvider: "<{{name}}> wraps the blocks on the public route but not on {{editRoute}}, so the editor renders them without whatever it provides. A missing animation provider is the loudest case - the reveals never attach and the preview stays blank - but any context the blocks read is gone the same way.\nMove it into cmssy/site-providers.tsx and render that from both roots, or repeat <{{name}}> in the edit layout."
57
+ }
58
+ },
59
+ create(context) {
60
+ const filename = context.filename ?? context.getFilename();
61
+ const counterpart = editCounterpart(filename);
62
+ if (!counterpart) return {};
63
+ let editCode;
64
+ try {
65
+ editCode = readFileSync(counterpart, "utf8");
66
+ } catch {
67
+ return {};
68
+ }
69
+ const editRoute = counterpart.slice(counterpart.lastIndexOf("/app/") + 1);
70
+ const reported = /* @__PURE__ */ new Set();
71
+ return {
72
+ JSXElement(node) {
73
+ const name = elementName(node);
74
+ if (!name || reported.has(name)) return;
75
+ if (!isWrapper(name)) return;
76
+ if (!wrapsChildren(node)) return;
77
+ if (rendersElement(editCode, name)) return;
78
+ reported.add(name);
79
+ context.report({
80
+ node,
81
+ messageId: "missingProvider",
82
+ data: { name, editRoute }
83
+ });
84
+ }
85
+ };
86
+ }
87
+ };
5
88
  var SERVER_MODULES = [/^@cmssy\/next\/server$/];
6
89
  var SERVER_SYMBOLS = /* @__PURE__ */ new Set(["defineCmssyConfig"]);
7
90
  var EXTENSIONS = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs"];
@@ -182,13 +265,13 @@ function chainToServerConfig(file, cache2, seen = /* @__PURE__ */ new Set()) {
182
265
  cache2.set(file, null);
183
266
  return null;
184
267
  }
185
- function normalize(path) {
268
+ function normalize2(path) {
186
269
  return path.split(sep).join("/");
187
270
  }
188
271
  function isClientEntry(filename, entries) {
189
- const file = normalize(filename);
272
+ const file = normalize2(filename);
190
273
  return entries.some((entry) => {
191
- const wanted = normalize(entry);
274
+ const wanted = normalize2(entry);
192
275
  if (isAbsolute(wanted)) return file === wanted;
193
276
  return file === wanted || file.endsWith(`/${wanted}`);
194
277
  });
@@ -259,6 +342,7 @@ var noServerConfigInClient = {
259
342
 
260
343
  // src/index.ts
261
344
  var rules = {
345
+ "edit-route-provider-parity": editRouteProviderParity,
262
346
  "no-server-config-in-client": noServerConfigInClient
263
347
  };
264
348
  var plugin = {
@@ -269,9 +353,12 @@ var plugin = {
269
353
  plugin.configs.recommended = [
270
354
  {
271
355
  plugins: { cmssy: plugin },
272
- rules: { "cmssy/no-server-config-in-client": "error" }
356
+ rules: {
357
+ "cmssy/edit-route-provider-parity": "error",
358
+ "cmssy/no-server-config-in-client": "error"
359
+ }
273
360
  }
274
361
  ];
275
362
  var index_default = plugin;
276
363
 
277
- export { index_default as default, noServerConfigInClient, rules };
364
+ export { index_default as default, editRouteProviderParity, noServerConfigInClient, rules };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cmssy/eslint-plugin",
3
- "version": "12.7.1",
4
- "description": "Catches the cmssy wiring mistakes that a build cannot: server config dragged into a client bundle.",
3
+ "version": "12.8.0",
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",
7
7
  "eslint",