UNPKG

fresh-onion

Version:

## What is this?

243 lines 10.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateConfig = validateConfig; exports.findFileRecursive = findFileRecursive; exports.readConfig = readConfig; exports.getTsFilesInLayer = getTsFilesInLayer; exports.getImportsInTsFile = getImportsInTsFile; exports.checkLayer = checkLayer; exports.checkLayers = checkLayers; exports.main = main; const promises_1 = require("fs/promises"); const path = __importStar(require("path")); const typescript_1 = __importDefault(require("typescript")); let hasErrors = false; function validateConfig(config) { const { layers, rules } = config; const layerNames = Object.keys(layers); const ruleFromLayers = rules.map((rule) => rule.from); for (const rule of rules) { if (!layerNames.includes(rule.from)) { console.log(`Rule from layer ${rule.from} does not exist`); hasErrors = true; return; } for (const allowedImport of rule.allowedImports) { if (!layerNames.includes(allowedImport)) { console.log(`Rule from layer ${rule.from} allows import from non-existent layer ${allowedImport}`); hasErrors = true; return; } } } for (const layer of layerNames) { if (!ruleFromLayers.includes(layer)) { console.log(`Layer ${layer} has no rules`); hasErrors = true; return; } } } function findFileRecursive(dir, targetFile) { return __awaiter(this, void 0, void 0, function* () { const entries = yield (0, promises_1.readdir)(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isFile() && entry.name === targetFile) { return fullPath; } if (entry.isDirectory()) { const found = yield findFileRecursive(fullPath, targetFile); if (found) return found; } } return null; }); } function readConfig() { return __awaiter(this, void 0, void 0, function* () { const fileName = "onion.config.json"; const startDir = process.cwd(); try { const configPath = yield findFileRecursive(startDir, fileName); if (!configPath) { throw new Error(`❌ Could not find ${fileName}`); } console.log(`Using config ${configPath}`); const data = yield (0, promises_1.readFile)(configPath, "utf-8"); const config = JSON.parse(data); validateConfig(config); return config; } catch (err) { throw new Error(`Failed to load config: ${err}`); } }); } function getTsFilesInLayer(layerPath, baseDir) { return __awaiter(this, void 0, void 0, function* () { const results = []; const absoluteLayerPath = path.resolve(baseDir, layerPath); function walk(dir) { return __awaiter(this, void 0, void 0, function* () { const entries = yield (0, promises_1.readdir)(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { yield walk(fullPath); } else if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) { results.push(fullPath); } } }); } yield walk(absoluteLayerPath); return results; }); } ; function getImportsInTsFile(tsFile, baseDir) { const sourceCode = typescript_1.default.sys.readFile(tsFile); if (!sourceCode) return []; const sourceFile = typescript_1.default.createSourceFile(tsFile, sourceCode, typescript_1.default.ScriptTarget.Latest, true); const imports = []; sourceFile.forEachChild((node) => { if (typescript_1.default.isImportDeclaration(node) && node.moduleSpecifier && typescript_1.default.isStringLiteral(node.moduleSpecifier)) { const importPath = node.moduleSpecifier.text; if (!importPath.startsWith(".") && !importPath.startsWith("/")) return; const tsFileDir = path.dirname(tsFile); const absoluteImportPath = path.resolve(tsFileDir, importPath); const normalizedPath = path.relative(baseDir, absoluteImportPath); const line = typescript_1.default.getLineAndCharacterOfPosition(sourceFile, node.moduleSpecifier.getStart()).line; const character = typescript_1.default.getLineAndCharacterOfPosition(sourceFile, node.moduleSpecifier.getStart()).character; imports.push({ normalizedPath, line, character }); } }); return imports; } function checkLayer(layer, config, baseDir) { return __awaiter(this, void 0, void 0, function* () { var _a; const layerPath = config.layers[layer]; const tsFiles = yield getTsFilesInLayer(layerPath, baseDir); const rule = config.rules.find((rule) => rule.from === layer); if (!rule) { console.warn(`No rules defined for layer ${layer}`); return; } for (const tsFile of tsFiles) { const imports = getImportsInTsFile(tsFile, baseDir); for (const imported of imports) { const importedLayer = (_a = Object.entries(config.layers).find(([_, layerPath]) => { const resolvedPath = path.resolve(baseDir, layerPath); const absoluteImport = path.resolve(baseDir, imported.normalizedPath); return absoluteImport.startsWith(resolvedPath); })) === null || _a === void 0 ? void 0 : _a[0]; if (!importedLayer) { console.log(`❓ Could not determine layer for import path: ${imported} in ${tsFile}`); hasErrors = true; continue; } if (importedLayer === layer) { continue; } if (!rule.allowedImports.includes(importedLayer)) { const filePath = path.relative(baseDir, tsFile) + ":" + imported.line + ":" + imported.character; const importPath = path.relative(baseDir, imported.normalizedPath) + ".ts"; console.log(`❌ ${layer} (${filePath}) is importing from ${importedLayer} (${importPath})`); hasErrors = true; } } } }); } function checkLayers(config, baseDir) { return __awaiter(this, void 0, void 0, function* () { const layers = Object.keys(config.layers); for (const layer of layers) { yield checkLayer(layer, config, baseDir); } }); } function main() { return __awaiter(this, void 0, void 0, function* () { const config = yield readConfig(); const configPath = yield findFileRecursive(process.cwd(), "onion.config.json"); if (!configPath) { throw new Error("Config file not found."); } const baseDir = path.dirname(configPath); yield checkLayers(config, baseDir); if (hasErrors) { console.log("👎 Rotten 🧅"); process.exit(1); } else { console.log("👍 Fresh 🧅"); process.exit(0); } }); } (() => __awaiter(void 0, void 0, void 0, function* () { if (require.main === module) { yield main(); } }))(); //# sourceMappingURL=lib.js.map