A CLI for scaffolding ATProto web applications
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: init

+614
+2
.gitignore
··· 1 + node_modules/ 2 + dist
+21
LICENSE
··· 1 + MIT License 2 + 3 + Copyright (c) 2026 Dane Miller 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+28
package.json
··· 1 + { 2 + "name": "create-atproto-app", 3 + "version": "0.0.1", 4 + "description": "CLI to bootstrap ATProto projects", 5 + "type": "module", 6 + "main": "dist/index.js", 7 + "bin": { 8 + "create-atproto-app": "dist/index.js" 9 + }, 10 + "scripts": { 11 + "dev": "tsx src/index.ts", 12 + "build": "tsc", 13 + "start": "node dist/index.js" 14 + }, 15 + "keywords": ["atproto", "cli"], 16 + "author": "Dane MIller <me@dane.computer>", 17 + "license": "MIT", 18 + "packageManager": "pnpm@10.15.0", 19 + "devDependencies": { 20 + "@types/node": "^22.0.0", 21 + "citty": "^0.2.1", 22 + "tsx": "^4.19.0", 23 + "typescript": "^5.6.0" 24 + }, 25 + "dependencies": { 26 + "@clack/prompts": "^1.0.1" 27 + } 28 + }
+381
pnpm-lock.yaml
··· 1 + lockfileVersion: '9.0' 2 + 3 + settings: 4 + autoInstallPeers: true 5 + excludeLinksFromLockfile: false 6 + 7 + importers: 8 + 9 + .: 10 + dependencies: 11 + '@clack/prompts': 12 + specifier: ^1.0.1 13 + version: 1.0.1 14 + devDependencies: 15 + '@types/node': 16 + specifier: ^22.0.0 17 + version: 22.19.11 18 + citty: 19 + specifier: ^0.2.1 20 + version: 0.2.1 21 + tsx: 22 + specifier: ^4.19.0 23 + version: 4.21.0 24 + typescript: 25 + specifier: ^5.6.0 26 + version: 5.9.3 27 + 28 + packages: 29 + 30 + '@clack/core@1.0.1': 31 + resolution: {integrity: sha512-WKeyK3NOBwDOzagPR5H08rFk9D/WuN705yEbuZvKqlkmoLM2woKtXb10OO2k1NoSU4SFG947i2/SCYh+2u5e4g==} 32 + 33 + '@clack/prompts@1.0.1': 34 + resolution: {integrity: sha512-/42G73JkuYdyWZ6m8d/CJtBrGl1Hegyc7Fy78m5Ob+jF85TOUmLR5XLce/U3LxYAw0kJ8CT5aI99RIvPHcGp/Q==} 35 + 36 + '@esbuild/aix-ppc64@0.27.3': 37 + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} 38 + engines: {node: '>=18'} 39 + cpu: [ppc64] 40 + os: [aix] 41 + 42 + '@esbuild/android-arm64@0.27.3': 43 + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} 44 + engines: {node: '>=18'} 45 + cpu: [arm64] 46 + os: [android] 47 + 48 + '@esbuild/android-arm@0.27.3': 49 + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} 50 + engines: {node: '>=18'} 51 + cpu: [arm] 52 + os: [android] 53 + 54 + '@esbuild/android-x64@0.27.3': 55 + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} 56 + engines: {node: '>=18'} 57 + cpu: [x64] 58 + os: [android] 59 + 60 + '@esbuild/darwin-arm64@0.27.3': 61 + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} 62 + engines: {node: '>=18'} 63 + cpu: [arm64] 64 + os: [darwin] 65 + 66 + '@esbuild/darwin-x64@0.27.3': 67 + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} 68 + engines: {node: '>=18'} 69 + cpu: [x64] 70 + os: [darwin] 71 + 72 + '@esbuild/freebsd-arm64@0.27.3': 73 + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} 74 + engines: {node: '>=18'} 75 + cpu: [arm64] 76 + os: [freebsd] 77 + 78 + '@esbuild/freebsd-x64@0.27.3': 79 + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} 80 + engines: {node: '>=18'} 81 + cpu: [x64] 82 + os: [freebsd] 83 + 84 + '@esbuild/linux-arm64@0.27.3': 85 + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} 86 + engines: {node: '>=18'} 87 + cpu: [arm64] 88 + os: [linux] 89 + 90 + '@esbuild/linux-arm@0.27.3': 91 + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} 92 + engines: {node: '>=18'} 93 + cpu: [arm] 94 + os: [linux] 95 + 96 + '@esbuild/linux-ia32@0.27.3': 97 + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} 98 + engines: {node: '>=18'} 99 + cpu: [ia32] 100 + os: [linux] 101 + 102 + '@esbuild/linux-loong64@0.27.3': 103 + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} 104 + engines: {node: '>=18'} 105 + cpu: [loong64] 106 + os: [linux] 107 + 108 + '@esbuild/linux-mips64el@0.27.3': 109 + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} 110 + engines: {node: '>=18'} 111 + cpu: [mips64el] 112 + os: [linux] 113 + 114 + '@esbuild/linux-ppc64@0.27.3': 115 + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} 116 + engines: {node: '>=18'} 117 + cpu: [ppc64] 118 + os: [linux] 119 + 120 + '@esbuild/linux-riscv64@0.27.3': 121 + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} 122 + engines: {node: '>=18'} 123 + cpu: [riscv64] 124 + os: [linux] 125 + 126 + '@esbuild/linux-s390x@0.27.3': 127 + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} 128 + engines: {node: '>=18'} 129 + cpu: [s390x] 130 + os: [linux] 131 + 132 + '@esbuild/linux-x64@0.27.3': 133 + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} 134 + engines: {node: '>=18'} 135 + cpu: [x64] 136 + os: [linux] 137 + 138 + '@esbuild/netbsd-arm64@0.27.3': 139 + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} 140 + engines: {node: '>=18'} 141 + cpu: [arm64] 142 + os: [netbsd] 143 + 144 + '@esbuild/netbsd-x64@0.27.3': 145 + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} 146 + engines: {node: '>=18'} 147 + cpu: [x64] 148 + os: [netbsd] 149 + 150 + '@esbuild/openbsd-arm64@0.27.3': 151 + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} 152 + engines: {node: '>=18'} 153 + cpu: [arm64] 154 + os: [openbsd] 155 + 156 + '@esbuild/openbsd-x64@0.27.3': 157 + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} 158 + engines: {node: '>=18'} 159 + cpu: [x64] 160 + os: [openbsd] 161 + 162 + '@esbuild/openharmony-arm64@0.27.3': 163 + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} 164 + engines: {node: '>=18'} 165 + cpu: [arm64] 166 + os: [openharmony] 167 + 168 + '@esbuild/sunos-x64@0.27.3': 169 + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} 170 + engines: {node: '>=18'} 171 + cpu: [x64] 172 + os: [sunos] 173 + 174 + '@esbuild/win32-arm64@0.27.3': 175 + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} 176 + engines: {node: '>=18'} 177 + cpu: [arm64] 178 + os: [win32] 179 + 180 + '@esbuild/win32-ia32@0.27.3': 181 + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} 182 + engines: {node: '>=18'} 183 + cpu: [ia32] 184 + os: [win32] 185 + 186 + '@esbuild/win32-x64@0.27.3': 187 + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} 188 + engines: {node: '>=18'} 189 + cpu: [x64] 190 + os: [win32] 191 + 192 + '@types/node@22.19.11': 193 + resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==} 194 + 195 + citty@0.2.1: 196 + resolution: {integrity: sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==} 197 + 198 + esbuild@0.27.3: 199 + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} 200 + engines: {node: '>=18'} 201 + hasBin: true 202 + 203 + fsevents@2.3.3: 204 + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 205 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 206 + os: [darwin] 207 + 208 + get-tsconfig@4.13.6: 209 + resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} 210 + 211 + picocolors@1.1.1: 212 + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 213 + 214 + resolve-pkg-maps@1.0.0: 215 + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} 216 + 217 + sisteransi@1.0.5: 218 + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} 219 + 220 + tsx@4.21.0: 221 + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} 222 + engines: {node: '>=18.0.0'} 223 + hasBin: true 224 + 225 + typescript@5.9.3: 226 + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} 227 + engines: {node: '>=14.17'} 228 + hasBin: true 229 + 230 + undici-types@6.21.0: 231 + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 232 + 233 + snapshots: 234 + 235 + '@clack/core@1.0.1': 236 + dependencies: 237 + picocolors: 1.1.1 238 + sisteransi: 1.0.5 239 + 240 + '@clack/prompts@1.0.1': 241 + dependencies: 242 + '@clack/core': 1.0.1 243 + picocolors: 1.1.1 244 + sisteransi: 1.0.5 245 + 246 + '@esbuild/aix-ppc64@0.27.3': 247 + optional: true 248 + 249 + '@esbuild/android-arm64@0.27.3': 250 + optional: true 251 + 252 + '@esbuild/android-arm@0.27.3': 253 + optional: true 254 + 255 + '@esbuild/android-x64@0.27.3': 256 + optional: true 257 + 258 + '@esbuild/darwin-arm64@0.27.3': 259 + optional: true 260 + 261 + '@esbuild/darwin-x64@0.27.3': 262 + optional: true 263 + 264 + '@esbuild/freebsd-arm64@0.27.3': 265 + optional: true 266 + 267 + '@esbuild/freebsd-x64@0.27.3': 268 + optional: true 269 + 270 + '@esbuild/linux-arm64@0.27.3': 271 + optional: true 272 + 273 + '@esbuild/linux-arm@0.27.3': 274 + optional: true 275 + 276 + '@esbuild/linux-ia32@0.27.3': 277 + optional: true 278 + 279 + '@esbuild/linux-loong64@0.27.3': 280 + optional: true 281 + 282 + '@esbuild/linux-mips64el@0.27.3': 283 + optional: true 284 + 285 + '@esbuild/linux-ppc64@0.27.3': 286 + optional: true 287 + 288 + '@esbuild/linux-riscv64@0.27.3': 289 + optional: true 290 + 291 + '@esbuild/linux-s390x@0.27.3': 292 + optional: true 293 + 294 + '@esbuild/linux-x64@0.27.3': 295 + optional: true 296 + 297 + '@esbuild/netbsd-arm64@0.27.3': 298 + optional: true 299 + 300 + '@esbuild/netbsd-x64@0.27.3': 301 + optional: true 302 + 303 + '@esbuild/openbsd-arm64@0.27.3': 304 + optional: true 305 + 306 + '@esbuild/openbsd-x64@0.27.3': 307 + optional: true 308 + 309 + '@esbuild/openharmony-arm64@0.27.3': 310 + optional: true 311 + 312 + '@esbuild/sunos-x64@0.27.3': 313 + optional: true 314 + 315 + '@esbuild/win32-arm64@0.27.3': 316 + optional: true 317 + 318 + '@esbuild/win32-ia32@0.27.3': 319 + optional: true 320 + 321 + '@esbuild/win32-x64@0.27.3': 322 + optional: true 323 + 324 + '@types/node@22.19.11': 325 + dependencies: 326 + undici-types: 6.21.0 327 + 328 + citty@0.2.1: {} 329 + 330 + esbuild@0.27.3: 331 + optionalDependencies: 332 + '@esbuild/aix-ppc64': 0.27.3 333 + '@esbuild/android-arm': 0.27.3 334 + '@esbuild/android-arm64': 0.27.3 335 + '@esbuild/android-x64': 0.27.3 336 + '@esbuild/darwin-arm64': 0.27.3 337 + '@esbuild/darwin-x64': 0.27.3 338 + '@esbuild/freebsd-arm64': 0.27.3 339 + '@esbuild/freebsd-x64': 0.27.3 340 + '@esbuild/linux-arm': 0.27.3 341 + '@esbuild/linux-arm64': 0.27.3 342 + '@esbuild/linux-ia32': 0.27.3 343 + '@esbuild/linux-loong64': 0.27.3 344 + '@esbuild/linux-mips64el': 0.27.3 345 + '@esbuild/linux-ppc64': 0.27.3 346 + '@esbuild/linux-riscv64': 0.27.3 347 + '@esbuild/linux-s390x': 0.27.3 348 + '@esbuild/linux-x64': 0.27.3 349 + '@esbuild/netbsd-arm64': 0.27.3 350 + '@esbuild/netbsd-x64': 0.27.3 351 + '@esbuild/openbsd-arm64': 0.27.3 352 + '@esbuild/openbsd-x64': 0.27.3 353 + '@esbuild/openharmony-arm64': 0.27.3 354 + '@esbuild/sunos-x64': 0.27.3 355 + '@esbuild/win32-arm64': 0.27.3 356 + '@esbuild/win32-ia32': 0.27.3 357 + '@esbuild/win32-x64': 0.27.3 358 + 359 + fsevents@2.3.3: 360 + optional: true 361 + 362 + get-tsconfig@4.13.6: 363 + dependencies: 364 + resolve-pkg-maps: 1.0.0 365 + 366 + picocolors@1.1.1: {} 367 + 368 + resolve-pkg-maps@1.0.0: {} 369 + 370 + sisteransi@1.0.5: {} 371 + 372 + tsx@4.21.0: 373 + dependencies: 374 + esbuild: 0.27.3 375 + get-tsconfig: 4.13.6 376 + optionalDependencies: 377 + fsevents: 2.3.3 378 + 379 + typescript@5.9.3: {} 380 + 381 + undici-types@6.21.0: {}
+85
src/commands/init.ts
··· 1 + import { intro, outro, text, select, confirm, spinner, isCancel, cancel } from '@clack/prompts'; 2 + import type { CommandDef } from 'citty'; 3 + import { generateProject } from '../utils/generate.js'; 4 + 5 + const TEMPLATES = [ 6 + { value: 'web-application', label: 'Web Application (CSR + No OAuth)', hint: 'Minimal Client Side Rendered that uses public APIs' }, 7 + ] as const; 8 + 9 + type Template = typeof TEMPLATES[number]['value']; 10 + 11 + export const initCommand: CommandDef = { 12 + meta: { 13 + name: 'init', 14 + description: 'Create a new ATProto project', 15 + }, 16 + args: { 17 + name: { 18 + type: 'positional', 19 + description: 'Project name', 20 + required: false, 21 + }, 22 + template: { 23 + type: 'string', 24 + description: 'Template to use', 25 + alias: 't', 26 + }, 27 + }, 28 + async run({ args }) { 29 + intro(`create-atproto-app`); 30 + 31 + const template = args.template ?? await select({ 32 + message: 'Select the type of app you would like to create:', 33 + options: TEMPLATES, 34 + }); 35 + 36 + if (isCancel(template)) { 37 + cancel('Operation cancelled'); 38 + return; 39 + } 40 + 41 + const projectName = args.name ?? await text({ 42 + message: 'Please provide a name for this project:', 43 + placeholder: 'my-atproto-app', 44 + validate: (value) => { 45 + if (!value || !value.trim()) return 'Project name is required'; 46 + if (!/^[a-z0-9-]+$/.test(value)) { 47 + return 'Use only lowercase letters, numbers, and hyphens'; 48 + } 49 + }, 50 + }); 51 + 52 + if (isCancel(projectName)) { 53 + cancel('Operation cancelled'); 54 + return; 55 + } 56 + 57 + const useTypeScript = await confirm({ 58 + message: 'Use TypeScript?', 59 + initialValue: true, 60 + }); 61 + 62 + if (isCancel(useTypeScript)) { 63 + cancel('Operation cancelled'); 64 + return; 65 + } 66 + 67 + const s = spinner(); 68 + s.start('Scaffolding project...'); 69 + 70 + try { 71 + await generateProject({ 72 + name: projectName, 73 + template: template, 74 + typescript: useTypeScript, 75 + }); 76 + s.stop('Project created!'); 77 + } catch (err) { 78 + s.stop('Failed to create project'); 79 + console.error(err); 80 + return; 81 + } 82 + 83 + outro(`Done! Run \`cd ${projectName}\` to get started`); 84 + }, 85 + };
+15
src/index.ts
··· 1 + import { defineCommand, runMain } from 'citty'; 2 + import { initCommand } from './commands/init.js'; 3 + 4 + const main = defineCommand({ 5 + meta: { 6 + name: 'create-atproto-app', 7 + version: '1.0.0', 8 + description: 'Bootstrap a new ATProto project', 9 + }, 10 + subCommands: { 11 + init: initCommand, 12 + }, 13 + }); 14 + 15 + runMain(main);
+9
src/templates/web-application/package.json
··· 1 + { 2 + "name": "template", 3 + "version": "1.0.0", 4 + "type": "module", 5 + "scripts": { 6 + "dev": "tsx src/index.ts", 7 + "build": "tsc" 8 + } 9 + }
+3
src/templates/web-application/src/app.ts
··· 1 + export function createApp() { 2 + console.log('Hello ATProto!'); 3 + }
+3
src/templates/web-application/src/index.ts
··· 1 + import { createApp } from './app.js'; 2 + 3 + createApp();
+13
src/templates/web-application/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "ESNext", 5 + "moduleResolution": "bundler", 6 + "strict": true, 7 + "esModuleInterop": true, 8 + "skipLibCheck": true, 9 + "outDir": "dist", 10 + "rootDir": "src" 11 + }, 12 + "include": ["src/**/*"] 13 + }
+40
src/utils/generate.ts
··· 1 + import { cp, mkdir, writeFile, readFile } from 'node:fs/promises'; 2 + import { join, dirname } from 'node:path'; 3 + import { fileURLToPath } from 'node:url'; 4 + 5 + const __dirname = dirname(fileURLToPath(import.meta.url)); 6 + 7 + export interface GenerateOptions { 8 + name: string; 9 + template: 'wep-application' 10 + typescript: boolean; 11 + } 12 + 13 + export async function generateProject(opts: GenerateOptions): Promise<void> { 14 + const targetDir = join(process.cwd(), opts.name); 15 + const templateDir = join(__dirname, '../templates', opts.template); 16 + 17 + await mkdir(targetDir, { recursive: true }); 18 + 19 + await cp(templateDir, targetDir, { recursive: true }); 20 + 21 + const packageJsonPath = join(targetDir, 'package.json'); 22 + const packageJsonContent = await readFile(packageJsonPath, 'utf-8'); 23 + const packageJson = JSON.parse(packageJsonContent); 24 + packageJson.name = opts.name; 25 + await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); 26 + 27 + if (!opts.typescript) { 28 + const jsDir = join(targetDir, 'src'); 29 + const tsFiles = ['index.ts', 'app.ts']; 30 + for (const file of tsFiles) { 31 + const tsPath = join(jsDir, file); 32 + const jsPath = join(jsDir, file.replace('.ts', '.js')); 33 + try { 34 + const content = await readFile(tsPath, 'utf-8'); 35 + await writeFile(jsPath, content); 36 + await cp(jsPath, tsPath); 37 + } catch {} 38 + } 39 + } 40 + }
+14
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "ESNext", 5 + "moduleResolution": "bundler", 6 + "esModuleInterop": true, 7 + "strict": true, 8 + "skipLibCheck": true, 9 + "outDir": "dist", 10 + "rootDir": "src", 11 + "declaration": true 12 + }, 13 + "include": ["src/**/*"] 14 + }