UNPKG

preamble

Version:

Automated License & Metadata applicators for Codebases.

225 lines (188 loc) 6.42 kB
#!/usr/bin/env node /* ******************************************************* * preamble * * @license * * Apache-2.0 * * Copyright 2024 Alex Stevovich * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @meta * * package_name: preamble * file_name: src/cli.js * purpose: {{PURPOSE}} * * @system * * generated_on: 2025-03-09T02:11:28.478Z * certified_version: 1.0.0 * file_uuid: cdd4332d-5f04-4a4e-a453-7854ac4ce38c * file_size: 6423 bytes * file_hash: 0d34fd40b0c619ded8b081a4bfe2104bc833aa6add67f4437e0f6a062e4cef66 * mast_hash: 9065cac05eb806118170eae0c5af6908d4a54a1cd7b6eae679a0981bab28c8c8 * generated_by: preamble on npm! * * [Preamble Metadata] ********************************************************/ import fs from 'fs/promises'; import path from 'path'; import { inscribeJs } from './index.js'; // Utility Functions async function getPackageJson() { try { const packageJsonPath = path.resolve(process.cwd(), 'package.json'); const packageData = await fs.readFile(packageJsonPath, 'utf-8'); return JSON.parse(packageData); } catch (_error) { console.warn( '⚠️ Warning: Could not read package.json. Defaulting to empty object.', ); return {}; } } async function findFilesByExtension(dir, extensions, ignoredPaths = []) { const results = []; async function searchDirectory(directory) { const entries = await fs.readdir(directory, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(directory, entry.name); if (entry.isDirectory()) { if (!ignoredPaths.includes(entry.name)) { await searchDirectory(fullPath); } } else if (extensions.some((ext) => entry.name.endsWith(ext))) { results.push(fullPath); } } } await searchDirectory(dir); return results; } async function runSequentially(tasks, fn) { for (const task of tasks) { await fn(task); } } // Parse CLI Arguments const args = new Set(process.argv.slice(2)); if (args.has('--help')) { console.log(` Usage: npx preamble --apply --dir <directory> [--template <preamble>] Options: --apply Required. Enables update mode. --dir Required. Directory containing files to process. --template Optional. Path to the preamble template (defaults to './.preamble'). --help Show this help message. Examples: npx preamble --apply --dir ./src/ npx preamble --apply --dir ./src/ --template ./internal/.preamble Errors: - Fails if --apply is missing. - Fails if --dir is missing. - Defaults to './.preamble' if --template is not provided. `); process.exit(0); } // Ensure Required Flags if (!args.has('--apply')) { console.error('❌ Error: --apply is required.'); process.exit(1); } if (!args.has('--dir')) { console.error('❌ Error: --dir is required.'); process.exit(1); } // Extract `--dir` and `--template` const dirIndex = process.argv.indexOf('--dir'); const templateIndex = process.argv.indexOf('--template'); if (dirIndex === -1 || !process.argv[dirIndex + 1]) { console.error('❌ Error: No valid directory provided for --dir.'); process.exit(1); } const configPath = process.argv[dirIndex + 1]; const configMastPath = templateIndex !== -1 && process.argv[templateIndex + 1] ? process.argv[templateIndex + 1] : './.preamble'; runUpdateMode(configPath, configMastPath); async function runUpdateMode(configPath, configMastPath) { const projectRoot = process.cwd(); const resolvedPath = path.resolve(configPath); configMastPath = path.resolve(configMastPath); if (!resolvedPath.startsWith(projectRoot)) { console.error( `❌ Refusing to run outside project directory.\nProject root: ${projectRoot}\nAttempted root: ${resolvedPath}`, ); process.exit(1); } const forbiddenRoots = ['/', 'C:\\', 'C:/']; if (forbiddenRoots.includes(resolvedPath)) { console.error( `❌ Refusing to run on entire filesystem root (${resolvedPath}).`, ); process.exit(1); } try { await fs.access(configMastPath); } catch { console.error(`❌ Template not found at "${configMastPath}".`); process.exit(1); } const extensions = ['.mjs', '.js', '.cjs', '.ts', '.tsx', '.jsx']; const ignoredPaths = ['node_modules', '.git', 'dist', 'build']; const template = await fs.readFile(configMastPath, 'utf-8'); const files = await findFilesByExtension( resolvedPath, extensions, ignoredPaths, ); const visibleFiles = files.filter( (file) => !path .relative(projectRoot, file) .split(path.sep) .some((part) => part.startsWith('.')), ); const MAX_FILES = 1000; if (visibleFiles.length > MAX_FILES) { console.warn( `⚠️ Warning: You're about to process ${visibleFiles.length} files.`, ); } const MAX_FILE_SIZE = 1024 * 100; // 100 KB const pkg = await getPackageJson(); await runSequentially(files, async (file) => { const stats = await fs.stat(file); if (stats.size > MAX_FILE_SIZE) { console.warn( `⚠️ Skipping large file (${stats.size} bytes): ${file}`, ); return; } const content = await fs.readFile(file, 'utf-8'); const data = { file: { path: file, size: stats.size }, pkg, }; const { result, updated } = await inscribeJs(content, template, data); if (updated) { await fs.writeFile(file, result, 'utf-8'); console.log('✅ Preamble applied to', file); } }); }