diff --git a/.gitignore b/.gitignore index 505b3e4..3814619 100644 --- a/.gitignore +++ b/.gitignore @@ -174,5 +174,5 @@ dist # Finder (MacOS) folder config .DS_Store -bin -build +# And of course, +.run \ No newline at end of file diff --git a/.run/build b/.run/build deleted file mode 100755 index 79ed72f..0000000 --- a/.run/build +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# Cross build rundir for multiple platforms - -echo "Clean" -rm -rf bin build - -mkdir bin - -echo "linux-x64" -bun build --compile --minify --target=bun-linux-x64 ./src/cli.ts --outfile bin/rundir-linux-x64 - -echo "linux-arm64" -bun build --compile --minify --target=bun-linux-arm64 ./src/cli.ts --outfile bin/rundir-linux-arm64 - -echo "windows-x64" -bun build --compile --minify --target=bun-windows-x64 ./src/cli.ts --outfile bin/rundir-windows-x64 - -echo "macos-arm" -bun build --compile --minify --target=bun-darwin-arm64 ./src/cli.ts --outfile bin/rundir-macos-apple-silicon - -echo "macos-x64" -bun build --compile --minify --target=bun-darwin-x64 ./src/cli.ts --outfile bin/rundir-macos-x64 - -mkdir build - -echo "package" -bun build --target bun --outdir build ./src/cli.ts diff --git a/.run/clean b/.run/clean deleted file mode 100755 index 5c5e0ae..0000000 --- a/.run/clean +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -# Clean build artifacts -rm -rf build diff --git a/.run/format b/.run/format deleted file mode 100755 index 4570fa7..0000000 --- a/.run/format +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -# Format the repo, writing the formatted files to disk -bunx biome format . --write - diff --git a/.run/test b/.run/test deleted file mode 100755 index 5a3f48e..0000000 --- a/.run/test +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -# A rundir script -echo 'Hello, world!' diff --git a/LICENSE b/LICENSE deleted file mode 100644 index efb9808..0000000 --- a/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to diff --git a/README.md b/README.md index e98b9b2..a474774 100644 --- a/README.md +++ b/README.md @@ -24,30 +24,6 @@ $ cd some/nested/path $ rundir x build # runs the `build` script in the original `.run` directory ``` -## CLI +## Why? -``` -Usage: rundir [command] - -Commands: - x Run a script from the nearest rundir (alias: run) - new Create a new script in the rundir (alias: create) - ls List all scripts in the rundir - ls Print the absolute path to a specific script - which Print the absolute path of the rundir - help Show this help message -``` - -## `rdx` alias - -Consider setting an alias for `rundir x` to `rdx` for convenience: - -```shell -alias rdx="rundir x" -``` - -Then you can run scripts with `rdx` instead of `rundir x`: - -```shell -rdx build -``` +Cuz \ No newline at end of file diff --git a/biome.json b/biome.json deleted file mode 100644 index b6909ea..0000000 --- a/biome.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": true - }, - "files": { - "ignoreUnknown": false, - "ignore": ["dist", "*.d.ts", "*.json", "build"] - }, - "formatter": { - "enabled": true, - "indentStyle": "space", - "indentWidth": 2 - }, - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "suspicious": { - "noArrayIndexKey": "off" - }, - "complexity": { - "useLiteralKeys": "off" - } - } - }, - "javascript": { - "formatter": { - "quoteStyle": "double" - } - } -} diff --git a/bun.lock b/bun.lock deleted file mode 100644 index eeafa95..0000000 --- a/bun.lock +++ /dev/null @@ -1,49 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "rundir", - "dependencies": { - "chalk": "^5.4.1", - }, - "devDependencies": { - "@biomejs/biome": "^1.9.4", - "@types/bun": "latest", - }, - "peerDependencies": { - "typescript": "^5.8.3", - }, - }, - }, - "packages": { - "@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="], - - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="], - - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="], - - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="], - - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="], - - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="], - - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="], - - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="], - - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="], - - "@types/bun": ["@types/bun@1.2.12", "", { "dependencies": { "bun-types": "1.2.12" } }, "sha512-lY/GQTXDGsolT/TiH72p1tuyUORuRrdV7VwOTOjDOt8uTBJQOJc5zz3ufwwDl0VBaoxotSk4LdP0hhjLJ6ypIQ=="], - - "@types/node": ["@types/node@22.15.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw=="], - - "bun-types": ["bun-types@1.2.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-tvWMx5vPqbRXgE8WUZI94iS1xAYs8bkqESR9cxBB1Wi+urvfTrF1uzuDgBHFAdO0+d2lmsbG3HmeKMvUyj6pWA=="], - - "chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], - - "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - } -} diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..94404a3 Binary files /dev/null and b/bun.lockb differ diff --git a/package.json b/package.json index 1ecad94..15aae40 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,15 @@ { - "name": "@endeavorance/rundir", - "type": "module", - "module": "src/cli.ts", - "version": "0.2.0", - "bin": { - "rundir": "build/cli.js" - }, - "files": [ - "build" - ], - "devDependencies": { - "@biomejs/biome": "^1.9.4", - "@types/bun": "latest" - }, - "peerDependencies": { - "typescript": "^5.8.3" - }, - "dependencies": { - "chalk": "^5.4.1" - } + "name": "rundir", + "module": "src/cli.ts", + "type": "module", + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "chalk": "^5.3.0" + } } diff --git a/src/cli.ts b/src/cli.ts index d27544a..40c4507 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,357 +1,102 @@ -import { existsSync } from "node:fs"; -import path from "node:path"; import { parseArgs } from "node:util"; -import { $, Glob } from "bun"; -import chalk from "chalk"; -import pkg from "../package.json" assert { type: "json" }; +import { edit, ls, newScript, runScript, which } from "./scriptFile"; -interface ScriptData { - name: string; - description: string; - absolutePath: string; +const KNOWN_COMMANDS = [ + "ls", + "help", + "new", + "create", + "which", + "x", + "run", + "edit", +] as const; +type Command = (typeof KNOWN_COMMANDS)[number]; + +function isCommand(command: string): command is Command { + return KNOWN_COMMANDS.includes(command as Command); } -class CLIError extends Error { - name = "CLIError"; - - constructor(message: string) { - super(`rundir error: ${message}`); - } -} - -class NoRundirError extends CLIError { - name = "NoRundirError"; - - constructor() { - super("No rundir found in current directory tree"); - } -} - -const TEMPLATE_FILE_PATH = process.env["RUNDIR_TEMPLATE"] ?? null; -const DEFAULT_TEMPLATE = ` -#!/usr/bin/env bash -# Description... -`.trim(); -const VERSION = pkg.version; -const COMMENT_STARTERS = ["#", "//", "--"]; -const COMMANDS = ["ls", "help", "new", "create", "which", "x", "run"] as const; -type Command = (typeof COMMANDS)[number]; - -/** - * Given an unknown string, parse a valid command or throw - * - * @param command The command to parse - * @returns The parsed command - */ -function parseCommand(command: string): Command { - if (COMMANDS.includes(command as Command)) { - return command as Command; - } - - throw new CLIError(`Unknown command: ${command}`); -} - -/** - * Fund a rundir, checking the current or provided directory - * path and walking up the tree if no dir is found - * - * @param fromPath The path to start searching from - * @returns The path to the rundir, or null if not found - */ -function getRundirPath(fromPath?: string): string { - const currentDir = fromPath ?? process.cwd(); - const potentialPath = path.resolve(currentDir, ".run"); - - if (currentDir === path.dirname(currentDir)) { - throw new NoRundirError(); - } - - if (existsSync(potentialPath)) { - return potentialPath; - } - - return getRundirPath(path.dirname(currentDir)); -} - -/** - * Given the name of a script, return the absolute path to the script - * - * @param name The name of the script - * @returns The absolute path to the script - */ -function getScriptPath(name: string): string { - return path.resolve(getRundirPath(), name); -} - -async function scriptExists(name: string): Promise { - return Bun.file(getScriptPath(name)).exists(); -} - -/** - * Given the name of a script, search for the script in the rundir and - * return the absolute path to the script. - * Searches the current directory's `.run` directory first, then works - * up the directory tree until it reaches the home directory. - * - * If the script is not found, returns `null`. - * - * @param name The name of the script to find the path of - */ -async function findScriptPath(name: string): Promise { - const currentRundir = getRundirPath(); - - if (!currentRundir) { - throw new NoRundirError(); - } - - const expectedPath = path.resolve(currentRundir, name); - - const checkFile = Bun.file(expectedPath); - - const exists = await checkFile.exists(); - - if (!exists) { - return null; - } - - return expectedPath; -} - -/** - * Parses a line of text and returns the comment line or null - * if the line does not start with a comment starter - * - * @param line The line of text to parse - * @returns The comment line or null - */ -function getCommentLine(line: string): string | null { - for (const starter of COMMENT_STARTERS) { - if (line.startsWith(starter)) { - return line.replace(starter, "").trim(); - } - } - - return null; -} - -/** - * Given a script name, read the script file and return - * - * @param scriptName The name of the script to read - * @returns The script data - */ -async function readScriptData(scriptName: string): Promise { - const scriptPath = await findScriptPath(scriptName); - - if (!scriptPath) { - throw new CLIError( - `Script "${scriptName}" not found in any rundir in the directory tree.`, - ); - } - - const scriptFile = Bun.file(scriptPath); - const scriptContent = await scriptFile.text(); - const [_, ...lines] = scriptContent.split("\n"); - - const descriptionLines: string[] = []; - - for (const line of lines) { - const commentLine = getCommentLine(line); - if (!commentLine) { - break; - } - - descriptionLines.push(commentLine); - } - - const description = - descriptionLines.length > 0 - ? descriptionLines.join(" ") - : "No description provided"; - - return { - absolutePath: scriptPath, - name: path.basename(scriptPath), - description: description.trim(), - }; -} - -/** - * List the scripts in the rundir or the path to a script if provided - * @command - */ -async function ls(scriptName?: string) { - if (scriptName) { - const pathname = getScriptPath(scriptName); - const exists = await scriptExists(pathname); - if (!exists) { - throw new CLIError(`Script "${scriptName}" not found.`); - } - - console.log(pathname); - return; - } - - const currentRundir = getRundirPath(); - const filesInDir = new Glob(`${currentRundir}/*`).scan(); - const scriptLines: string[] = []; - - for await (const file of filesInDir) { - const scriptName = path.basename(file); - const scriptData = await readScriptData(scriptName); - scriptLines.push( - `${chalk.dim("$")} ${scriptName} ${chalk.dim(`- ${scriptData.description}`)}`, - ); - } - - const sorted = scriptLines.sort((a, b) => a.localeCompare(b)); - - console.log(chalk.dim(currentRundir)); - console.log(sorted.join("\n")); -} - -/** - * Prints the absolute path of the rundir - * @command - */ -async function which() { - console.log(getRundirPath()); -} - -/** - * Create a new script in the rundir - * @command - */ -async function newScript(name: string) { - const scriptPath = getScriptPath(name); - const scriptFile = Bun.file(scriptPath); - let template = DEFAULT_TEMPLATE; - - if (await scriptFile.exists()) { - throw new CLIError(`Script "${name}" already exists.`); - } - - if (TEMPLATE_FILE_PATH) { - const templateFile = Bun.file(TEMPLATE_FILE_PATH); - if (await templateFile.exists()) { - template = await templateFile.text(); - } else { - console.warn( - chalk.yellow(`Warn: Template "${TEMPLATE_FILE_PATH}" not found`), - ); - } - } - - await Bun.write(scriptFile, template); - await $`chmod +x ${scriptPath}`; - console.log(scriptFile); -} - -/** - * Executes a script from the rundir - * @command - */ -async function runScript(name: string) { - const scriptPath = await findScriptPath(name); - - if (!scriptPath) { - console.error(`Script "${name}" not found.`); - process.exit(1); - } - - const rundir = path.dirname(scriptPath); - const cwd = path.resolve(rundir, ".."); - - const proc = Bun.spawn([scriptPath], { - cwd, - env: { - ...process.env, - RUNDIR_PATH: rundir, - RUNDIR_CWD: cwd, - RUNDIR_SCRIPT_PATH: scriptPath, - }, - stdout: Bun.stdout, - stderr: Bun.stderr, - stdin: Bun.stdin, - }); - - await proc.exited; -} - -/** - * Prints the help message - * @command - */ -async function help() { - console.log( - ` -rundir ${VERSION} +function help() { + console.log( + ` Usage: rundir [command] Commands: - x Run a script from the nearest rundir (alias: run) - new Create a new script in the rundir (alias: create) - ls List all scripts in the rundir - ls Print the absolute path to a specific script - which Print the absolute path of the rundir - help Show this help message + new - Create a new script in the rundir (alias: create) + edit - Open a script in the editor + x - Run a script from the nearest rundir (alias: run) + which - Print the absolute path of the rundir + ls - List all scripts in the rundir + help - Show this help message `.trim(), - ); + ); } -/** - * Parses the command line arguments and returns the positionals - * @returns The positional arguments for the command - */ function readArgs(): string[] { - const { positionals } = parseArgs({ - args: Bun.argv, - allowPositionals: true, - strict: true, - }); + const { positionals } = parseArgs({ + args: Bun.argv, + allowPositionals: true, + strict: true, + }); - return positionals.slice(2); + return positionals.slice(2); } -type Handler = (...args: string[]) => void | Promise; -type HandlerDescriptor = [number, Handler]; -const COMMAND_HANDLERS: Record = { - ls: [0, ls], - help: [0, help], - which: [0, which], - new: [1, newScript], - create: [1, newScript], - x: [1, runScript], - run: [1, runScript], -}; +const args = readArgs(); -async function main() { - const [rawCommand, ...params] = readArgs(); - - // No arguments provided - list scripts - if (rawCommand === undefined) { - return ls(); - } - - const command = parseCommand(rawCommand); - const [requiredArgs, handler] = COMMAND_HANDLERS[command]; - - if (params.length < requiredArgs) { - throw new CLIError(`Missing required arguments for command "${command}"`); - } - - return handler(...params); +// Default functionality is to list +if (args.length === 0) { + await ls(); + process.exit(0); } -try { - await main(); -} catch (e) { - if (e instanceof CLIError) { - console.error(chalk.red(e.message)); - process.exit(0); - } +const command = args[0]; - console.error(chalk.red("An unexpected error occurred:")); - console.error(e); - process.exit(0); +if (!isCommand(command)) { + console.error(`Unknown command: ${args[0]}`); + process.exit(1); } + +switch (command) { + case "ls": + await ls(); + break; + case "help": + help(); + break; + case "which": + which(); + break; + case "new": + case "create": + if (args.length < 2) { + console.error("Missing script name"); + process.exit(1); + } + + await newScript(args[1]); + break; + case "x": + case "run": + if (args.length < 2) { + console.error("Missing script name"); + process.exit(1); + } + + await runScript(args[1]); + break; + case "edit": + if (args.length < 2) { + console.error("Missing script name"); + process.exit(1); + } + + edit(args[1]); + break; + default: + console.error(`Unknown command "${command}"`); + break; +} + +process.exit(0); diff --git a/src/scriptFile.ts b/src/scriptFile.ts new file mode 100644 index 0000000..8d323ed --- /dev/null +++ b/src/scriptFile.ts @@ -0,0 +1,167 @@ +import { $, Glob } from "bun"; +import chalk from "chalk"; +import path from "node:path"; +import { homedir } from "node:os"; +import { existsSync } from "node:fs"; + +const RUNDIR_PATH = path.join(process.cwd(), ".run"); + +interface ScriptData { + name: string; + description: string; + absolutePath: string; +} + +function findNearestRundir(): string | null { + let currentDir = process.cwd(); + + while (currentDir !== path.dirname(currentDir)) { + const potentialPath = path.join(currentDir, ".run"); + if (existsSync(potentialPath)) { + return potentialPath; + } + currentDir = path.dirname(currentDir); + } + + const homeDirPath = path.join(homedir(), ".run"); + if (existsSync(homeDirPath)) { + return homeDirPath; + } + + return null; +} + +function makeScriptPath(name: string): string { + const rundir = findNearestRundir(); + + if (!rundir) { + throw new Error("No rundir found in the current directory tree."); + } + + return path.join(rundir, name); +} + +/** + * Given the name of a script, search for the script in the rundir and + * return the absolute path to the script. + * Searches the current directory's `.run` directory first, then works + * up the directory tree until it reaches the home directory. + * + * If the script is not found, returns `null`. + * + * @param name The name of the script to find the path of + */ +async function findScriptPath(name: string): Promise { + let currentDir = process.cwd(); + + while (currentDir !== path.dirname(currentDir)) { + const potentialPath = path.join(currentDir, ".run", name); + if (await Bun.file(potentialPath).exists()) { + return potentialPath; + } + currentDir = path.dirname(currentDir); + } + + const homeDirPath = path.join(homedir(), ".run", name); + if (await Bun.file(homeDirPath).exists()) { + return homeDirPath; + } + + return null; +} + +export async function readScriptData(scriptName: string): Promise { + const scriptPath = await findScriptPath(scriptName); + + if (!scriptPath) { + throw new Error( + `Script "${scriptName}" not found in any rundir in the directory tree.`, + ); + } + + const scriptFile = Bun.file(scriptPath); + const scriptContent = await scriptFile.text(); + + const lines = scriptContent.split("\n"); + + const description = + lines.find((line) => line.startsWith("#DESCRIPTION#")) || + "No description provided."; + + return { + absolutePath: scriptPath, + name: path.basename(scriptPath), + description: description.replace("#DESCRIPTION#", "").trim(), + }; +} + +export async function ls() { + const filesInDir = new Glob(`${RUNDIR_PATH}/*`).scan(); + const scriptLines: string[] = []; + + for await (const file of filesInDir) { + const scriptName = path.basename(file); + const scriptData = await readScriptData(scriptName); + scriptLines.push( + `${chalk.dim("$")} ${scriptName} ${chalk.dim(`- ${scriptData.description}`)}`, + ); + } + + const sorted = scriptLines.sort((a, b) => a.localeCompare(b)); + + console.log(sorted.join("\n")); +} + +export async function edit(scriptName: string) { + const scriptPath = await findScriptPath(scriptName); + + if (!scriptPath) { + console.error(`Script "${scriptName}" not found.`); + process.exit(1); + } + + Bun.openInEditor(scriptPath); +} + +/** + * Prints the absolute path of the rundir + */ +export async function which() { + const nearest = findNearestRundir(); + + if (!nearest) { + console.error("No rundir found in the current directory tree."); + process.exit(1); + } + + console.log(path.resolve(nearest)); +} + +export async function newScript(name: string) { + const scriptPath = makeScriptPath(name); + const scriptFile = Bun.file(scriptPath); + + if (await scriptFile.exists()) { + console.error(`Script "${name}" already exists.`); + process.exit(1); + } + + await Bun.write( + scriptFile, + "#!/usr/bin/env bash\n#DESCRIPTION# A rundir script\necho 'Hello, world!'\n", + ); + await $`chmod +x ${scriptPath}`; + + console.log(`Created script "${name}"`); +} + +export async function runScript(name: string) { + const scriptPath = await findScriptPath(name); + + if (!scriptPath) { + console.error(`Script "${name}" not found.`); + process.exit(1); + } + + await $`${scriptPath}`; +} diff --git a/tsconfig.json b/tsconfig.json index 70725ca..ffc08ab 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,28 +1,27 @@ { - "compilerOptions": { - // Enable latest features - "lib": [ - "ESNext", - "DOM" - ], - "target": "ESNext", - "module": "ESNext", - "moduleDetection": "force", - "jsx": "react-jsx", - "allowJs": true, - // Bundler mode - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "noEmit": true, - // Best practices - "strict": true, - "skipLibCheck": true, - "noFallthroughCasesInSwitch": true, - // Some stricter flags (disabled by default) - "noUnusedLocals": true, - "noUnusedParameters": true, - "noPropertyAccessFromIndexSignature": true, - "noUncheckedIndexedAccess": true, - } + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } }