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.
- package/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +289 -0
- package/dist/analyze-DS51xy6X.mjs +337 -0
- package/dist/analyze-YePAJEVS.mjs +2 -0
- package/dist/api.d.mts +148 -0
- package/dist/api.mjs +6 -0
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +374 -0
- package/dist/component-files-DgOXNE6l.mjs +12 -0
- package/dist/define-DNRGLVG_.d.mts +126 -0
- package/dist/define-l62B9fVk.mjs +6 -0
- package/dist/define.d.mts +2 -0
- package/dist/define.mjs +2 -0
- package/dist/effective-Dqg4EwH_.d.mts +7 -0
- package/dist/eslint.d.mts +7 -0
- package/dist/eslint.mjs +213 -0
- package/dist/file-analysis-Bkq_69x4.mjs +1085 -0
- package/dist/index.d.mts +24 -0
- package/dist/index.mjs +364 -0
- package/dist/layer-factory-xZmIAPrA.mjs +168 -0
- package/dist/report-BNW0GCl9.mjs +131 -0
- package/dist/report-DtAi0vPb.mjs +2 -0
- package/dist/strings-CrOjNZmB.mjs +17 -0
- package/dist/summary-DiUYKMPe.mjs +10 -0
- package/dist/unused-Dxep1p6u.mjs +164 -0
- package/dist/version-D6whhsHH.mjs +9 -0
- package/package.json +93 -0
|
@@ -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,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
|
+
}
|