name: deno-script description: Write or modify Cataclysm-BN Deno/TypeScript scripts. Use for scripts, tools, migrations, generators, git hooks, and CLI utilities.
Deno Script Development
Write Deno/TypeScript scripts following Cataclysm: Bright Nights conventions.
Use When
- "create deno script"
- "write deno script"
- "add typescript script"
- "new deno tool"
Required Conventions
1. Shebang and Permissions
Start scripts with the proper shebang and required permissions:
#!/usr/bin/env -S deno run --allow-read --allow-write --allow-run --allow-env
Common permission flags:
--allow-read- File system read access--allow-write- File system write access--allow-run- Run subprocesses--allow-env- Access environment variables--allow-net- Network access (only if needed)
2. Module Documentation
Add a module-level JSDoc comment explaining the script's purpose:
/**
* @module
*
* Brief description of what this script does
*
* Longer explanation if needed with examples or usage notes.
*/
This pattern is used in existing scripts like:
scripts/gen_cli_docs.tsscripts/utils.tsscripts/affected_files.ts
3. Import Conventions
Use JSR imports for standard library and well-known packages:
import { Command } from "@cliffy/command"
import { exists } from "@std/fs"
import { join } from "@std/path"
import { partition } from "@std/collections"
import * as v from "@valibot/valibot"
DO NOT use npm or http imports unless absolutely necessary. Prefer JSR.
4. Validation with Valibot
For data validation, use Valibot (NOT Zod):
import * as v from "@valibot/valibot"
const MySchema = v.object({
name: v.string(),
count: v.pipe(v.number(), v.minValue(0)),
tags: v.array(v.string()),
})
type MyType = v.InferOutput<typeof MySchema>
const parser = v.safeParser(MySchema)
const result = parser(data)
if (result.success) {
const validated = result.output
}
5. CLI with Cliffy
For command-line interfaces, use Cliffy:
import { Command } from "@cliffy/command"
const main = new Command()
.name("script-name")
.version("1.0.0")
.description("What this script does")
.option("-o, --output <file>", "Output file path")
.arguments("<input:string>")
.action(async ({ output }, input) => {
// Implementation
})
if (import.meta.main) {
await main.parse(Deno.args)
}
6. File Operations
Use @std/fs for file system operations:
import { exists, walk } from "@std/fs"
import { basename, join } from "@std/path"
if (await exists("path/to/file")) {
const content = await Deno.readTextFile("path/to/file")
}
for await (const entry of walk("src", { exts: [".ts"] })) {
console.log(entry.path)
}
7. Running Commands
Use Deno.Command for subprocesses:
const cmd = new Deno.Command("git", {
args: ["status"],
stdout: "piped",
stderr: "piped",
})
const { success, stdout, stderr } = await cmd.output()
if (success) {
const output = new TextDecoder().decode(stdout)
}
8. Error Handling
Handle errors with proper types:
try {
await someOperation()
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
console.error(`Error: ${message}`)
Deno.exit(1)
}
9. Color Output
Define colors as utility functions:
const colors = {
red: (text: string) => `\x1b[31m${text}\x1b[0m`,
yellow: (text: string) => `\x1b[33m${text}\x1b[0m`,
green: (text: string) => `\x1b[32m${text}\x1b[0m`,
dim: (text: string) => `\x1b[2m${text}\x1b[0m`,
}
console.log(colors.green("Success!"))
console.warn(colors.yellow("[WARNING] Something happened"))
console.error(colors.red("[ERROR] Failed"))
Example Scripts to Reference
Look at these existing scripts for patterns:
- CLI Tool:
scripts/gen_cli_docs.ts- Shows proper shebang, module docs, CLI parsing
- Data Processing:
scripts/utils.ts- Shows Valibot usage, type utilities
- GitHub API:
scripts/affected_files.ts- Shows async iteration, external API calls
- Git Hooks:
tools/hooks/pre-commit.ts- Shows command execution, file categorization
- Installer:
tools/hooks/install-hooks.ts- Shows user interaction, file manipulation
Project Structure
Scripts location:
- General utilities:
scripts/ - Build/development tools:
tools/ - Git hooks:
tools/hooks/
Testing
Before submitting:
deno check your-script.ts
deno fmt your-script.ts
deno lint your-script.ts
Make the script executable:
chmod +x your-script.ts
Documentation
Add to deno.jsonc tasks if the script should be easily runnable:
{
"tasks": {
"your-task": "deno run --allow-... scripts/your-script.ts"
}
}
Must Do
- ALWAYS start with shebang:
#!/usr/bin/env -S deno run [permissions] - ALWAYS add
@moduleJSDoc comment - ALWAYS use Valibot for validation (NOT Zod)
- ALWAYS prefer JSR imports over npm/http
- ALWAYS use Cliffy for CLI interfaces
- ALWAYS check with
deno checkbefore considering complete - ALWAYS make script executable (
chmod +x) - ALWAYS use
if (import.meta.main)guard for executable scripts
Must Not Do
- NEVER use Zod (use Valibot)
- NEVER use npm: imports (use jsr:)
- NEVER forget to add required permissions in shebang
- NEVER commit without running
deno checkanddeno fmt - NEVER use
@ts-ignoreor@ts-expect-error - NEVER leave scripts non-executable
Common Patterns
Check if Command Exists
const commandExists = async (cmd: string): Promise<boolean> => {
try {
const process = new Deno.Command("command", {
args: ["-v", cmd],
stdout: "piped",
stderr: "piped",
})
const { success } = await process.output()
return success
} catch {
return false
}
}
Get Git Repository Root
const getRepoRoot = async (): Promise<string> => {
const cmd = new Deno.Command("git", {
args: ["rev-parse", "--show-toplevel"],
stdout: "piped",
})
const { stdout } = await cmd.output()
return new TextDecoder().decode(stdout).trim()
}
Read JSON Files Recursively
import { walk } from "@std/fs"
for await (const entry of walk("data", { exts: [".json"] })) {
const content = await Deno.readTextFile(entry.path)
const data = JSON.parse(content)
// Process data
}
Parallel Async Operations
const results = await Promise.all([
operation1(),
operation2(),
operation3(),
])
When to Write a Deno Script
Use Deno scripts for:
- Data migration/transformation
- Build automation
- Development tools
- Git hooks
- Code generation
- Documentation generation
- CLI utilities
Don't use Deno scripts for:
- Core game logic (use C++)
- Performance-critical operations (use C++)
- Things that should be in-game (use Lua bindings)