diff --git a/.gitignore b/.gitignore
index ce007ac..505b3e4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -172,4 +172,7 @@ dist
.idea
# Finder (MacOS) folder config
-.DS_Store
\ No newline at end of file
+.DS_Store
+
+bin
+build
diff --git a/.run/build b/.run/build
index 919efcd..79ed72f 100755
--- a/.run/build
+++ b/.run/build
@@ -1,3 +1,27 @@
#!/usr/bin/env bash
-#DESCRIPTION# Compile the binary cli
-bun build --target bun --compile ./src/cli.ts --outfile rundir
+# 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
new file mode 100755
index 0000000..5c5e0ae
--- /dev/null
+++ b/.run/clean
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+# Clean build artifacts
+rm -rf build
diff --git a/.run/format b/.run/format
index fbe8e31..4570fa7 100755
--- a/.run/format
+++ b/.run/format
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
-#DESCRIPTION# Format the repo, writing the formatted files to disk
-bunx biome format . --write
\ No newline at end of file
+# Format the repo, writing the formatted files to disk
+bunx biome format . --write
+
diff --git a/.run/test b/.run/test
index 2c1e99d..5a3f48e 100755
--- a/.run/test
+++ b/.run/test
@@ -1,3 +1,3 @@
#!/usr/bin/env bash
-#DESCRIPTION# A rundir script
+# A rundir script
echo 'Hello, world!'
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..efb9808
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+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 a396e1c..e98b9b2 100644
--- a/README.md
+++ b/README.md
@@ -26,40 +26,28 @@ $ rundir x build # runs the `build` script in the original `.run` directory
## CLI
-### List scripts
+```
+Usage: rundir [command]
-```shell
-$ rundir
+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
```
-### Create script
+## `rdx` alias
+
+Consider setting an alias for `rundir x` to `rdx` for convenience:
```shell
-$ rundir create myscript
+alias rdx="rundir x"
```
-### Run a script
+Then you can run scripts with `rdx` instead of `rundir x`:
```shell
-$ rundir x myscript
-
-# ... or if you're in the root directory just...
-$ .run/myscript
+rdx build
```
-
-### Print the path of the current rundir
-
-```shell
-$ rundir which
-```
-
-### Open a script in your editor
-
-```shell
-$ rundir edit myscript
-```
-
-### Print help message
-```shell
-$ rundir help
-```
\ No newline at end of file
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..b6909ea
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,37 @@
+{
+ "$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
new file mode 100644
index 0000000..eeafa95
--- /dev/null
+++ b/bun.lock
@@ -0,0 +1,49 @@
+{
+ "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
deleted file mode 100755
index 94404a3..0000000
Binary files a/bun.lockb and /dev/null differ
diff --git a/package.json b/package.json
index d61d3db..744506c 100644
--- a/package.json
+++ b/package.json
@@ -1,16 +1,22 @@
{
- "name": "rundir",
- "module": "src/cli.ts",
- "version": "0.0.1",
- "type": "module",
- "devDependencies": {
- "@biomejs/biome": "^1.9.4",
- "@types/bun": "latest"
- },
- "peerDependencies": {
- "typescript": "^5.0.0"
- },
- "dependencies": {
- "chalk": "^5.3.0"
- }
+ "name": "@endeavorance/rundir",
+ "type": "module",
+ "module": "src/cli.ts",
+ "version": "0.1.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"
+ }
}
diff --git a/src/cli.ts b/src/cli.ts
index 40c4507..bf79e02 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -1,102 +1,340 @@
+import { existsSync } from "node:fs";
+import { homedir } from "node:os";
+import path from "node:path";
import { parseArgs } from "node:util";
-import { edit, ls, newScript, runScript, which } from "./scriptFile";
+import { $, Glob } from "bun";
+import chalk from "chalk";
+import pkg from "../package.json" assert { type: "json" };
-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);
+interface ScriptData {
+ name: string;
+ description: string;
+ absolutePath: string;
}
-function help() {
- console.log(
- `
+class CLIError extends Error {
+ name = "CLIError";
+
+ constructor(message: string) {
+ super(`rundir error: ${message}`);
+ }
+}
+
+const TEMPLATE_FILE_PATH = process.env["RUNDIR_TEMPLATE"] ?? null;
+const DEFAULT_TEMPLATE = `
+#!/usr/bin/env bash
+# Description...
+`.trim();
+const VERSION = pkg.version;
+const RUNDIR_PATH = path.join(process.cwd(), ".run");
+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 findNearestRundir(fromPath?: string): string | null {
+ const currentDir = fromPath ?? process.cwd();
+ const potentialPath = path.join(currentDir, ".run");
+
+ if (currentDir === path.dirname(currentDir)) {
+ return null;
+ }
+
+ if (existsSync(potentialPath)) {
+ return potentialPath;
+ }
+
+ return findNearestRundir(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 makeScriptPath(name: string): string {
+ const rundir = findNearestRundir();
+
+ if (!rundir) {
+ throw new CLIError("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;
+}
+
+/**
+ * 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 = await findScriptPath(scriptName);
+ if (pathname === null) {
+ throw new CLIError(`Script "${scriptName}" not found.`);
+ }
+
+ console.log(pathname);
+ return;
+ }
+
+ 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"));
+}
+
+/**
+ * Prints the absolute path of the rundir
+ * @command
+ */
+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));
+}
+
+/**
+ * Create a new script in the rundir
+ * @command
+ */
+async function newScript(name: string) {
+ const scriptPath = makeScriptPath(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(name);
+}
+
+/**
+ * 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);
+ }
+
+ await $`${scriptPath}`;
+}
+
+/**
+ * Prints the help message
+ * @command
+ */
+async function help() {
+ console.log(
+ `
+rundir ${VERSION}
Usage: rundir [command]
Commands:
- 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
+ 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
`.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);
}
-const args = readArgs();
+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],
+};
-// Default functionality is to list
-if (args.length === 0) {
- await ls();
- process.exit(0);
+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);
}
-const command = args[0];
+try {
+ await main();
+} catch (e) {
+ if (e instanceof CLIError) {
+ console.error(chalk.red(e.message));
+ process.exit(0);
+ }
-if (!isCommand(command)) {
- console.error(`Unknown command: ${args[0]}`);
- process.exit(1);
+ console.error(chalk.red("An unexpected error occurred:"));
+ console.error(e);
+ process.exit(0);
}
-
-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
deleted file mode 100644
index 8d323ed..0000000
--- a/src/scriptFile.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-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 ffc08ab..70725ca 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,27 +1,28 @@
{
- "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
- }
+ "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,
+ }
}