This commit is contained in:
Endeavorance 2025-05-10 10:46:40 -04:00
parent cc37b06d8b
commit cc19888102
14 changed files with 529 additions and 322 deletions

3
.gitignore vendored
View file

@ -173,3 +173,6 @@ dist
# Finder (MacOS) folder config # Finder (MacOS) folder config
.DS_Store .DS_Store
bin
build

View file

@ -1,3 +1,27 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#DESCRIPTION# Compile the binary cli # Cross build rundir for multiple platforms
bun build --target bun --compile ./src/cli.ts --outfile rundir
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

3
.run/clean Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env bash
# Clean build artifacts
rm -rf build

View file

@ -1,3 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#DESCRIPTION# Format the repo, writing the formatted files to disk # Format the repo, writing the formatted files to disk
bunx biome format . --write bunx biome format . --write

View file

@ -1,3 +1,3 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#DESCRIPTION# A rundir script # A rundir script
echo 'Hello, world!' echo 'Hello, world!'

24
LICENSE Normal file
View file

@ -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 <https://unlicense.org/>

View file

@ -26,40 +26,28 @@ $ rundir x build # runs the `build` script in the original `.run` directory
## CLI ## CLI
### List scripts ```
Usage: rundir [command]
```shell Commands:
$ rundir x <name> Run a script from the nearest rundir (alias: run)
new <name> Create a new script in the rundir (alias: create)
ls List all scripts in the rundir
ls <name> 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 ```shell
$ rundir create myscript alias rdx="rundir x"
``` ```
### Run a script Then you can run scripts with `rdx` instead of `rundir x`:
```shell ```shell
$ rundir x myscript rdx build
# ... or if you're in the root directory just...
$ .run/myscript
```
### 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
``` ```

37
biome.json Normal file
View file

@ -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"
}
}
}

49
bun.lock Normal file
View file

@ -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=="],
}
}

BIN
bun.lockb

Binary file not shown.

View file

@ -1,16 +1,22 @@
{ {
"name": "rundir", "name": "@endeavorance/rundir",
"module": "src/cli.ts",
"version": "0.0.1",
"type": "module", "type": "module",
"module": "src/cli.ts",
"version": "0.1.0",
"bin": {
"rundir": "build/cli.js"
},
"files": [
"build"
],
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.9.4", "@biomejs/biome": "^1.9.4",
"@types/bun": "latest" "@types/bun": "latest"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5.0.0" "typescript": "^5.8.3"
}, },
"dependencies": { "dependencies": {
"chalk": "^5.3.0" "chalk": "^5.4.1"
} }
} }

View file

@ -1,38 +1,291 @@
import { existsSync } from "node:fs";
import { homedir } from "node:os";
import path from "node:path";
import { parseArgs } from "node:util"; 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 = [ interface ScriptData {
"ls", name: string;
"help", description: string;
"new", absolutePath: string;
"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);
} }
function help() { 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<string | null> {
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<ScriptData> {
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( console.log(
` `
rundir ${VERSION}
Usage: rundir [command] Usage: rundir [command]
Commands: Commands:
new <name> - Create a new script in the rundir (alias: create) x <name> Run a script from the nearest rundir (alias: run)
edit <name> - Open a script in the editor new <name> Create a new script in the rundir (alias: create)
x <name> - Run a script from the nearest rundir (alias: run) ls List all scripts in the rundir
which - Print the absolute path of the rundir ls <name> Print the absolute path to a specific script
ls - List all scripts in the rundir which Print the absolute path of the rundir
help - Show this help message help Show this help message
`.trim(), `.trim(),
); );
} }
/**
* Parses the command line arguments and returns the positionals
* @returns The positional arguments for the command
*/
function readArgs(): string[] { function readArgs(): string[] {
const { positionals } = parseArgs({ const { positionals } = parseArgs({
args: Bun.argv, args: Bun.argv,
@ -43,60 +296,45 @@ function readArgs(): string[] {
return positionals.slice(2); return positionals.slice(2);
} }
const args = readArgs(); type Handler = (...args: string[]) => void | Promise<void>;
type HandlerDescriptor = [number, Handler];
const COMMAND_HANDLERS: Record<Command, HandlerDescriptor> = {
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 async function main() {
if (args.length === 0) { const [rawCommand, ...params] = readArgs();
await ls();
// 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);
}
try {
await main();
} catch (e) {
if (e instanceof CLIError) {
console.error(chalk.red(e.message));
process.exit(0);
}
console.error(chalk.red("An unexpected error occurred:"));
console.error(e);
process.exit(0); process.exit(0);
} }
const command = args[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);

View file

@ -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<string | null> {
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<ScriptData> {
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}`;
}

View file

@ -1,27 +1,28 @@
{ {
"compilerOptions": { "compilerOptions": {
// Enable latest features // Enable latest features
"lib": ["ESNext", "DOM"], "lib": [
"ESNext",
"DOM"
],
"target": "ESNext", "target": "ESNext",
"module": "ESNext", "module": "ESNext",
"moduleDetection": "force", "moduleDetection": "force",
"jsx": "react-jsx", "jsx": "react-jsx",
"allowJs": true, "allowJs": true,
// Bundler mode // Bundler mode
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"noEmit": true, "noEmit": true,
// Best practices // Best practices
"strict": true, "strict": true,
"skipLibCheck": true, "skipLibCheck": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
// Some stricter flags (disabled by default) // Some stricter flags (disabled by default)
"noUnusedLocals": false, "noUnusedLocals": true,
"noUnusedParameters": false, "noUnusedParameters": true,
"noPropertyAccessFromIndexSignature": false "noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
} }
} }