nuxt-layerscope 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.
@@ -0,0 +1,2 @@
1
+ import { n as formatResult, r as isOutputFormat, t as OUTPUT_FORMATS } from "./report-BNW0GCl9.mjs";
2
+ export { OUTPUT_FORMATS, formatResult, isOutputFormat };
@@ -0,0 +1,17 @@
1
+ //#region src/utils/strings.ts
2
+ function pascalCase(name) {
3
+ const camel = name.replaceAll(/-(\w)/gu, (_, c) => c.toUpperCase());
4
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
5
+ }
6
+ function escapeRegExp(text) {
7
+ return text.replaceAll(/[$()*+.?[\\\]^{|}]/gu, "\\$&");
8
+ }
9
+ function plural(count, word, many = `${word}s`) {
10
+ return `${count} ${count === 1 ? word : many}`;
11
+ }
12
+ function compareStrings(a, b) {
13
+ if (a === b) return 0;
14
+ return a < b ? -1 : 1;
15
+ }
16
+ //#endregion
17
+ export { plural as i, escapeRegExp as n, pascalCase as r, compareStrings as t };
@@ -0,0 +1,10 @@
1
+ //#region src/report/summary.ts
2
+ function summarize(findings) {
3
+ const errors = findings.filter((finding) => finding.severity === "error").length;
4
+ return {
5
+ errors,
6
+ warnings: findings.length - errors
7
+ };
8
+ }
9
+ //#endregion
10
+ export { summarize as t };
@@ -0,0 +1,164 @@
1
+ import { t as compareStrings } from "./strings-CrOjNZmB.mjs";
2
+ import { c as createOwnerLookup, g as isAllowed } from "./file-analysis-Bkq_69x4.mjs";
3
+ import { relative } from "pathe";
4
+ //#region src/rules/edge-status.ts
5
+ /** How the layer rules see one edge. */
6
+ function edgeStatus(edge, config) {
7
+ if (edge.toLayer === null) return "external";
8
+ if (edge.toLayer === edge.fromLayer) return "same-layer";
9
+ const allow = config.layers?.[edge.fromLayer]?.allow;
10
+ if (allow === void 0) return "unrestricted";
11
+ return isAllowed(edge, allow) ? "allowed" : "not-allowed";
12
+ }
13
+ //#endregion
14
+ //#region src/graph/index.ts
15
+ const GRAPH_LEVELS = ["layer", "file"];
16
+ const STATUS_ORDER = [
17
+ "not-allowed",
18
+ "allowed",
19
+ "unrestricted",
20
+ "same-layer"
21
+ ];
22
+ function worse(a, b) {
23
+ return STATUS_ORDER.indexOf(a) <= STATUS_ORDER.indexOf(b) ? a : b;
24
+ }
25
+ function endpoints(edge, level, rootDir) {
26
+ if (edge.toLayer === null || edge.to === null) return null;
27
+ if (level === "layer") return edge.toLayer === edge.fromLayer ? null : [edge.fromLayer, edge.toLayer];
28
+ const from = relative(rootDir, edge.file);
29
+ const to = relative(rootDir, edge.to);
30
+ return from === to ? null : [from, to];
31
+ }
32
+ function collectNodes(result, edges, level) {
33
+ if (level === "layer") return result.layers.map((layer) => ({
34
+ id: layer.name,
35
+ layer: layer.name
36
+ }));
37
+ const layerByFile = /* @__PURE__ */ new Map();
38
+ for (const edge of edges) {
39
+ layerByFile.set(relative(result.rootDir, edge.file), edge.fromLayer);
40
+ if (edge.to !== null && edge.toLayer !== null) layerByFile.set(relative(result.rootDir, edge.to), edge.toLayer);
41
+ }
42
+ return [...layerByFile].map(([id, layer]) => ({
43
+ id,
44
+ layer
45
+ })).toSorted((a, b) => compareStrings(a.id, b.id));
46
+ }
47
+ /** Dependencies between layers or files inside layers; packages are left out. */
48
+ function buildGraph(result, config, level) {
49
+ const edges = /* @__PURE__ */ new Map();
50
+ const internal = [];
51
+ for (const edge of result.edges) {
52
+ const ends = endpoints(edge, level, result.rootDir);
53
+ if (ends === null) continue;
54
+ internal.push(edge);
55
+ const [from, to] = ends;
56
+ const key = `${from}\0${to}`;
57
+ const status = edgeStatus(edge, config);
58
+ const existing = edges.get(key);
59
+ edges.set(key, existing === void 0 ? {
60
+ from,
61
+ to,
62
+ count: 1,
63
+ status
64
+ } : {
65
+ ...existing,
66
+ count: existing.count + 1,
67
+ status: worse(existing.status, status)
68
+ });
69
+ }
70
+ return {
71
+ level,
72
+ nodes: collectNodes(result, internal, level),
73
+ edges: [...edges.values()].toSorted((a, b) => compareStrings(a.from, b.from) || compareStrings(a.to, b.to))
74
+ };
75
+ }
76
+ //#endregion
77
+ //#region src/unused/index.ts
78
+ const CONTEXTS = [
79
+ "app",
80
+ "server",
81
+ "shared"
82
+ ];
83
+ const VIRTUAL_IMPORT = /^#(?:imports|components):(.+)$/u;
84
+ function symbolKey(file, name) {
85
+ return `${file}\0${name}`;
86
+ }
87
+ function collectUses(edges) {
88
+ const uses = {
89
+ files: /* @__PURE__ */ new Set(),
90
+ symbols: /* @__PURE__ */ new Set()
91
+ };
92
+ for (const edge of edges) {
93
+ if (edge.to === null) continue;
94
+ const virtual = VIRTUAL_IMPORT.exec(edge.symbol);
95
+ if (edge.kind === "import" && virtual === null) uses.files.add(edge.to);
96
+ else uses.symbols.add(symbolKey(edge.to, virtual?.[1] ?? edge.symbol));
97
+ }
98
+ return uses;
99
+ }
100
+ function isUsed(uses, file, names) {
101
+ return uses.files.has(file) || names.some((name) => uses.symbols.has(symbolKey(file, name)));
102
+ }
103
+ /** Component names by file; `BaseButton` and `LazyBaseButton` are one component. */
104
+ function componentsByFile(components) {
105
+ const byFile = /* @__PURE__ */ new Map();
106
+ for (const [name, target] of components) if (target.file !== null) byFile.set(target.file, [...byFile.get(target.file) ?? [], name]);
107
+ return byFile;
108
+ }
109
+ function shortestName(names) {
110
+ return names.toSorted((a, b) => a.length - b.length || compareStrings(a, b))[0];
111
+ }
112
+ /**
113
+ * Components and auto-imports registered by the project's own layers that nothing references.
114
+ * Layers installed as packages are not checked, so their symbols are never reported.
115
+ */
116
+ function findUnused(result) {
117
+ const ownerOf = createOwnerLookup(result.layers);
118
+ const checked = (target) => {
119
+ const layer = target.file === null ? null : ownerOf(target.file);
120
+ return layer === null || layer.root.includes("/node_modules/") ? null : layer.name;
121
+ };
122
+ const uses = collectUses(result.edges);
123
+ const possiblyUsed = result.dynamicComponentFiles.length > 0;
124
+ const unused = [];
125
+ for (const [file, names] of componentsByFile(result.symbols.components)) {
126
+ const layer = checked({
127
+ file,
128
+ module: null
129
+ });
130
+ if (layer !== null && !isUsed(uses, file, names)) {
131
+ const name = shortestName(names);
132
+ unused.push({
133
+ name,
134
+ kind: "component",
135
+ context: null,
136
+ file,
137
+ layer,
138
+ possiblyUsed
139
+ });
140
+ }
141
+ }
142
+ const seen = /* @__PURE__ */ new Set();
143
+ for (const context of CONTEXTS) for (const [name, target] of result.symbols.imports[context]) {
144
+ const layer = checked(target);
145
+ const key = target.file === null ? "" : symbolKey(target.file, name);
146
+ if (layer === null || target.file === null || seen.has(key)) continue;
147
+ seen.add(key);
148
+ if (!isUsed(uses, target.file, [name])) {
149
+ const { file } = target;
150
+ unused.push({
151
+ name,
152
+ kind: "auto-import",
153
+ context,
154
+ file,
155
+ layer,
156
+ possiblyUsed: false
157
+ });
158
+ }
159
+ }
160
+ const order = new Map(result.layers.map((layer, index) => [layer.name, index]));
161
+ return unused.toSorted((a, b) => (order.get(a.layer) ?? 0) - (order.get(b.layer) ?? 0) || compareStrings(a.file, b.file) || compareStrings(a.name, b.name));
162
+ }
163
+ //#endregion
164
+ export { edgeStatus as i, GRAPH_LEVELS as n, buildGraph as r, findUnused as t };
@@ -0,0 +1,9 @@
1
+ import { s as readJson } from "./layer-factory-xZmIAPrA.mjs";
2
+ import { fileURLToPath } from "node:url";
3
+ //#region src/version.ts
4
+ /** Both `src/` and the bundled `dist/` sit next to package.json. */
5
+ function packageVersion() {
6
+ return readJson(fileURLToPath(new URL("../package.json", import.meta.url))).version ?? "0.0.0";
7
+ }
8
+ //#endregion
9
+ export { packageVersion as t };
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "nuxt-layerscope",
3
+ "version": "0.1.0",
4
+ "description": "Layer boundary checks for Nuxt apps that see auto-imports",
5
+ "keywords": [
6
+ "architecture",
7
+ "auto-imports",
8
+ "boundaries",
9
+ "cli",
10
+ "lint",
11
+ "nuxt",
12
+ "nuxt-layers",
13
+ "nuxt-module"
14
+ ],
15
+ "homepage": "https://github.com/hamedniroomand/nuxt-layerscope/tree/main/packages/layerscope#readme",
16
+ "bugs": "https://github.com/hamedniroomand/nuxt-layerscope/issues",
17
+ "license": "MIT",
18
+ "author": "Hamed Niroomand",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/hamedniroomand/nuxt-layerscope.git",
22
+ "directory": "packages/layerscope"
23
+ },
24
+ "bin": {
25
+ "layerscope": "./dist/bin.mjs"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "CHANGELOG.md"
30
+ ],
31
+ "type": "module",
32
+ "imports": {
33
+ "#src/*": "./src/*",
34
+ "#test/*": "./test/*"
35
+ },
36
+ "exports": {
37
+ ".": {
38
+ "types": "./dist/index.d.mts",
39
+ "default": "./dist/index.mjs"
40
+ },
41
+ "./api": {
42
+ "types": "./dist/api.d.mts",
43
+ "default": "./dist/api.mjs"
44
+ },
45
+ "./eslint": {
46
+ "types": "./dist/eslint.d.mts",
47
+ "default": "./dist/eslint.mjs"
48
+ }
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "dependencies": {
54
+ "@nuxt/kit": "^4.5.2",
55
+ "@nuxt/schema": "^4.5.2",
56
+ "@vue/compiler-sfc": "^3.5.43",
57
+ "cac": "^7.0.0",
58
+ "globals": "^17.12.0",
59
+ "h3": "^1.15.11",
60
+ "jiti": "^2.7.0",
61
+ "oxc-parser": "^0.151.0",
62
+ "oxc-walker": "^1.1.1",
63
+ "pathe": "^2.0.3",
64
+ "tinyglobby": "^0.2.17"
65
+ },
66
+ "devDependencies": {
67
+ "@types/node": "^26.6.3",
68
+ "eslint": "^10.11.0",
69
+ "typescript-eslint": "^8.70.1",
70
+ "vite-plus": "1.0.0",
71
+ "vue-eslint-parser": "^10.4.1"
72
+ },
73
+ "peerDependencies": {
74
+ "eslint": ">=9.0.0",
75
+ "nuxt": "^3.12.0 || ^4.0.0"
76
+ },
77
+ "peerDependenciesMeta": {
78
+ "nuxt": {
79
+ "optional": true
80
+ },
81
+ "eslint": {
82
+ "optional": true
83
+ }
84
+ },
85
+ "engines": {
86
+ "node": ">=22.18.0"
87
+ },
88
+ "scripts": {
89
+ "build": "vp pack",
90
+ "test": "vp test",
91
+ "tarball": "vp run build && vp pm pack --pack-destination ../../.release"
92
+ }
93
+ }