larrylint 0.2.1 → 0.2.2
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/README.md +21 -26
- package/dist/_chunks/package.mjs +1 -1
- package/dist/_chunks/preset.mjs +180 -189
- package/dist/index.d.mts +0 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,12 +33,12 @@ After that, `eslint .` and your editor report larrylint's rules next to your own
|
|
|
33
33
|
|
|
34
34
|
| Rule | Description |
|
|
35
35
|
| --- | --- |
|
|
36
|
-
| `larrylint/layers` | App, server and shared code
|
|
36
|
+
| `larrylint/layers` | App, server and shared code don't import each other, runtime code doesn't import build-time code, and `node:` modules stay on the server. As Laioutr's coding standards ask, handlers get API clients from the orchestr context, nothing imports handler files, and components don't import sections or blocks. |
|
|
37
37
|
| `larrylint/heavy-imports` | Sections, blocks and plugins load the packages in `heavyPackages` with `import()`: frontend-core imports every section and block up front, and plugins run on every page, so a static import loads the package on every page. |
|
|
38
38
|
| `larrylint/config-keys` | Runtime config keys like `'@laioutr-app/shopware'` are the app itself or one of its dependencies, otherwise the config is missing at runtime. |
|
|
39
39
|
| `larrylint/public-config` | The module doesn't copy its whole options object into `runtimeConfig.public`, which would send every secret in it to the browser. |
|
|
40
40
|
|
|
41
|
-
Type imports are fine
|
|
41
|
+
Type imports are always fine, since they don't end up in the bundle.
|
|
42
42
|
|
|
43
43
|
### Orchestr
|
|
44
44
|
|
|
@@ -47,7 +47,6 @@ Type imports are fine across most layers, since they don't end up in the bundle.
|
|
|
47
47
|
| `larrylint/orchestr-files` | Laioutr loads every file in `orchestr/` as a Nitro plugin, so only handler files belong there. |
|
|
48
48
|
| `larrylint/orchestr-cookies` | Cookies and headers are only written in `extendRequest()` and action handlers, since query, link and resolver handlers and `use()` middleware can run after the headers are sent. Cookies go through frontend-core's `setManagedCookie()` and `deleteManagedCookie()`, which the Studio preview needs. |
|
|
49
49
|
| `larrylint/handler-exports` | Handler files export their handler as default. Without one, the build fails. |
|
|
50
|
-
| `larrylint/middleware-files` | `server/middleware/` only holds orchestr and Nitro middleware; helpers go to `server/utils/`. |
|
|
51
50
|
| `larrylint/initware-throws` | `extendRequest()` doesn't throw, directly or through a function it calls: it runs before every query, so a throw breaks every page. |
|
|
52
51
|
| `larrylint/token-namespaces` | Your own tokens stay out of the namespaces of Laioutr's canonical types, like `ecommerce/`, where a later canonical-types release or another app can take the same id. |
|
|
53
52
|
|
|
@@ -55,7 +54,7 @@ Type imports are fine across most layers, since they don't end up in the bundle.
|
|
|
55
54
|
|
|
56
55
|
| Rule | Description |
|
|
57
56
|
| --- | --- |
|
|
58
|
-
| `larrylint/definition-folder` | `defineSection()`
|
|
57
|
+
| `larrylint/definition-folder` | `defineSection()` and `defineBlock()` live where `module.ts` registers sections and blocks, and every `.vue` registered there has one. Files that `module.ts` wires up by hand, like replacements for upstream sections, can live anywhere. |
|
|
59
58
|
| `larrylint/component-name` | The `component` of a definition matches its file name. |
|
|
60
59
|
| `larrylint/definition-description` | Definitions have a `studio.description`, which Studio shows in its section picker and AI agents read through Laioutr's MCP server. |
|
|
61
60
|
| `larrylint/single-root` | Sections and blocks render one root element, also through a component they wrap. Otherwise Vue drops the `data-lfc-*` attributes Studio needs to select them in the preview. |
|
|
@@ -78,35 +77,31 @@ Type imports are fine across most layers, since they don't end up in the bundle.
|
|
|
78
77
|
|
|
79
78
|
## Layout
|
|
80
79
|
|
|
81
|
-
larrylint
|
|
80
|
+
larrylint follows the runtime layout from Laioutr's [coding standards](https://docs.laioutr.com/apps/app-development/coding-standards):
|
|
82
81
|
|
|
83
82
|
```
|
|
84
83
|
src/
|
|
85
|
-
├── module.ts
|
|
84
|
+
├── module.ts # registers sections, blocks, orchestr handlers and plugins
|
|
86
85
|
└── runtime/
|
|
87
|
-
├── app/
|
|
88
|
-
│ ├──
|
|
89
|
-
│ ├──
|
|
90
|
-
│
|
|
91
|
-
|
|
92
|
-
│ ├──
|
|
93
|
-
│ ├──
|
|
94
|
-
│ ├──
|
|
95
|
-
│ ├──
|
|
86
|
+
├── app/ # client code
|
|
87
|
+
│ ├── components/ # UI components
|
|
88
|
+
│ ├── sections/ # defineSection() components
|
|
89
|
+
│ └── blocks/ # defineBlock() components
|
|
90
|
+
├── server/ # server code
|
|
91
|
+
│ ├── client/ # API client factories
|
|
92
|
+
│ ├── const/
|
|
93
|
+
│ ├── mappers/
|
|
94
|
+
│ ├── middleware/ # defineOrchestr() with extendRequest()
|
|
95
|
+
│ ├── orchestr/ # *.query.ts, *.resolver.ts, *.link.ts, *.action.ts, *.template.ts, *.page-index.ts
|
|
96
|
+
│ ├── orchestr-helper/ # helper logic for handlers
|
|
96
97
|
│ └── utils/
|
|
97
|
-
|
|
98
|
-
│ ├── orchestr/
|
|
99
|
-
│ │ ├── <entity>/ # *.query.ts, *.resolver.ts, *.link.ts, *.action.ts, *.template.ts, *.page-index.ts
|
|
100
|
-
│ │ └── plugins/
|
|
101
|
-
│ ├── middleware/ # orchestr middleware and the builders handlers use
|
|
102
|
-
│ ├── client/ # API clients, nothing else
|
|
103
|
-
│ ├── api/ # server routes, or routes/
|
|
104
|
-
│ ├── plugins/ # Nitro plugins
|
|
105
|
-
│ ├── media-library/ # or media-libraries/
|
|
106
|
-
│ └── utils/
|
|
107
|
-
└── shared/ # code for both the app and the server
|
|
98
|
+
└── shared/ # code for both sides, like orchestr tokens
|
|
108
99
|
```
|
|
109
100
|
|
|
101
|
+
Where your sections, blocks and handlers live, larrylint reads from your `module.ts`: the folders and globs you pass to `registerLaioutrApp()`, and the plugins you pass to `addPlugin()`. So other folders work too, like `section/` and `block/` in Laioutr's own ui-app. Without a `module.ts` it can read, larrylint assumes the folders of Laioutr's [app starter](https://github.com/laioutr/app-starter): `app/sections/`, `app/blocks/` and `server/orchestr/`.
|
|
102
|
+
|
|
103
|
+
Besides that, larrylint only relies on the names `app/components/` and `server/client/`, and on the split into `app/`, `server/` and `shared/`.
|
|
104
|
+
|
|
110
105
|
## Baseline
|
|
111
106
|
|
|
112
107
|
On an existing codebase, `larrylint init` records all current violations in `larrylint-baseline.json`, counted per file and rule. A file stays quiet as long as it has no more violations of a rule than recorded. Once it gets more, all of them show again, like ESLint's bulk suppressions. The baseline applies in your editor, in `eslint .` and in the CLI.
|
package/dist/_chunks/package.mjs
CHANGED
package/dist/_chunks/preset.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { name, version } from "./package.mjs";
|
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { loadConfig } from "c12";
|
|
4
4
|
import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { dirname, join, normalize, parse, relative, resolve } from "pathe";
|
|
5
|
+
import { dirname, join, matchesGlob, normalize, parse, relative, resolve } from "pathe";
|
|
6
6
|
import tsParser, { parse as parse$1 } from "@typescript-eslint/parser";
|
|
7
7
|
import { parse as parse$2 } from "vue-eslint-parser";
|
|
8
8
|
function defineLarrylintConfig(config) {
|
|
@@ -30,6 +30,7 @@ const EXTENSIONS = [
|
|
|
30
30
|
"/index.ts",
|
|
31
31
|
"/index.js"
|
|
32
32
|
];
|
|
33
|
+
const SOURCE_EXTENSION = /\.(?:vue|[cm]?[jt]sx?)$/;
|
|
33
34
|
const directories = /* @__PURE__ */ new Map();
|
|
34
35
|
function cachedReader(parse) {
|
|
35
36
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -62,6 +63,9 @@ function isDirectory(path) {
|
|
|
62
63
|
}
|
|
63
64
|
return result;
|
|
64
65
|
}
|
|
66
|
+
function withoutExtension(path) {
|
|
67
|
+
return path.replace(SOURCE_EXTENSION, "");
|
|
68
|
+
}
|
|
65
69
|
function resolveImport(importer, source) {
|
|
66
70
|
if (!source.startsWith("./") && !source.startsWith("../")) return;
|
|
67
71
|
const target = resolve(dirname(importer), source.split("?")[0]);
|
|
@@ -129,31 +133,123 @@ function applyBaseline(cwd, results) {
|
|
|
129
133
|
improved
|
|
130
134
|
};
|
|
131
135
|
}
|
|
136
|
+
function findProperty(object, key) {
|
|
137
|
+
for (const entry of object.properties) if (entry.type === "Property" && !entry.computed && (entry.key.type === "Identifier" && entry.key.name === key || entry.key.type === "Literal" && entry.key.value === key)) return entry.value;
|
|
138
|
+
}
|
|
139
|
+
function findStringProperty(object, key) {
|
|
140
|
+
const value = findProperty(object, key);
|
|
141
|
+
return value?.type === "Literal" && typeof value.value === "string" ? {
|
|
142
|
+
value: value.value,
|
|
143
|
+
node: value
|
|
144
|
+
} : void 0;
|
|
145
|
+
}
|
|
146
|
+
function objectElements(node) {
|
|
147
|
+
if (node?.type !== "ArrayExpression") return [];
|
|
148
|
+
return node.elements.filter((element) => element?.type === "ObjectExpression");
|
|
149
|
+
}
|
|
150
|
+
const parseFile = cachedReader((text) => {
|
|
151
|
+
try {
|
|
152
|
+
return parse$1(text, {
|
|
153
|
+
range: true,
|
|
154
|
+
loc: true
|
|
155
|
+
});
|
|
156
|
+
} catch {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
const SKIPPED = /* @__PURE__ */ new Set([
|
|
161
|
+
"parent",
|
|
162
|
+
"loc",
|
|
163
|
+
"range",
|
|
164
|
+
"tokens",
|
|
165
|
+
"comments"
|
|
166
|
+
]);
|
|
167
|
+
function children(node) {
|
|
168
|
+
const result = [];
|
|
169
|
+
for (const [key, value] of Object.entries(node)) {
|
|
170
|
+
if (SKIPPED.has(key)) continue;
|
|
171
|
+
for (const child of Array.isArray(value) ? value : [value]) if (typeof child === "object" && child !== null && typeof child.type === "string") result.push(child);
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
function walk(node, enter) {
|
|
176
|
+
if (enter(node) === false) return;
|
|
177
|
+
for (const child of children(node)) walk(child, enter);
|
|
178
|
+
}
|
|
179
|
+
const STARTER = {
|
|
180
|
+
sections: ["app/sections"],
|
|
181
|
+
blocks: ["app/blocks"],
|
|
182
|
+
orchestr: ["server/orchestr"],
|
|
183
|
+
plugins: /* @__PURE__ */ new Set(),
|
|
184
|
+
references: /* @__PURE__ */ new Set()
|
|
185
|
+
};
|
|
186
|
+
const registrations = /* @__PURE__ */ new WeakMap();
|
|
187
|
+
function readRegistration(root) {
|
|
188
|
+
const program = parseFile(join(root, "src/module.ts"));
|
|
189
|
+
if (!program) return STARTER;
|
|
190
|
+
let registration = registrations.get(program);
|
|
191
|
+
if (!registration) {
|
|
192
|
+
registration = readModule(program);
|
|
193
|
+
registrations.set(program, registration);
|
|
194
|
+
}
|
|
195
|
+
return registration;
|
|
196
|
+
}
|
|
197
|
+
function readModule(program) {
|
|
198
|
+
const apps = [];
|
|
199
|
+
const plugins = /* @__PURE__ */ new Set();
|
|
200
|
+
const references = /* @__PURE__ */ new Set();
|
|
201
|
+
walk(program, (node) => {
|
|
202
|
+
const reference = node.type === "Literal" || node.type === "TemplateLiteral" ? runtimePath(node) : void 0;
|
|
203
|
+
if (reference) references.add(withoutExtension(reference));
|
|
204
|
+
if (node.type !== "CallExpression" || node.callee.type !== "Identifier") return;
|
|
205
|
+
const [argument] = node.arguments;
|
|
206
|
+
if (node.callee.name === "registerLaioutrApp" && argument?.type === "ObjectExpression") apps.push(argument);
|
|
207
|
+
if (node.callee.name === "addPlugin" && argument && argument.type !== "SpreadElement") {
|
|
208
|
+
const src = argument.type === "ObjectExpression" ? findProperty(argument, "src") : argument;
|
|
209
|
+
const plugin = src && runtimePath(src);
|
|
210
|
+
if (plugin) plugins.add(withoutExtension(plugin));
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
const [app] = apps;
|
|
214
|
+
return {
|
|
215
|
+
sections: app ? readEntries(app, "sections", STARTER.sections) : STARTER.sections,
|
|
216
|
+
blocks: app ? readEntries(app, "blocks", STARTER.blocks) : STARTER.blocks,
|
|
217
|
+
orchestr: app ? readEntries(app, "orchestrDirs", STARTER.orchestr) : STARTER.orchestr,
|
|
218
|
+
plugins,
|
|
219
|
+
references
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function readEntries(app, option, fallback) {
|
|
223
|
+
const value = findProperty(app, option);
|
|
224
|
+
if (!value) return [];
|
|
225
|
+
const paths = value.type === "ArrayExpression" ? value.elements.map((element) => element && element.type !== "SpreadElement" ? runtimePath(element) : void 0) : [void 0];
|
|
226
|
+
const known = paths.filter((path) => path !== void 0);
|
|
227
|
+
return known.length > 0 || paths.length === 0 ? known : fallback;
|
|
228
|
+
}
|
|
229
|
+
function runtimePath(node) {
|
|
230
|
+
const parts = [];
|
|
231
|
+
walk(node, (child) => {
|
|
232
|
+
if (child.type === "Literal" && typeof child.value === "string") parts.push(child.value);
|
|
233
|
+
else if (child.type === "TemplateElement" && child.value.cooked) parts.push(child.value.cooked);
|
|
234
|
+
});
|
|
235
|
+
const path = normalize(parts.join("/")).replace(/^(?:.*\/)?runtime\//, "").replace(/^\/|\/$/g, "");
|
|
236
|
+
return /^(?:app|server|shared)\//.test(path) ? path : void 0;
|
|
237
|
+
}
|
|
238
|
+
function isRegistered(path, entries) {
|
|
239
|
+
return entries.some((entry) => matchesGlob(path, entry.includes(".vue") ? entry : `${entry}/**/*.vue`));
|
|
240
|
+
}
|
|
241
|
+
function entryFolder(entry) {
|
|
242
|
+
const parts = entry.split("/");
|
|
243
|
+
const end = parts.findIndex((part) => /[*?{[]/.test(part) || part.endsWith(".vue"));
|
|
244
|
+
return (end === -1 ? parts : parts.slice(0, end)).join("/");
|
|
245
|
+
}
|
|
246
|
+
function isReferenced(file) {
|
|
247
|
+
return readRegistration(file.root).references.has(withoutExtension(file.path.replace(/^src\/runtime\//, "")));
|
|
248
|
+
}
|
|
132
249
|
const HANDLER_FILE = /\.(query|resolver|link|action|templates?|page-index)(?:\.[cm]?[jt]s)?$/;
|
|
133
250
|
const TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
134
|
-
const APP_FOLDERS = {
|
|
135
|
-
|
|
136
|
-
"section": "section",
|
|
137
|
-
"blocks": "block",
|
|
138
|
-
"block": "block",
|
|
139
|
-
"components": "component",
|
|
140
|
-
"composables": "composable",
|
|
141
|
-
"utils": "app-util",
|
|
142
|
-
"plugins": "app-plugin",
|
|
143
|
-
"overrides": "override",
|
|
144
|
-
"theme": "theme",
|
|
145
|
-
"shared-fields": "shared-field"
|
|
146
|
-
};
|
|
147
|
-
const SERVER_FOLDERS = {
|
|
148
|
-
"middleware": "middleware",
|
|
149
|
-
"client": "client",
|
|
150
|
-
"utils": "server-util",
|
|
151
|
-
"api": "route",
|
|
152
|
-
"routes": "route",
|
|
153
|
-
"plugins": "nitro-plugin",
|
|
154
|
-
"media-library": "media-library",
|
|
155
|
-
"media-libraries": "media-library"
|
|
156
|
-
};
|
|
251
|
+
const APP_FOLDERS = { components: "component" };
|
|
252
|
+
const SERVER_FOLDERS = { client: "client" };
|
|
157
253
|
function classify(file) {
|
|
158
254
|
const absolute = normalize(file);
|
|
159
255
|
const test = TEST_FILE.test(absolute);
|
|
@@ -164,7 +260,7 @@ function classify(file) {
|
|
|
164
260
|
root,
|
|
165
261
|
path: absolute.slice(root.length + 1),
|
|
166
262
|
test,
|
|
167
|
-
...classifyRuntime(absolute.slice(runtimeIndex + 13)
|
|
263
|
+
...classifyRuntime(absolute.slice(runtimeIndex + 13), readRegistration(root))
|
|
168
264
|
};
|
|
169
265
|
}
|
|
170
266
|
const srcIndex = absolute.lastIndexOf("/src/");
|
|
@@ -178,33 +274,40 @@ function classify(file) {
|
|
|
178
274
|
test
|
|
179
275
|
};
|
|
180
276
|
}
|
|
181
|
-
function classifyRuntime(
|
|
182
|
-
const [side, folder = ""
|
|
183
|
-
if (
|
|
184
|
-
if (side === "shared") return {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
};
|
|
192
|
-
if (folder === "orchestr") {
|
|
193
|
-
if (rest.length > 1 && rest[0] === "plugins") return {
|
|
277
|
+
function classifyRuntime(path, registration) {
|
|
278
|
+
const [side, folder] = path.split("/");
|
|
279
|
+
if (!folder || side !== "app" && side !== "server" && side !== "shared") return { side: "other" };
|
|
280
|
+
if (side === "shared") return { side };
|
|
281
|
+
if (side === "app") {
|
|
282
|
+
if (isRegistered(path, registration.sections)) return {
|
|
283
|
+
side,
|
|
284
|
+
kind: "section"
|
|
285
|
+
};
|
|
286
|
+
if (isRegistered(path, registration.blocks)) return {
|
|
194
287
|
side,
|
|
195
|
-
kind: "
|
|
288
|
+
kind: "block"
|
|
196
289
|
};
|
|
197
|
-
const handler = HANDLER_FILE.exec(parts.at(-1))?.[1]?.replace("templates", "template");
|
|
198
290
|
return {
|
|
199
291
|
side,
|
|
200
|
-
kind:
|
|
201
|
-
handler
|
|
292
|
+
kind: registration.plugins.has(withoutExtension(path)) ? "app-plugin" : APP_FOLDERS[folder] ?? "other"
|
|
202
293
|
};
|
|
203
294
|
}
|
|
204
|
-
|
|
295
|
+
const orchestr = registration.orchestr.find((dir) => path.startsWith(`${dir}/`));
|
|
296
|
+
if (!orchestr) return {
|
|
205
297
|
side,
|
|
206
298
|
kind: SERVER_FOLDERS[folder] ?? "other"
|
|
207
299
|
};
|
|
300
|
+
const [first, ...rest] = path.slice(orchestr.length + 1).split("/");
|
|
301
|
+
if (first === "plugins" && rest.length > 0) return {
|
|
302
|
+
side,
|
|
303
|
+
kind: "orchestr-plugin"
|
|
304
|
+
};
|
|
305
|
+
const handler = HANDLER_FILE.exec(path)?.[1]?.replace("templates", "template");
|
|
306
|
+
return {
|
|
307
|
+
side,
|
|
308
|
+
kind: handler ? "handler" : "orchestr-file",
|
|
309
|
+
handler
|
|
310
|
+
};
|
|
208
311
|
}
|
|
209
312
|
function defineRule(rule) {
|
|
210
313
|
return {
|
|
@@ -331,25 +434,6 @@ function isTested(node) {
|
|
|
331
434
|
default: return false;
|
|
332
435
|
}
|
|
333
436
|
}
|
|
334
|
-
const SKIPPED = /* @__PURE__ */ new Set([
|
|
335
|
-
"parent",
|
|
336
|
-
"loc",
|
|
337
|
-
"range",
|
|
338
|
-
"tokens",
|
|
339
|
-
"comments"
|
|
340
|
-
]);
|
|
341
|
-
function children(node) {
|
|
342
|
-
const result = [];
|
|
343
|
-
for (const [key, value] of Object.entries(node)) {
|
|
344
|
-
if (SKIPPED.has(key)) continue;
|
|
345
|
-
for (const child of Array.isArray(value) ? value : [value]) if (typeof child === "object" && child !== null && typeof child.type === "string") result.push(child);
|
|
346
|
-
}
|
|
347
|
-
return result;
|
|
348
|
-
}
|
|
349
|
-
function walk(node, enter) {
|
|
350
|
-
if (enter(node) === false) return;
|
|
351
|
-
for (const child of children(node)) walk(child, enter);
|
|
352
|
-
}
|
|
353
437
|
function isFunction(node) {
|
|
354
438
|
return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
|
355
439
|
}
|
|
@@ -450,16 +534,6 @@ function isRouter(node) {
|
|
|
450
534
|
const name = nameOf(node.type === "CallExpression" ? node.callee : node);
|
|
451
535
|
return node.type === "CallExpression" ? name === "useRouter" : name === "router" || name === "$router";
|
|
452
536
|
}
|
|
453
|
-
const parseFile = cachedReader((text) => {
|
|
454
|
-
try {
|
|
455
|
-
return parse$1(text, {
|
|
456
|
-
range: true,
|
|
457
|
-
loc: true
|
|
458
|
-
});
|
|
459
|
-
} catch {
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
537
|
function isTypeOnly(node) {
|
|
464
538
|
const { importKind, exportKind } = node;
|
|
465
539
|
if (importKind === "type" || exportKind === "type") return true;
|
|
@@ -899,12 +973,12 @@ const FILE_START = {
|
|
|
899
973
|
column: 0
|
|
900
974
|
}
|
|
901
975
|
};
|
|
902
|
-
var files_default
|
|
976
|
+
var files_default = defineRule({
|
|
903
977
|
meta: {
|
|
904
978
|
type: "problem",
|
|
905
979
|
docs: { description: "Disallow files other than handlers in `orchestr/`" },
|
|
906
980
|
schema: [],
|
|
907
|
-
messages: { notAHandler: "Only handlers belong in orchestr/. Move this file to server/
|
|
981
|
+
messages: { notAHandler: "Only handlers belong in orchestr/. Move this file to server/orchestr-helper/, or give it a handler suffix like .query.ts or .action.ts." }
|
|
908
982
|
},
|
|
909
983
|
applies: (file) => file.kind === "orchestr-file",
|
|
910
984
|
create: ({ report }) => ({ Program: () => {
|
|
@@ -940,39 +1014,6 @@ var exports_default = defineRule({
|
|
|
940
1014
|
};
|
|
941
1015
|
}
|
|
942
1016
|
});
|
|
943
|
-
const MIDDLEWARE = /* @__PURE__ */ new Set([
|
|
944
|
-
"defineOrchestr",
|
|
945
|
-
"defineEventHandler",
|
|
946
|
-
"eventHandler",
|
|
947
|
-
"defineRequestMiddleware",
|
|
948
|
-
"fromNodeMiddleware"
|
|
949
|
-
]);
|
|
950
|
-
var files_default = defineRule({
|
|
951
|
-
meta: {
|
|
952
|
-
type: "suggestion",
|
|
953
|
-
docs: { description: "Disallow files other than middleware in `server/middleware/`" },
|
|
954
|
-
schema: [],
|
|
955
|
-
messages: { helper: "Only middleware belongs in server/middleware/. Move this file to server/utils/." }
|
|
956
|
-
},
|
|
957
|
-
applies: (file) => file.kind === "middleware",
|
|
958
|
-
create: ({ report }) => {
|
|
959
|
-
let middleware = false;
|
|
960
|
-
return {
|
|
961
|
-
"Identifier": (node) => {
|
|
962
|
-
middleware ||= MIDDLEWARE.has(node.name);
|
|
963
|
-
},
|
|
964
|
-
"CallExpression": (node) => {
|
|
965
|
-
middleware ||= readMiddleware(node) !== void 0;
|
|
966
|
-
},
|
|
967
|
-
"Program:exit": () => {
|
|
968
|
-
if (!middleware) report({
|
|
969
|
-
loc: FILE_START,
|
|
970
|
-
messageId: "helper"
|
|
971
|
-
});
|
|
972
|
-
}
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
});
|
|
976
1017
|
var throws_default = defineRule({
|
|
977
1018
|
meta: {
|
|
978
1019
|
type: "problem",
|
|
@@ -1050,28 +1091,14 @@ var namespaces_default = defineRule({
|
|
|
1050
1091
|
});
|
|
1051
1092
|
} })
|
|
1052
1093
|
});
|
|
1053
|
-
function findProperty(object, key) {
|
|
1054
|
-
for (const entry of object.properties) if (entry.type === "Property" && !entry.computed && (entry.key.type === "Identifier" && entry.key.name === key || entry.key.type === "Literal" && entry.key.value === key)) return entry.value;
|
|
1055
|
-
}
|
|
1056
|
-
function findStringProperty(object, key) {
|
|
1057
|
-
const value = findProperty(object, key);
|
|
1058
|
-
return value?.type === "Literal" && typeof value.value === "string" ? {
|
|
1059
|
-
value: value.value,
|
|
1060
|
-
node: value
|
|
1061
|
-
} : void 0;
|
|
1062
|
-
}
|
|
1063
|
-
function objectElements(node) {
|
|
1064
|
-
if (node?.type !== "ArrayExpression") return [];
|
|
1065
|
-
return node.elements.filter((element) => element?.type === "ObjectExpression");
|
|
1066
|
-
}
|
|
1067
1094
|
const DEFINERS = {
|
|
1068
1095
|
defineSection: {
|
|
1069
1096
|
kind: "section",
|
|
1070
|
-
|
|
1097
|
+
option: "sections"
|
|
1071
1098
|
},
|
|
1072
1099
|
defineBlock: {
|
|
1073
1100
|
kind: "block",
|
|
1074
|
-
|
|
1101
|
+
option: "blocks"
|
|
1075
1102
|
}
|
|
1076
1103
|
};
|
|
1077
1104
|
const PICKERS = /* @__PURE__ */ new Set([
|
|
@@ -1082,7 +1109,7 @@ const PICKERS = /* @__PURE__ */ new Set([
|
|
|
1082
1109
|
const TEXTS = /* @__PURE__ */ new Set([
|
|
1083
1110
|
"text",
|
|
1084
1111
|
"textarea",
|
|
1085
|
-
"
|
|
1112
|
+
"richtext"
|
|
1086
1113
|
]);
|
|
1087
1114
|
function readDefinition(call) {
|
|
1088
1115
|
if (call.callee.type !== "Identifier" || !Object.hasOwn(DEFINERS, call.callee.name)) return;
|
|
@@ -1176,11 +1203,12 @@ var description_default = defineRule({
|
|
|
1176
1203
|
var folder_default = defineRule({
|
|
1177
1204
|
meta: {
|
|
1178
1205
|
type: "problem",
|
|
1179
|
-
docs: { description: "Require sections
|
|
1206
|
+
docs: { description: "Require sections and blocks in the folders `module.ts` registers for them, and a definition in every `.vue` registered there" },
|
|
1180
1207
|
schema: [],
|
|
1181
1208
|
messages: {
|
|
1182
|
-
notDefined: "
|
|
1183
|
-
wrongFolder: "{{definer}}() belongs in
|
|
1209
|
+
notDefined: "This file is registered as a {{kind}}, so it needs {{definer}}(). Add it, or move the file to components/.",
|
|
1210
|
+
wrongFolder: "{{definer}}() belongs in {{folder}}/. Move the file there.",
|
|
1211
|
+
notRegistered: "module.ts registers no {{kind}}s, so this {{kind}} never shows up in Studio. Add its folder to registerLaioutrApp()."
|
|
1184
1212
|
}
|
|
1185
1213
|
},
|
|
1186
1214
|
applies: (file) => file.side === "app",
|
|
@@ -1190,15 +1218,21 @@ var folder_default = defineRule({
|
|
|
1190
1218
|
"CallExpression": (node) => {
|
|
1191
1219
|
const definition = readDefinition(node);
|
|
1192
1220
|
if (!definition) return;
|
|
1193
|
-
const { kind,
|
|
1221
|
+
const { kind, option } = DEFINERS[definition.definer];
|
|
1194
1222
|
definers.add(definition.definer);
|
|
1195
|
-
if (file.kind
|
|
1223
|
+
if (file.kind === kind || isReferenced(file)) return;
|
|
1224
|
+
const [entry] = readRegistration(file.root)[option];
|
|
1225
|
+
report(entry ? {
|
|
1196
1226
|
node: node.callee,
|
|
1197
1227
|
messageId: "wrongFolder",
|
|
1198
1228
|
data: {
|
|
1199
1229
|
definer: definition.definer,
|
|
1200
|
-
folder
|
|
1230
|
+
folder: entryFolder(entry)
|
|
1201
1231
|
}
|
|
1232
|
+
} : {
|
|
1233
|
+
node: node.callee,
|
|
1234
|
+
messageId: "notRegistered",
|
|
1235
|
+
data: { kind }
|
|
1202
1236
|
});
|
|
1203
1237
|
},
|
|
1204
1238
|
"Program:exit": () => {
|
|
@@ -1206,7 +1240,10 @@ var folder_default = defineRule({
|
|
|
1206
1240
|
if (expected && file.path.endsWith(".vue") && !definers.has(expected)) report({
|
|
1207
1241
|
loc: FILE_START,
|
|
1208
1242
|
messageId: "notDefined",
|
|
1209
|
-
data: {
|
|
1243
|
+
data: {
|
|
1244
|
+
kind: DEFINERS[expected].kind,
|
|
1245
|
+
definer: expected
|
|
1246
|
+
}
|
|
1210
1247
|
});
|
|
1211
1248
|
}
|
|
1212
1249
|
};
|
|
@@ -1238,7 +1275,7 @@ const MESSAGES = {
|
|
|
1238
1275
|
content_alignment: "alignment",
|
|
1239
1276
|
text: "text",
|
|
1240
1277
|
textarea: "text",
|
|
1241
|
-
|
|
1278
|
+
richtext: "text"
|
|
1242
1279
|
};
|
|
1243
1280
|
var fallbacks_default = defineRule({
|
|
1244
1281
|
meta: {
|
|
@@ -1681,65 +1718,23 @@ var public_default = defineRule({
|
|
|
1681
1718
|
});
|
|
1682
1719
|
} })
|
|
1683
1720
|
});
|
|
1684
|
-
const LABELS = {
|
|
1685
|
-
"section": "sections",
|
|
1686
|
-
"block": "blocks",
|
|
1687
|
-
"component": "components",
|
|
1688
|
-
"composable": "composables",
|
|
1689
|
-
"app-plugin": "plugins",
|
|
1690
|
-
"override": "overrides",
|
|
1691
|
-
"route": "API routes",
|
|
1692
|
-
"nitro-plugin": "Nitro plugins",
|
|
1693
|
-
"orchestr-plugin": "orchestr plugins",
|
|
1694
|
-
"orchestr-file": "orchestr files",
|
|
1695
|
-
"media-library": "media libraries"
|
|
1696
|
-
};
|
|
1697
|
-
const ABOVE_APP_UTILS = /* @__PURE__ */ new Set([
|
|
1698
|
-
"section",
|
|
1699
|
-
"block",
|
|
1700
|
-
"component",
|
|
1701
|
-
"composable",
|
|
1702
|
-
"app-plugin",
|
|
1703
|
-
"override"
|
|
1704
|
-
]);
|
|
1705
|
-
const ABOVE_COMPOSABLES = /* @__PURE__ */ new Set([
|
|
1706
|
-
"section",
|
|
1707
|
-
"block",
|
|
1708
|
-
"component",
|
|
1709
|
-
"override"
|
|
1710
|
-
]);
|
|
1711
|
-
const ABOVE_SERVER_UTILS = /* @__PURE__ */ new Set([
|
|
1712
|
-
"route",
|
|
1713
|
-
"nitro-plugin",
|
|
1714
|
-
"orchestr-plugin",
|
|
1715
|
-
"orchestr-file",
|
|
1716
|
-
"media-library"
|
|
1717
|
-
]);
|
|
1718
1721
|
function findViolation(importer, target, typeOnly) {
|
|
1722
|
+
if (typeOnly) return;
|
|
1719
1723
|
if (importer.side === "app" && target.side === "server") return { messageId: "appImportsServer" };
|
|
1720
1724
|
if (importer.side === "server" && target.side === "app") return { messageId: "serverImportsApp" };
|
|
1721
1725
|
if (importer.side === "shared" && (target.side === "app" || target.side === "server")) return {
|
|
1722
1726
|
messageId: "sharedImportsSide",
|
|
1723
1727
|
data: { side: target.side }
|
|
1724
1728
|
};
|
|
1725
|
-
if (target.side === "build"
|
|
1729
|
+
if (target.side === "build") return { messageId: "runtimeImportsBuild" };
|
|
1726
1730
|
if (target.kind === "handler") return { messageId: "handlerImported" };
|
|
1727
|
-
if (typeOnly) return;
|
|
1728
1731
|
if (target.kind === "client" && importer.kind === "handler") return { messageId: "clientInHandler" };
|
|
1729
|
-
if (importer.kind === "
|
|
1730
|
-
messageId: "
|
|
1731
|
-
data: { kind:
|
|
1732
|
-
};
|
|
1733
|
-
if (importer.kind === "app-util" && ABOVE_APP_UTILS.has(target.kind)) return {
|
|
1734
|
-
messageId: "appUtilImportsUp",
|
|
1735
|
-
data: { kind: LABELS[target.kind] }
|
|
1736
|
-
};
|
|
1737
|
-
if (importer.kind === "composable" && ABOVE_COMPOSABLES.has(target.kind)) return {
|
|
1738
|
-
messageId: "composableImportsUp",
|
|
1739
|
-
data: { kind: LABELS[target.kind] }
|
|
1732
|
+
if (importer.kind === "component" && (target.kind === "section" || target.kind === "block")) return {
|
|
1733
|
+
messageId: "schemaInComponent",
|
|
1734
|
+
data: { kind: target.kind }
|
|
1740
1735
|
};
|
|
1741
|
-
if (importer.kind === "component" && target.kind === "section") return { messageId: "componentImportsSection" };
|
|
1742
1736
|
}
|
|
1737
|
+
const SERVER_ONLY = /\.server\.[^/]+$/;
|
|
1743
1738
|
var layers_default = defineRule({
|
|
1744
1739
|
meta: {
|
|
1745
1740
|
type: "problem",
|
|
@@ -1750,12 +1745,9 @@ var layers_default = defineRule({
|
|
|
1750
1745
|
serverImportsApp: "Server code can't import app code. Move what both need to src/runtime/shared/.",
|
|
1751
1746
|
sharedImportsSide: "Shared code can't import from {{side}}/.",
|
|
1752
1747
|
runtimeImportsBuild: "Runtime code can't import build code from src/, like module.ts. Type imports are fine.",
|
|
1753
|
-
handlerImported: "Don't import orchestr handlers. Move the code you need to server/
|
|
1754
|
-
clientInHandler: "Handlers get API clients from the orchestr context, not by import. Move
|
|
1755
|
-
|
|
1756
|
-
appUtilImportsUp: "App utils can't import {{kind}}. Type imports are fine.",
|
|
1757
|
-
composableImportsUp: "Composables can't import {{kind}}. Type imports are fine.",
|
|
1758
|
-
componentImportsSection: "Components can't import sections.",
|
|
1748
|
+
handlerImported: "Don't import orchestr handlers. Move the code you need to server/orchestr-helper/.",
|
|
1749
|
+
clientInHandler: "Handlers get API clients from the orchestr context, not by import. Move constants to server/const/ and helpers to server/orchestr-helper/.",
|
|
1750
|
+
schemaInComponent: "Components can't import {{kind}}s.",
|
|
1759
1751
|
nodeBuiltin: "'{{name}}' only works on the server. Move this code to server/."
|
|
1760
1752
|
}
|
|
1761
1753
|
},
|
|
@@ -1764,7 +1756,7 @@ var layers_default = defineRule({
|
|
|
1764
1756
|
const checkImport = (node, source, typeOnly) => {
|
|
1765
1757
|
if (typeof source !== "string") return;
|
|
1766
1758
|
if (source.startsWith("node:")) {
|
|
1767
|
-
if (importer.side !== "server" && !typeOnly) report({
|
|
1759
|
+
if (importer.side !== "server" && !typeOnly && !SERVER_ONLY.test(importer.path)) report({
|
|
1768
1760
|
node,
|
|
1769
1761
|
messageId: "nodeBuiltin",
|
|
1770
1762
|
data: { name: source }
|
|
@@ -1805,10 +1797,9 @@ const plugin = {
|
|
|
1805
1797
|
"heavy-imports": bundle_default,
|
|
1806
1798
|
"config-keys": keys_default,
|
|
1807
1799
|
"public-config": public_default,
|
|
1808
|
-
"orchestr-files": files_default
|
|
1800
|
+
"orchestr-files": files_default,
|
|
1809
1801
|
"orchestr-cookies": cookies_default,
|
|
1810
1802
|
"handler-exports": exports_default,
|
|
1811
|
-
"middleware-files": files_default,
|
|
1812
1803
|
"initware-throws": throws_default,
|
|
1813
1804
|
"token-namespaces": namespaces_default,
|
|
1814
1805
|
"definition-folder": folder_default,
|
package/dist/index.d.mts
CHANGED
|
@@ -34,7 +34,6 @@ export declare const plugin: {
|
|
|
34
34
|
'orchestr-files': import("eslint").Rule.RuleModule;
|
|
35
35
|
'orchestr-cookies': import("eslint").Rule.RuleModule;
|
|
36
36
|
'handler-exports': import("eslint").Rule.RuleModule;
|
|
37
|
-
'middleware-files': import("eslint").Rule.RuleModule;
|
|
38
37
|
'initware-throws': import("eslint").Rule.RuleModule;
|
|
39
38
|
'token-namespaces': import("eslint").Rule.RuleModule;
|
|
40
39
|
'definition-folder': import("eslint").Rule.RuleModule;
|
package/package.json
CHANGED