├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── .gitignore ├── .prettierrc ├── README.md ├── args.ts ├── bun.lock ├── ctsm.test.ts ├── git.ts ├── index.ts ├── mit.ts ├── package.json ├── tsconfig.json └── util.ts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [alii] 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | 8 | jobs: 9 | test: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macos-latest, windows-latest] 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Setup Bun 19 | uses: oven-sh/setup-bun@v1 20 | with: 21 | bun-version: latest 22 | 23 | - name: Install dependencies 24 | run: bun install 25 | 26 | - name: Run tests 27 | run: bun test 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies (bun install) 2 | node_modules 3 | 4 | # output 5 | out 6 | dist 7 | *.tgz 8 | 9 | # code coverage 10 | coverage 11 | *.lcov 12 | 13 | # logs 14 | logs 15 | _.log 16 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 17 | 18 | # dotenv environment variable files 19 | .env 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | .env.local 24 | 25 | # caches 26 | .eslintcache 27 | .cache 28 | *.tsbuildinfo 29 | 30 | # IntelliJ based IDEs 31 | .idea 32 | 33 | # Finder (MacOS) folder config 34 | .DS_Store 35 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/prettierrc", 3 | "singleQuote": true, 4 | "semi": true, 5 | "printWidth": 100, 6 | "trailingComma": "all", 7 | "arrowParens": "avoid", 8 | "bracketSpacing": false, 9 | "useTabs": true, 10 | "quoteProps": "consistent" 11 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 CTSM - Create TypeScript Module 2 | 3 | [![Tests](https://github.com/alii/ctsm/actions/workflows/test.yml/badge.svg)](https://github.com/alii/ctsm/actions/workflows/test.yml) 4 | [![npm version](https://img.shields.io/npm/v/ctsm.svg)](https://www.npmjs.com/package/ctsm) 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 6 | 7 |

Create a modern TypeScript library with a single command! ✨

8 | 9 | ## 💫 Features 10 | 11 | - **Zero Config** - Get up and running in seconds 12 | - **Modern Defaults** - ESM and CJS dual package setup 13 | - **TypeScript Ready** - Full TypeScript support with declaration files 14 | - **Multiple Package Managers** - Works with npm, yarn, pnpm, and bun 15 | - **Git Integration** - Auto-initializes your repo and adds proper .gitignore 16 | - **License Generation** - Automatically creates MIT license with your name 17 | - **Optimized Builds** - Uses tsup for fast, efficient builds 18 | - **Smart Defaults** - Sensible configs for prettier and TypeScript 19 | - **Tree Shaking** - Built-in support for optimized bundles 20 | 21 | ## 🚀 Quick Start 22 | 23 | ```bash 24 | bunx ctsm@latest my-awesome-library 25 | ``` 26 | 27 | ## 🛠️ Command Line Options 28 | 29 | ```bash 30 | ctsm [name] [options] 31 | ``` 32 | 33 | ### Options: 34 | 35 | - `-y` - Skip confirmation prompts 36 | - `-p=` - Specify package manager to use for the package (options: `bun,npm,yarn,pnpm`) 37 | 38 | ## 📦 What You Get 39 | 40 | CTSM scaffolds a complete TypeScript library project with: 41 | 42 | - Modern `package.json` with ESM and CJS support 43 | - TypeScript configuration 44 | - Prettier formatting 45 | - tsup build setup 46 | - Git initialization 47 | - MIT license 48 | - Clean directory structure 49 | - Development dependencies installed 50 | 51 | ## 📂 Project Structure 52 | 53 | ``` 54 | my-awesome-library/ 55 | ├── src/ 56 | │ └── index.ts # Your library entry point 57 | ├── .gitignore # Git ignore file 58 | ├── .prettierrc # Prettier configuration 59 | ├── LICENSE # MIT license 60 | ├── package.json # Package configuration 61 | ├── tsconfig.json # TypeScript configuration 62 | └── tsup.config.ts # Build configuration 63 | ``` 64 | 65 | ## 🧙‍♂️ Advanced Usage 66 | 67 | CTSM detects your Git user info to automatically: 68 | 69 | - Populate package.json author fields 70 | - Generate license with your name 71 | - Initialize Git repository 72 | 73 | ## 💖 Contributing 74 | 75 | Contributions are welcome! Feel free to open an issue or submit a pull request. 76 | 77 | ## 📜 License 78 | 79 | MIT © [Alistair Smith](https://github.com/alii) 80 | 81 | ```bash 82 | bunx ctsm [name] 83 | ``` 84 | 85 | Flags: 86 | 87 | - `-y`: Skips confirmations 88 | - `-p=bun|npm|yarn|pnpm`: Sets the package manager to install with 89 | -------------------------------------------------------------------------------- /args.ts: -------------------------------------------------------------------------------- 1 | export class Flags { 2 | private map = new Map(); 3 | 4 | /** 5 | * Sets a key-value pair in the flags map 6 | * @param key - The flag key to set 7 | * @param value - The value to associate with the key (can be string or boolean) 8 | */ 9 | public set(key: string, value: string | boolean) { 10 | this.map.set(key, value); 11 | } 12 | 13 | /** 14 | * Gets the number of flags stored in the map 15 | * @returns The size of the flags map 16 | */ 17 | public get size() { 18 | return this.map.size; 19 | } 20 | 21 | /** 22 | * Converts the flags map to a plain JavaScript object 23 | * @returns An object containing all flag key-value pairs 24 | */ 25 | public toJSON() { 26 | return Object.fromEntries(this.map); 27 | } 28 | 29 | /** 30 | * Gets a boolean flag value, throwing an error if it doesn't exist or isn't boolean 31 | * @param key - The flag key to retrieve 32 | * @returns The boolean value associated with the key 33 | * @throws {Error} If the flag is missing or not a boolean 34 | */ 35 | public getBooleanOrThrow(key: string) { 36 | const value = this.map.get(key); 37 | 38 | if (value === undefined) { 39 | throw new Error(`Missing required flag: ${key}`); 40 | } 41 | 42 | if (typeof value !== 'boolean') { 43 | throw new Error(`Flag ${key} is not a boolean`); 44 | } 45 | 46 | return value; 47 | } 48 | 49 | /** 50 | * Gets a string flag value, throwing an error if it doesn't exist or isn't a string 51 | * @param key - The flag key to retrieve 52 | * @returns The string value associated with the key 53 | * @throws {Error} If the flag is missing or not a string 54 | */ 55 | public getStringOrThrow(key: string) { 56 | const value = this.map.get(key); 57 | 58 | if (value === undefined) { 59 | throw new Error(`Missing required flag: ${key}`); 60 | } 61 | 62 | if (typeof value !== 'string') { 63 | throw new Error(`Flag ${key} is not a string`); 64 | } 65 | 66 | return value; 67 | } 68 | 69 | /** 70 | * Gets a boolean flag value with a fallback default 71 | * @param key - The flag key to retrieve 72 | * @param defaultValue - The default value to return if the key doesn't exist 73 | * @returns The boolean value associated with the key or the default value 74 | * @throws {Error} If the flag exists but is not a boolean 75 | */ 76 | public getBooleanOrDefault(key: string, defaultValue: boolean) { 77 | const value = this.map.get(key); 78 | 79 | if (value === undefined) { 80 | return defaultValue; 81 | } 82 | 83 | if (typeof value !== 'boolean') { 84 | throw new Error(`Flag ${key} is not a boolean`); 85 | } 86 | 87 | return value; 88 | } 89 | 90 | /** 91 | * Gets a string flag value with a fallback default 92 | * @param key - The flag key to retrieve 93 | * @param defaultValue - The default value to return if the key doesn't exist 94 | * @returns The string value associated with the key or the default value 95 | * @throws {Error} If the flag exists but is not a string 96 | */ 97 | public getStringOrDefault(key: string, defaultValue: string) { 98 | const value = this.map.get(key); 99 | 100 | if (value === undefined) { 101 | return defaultValue; 102 | } 103 | 104 | if (typeof value !== 'string') { 105 | throw new Error(`Flag ${key} is not a string`); 106 | } 107 | 108 | return value; 109 | } 110 | 111 | /** 112 | * Gets a string flag value that must be one of the provided options 113 | * @param key - The flag key to retrieve 114 | * @param options - Array of valid string options 115 | * @returns The string value if it matches one of the options 116 | * @throws {Error} If the flag is missing, not a string, or not in the options array 117 | */ 118 | public getStringAsOptionOrThrow(key: string, options: T[]): T { 119 | const value = this.getStringOrThrow(key); 120 | 121 | if (!options.includes(value as T)) { 122 | throw new Error(`Flag ${key} is not a valid option`); 123 | } 124 | 125 | return value as T; 126 | } 127 | 128 | /** 129 | * Gets a string flag value that must be one of the provided options, with a fallback default 130 | * @param key - The flag key to retrieve 131 | * @param options - Array of valid string options 132 | * @param defaultValue - The default value to return if the key doesn't exist or value is invalid 133 | * @returns The string value if it matches one of the options, otherwise the default value 134 | */ 135 | public getStringAsOptionOrDefault( 136 | key: string, 137 | options: T[], 138 | defaultValue: T, 139 | ): T { 140 | const value = this.getStringOrDefault(key, defaultValue); 141 | 142 | if (!options.includes(value as T)) { 143 | return defaultValue; 144 | } 145 | 146 | return value as T; 147 | } 148 | } 149 | 150 | export function parse(argv: string[] = process.argv.slice(2)): [flags: Flags, args: string[]] { 151 | const flags = new Flags(); 152 | const args: string[] = []; 153 | 154 | for (const arg of argv) { 155 | if (arg.startsWith('--')) { 156 | const [key, value] = arg.slice(2).split('='); 157 | if (!key) continue; 158 | flags.set(key, value ?? true); 159 | } else if (arg.startsWith('-')) { 160 | const [key, value] = arg.slice(1).split('='); 161 | if (!key) continue; 162 | flags.set(key, value ?? true); 163 | } else { 164 | args.push(arg); 165 | } 166 | } 167 | 168 | return [flags, args] as const; 169 | } 170 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "workspaces": { 4 | "": { 5 | "name": "ctsm", 6 | "devDependencies": { 7 | "@schemastore/package": "^0.0.10", 8 | "@schemastore/prettierrc": "^0.0.10", 9 | "@schemastore/tsconfig": "^0.0.11", 10 | "@types/bun": "latest", 11 | "prettier": "^3.5.3", 12 | "tsup": "^8.4.0", 13 | "typescript": "^5.8.3", 14 | }, 15 | }, 16 | }, 17 | "overrides": { 18 | "bun-types": "canary", 19 | }, 20 | "packages": { 21 | "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag=="], 22 | 23 | "@esbuild/android-arm": ["@esbuild/android-arm@0.25.2", "", { "os": "android", "cpu": "arm" }, "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA=="], 24 | 25 | "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w=="], 26 | 27 | "@esbuild/android-x64": ["@esbuild/android-x64@0.25.2", "", { "os": "android", "cpu": "x64" }, "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg=="], 28 | 29 | "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA=="], 30 | 31 | "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA=="], 32 | 33 | "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w=="], 34 | 35 | "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ=="], 36 | 37 | "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.2", "", { "os": "linux", "cpu": "arm" }, "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g=="], 38 | 39 | "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g=="], 40 | 41 | "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ=="], 42 | 43 | "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w=="], 44 | 45 | "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q=="], 46 | 47 | "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g=="], 48 | 49 | "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw=="], 50 | 51 | "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q=="], 52 | 53 | "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.2", "", { "os": "linux", "cpu": "x64" }, "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg=="], 54 | 55 | "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.2", "", { "os": "none", "cpu": "arm64" }, "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw=="], 56 | 57 | "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.2", "", { "os": "none", "cpu": "x64" }, "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg=="], 58 | 59 | "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg=="], 60 | 61 | "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw=="], 62 | 63 | "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA=="], 64 | 65 | "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q=="], 66 | 67 | "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg=="], 68 | 69 | "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.2", "", { "os": "win32", "cpu": "x64" }, "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA=="], 70 | 71 | "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], 72 | 73 | "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], 74 | 75 | "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 76 | 77 | "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], 78 | 79 | "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], 80 | 81 | "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], 82 | 83 | "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], 84 | 85 | "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.39.0", "", { "os": "android", "cpu": "arm" }, "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA=="], 86 | 87 | "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.39.0", "", { "os": "android", "cpu": "arm64" }, "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ=="], 88 | 89 | "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.39.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q=="], 90 | 91 | "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.39.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ=="], 92 | 93 | "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.39.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ=="], 94 | 95 | "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.39.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q=="], 96 | 97 | "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.39.0", "", { "os": "linux", "cpu": "arm" }, "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g=="], 98 | 99 | "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.39.0", "", { "os": "linux", "cpu": "arm" }, "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw=="], 100 | 101 | "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.39.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ=="], 102 | 103 | "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.39.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA=="], 104 | 105 | "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.39.0", "", { "os": "linux", "cpu": "none" }, "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw=="], 106 | 107 | "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.39.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ=="], 108 | 109 | "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.39.0", "", { "os": "linux", "cpu": "none" }, "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ=="], 110 | 111 | "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.39.0", "", { "os": "linux", "cpu": "none" }, "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA=="], 112 | 113 | "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.39.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA=="], 114 | 115 | "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.39.0", "", { "os": "linux", "cpu": "x64" }, "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA=="], 116 | 117 | "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.39.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg=="], 118 | 119 | "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.39.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ=="], 120 | 121 | "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.39.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ=="], 122 | 123 | "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.39.0", "", { "os": "win32", "cpu": "x64" }, "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug=="], 124 | 125 | "@schemastore/package": ["@schemastore/package@0.0.10", "", {}, "sha512-D3LxMCnkgsb4LO5sDKf6E+yahM2SqpEHmkqMPDSJis5Cy/j2MgWo/g/iq0lECK0mrPWfx3hqKm2ZJlqxwbRJQA=="], 126 | 127 | "@schemastore/prettierrc": ["@schemastore/prettierrc@0.0.10", "", {}, "sha512-zXu/8u6/dV4Xw+gJMSt5Pm5RzTs8fjaCSvoeuXPcZjV8Q2Rz7y4eDs97W3DIOMr1uJJGfd5XJCB7/6L+FHp15Q=="], 128 | 129 | "@schemastore/tsconfig": ["@schemastore/tsconfig@0.0.11", "", {}, "sha512-glWQsBTycNVlq8ZrdvvEGyvHCmTfJy+inpjby4DokwBEyRj5+SFyjWbh1OkZf1cXbdCzrR/fZAQSm5vS73PaYA=="], 130 | 131 | "@types/bun": ["@types/bun@1.2.8", "", { "dependencies": { "bun-types": "1.2.7" } }, "sha512-t8L1RvJVUghW5V+M/fL3Thbxcs0HwNsXsnTEBEfEVqGteiJToOlZ/fyOEaR1kZsNqnu+3XA4RI/qmnX4w6+S+w=="], 132 | 133 | "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], 134 | 135 | "@types/node": ["@types/node@22.14.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA=="], 136 | 137 | "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], 138 | 139 | "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], 140 | 141 | "ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], 142 | 143 | "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], 144 | 145 | "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 146 | 147 | "brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], 148 | 149 | "bun-types": ["bun-types@1.2.9-canary.20250405T140519", "", { "dependencies": { "@types/node": "*", "@types/ws": "*" } }, "sha512-p82FcbePltKtYgXrOlayE5fVyjCVB/pZfVJwy8z0fnp2kQUxRJNLkM3s0HuzyET6lVX0VNWLQv/HhRxNNW7QuQ=="], 150 | 151 | "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], 152 | 153 | "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], 154 | 155 | "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], 156 | 157 | "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 158 | 159 | "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 160 | 161 | "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], 162 | 163 | "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], 164 | 165 | "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 166 | 167 | "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], 168 | 169 | "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], 170 | 171 | "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], 172 | 173 | "esbuild": ["esbuild@0.25.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.2", "@esbuild/android-arm": "0.25.2", "@esbuild/android-arm64": "0.25.2", "@esbuild/android-x64": "0.25.2", "@esbuild/darwin-arm64": "0.25.2", "@esbuild/darwin-x64": "0.25.2", "@esbuild/freebsd-arm64": "0.25.2", "@esbuild/freebsd-x64": "0.25.2", "@esbuild/linux-arm": "0.25.2", "@esbuild/linux-arm64": "0.25.2", "@esbuild/linux-ia32": "0.25.2", "@esbuild/linux-loong64": "0.25.2", "@esbuild/linux-mips64el": "0.25.2", "@esbuild/linux-ppc64": "0.25.2", "@esbuild/linux-riscv64": "0.25.2", "@esbuild/linux-s390x": "0.25.2", "@esbuild/linux-x64": "0.25.2", "@esbuild/netbsd-arm64": "0.25.2", "@esbuild/netbsd-x64": "0.25.2", "@esbuild/openbsd-arm64": "0.25.2", "@esbuild/openbsd-x64": "0.25.2", "@esbuild/sunos-x64": "0.25.2", "@esbuild/win32-arm64": "0.25.2", "@esbuild/win32-ia32": "0.25.2", "@esbuild/win32-x64": "0.25.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ=="], 174 | 175 | "fdir": ["fdir@6.4.3", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw=="], 176 | 177 | "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], 178 | 179 | "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 180 | 181 | "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], 182 | 183 | "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], 184 | 185 | "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 186 | 187 | "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], 188 | 189 | "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], 190 | 191 | "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], 192 | 193 | "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], 194 | 195 | "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], 196 | 197 | "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], 198 | 199 | "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], 200 | 201 | "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 202 | 203 | "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], 204 | 205 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 206 | 207 | "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], 208 | 209 | "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], 210 | 211 | "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], 212 | 213 | "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 214 | 215 | "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], 216 | 217 | "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 218 | 219 | "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], 220 | 221 | "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], 222 | 223 | "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], 224 | 225 | "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], 226 | 227 | "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 228 | 229 | "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], 230 | 231 | "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], 232 | 233 | "rollup": ["rollup@4.39.0", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.39.0", "@rollup/rollup-android-arm64": "4.39.0", "@rollup/rollup-darwin-arm64": "4.39.0", "@rollup/rollup-darwin-x64": "4.39.0", "@rollup/rollup-freebsd-arm64": "4.39.0", "@rollup/rollup-freebsd-x64": "4.39.0", "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", "@rollup/rollup-linux-arm-musleabihf": "4.39.0", "@rollup/rollup-linux-arm64-gnu": "4.39.0", "@rollup/rollup-linux-arm64-musl": "4.39.0", "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", "@rollup/rollup-linux-riscv64-gnu": "4.39.0", "@rollup/rollup-linux-riscv64-musl": "4.39.0", "@rollup/rollup-linux-s390x-gnu": "4.39.0", "@rollup/rollup-linux-x64-gnu": "4.39.0", "@rollup/rollup-linux-x64-musl": "4.39.0", "@rollup/rollup-win32-arm64-msvc": "4.39.0", "@rollup/rollup-win32-ia32-msvc": "4.39.0", "@rollup/rollup-win32-x64-msvc": "4.39.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g=="], 234 | 235 | "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 236 | 237 | "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 238 | 239 | "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], 240 | 241 | "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], 242 | 243 | "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], 244 | 245 | "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], 246 | 247 | "strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], 248 | 249 | "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 250 | 251 | "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], 252 | 253 | "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], 254 | 255 | "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], 256 | 257 | "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], 258 | 259 | "tinyglobby": ["tinyglobby@0.2.12", "", { "dependencies": { "fdir": "^6.4.3", "picomatch": "^4.0.2" } }, "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww=="], 260 | 261 | "tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="], 262 | 263 | "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], 264 | 265 | "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], 266 | 267 | "tsup": ["tsup@8.4.0", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ=="], 268 | 269 | "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], 270 | 271 | "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 272 | 273 | "webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], 274 | 275 | "whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="], 276 | 277 | "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 278 | 279 | "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], 280 | 281 | "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], 282 | 283 | "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], 284 | 285 | "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 286 | 287 | "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 288 | 289 | "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 290 | 291 | "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], 292 | 293 | "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 294 | 295 | "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 296 | 297 | "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], 298 | 299 | "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /ctsm.test.ts: -------------------------------------------------------------------------------- 1 | import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'bun:test'; 2 | import * as fs from 'node:fs/promises'; 3 | import * as os from 'node:os'; 4 | import * as path from 'node:path'; 5 | import {parse} from './args'; 6 | import {code, json} from './util'; 7 | 8 | const isCI = process.env.CI === 'true'; 9 | 10 | it('code() util function', () => { 11 | expect( 12 | code(` 13 | function add(a: number, b: number) { 14 | return a + b; 15 | } 16 | `), 17 | ).toBe(`function add(a: number, b: number) { 18 | return a + b; 19 | }`); 20 | 21 | expect( 22 | code(` 23 | 24 | 25 | 26 | function add(a: number, b: number) { 27 | return a + b; 28 | } 29 | 30 | 31 | 32 | `), 33 | ).toBe(`function add(a: number, b: number) { 34 | return a + b; 35 | }`); 36 | }); 37 | 38 | it('json() util function', () => { 39 | expect(json({a: 1, b: 2})).toBe(`{\n\t"a": 1,\n\t"b": 2\n}`); 40 | }); 41 | 42 | describe('args parser', () => { 43 | it('should parse empty args', () => { 44 | const [flags, args] = parse([]); 45 | expect(flags.size).toEqual(0); 46 | expect(args).toEqual([]); 47 | }); 48 | 49 | it('should parse positional arguments', () => { 50 | const [flags, args] = parse(['file1.txt', 'file2.txt']); 51 | expect(flags.size).toEqual(0); 52 | expect(args).toEqual(['file1.txt', 'file2.txt']); 53 | }); 54 | 55 | it('should parse boolean flags with --', () => { 56 | const [flags, args] = parse(['--verbose', '--debug']); 57 | expect(flags.getBooleanOrThrow('verbose')).toEqual(true); 58 | expect(flags.getBooleanOrThrow('debug')).toEqual(true); 59 | expect(args).toEqual([]); 60 | }); 61 | 62 | it('should parse boolean flags with single -', () => { 63 | const [flags, args] = parse(['-v', '-d']); 64 | expect(flags.getBooleanOrThrow('v')).toEqual(true); 65 | expect(flags.getBooleanOrThrow('d')).toEqual(true); 66 | expect(args).toEqual([]); 67 | }); 68 | 69 | it('should parse flags with values', () => { 70 | const [flags, args] = parse(['--port=3000', '--host=localhost']); 71 | expect(flags.getStringOrThrow('port')).toEqual('3000'); 72 | expect(flags.getStringOrThrow('host')).toEqual('localhost'); 73 | expect(args).toEqual([]); 74 | }); 75 | 76 | it('should handle mixed flags and positional arguments', () => { 77 | const [flags, args] = parse(['--verbose', 'input.txt', '-o', '--format=json', 'output.txt']); 78 | expect(flags.getBooleanOrThrow('verbose')).toEqual(true); 79 | expect(flags.getBooleanOrThrow('o')).toEqual(true); 80 | expect(flags.getStringOrThrow('format')).toEqual('json'); 81 | expect(args).toEqual(['input.txt', 'output.txt']); 82 | }); 83 | 84 | it('should ignore empty flag names', () => { 85 | const [flags, args] = parse(['--', '--=value']); 86 | expect(flags.size).toEqual(0); 87 | expect(args).toEqual([]); 88 | }); 89 | }); 90 | 91 | describe('CTSM Integration Tests', () => { 92 | const tmpDir = path.join(os.tmpdir(), 'ctsm-integration-tests'); 93 | const ctsmBin = path.resolve('./dist/index.js'); 94 | 95 | async function runCTSM( 96 | args: string[] = [], 97 | cwd: string = tmpDir, 98 | ): Promise<{ 99 | exitCode: number; 100 | stdout: string; 101 | stderr: string; 102 | }> { 103 | let stdout = ''; 104 | let stderr = ''; 105 | 106 | const proc = Bun.spawn({ 107 | cmd: ['bun', ctsmBin, ...args, '-y'], 108 | cwd, 109 | stdout: 'pipe', 110 | stderr: 'pipe', 111 | env: {...process.env, NODE_ENV: 'test'}, 112 | }); 113 | 114 | const stdoutReader = proc.stdout.getReader(); 115 | const stderrReader = proc.stderr.getReader(); 116 | 117 | while (true) { 118 | const {done, value} = await stdoutReader.read(); 119 | if (done) break; 120 | stdout += new TextDecoder().decode(value); 121 | } 122 | 123 | while (true) { 124 | const {done, value} = await stderrReader.read(); 125 | if (done) break; 126 | stderr += new TextDecoder().decode(value); 127 | } 128 | 129 | const exitCode = await proc.exited; 130 | 131 | console.log(`Command executed: bun ${ctsmBin} ${args.join(' ')} -y`); 132 | console.log(`Exit code: ${exitCode}`); 133 | console.log(`Stdout: ${stdout}`); 134 | console.log(`Stderr: ${stderr}`); 135 | 136 | return {exitCode, stdout, stderr}; 137 | } 138 | 139 | async function verifyProject(projectPath: string, packageManager: string) { 140 | const files = await fs.readdir(projectPath); 141 | console.log(`Files in ${projectPath}:`, files); 142 | 143 | expect(files).toContain('package.json'); 144 | expect(files).toContain('.prettierrc'); 145 | expect(files).toContain('tsup.config.ts'); 146 | expect(files).toContain('LICENSE'); 147 | expect(files).toContain('.gitignore'); 148 | expect(files).toContain('src'); 149 | 150 | const srcFiles = await fs.readdir(path.join(projectPath, 'src')); 151 | expect(srcFiles).toContain('index.ts'); 152 | 153 | const indexContent = await fs.readFile(path.join(projectPath, 'src', 'index.ts'), 'utf-8'); 154 | expect(indexContent).toContain('export function add(a: number, b: number)'); 155 | 156 | const packageJsonContent = await fs.readFile(path.join(projectPath, 'package.json'), 'utf-8'); 157 | const packageJson = JSON.parse(packageJsonContent); 158 | 159 | expect(packageJson.name).toBe(path.basename(projectPath)); 160 | expect(packageJson.scripts).toHaveProperty('build'); 161 | expect(packageJson.scripts).toHaveProperty('release'); 162 | 163 | const expectedBuildScript = packageManager === 'npm' ? 'npx tsup' : `${packageManager} tsup`; 164 | expect(packageJson.scripts.build).toBe(expectedBuildScript); 165 | expect(packageJson.scripts.release).toContain(packageManager); 166 | 167 | expect(await fs.stat(path.join(projectPath, '.git')).catch(() => false)).toBeTruthy(); 168 | } 169 | 170 | beforeAll(async () => { 171 | await fs.mkdir(tmpDir, {recursive: true}); 172 | 173 | const buildProc = Bun.spawn(['bun', 'run', 'build'], { 174 | cwd: '.', 175 | stdout: 'inherit', 176 | stderr: 'inherit', 177 | }); 178 | 179 | const exitCode = await buildProc.exited; 180 | expect(exitCode).toBe(0); 181 | 182 | if (isCI) { 183 | console.log('Testing environment in CI...'); 184 | try { 185 | const whoami = Bun.spawn({ 186 | cmd: ['whoami'], 187 | stdout: 'pipe', 188 | }); 189 | const reader = whoami.stdout.getReader(); 190 | let output = ''; 191 | while (true) { 192 | const {done, value} = await reader.read(); 193 | if (done) break; 194 | output += new TextDecoder().decode(value); 195 | } 196 | console.log('Current user:', output.trim()); 197 | 198 | const gitVersion = Bun.spawn({ 199 | cmd: ['git', '--version'], 200 | stdout: 'pipe', 201 | }); 202 | const gitReader = gitVersion.stdout.getReader(); 203 | let gitOutput = ''; 204 | while (true) { 205 | const {done, value} = await gitReader.read(); 206 | if (done) break; 207 | gitOutput += new TextDecoder().decode(value); 208 | } 209 | console.log('Git version:', gitOutput.trim()); 210 | 211 | console.log('Temp directory:', tmpDir); 212 | const tmpStat = await fs.stat(tmpDir); 213 | console.log('Temp directory exists:', tmpStat.isDirectory()); 214 | console.log('Temp directory permissions:', tmpStat.mode.toString(8)); 215 | 216 | const testFile = path.join(tmpDir, 'test-file.txt'); 217 | await fs.writeFile(testFile, 'test'); 218 | console.log('Successfully wrote to temp directory'); 219 | } catch (error) { 220 | console.error('Error in smoke test:', error); 221 | } 222 | } 223 | }); 224 | 225 | afterAll(async () => { 226 | await fs.rm(tmpDir, {recursive: true, force: true}); 227 | }); 228 | 229 | beforeEach(async () => { 230 | const files = await fs.readdir(tmpDir); 231 | for (const file of files) { 232 | await fs.rm(path.join(tmpDir, file), {recursive: true, force: true}); 233 | } 234 | }); 235 | 236 | async function isPackageManagerAvailable(packageManager: string): Promise { 237 | try { 238 | const proc = Bun.spawn({ 239 | cmd: ['which', packageManager], 240 | stdout: 'pipe', 241 | stderr: 'pipe', 242 | }); 243 | const exitCode = await proc.exited; 244 | return exitCode === 0; 245 | } catch (error) { 246 | console.log(`Package manager ${packageManager} is not available`); 247 | return false; 248 | } 249 | } 250 | 251 | const packageManagers = ['bun', 'npm', 'yarn', 'pnpm']; 252 | 253 | for (const packageManager of packageManagers) { 254 | describe(`with ${packageManager}`, () => { 255 | it(`should create a new project with ${packageManager}`, async () => { 256 | if (!(await isPackageManagerAvailable(packageManager))) { 257 | console.log( 258 | `Skipping test for ${packageManager} as it's not available in this environment`, 259 | ); 260 | return; 261 | } 262 | 263 | const projectName = `test-project-${packageManager}`; 264 | const projectPath = path.join(tmpDir, projectName); 265 | 266 | const {exitCode, stdout, stderr} = await runCTSM([projectName, `--p=${packageManager}`]); 267 | 268 | expect(exitCode).toBe(0); 269 | 270 | await fs.access(projectPath); 271 | await verifyProject(projectPath, packageManager); 272 | }, 120000); 273 | 274 | it(`should fail when creating project in non-empty directory with ${packageManager}`, async () => { 275 | if (!(await isPackageManagerAvailable(packageManager))) { 276 | console.log( 277 | `Skipping test for ${packageManager} as it's not available in this environment`, 278 | ); 279 | return; 280 | } 281 | 282 | const projectName = `test-project-exists-${packageManager}`; 283 | const projectPath = path.join(tmpDir, projectName); 284 | 285 | await fs.mkdir(projectPath, {recursive: true}); 286 | await fs.writeFile(path.join(projectPath, 'some-file.txt'), 'content'); 287 | 288 | const {exitCode, stdout, stderr} = await runCTSM([], projectPath); 289 | 290 | expect(exitCode).not.toBe(0); 291 | expect(stdout.length + stderr.length).toBeGreaterThan(0); 292 | }, 30000); 293 | }); 294 | } 295 | 296 | it('should use directory name as package name when no name provided in empty directory', async () => { 297 | const emptyDirName = 'default-project-name'; 298 | const emptyDir = path.join(tmpDir, emptyDirName); 299 | await fs.mkdir(emptyDir, {recursive: true}); 300 | 301 | const {exitCode, stdout, stderr} = await runCTSM([], emptyDir); 302 | 303 | expect(exitCode).toBe(0); 304 | 305 | await verifyProject(emptyDir, 'bun'); 306 | }, 30000); 307 | 308 | it('should respect -y flag to skip confirmation', async () => { 309 | const projectName = 'test-project-y-flag'; 310 | const {exitCode, stdout, stderr} = await runCTSM([projectName, '-y']); 311 | 312 | expect(exitCode).toBe(0); 313 | 314 | expect(stdout).not.toContain('Y/n'); 315 | }, 30000); 316 | }); 317 | -------------------------------------------------------------------------------- /git.ts: -------------------------------------------------------------------------------- 1 | import {$} from 'bun'; 2 | 3 | export async function getGitUser(): Promise< 4 | { 5 | name?: string; 6 | email?: string; 7 | } & Record 8 | > { 9 | const text = await $`git config --get-regexp user.`.nothrow().text(); 10 | 11 | const map = text 12 | .trim() 13 | .split('\n') 14 | .map(line => { 15 | const [key, ...value] = line.split(' '); 16 | return [key!.replace('user.', ''), value.join(' ')]; 17 | }); 18 | 19 | return Object.fromEntries(map); 20 | } 21 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bun 2 | 3 | import type {JSONSchemaForNPMPackageJsonFiles2 as PackageJSON} from '@schemastore/package'; 4 | import type {SchemaForPrettierrc as PrettierRC} from '@schemastore/prettierrc'; 5 | import type {JSONSchemaForTheTypeScriptCompilerSConfigurationFile as TSConfig} from '@schemastore/tsconfig'; 6 | import {$} from 'bun'; 7 | import * as path from 'node:path'; 8 | import {parse} from './args.ts'; 9 | import {getGitUser} from './git.ts'; 10 | import {mit} from './mit.ts'; 11 | import {code, confirmOrDie, die, getBinName, isDirectoryEmpty, json, writeFiles} from './util.ts'; 12 | 13 | const cwd = process.cwd(); 14 | 15 | const probablyIsThisProject = await isDirectoryEmpty(cwd); 16 | 17 | const [flags, args] = parse(); 18 | const [directoryName = probablyIsThisProject ? cwd : null] = args; 19 | 20 | const SKIP_CONFIRMATION = flags.getBooleanOrDefault('y', false); 21 | const PACKAGE_MANAGER = flags.getStringAsOptionOrDefault( 22 | 'p', 23 | ['bun', 'npm', 'yarn', 'pnpm'], 24 | 'bun', 25 | ); 26 | 27 | function installCommand(packageManager: typeof PACKAGE_MANAGER, packages: string[], dev: boolean) { 28 | const p = {raw: packages.map(p => $.escape(p)).join(' ')}; 29 | 30 | const lastTwo = (dev_string: string, normal_string: string = '') => { 31 | if (dev) return [dev_string, p]; 32 | return [normal_string, p]; 33 | }; 34 | 35 | return { 36 | npm: () => $`npm install ${lastTwo(dev ? '--save-dev' : '--save')}`, 37 | yarn: () => $`yarn add ${lastTwo(dev ? '--dev' : '')}`, 38 | pnpm: () => $`pnpm add ${lastTwo(dev ? '--save-dev' : '')}`, 39 | bun: () => $`bun i ${lastTwo(dev ? '--bun' : '')}`, 40 | }[packageManager](); 41 | } 42 | 43 | if (!directoryName) { 44 | if (probablyIsThisProject) { 45 | die('Error: Expected this folder to have a name'); 46 | } else { 47 | die([ 48 | '', 49 | 'You ran CTSM in a folder that was not already empty!', 50 | 'You must specify a name of the package to create it', 51 | '', 52 | `Example: \`${getBinName()} my-awesome-library\``, 53 | '', 54 | ]); 55 | } 56 | } 57 | 58 | const realPackageDirectory = path.resolve(cwd, directoryName); 59 | const moduleName = path.basename(realPackageDirectory); 60 | 61 | if (!SKIP_CONFIRMATION) { 62 | await confirmOrDie( 63 | `Create '${moduleName}' with ${PACKAGE_MANAGER} at ${realPackageDirectory}? (Y/n) `, 64 | {acceptDefault: true}, 65 | ); 66 | } 67 | 68 | console.log(`Writing package ${moduleName} at ${realPackageDirectory}`); 69 | 70 | const user = await getGitUser(); 71 | 72 | await writeFiles(realPackageDirectory, { 73 | 'LICENSE': await mit(user.name), 74 | 'package.json': json({ 75 | name: moduleName, 76 | version: '0.0.1', 77 | description: 'Description', 78 | keywords: [], 79 | author: user.name 80 | ? { 81 | name: user.name, 82 | email: user.email, 83 | } 84 | : undefined, 85 | license: 'MIT', 86 | type: 'module', 87 | scripts: { 88 | build: PACKAGE_MANAGER === 'npm' ? 'npx tsup' : `${PACKAGE_MANAGER} tsup`, 89 | release: `${PACKAGE_MANAGER} run build && ${PACKAGE_MANAGER} publish`, 90 | }, 91 | exports: { 92 | './package.json': './package.json', 93 | '.': { 94 | import: './dist/index.js', 95 | require: './dist/index.cjs', 96 | }, 97 | }, 98 | files: ['LICENSE', 'README.md', 'dist'], 99 | }), 100 | 'tsup.config.ts': code(` 101 | import {defineConfig} from 'tsup'; 102 | 103 | export default defineConfig({ 104 | entry: ['./src/index.ts'], 105 | format: ['esm', 'cjs'], 106 | clean: true, 107 | dts: true, 108 | splitting: true, 109 | treeshake: true, 110 | }); 111 | `), 112 | '.prettierrc': json({ 113 | $schema: 'http://json.schemastore.org/prettierrc', 114 | singleQuote: true, 115 | semi: true, 116 | printWidth: 100, 117 | trailingComma: 'all', 118 | arrowParens: 'avoid', 119 | bracketSpacing: false, 120 | useTabs: true, 121 | quoteProps: 'consistent', 122 | }), 123 | 'src/index.ts': code(` 124 | export function add(a: number, b: number) { 125 | return a + b; 126 | } 127 | `), 128 | '.gitignore': code(` 129 | node_modules 130 | dist 131 | bun.lockb 132 | .DS_Store 133 | .idea 134 | .vscode 135 | `), 136 | 'tsconfig.json': json({ 137 | compilerOptions: { 138 | target: 'ESNext', 139 | lib: ['DOM', 'DOM.Iterable', 'ESNext'], 140 | allowJs: true, 141 | skipLibCheck: true, 142 | strict: true, 143 | forceConsistentCasingInFileNames: true, 144 | module: 'ESNext', 145 | moduleResolution: 'bundler', 146 | resolveJsonModule: true, 147 | isolatedModules: true, 148 | noEmit: true, 149 | allowImportingTsExtensions: true, 150 | jsx: 'react-jsx', 151 | }, 152 | exclude: ['node_modules', 'dist'], 153 | include: ['**/*.ts', '**/*.tsx '], 154 | }), 155 | }); 156 | 157 | const install = installCommand(PACKAGE_MANAGER, ['prettier', 'typescript', 'tsup'], true); 158 | await install.cwd(realPackageDirectory); 159 | 160 | await $`git init`.cwd(realPackageDirectory); 161 | -------------------------------------------------------------------------------- /mit.ts: -------------------------------------------------------------------------------- 1 | const template = ` 2 | MIT License 3 | 4 | Copyright (c) [year] [fullname] 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | `.trim(); 24 | 25 | export async function mit(name: string | undefined) { 26 | let t = template; 27 | 28 | t = t.replace('[year]', new Date().getFullYear().toString()); 29 | if (name) t = t.replace('[fullname]', name); 30 | 31 | return t; 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ctsm", 3 | "version": "0.0.15", 4 | "bin": { 5 | "ctsm": "./dist/index.js" 6 | }, 7 | "description": "Create a modern TypeScript library with a single command", 8 | "author": { 9 | "name": "Alistair Smith", 10 | "email": "hi@alistair.sh" 11 | }, 12 | "keywords": [ 13 | "typescript", 14 | "library", 15 | "cli" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/alii/ctsm" 20 | }, 21 | "homepage": "https://github.com/alii/ctsm", 22 | "bugs": { 23 | "url": "https://github.com/alii/ctsm/issues" 24 | }, 25 | "license": "MIT", 26 | "files": [ 27 | "dist", 28 | "package.json", 29 | "README.md" 30 | ], 31 | "scripts": { 32 | "build": "tsup index.ts --format esm --external bun", 33 | "release": "bun run build && bun publish" 34 | }, 35 | "resolutions": { 36 | "bun-types": "canary" 37 | }, 38 | "type": "module", 39 | "devDependencies": { 40 | "@schemastore/package": "^0.0.10", 41 | "@schemastore/prettierrc": "^0.0.10", 42 | "@schemastore/tsconfig": "^0.0.11", 43 | "@types/bun": "latest", 44 | "prettier": "^3.5.3", 45 | "tsup": "^8.4.0", 46 | "typescript": "^5.8.3" 47 | }, 48 | "packageManager": "bun@1.2.8" 49 | } 50 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Environment setup & latest features 4 | "lib": ["ESNext"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedIndexedAccess": true, 22 | 23 | // Some stricter flags (disabled by default) 24 | "noUnusedLocals": false, 25 | "noUnusedParameters": false, 26 | "noPropertyAccessFromIndexSignature": false 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /util.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'node:fs/promises'; 2 | import * as path from 'node:path'; 3 | import * as rl from 'node:readline/promises'; 4 | 5 | const ansi = { 6 | red: Bun.color({r: 255, g: 0, b: 0}, 'ansi-16m'), 7 | }; 8 | 9 | export function getBinName() { 10 | return process.argv[1]!; 11 | } 12 | 13 | export function die(messages?: undefined | string | string[], code = 1): never { 14 | if (messages) { 15 | const string = Array.isArray(messages) ? messages.join('\n') : messages; 16 | 17 | console.error(ansi.red + string); 18 | } 19 | 20 | process.exit(code); 21 | } 22 | 23 | export async function confirm(message: string, options: {acceptDefault: boolean}) { 24 | const i = rl.createInterface({ 25 | input: process.stdin, 26 | output: process.stdout, 27 | }); 28 | 29 | const answer = await i.question(message); 30 | 31 | i.close(); 32 | 33 | if (options.acceptDefault && answer === '') return true; 34 | 35 | return answer.toLowerCase().startsWith('y'); 36 | } 37 | 38 | export async function confirmOrDie(...args: Parameters) { 39 | const result = await confirm(...args); 40 | if (!result) die(); 41 | } 42 | 43 | export async function isDirectoryEmpty(path: string) { 44 | const files = await fs.readdir(path); 45 | return files.length === 0; 46 | } 47 | 48 | export async function writeFiles(dir: string, files: Record) { 49 | await Promise.all( 50 | Object.entries(files).map(([name, content]) => Bun.write(path.join(dir, name), content)), 51 | ); 52 | } 53 | 54 | export function json(value: T) { 55 | return JSON.stringify(value, null, '\t'); 56 | } 57 | 58 | function getTemplateStrings(strings: TemplateStringsArray | string) { 59 | if (typeof strings === 'string') { 60 | const arr = [strings] as ReadonlyArray & {raw?: string[]}; 61 | arr.raw = [strings]; 62 | 63 | return arr as TemplateStringsArray; 64 | } 65 | 66 | return strings; 67 | } 68 | 69 | function taggedTemplateFunction( 70 | cb: (strings: TemplateStringsArray, ...values: T[]) => string, 71 | ) { 72 | return (strings: TemplateStringsArray | string, ...values: T[]) => { 73 | return cb(getTemplateStrings(strings), ...values); 74 | }; 75 | } 76 | 77 | export const code = taggedTemplateFunction(strings => { 78 | const allLines = strings.join('').trimEnd().split('\n'); 79 | 80 | const indexOfFirstNonEmptyLine = allLines.findIndex(line => line.trim() !== ''); 81 | 82 | const lines = allLines.slice(indexOfFirstNonEmptyLine); 83 | 84 | const indent = lines[0]!.match(/^\s*/)?.[0]; 85 | 86 | return lines.map(line => line.slice(indent!.length)).join('\n'); 87 | }); 88 | 89 | export function flagIsElse( 90 | value: string | boolean | undefined, 91 | options: T[], 92 | elseValue: Else, 93 | ) { 94 | if (typeof value === 'string') { 95 | if (options.includes(value as T)) return value as T; 96 | } 97 | 98 | return elseValue; 99 | } 100 | --------------------------------------------------------------------------------