Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | 251x 251x 251x 251x 251x 251x 251x 251x 251x 251x 251x 251x 251x 251x 251x 251x 251x 251x 251x 6x 6x 6x 6x 6x 6x 251x 251x 251x 251x 251x 251x 251x 4x 4x 4x 4x 4x 4x 6x 8x 6x 6x 8x 6x 6x 8x 6x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 6x 6x 6x 6x 6x 4x 4x 4x 4x 4x 3x 3x 3x 3x 3x 3x 4x 4x 4x 4x 4x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 4x 4x 14x 14x 14x 14x 14x 14x 14x 14x 15x 15x 15x 15x 15x 15x 15x 14x 4x 4x 2x 2x 2x 2x 2x 2x 4x 251x 251x | import { AtlasEdgeKind, AtlasNodeKind } from '../../lib/compiler/atlas/AppModel.js';
import { buildModel } from './atlas.js';
import { bold, cyan, gray, yellow } from '../colors.js';
// `inspect` used to answer these questions with its own scanners - one regex
// for routes, one for class names, one substring search for "is this component
// used". The compiler already knows all of it, so this reads the model
// instead. The visible consequence is that "unused" now means nothing renders
// or imports it, rather than its name not appearing in another file.
/**
* Whether anything in the application renders or imports a component.
*
* A page is never reported unused: it is reached by a route, or it is an entry
* point the router has simply not been pointed at yet.
* @param {object} model - The model.
* @param {object} node - A component node.
* @returns {boolean} True when nothing reaches it.
*/
function isUnused(model, node) {
if (node.kind !== AtlasNodeKind.COMPONENT) return false;
return !model
.incoming(node.id)
.some((edge) => edge.kind === AtlasEdgeKind.RENDERS || edge.kind === AtlasEdgeKind.IMPORTS);
}
/**
* Prints the project's page, component and bridge hierarchy.
* @param {object} cli - The AvenxCLI instance.
* @returns {void}
*/
export function runInspect(cli) {
const srcRel = (cli.config && cli.config.srcDir) || 'src';
const model = buildModel(cli);
/** @type {Map<string, string>} */
const routeOfPage = new Map();
for (const route of model.nodesOfKind(AtlasNodeKind.ROUTE)) {
for (const edge of model.outgoing(route.id)) {
if (edge.kind !== AtlasEdgeKind.ROUTES_TO) continue;
const existing = routeOfPage.get(edge.to);
// Prefer a specific path over the root when a page answers both.
if (!existing || (existing === '/' && route.name !== '/')) {
routeOfPage.set(edge.to, route.name);
}
}
}
const pages = model.nodesOfKind(AtlasNodeKind.PAGE);
const components = model.nodesOfKind(AtlasNodeKind.COMPONENT);
const bridges = model.nodesOfKind(AtlasNodeKind.BRIDGE);
const guards = model.nodesOfKind(AtlasNodeKind.GUARD);
console.log(bold(cyan(`📦 Avenx Project Hierarchy (${srcRel}/)`)));
const categories = [
{
title: `📄 Pages (${pages.length})`,
items: pages.map((page) => {
const route = routeOfPage.get(page.id);
return { text: `${page.name}${route ? ` (${route})` : ''} -> ${page.file}`, warn: false };
}),
},
{
title: `🧩 Components (${components.length})`,
items: components.map((component) => {
const unused = isUnused(model, component);
return {
text: `${component.name} -> ${component.file}${unused ? ' (⚠️ Unused)' : ''}`,
warn: unused,
};
}),
},
{
title: `🌉 Bridges (${bridges.length})`,
items: bridges.map((bridge) => {
const consumers = model.incoming(bridge.id).filter((edge) => edge.kind === AtlasEdgeKind.IMPORTS).length;
const unused = consumers === 0;
return {
text: `${bridge.name} -> ${bridge.file}${unused ? ' (⚠️ Not imported anywhere)' : ''}`,
warn: unused,
};
}),
},
];
if (guards.length > 0) {
categories.push({
title: `🛡️ Guards (${guards.length})`,
items: guards.map((guard) => {
const routes = model
.incoming(guard.id)
.filter((edge) => edge.kind === AtlasEdgeKind.GUARDED_BY)
.map((edge) => model.getNode(edge.from).name);
const unused = routes.length === 0;
return {
text: `${guard.name} -> ${guard.file}${routes.length ? ` (${routes.join(', ')})` : ' (⚠️ Not attached to any route)'}`,
warn: unused,
};
}),
});
}
for (let cIdx = 0; cIdx < categories.length; cIdx++) {
const category = categories[cIdx];
const isLastCategory = cIdx === categories.length - 1;
const catPrefix = isLastCategory ? '└── ' : '├── ';
const childIndent = isLastCategory ? ' ' : '│ ';
console.log(bold(`${catPrefix}${category.title}`));
for (let iIdx = 0; iIdx < category.items.length; iIdx++) {
const item = category.items[iIdx];
const isLastItem = iIdx === category.items.length - 1;
const itemPrefix = isLastItem ? '└── ' : '├── ';
// Styling wraps the whole line so the tree stays aligned and greppable.
const line = `${childIndent}${itemPrefix}${item.text}`;
console.log(item.warn ? yellow(line) : line);
}
}
if (model.unresolved.length > 0) {
console.log(
gray(
`\n${model.unresolved.length} relationship${model.unresolved.length === 1 ? '' : 's'} could not be resolved; run \`avenx atlas\` for detail.`,
),
);
}
}
export default runInspect;
|