├── .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 | [](https://github.com/alii/ctsm/actions/workflows/test.yml)
4 | [](https://www.npmjs.com/package/ctsm)
5 | [](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 |
--------------------------------------------------------------------------------