@openload28/create-tt 1.0.0-dev.20260828.263
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/bin/create-tt.js +8 -0
- package/package.json +36 -0
- package/src/installer.js +337 -0
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @openload28/create-tt
|
|
2
|
+
|
|
3
|
+
Create a ready-to-run Vite + TypeScript + tt project:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
bunx @openload28/create-tt@next my-app
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Add tt to an existing TypeScript project. The initializer detects Vite,
|
|
10
|
+
Rollup, Rolldown, webpack, Rspack, esbuild, or Farm from `package.json`:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
bunx @openload28/create-tt@next init
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The initializer updates `package.json` structurally. For bundlers with a
|
|
17
|
+
declarative config, it generates an `tt.*.config.mjs` wrapper instead of
|
|
18
|
+
rewriting arbitrary user code. Existing scripts and config files stay intact.
|
|
19
|
+
Use `--no-install` in CI or when dependencies will be installed later.
|
|
20
|
+
New projects use Bun for dependency installation and scripts. Existing projects
|
|
21
|
+
keep the package manager declared in `package.json` or selected by their lockfile.
|
|
22
|
+
|
|
23
|
+
For packages published to a private or local npm-compatible registry, pass the
|
|
24
|
+
registry through the entire bootstrap:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
BUN_CONFIG_REGISTRY=http://127.0.0.1:4873 \
|
|
28
|
+
bunx @openload28/create-tt@next my-app --registry http://127.0.0.1:4873
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The Nightly initializer installs the `next` channels of
|
|
32
|
+
`@openload28/tt-lang` and `@openload28/unplugin-tt`. The current TypeScript toolchain
|
|
33
|
+
prerequisite is documented in the repository
|
|
34
|
+
[installation guide](https://github.com/load28/tt/blob/main/docs/getting-started.md).
|
package/bin/create-tt.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openload28/create-tt",
|
|
3
|
+
"version": "1.0.0-dev.20260828.263",
|
|
4
|
+
"description": "Create a new tt project or add tt to an existing TypeScript project",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public",
|
|
8
|
+
"registry": "https://registry.npmjs.org/"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"bin": {
|
|
12
|
+
"create-tt": "bin/create-tt.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/create-tt.js",
|
|
16
|
+
"src/installer.js",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/load28/tt",
|
|
28
|
+
"directory": "packages/create-tt"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"tt",
|
|
32
|
+
"typescript",
|
|
33
|
+
"scaffold",
|
|
34
|
+
"vite"
|
|
35
|
+
]
|
|
36
|
+
}
|
package/src/installer.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { basename, join, relative, resolve } from 'node:path'
|
|
4
|
+
import { spawnSync } from 'node:child_process'
|
|
5
|
+
|
|
6
|
+
const ownManifest = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'))
|
|
7
|
+
const ttChannel = dependencyChannel(ownManifest.version)
|
|
8
|
+
const versions = {
|
|
9
|
+
'@openload28/tt-lang': ttChannel,
|
|
10
|
+
'@openload28/unplugin-tt': ttChannel,
|
|
11
|
+
vite: '^8.0.0',
|
|
12
|
+
// ttc drives the TypeScript the project installs and looks nowhere else,
|
|
13
|
+
// so a scaffolded project has to install one — the exact version this
|
|
14
|
+
// repository tests against, which is the whole point: a user runs the
|
|
15
|
+
// compiler against the TypeScript it was verified with, and an upstream
|
|
16
|
+
// nightly cannot change their build overnight. Not `7`: declaration
|
|
17
|
+
// output (`ttc --types`, the editor's `.tt.d.ts` sidecars) needs the emit
|
|
18
|
+
// API that arrived in 7.1, and `7` resolves to the 7.0 line because
|
|
19
|
+
// ranges do not match prereleases (TASK-256). When 7.1 is released this
|
|
20
|
+
// moves to `7` everywhere at once, held by
|
|
21
|
+
// npm/scripts/typescript-version.test.mjs.
|
|
22
|
+
typescript: "7.1.0-dev.20260826.1",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function dependencyChannel(packageVersion) {
|
|
26
|
+
if (/-dev\./.test(packageVersion)) return 'next'
|
|
27
|
+
if (/-rc(?:\.|$)/.test(packageVersion)) return 'rc'
|
|
28
|
+
return 'latest'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const bundlers = {
|
|
32
|
+
vite: {
|
|
33
|
+
packages: ['vite'],
|
|
34
|
+
configs: ['vite.config.ts', 'vite.config.js', 'vite.config.mts', 'vite.config.mjs'],
|
|
35
|
+
module: '@openload28/unplugin-tt/vite',
|
|
36
|
+
wrapper: 'tt.vite.config.mjs',
|
|
37
|
+
commands: { dev: 'vite', build: 'vite build' },
|
|
38
|
+
},
|
|
39
|
+
rollup: {
|
|
40
|
+
packages: ['rollup'],
|
|
41
|
+
configs: ['rollup.config.ts', 'rollup.config.js', 'rollup.config.mjs'],
|
|
42
|
+
module: '@openload28/unplugin-tt/rollup',
|
|
43
|
+
wrapper: 'tt.rollup.config.mjs',
|
|
44
|
+
commands: { dev: 'rollup --watch', build: 'rollup' },
|
|
45
|
+
},
|
|
46
|
+
rolldown: {
|
|
47
|
+
packages: ['rolldown'],
|
|
48
|
+
configs: ['rolldown.config.ts', 'rolldown.config.js', 'rolldown.config.mjs'],
|
|
49
|
+
module: '@openload28/unplugin-tt/rolldown',
|
|
50
|
+
wrapper: 'tt.rolldown.config.mjs',
|
|
51
|
+
commands: { dev: 'rolldown --watch', build: 'rolldown' },
|
|
52
|
+
},
|
|
53
|
+
webpack: {
|
|
54
|
+
packages: ['webpack'],
|
|
55
|
+
configs: ['webpack.config.ts', 'webpack.config.js', 'webpack.config.cjs', 'webpack.config.mjs'],
|
|
56
|
+
module: '@openload28/unplugin-tt/webpack',
|
|
57
|
+
wrapper: 'tt.webpack.config.mjs',
|
|
58
|
+
commands: { dev: 'webpack serve', build: 'webpack' },
|
|
59
|
+
},
|
|
60
|
+
rspack: {
|
|
61
|
+
packages: ['@rspack/core'],
|
|
62
|
+
configs: ['rspack.config.ts', 'rspack.config.js', 'rspack.config.mjs'],
|
|
63
|
+
module: '@openload28/unplugin-tt/rspack',
|
|
64
|
+
wrapper: 'tt.rspack.config.mjs',
|
|
65
|
+
commands: { dev: 'rspack serve', build: 'rspack build' },
|
|
66
|
+
},
|
|
67
|
+
farm: {
|
|
68
|
+
packages: ['@farmfe/core'],
|
|
69
|
+
configs: ['farm.config.ts', 'farm.config.js', 'farm.config.mjs'],
|
|
70
|
+
module: '@openload28/unplugin-tt/farm',
|
|
71
|
+
wrapper: 'tt.farm.config.mjs',
|
|
72
|
+
commands: { dev: 'farm start', build: 'farm build' },
|
|
73
|
+
},
|
|
74
|
+
esbuild: {
|
|
75
|
+
packages: ['esbuild'],
|
|
76
|
+
configs: [],
|
|
77
|
+
module: '@openload28/unplugin-tt/esbuild',
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function run(argv, io = console) {
|
|
82
|
+
const options = parseArguments(argv)
|
|
83
|
+
if (options.help) {
|
|
84
|
+
io.log(help)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
const result = options.command === 'init'
|
|
88
|
+
? await initializeExisting(options)
|
|
89
|
+
: await createProject(options)
|
|
90
|
+
|
|
91
|
+
if (options.install) installDependencies(result.root, result.packageManager, options.registry)
|
|
92
|
+
printResult(result, io)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function createProject(options) {
|
|
96
|
+
const root = resolve(options.directory ?? 'my-tt-app')
|
|
97
|
+
if (existsSync(root) && (await readdir(root)).length > 0) {
|
|
98
|
+
throw new Error(`target directory is not empty: ${root}`)
|
|
99
|
+
}
|
|
100
|
+
await mkdir(join(root, 'src'), { recursive: true })
|
|
101
|
+
const packageManager = options.packageManager ?? 'bun'
|
|
102
|
+
const name = packageName(basename(root))
|
|
103
|
+
const manifest = {
|
|
104
|
+
name,
|
|
105
|
+
private: true,
|
|
106
|
+
version: '0.0.0',
|
|
107
|
+
type: 'module',
|
|
108
|
+
scripts: {
|
|
109
|
+
dev: 'vite',
|
|
110
|
+
build: 'ttc --check-types src && vite build',
|
|
111
|
+
check: 'ttc --check-types src',
|
|
112
|
+
},
|
|
113
|
+
devDependencies: {
|
|
114
|
+
'@openload28/tt-lang': versions['@openload28/tt-lang'],
|
|
115
|
+
'@openload28/unplugin-tt': versions['@openload28/unplugin-tt'],
|
|
116
|
+
typescript: versions.typescript,
|
|
117
|
+
vite: versions.vite,
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
await writeJson(join(root, 'package.json'), manifest)
|
|
121
|
+
await writeJson(join(root, 'tsconfig.json'), tsconfig())
|
|
122
|
+
await writeFile(join(root, 'vite.config.ts'), viteConfig)
|
|
123
|
+
await writeFile(join(root, 'index.html'), indexHtml)
|
|
124
|
+
await writeFile(join(root, 'src/main.ts'), "import './app.tt'\n")
|
|
125
|
+
await writeFile(join(root, 'src/app.tt'), starterSource)
|
|
126
|
+
await writeFile(join(root, '.gitignore'), 'node_modules/\ndist/\n.tt-types/\n')
|
|
127
|
+
if (options.registry) {
|
|
128
|
+
await writeFile(join(root, 'bunfig.toml'), `[install]\nregistry = ${JSON.stringify(options.registry)}\n`)
|
|
129
|
+
}
|
|
130
|
+
return { root, packageManager, mode: 'create', bundler: 'vite', files: ['src/main.ts', 'src/app.tt', 'vite.config.ts'] }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function initializeExisting(options) {
|
|
134
|
+
const root = resolve(options.directory ?? '.')
|
|
135
|
+
const manifestPath = join(root, 'package.json')
|
|
136
|
+
if (!existsSync(manifestPath)) throw new Error(`package.json not found in ${root}`)
|
|
137
|
+
|
|
138
|
+
const source = await readFile(manifestPath, 'utf8')
|
|
139
|
+
const manifest = JSON.parse(source)
|
|
140
|
+
const packageManager = options.packageManager ?? detectPackageManager(root, manifest)
|
|
141
|
+
const bundler = options.bundler === 'auto' ? detectBundler(manifest) : options.bundler
|
|
142
|
+
const devDependencies = manifest.devDependencies ?? {}
|
|
143
|
+
devDependencies['@openload28/tt-lang'] ??= versions['@openload28/tt-lang']
|
|
144
|
+
devDependencies.typescript ??= versions.typescript
|
|
145
|
+
manifest.devDependencies = devDependencies
|
|
146
|
+
manifest.scripts ??= {}
|
|
147
|
+
manifest.scripts['tt:check'] ??= 'ttc --check-types src'
|
|
148
|
+
|
|
149
|
+
const files = []
|
|
150
|
+
let manualModule
|
|
151
|
+
if (bundler && bundler !== 'none') {
|
|
152
|
+
devDependencies['@openload28/unplugin-tt'] ??= versions['@openload28/unplugin-tt']
|
|
153
|
+
const adapter = bundlers[bundler]
|
|
154
|
+
if (!adapter) throw new Error(`unsupported bundler: ${bundler}`)
|
|
155
|
+
const base = adapter.configs.find((file) => existsSync(join(root, file)))
|
|
156
|
+
if (bundler === 'esbuild') {
|
|
157
|
+
manualModule = adapter.module
|
|
158
|
+
} else {
|
|
159
|
+
await writeFile(join(root, adapter.wrapper), wrapperConfig(adapter.module, base))
|
|
160
|
+
files.push(adapter.wrapper)
|
|
161
|
+
manifest.scripts['tt:dev'] ??= `${adapter.commands.dev} --config ${adapter.wrapper}`
|
|
162
|
+
manifest.scripts['tt:build'] ??= `ttc --check-types src && ${adapter.commands.build} --config ${adapter.wrapper}`
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
manifest.scripts['tt:build'] ??= 'ttc -o .tt-build src'
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
await writeJson(manifestPath, manifest, indentation(source))
|
|
169
|
+
return { root, packageManager, mode: 'init', bundler: bundler ?? 'none', files, manualModule }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function detectBundler(manifest) {
|
|
173
|
+
const dependencies = { ...manifest.dependencies, ...manifest.devDependencies }
|
|
174
|
+
return Object.entries(bundlers).find(([, adapter]) =>
|
|
175
|
+
adapter.packages.some((name) => name in dependencies))?.[0] ?? 'none'
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function parseArguments(argv) {
|
|
179
|
+
const args = [...argv]
|
|
180
|
+
const options = {
|
|
181
|
+
command: args[0] === 'init' ? args.shift() : 'create',
|
|
182
|
+
install: true,
|
|
183
|
+
bundler: 'auto',
|
|
184
|
+
}
|
|
185
|
+
while (args.length) {
|
|
186
|
+
const arg = args.shift()
|
|
187
|
+
if (arg === '--help' || arg === '-h') options.help = true
|
|
188
|
+
else if (arg === '--no-install') options.install = false
|
|
189
|
+
else if (arg === '--bundler') options.bundler = requiredValue(arg, args)
|
|
190
|
+
else if (arg === '--package-manager') options.packageManager = requiredValue(arg, args)
|
|
191
|
+
else if (arg === '--registry') options.registry = registryUrl(requiredValue(arg, args))
|
|
192
|
+
else if (!arg.startsWith('-') && !options.directory) options.directory = arg
|
|
193
|
+
else throw new Error(`unknown argument: ${arg}`)
|
|
194
|
+
}
|
|
195
|
+
if (!['auto', 'none', ...Object.keys(bundlers)].includes(options.bundler)) {
|
|
196
|
+
throw new Error(`unsupported bundler: ${options.bundler}`)
|
|
197
|
+
}
|
|
198
|
+
if (options.packageManager && !['npm', 'pnpm', 'yarn', 'bun'].includes(options.packageManager)) {
|
|
199
|
+
throw new Error(`unsupported package manager: ${options.packageManager}`)
|
|
200
|
+
}
|
|
201
|
+
if (options.command === 'create' && options.packageManager && options.packageManager !== 'bun') {
|
|
202
|
+
throw new Error('new projects use Bun; --package-manager only applies to init')
|
|
203
|
+
}
|
|
204
|
+
if (options.command === 'create' && !['auto', 'vite'].includes(options.bundler)) {
|
|
205
|
+
throw new Error('new projects use Vite; --bundler only selects vite for create')
|
|
206
|
+
}
|
|
207
|
+
return options
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function requiredValue(flag, args) {
|
|
211
|
+
const value = args.shift()
|
|
212
|
+
if (!value || value.startsWith('-')) throw new Error(`${flag} needs a value`)
|
|
213
|
+
return value
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function detectPackageManager(root, manifest = {}) {
|
|
217
|
+
const declared = typeof manifest.packageManager === 'string'
|
|
218
|
+
? manifest.packageManager.split('@')[0]
|
|
219
|
+
: undefined
|
|
220
|
+
if (['npm', 'pnpm', 'yarn', 'bun'].includes(declared)) return declared
|
|
221
|
+
const locks = [['pnpm-lock.yaml', 'pnpm'], ['yarn.lock', 'yarn'], ['bun.lock', 'bun'], ['bun.lockb', 'bun']]
|
|
222
|
+
return locks.find(([file]) => existsSync(join(root, file)))?.[1] ?? 'npm'
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function installDependencies(root, packageManager, registry) {
|
|
226
|
+
const args = ['install']
|
|
227
|
+
if (registry) args.push('--registry', registry)
|
|
228
|
+
const result = spawnSync(packageManager, args, { cwd: root, stdio: 'inherit' })
|
|
229
|
+
if (result.error) throw result.error
|
|
230
|
+
if (result.status !== 0) throw new Error(`${packageManager} install exited with status ${result.status}`)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function registryUrl(value) {
|
|
234
|
+
let url
|
|
235
|
+
try {
|
|
236
|
+
url = new URL(value)
|
|
237
|
+
} catch {
|
|
238
|
+
throw new Error(`invalid registry URL: ${value}`)
|
|
239
|
+
}
|
|
240
|
+
if (!['http:', 'https:'].includes(url.protocol)) throw new Error('registry URL must use http or https')
|
|
241
|
+
if (url.username || url.password) throw new Error('pass registry credentials through Bun configuration or environment variables')
|
|
242
|
+
return url.href
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function printResult(result, io) {
|
|
246
|
+
const location = relative(process.cwd(), result.root) || '.'
|
|
247
|
+
io.log(result.mode === 'create' ? `Created a tt project in ${location}.` : `Added tt to ${location}.`)
|
|
248
|
+
if (result.files.length) io.log(`Generated: ${result.files.join(', ')}`)
|
|
249
|
+
if (result.manualModule) {
|
|
250
|
+
io.log(`Add tt() from ${result.manualModule} to your esbuild plugins array.`)
|
|
251
|
+
}
|
|
252
|
+
io.log(result.mode === 'create' ? `Run: cd ${location} && ${runScript(result.packageManager, 'dev')}` : `Run: ${runScript(result.packageManager, 'tt:check')}`)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function runScript(packageManager, script) {
|
|
256
|
+
return packageManager === 'npm' ? `npm run ${script}` : `${packageManager} run ${script}`
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function wrapperConfig(moduleName, base) {
|
|
260
|
+
const baseImport = base ? `import base from './${base}'\n` : 'const base = {}\n'
|
|
261
|
+
return `import tt from '${moduleName}'\n${baseImport}
|
|
262
|
+
const addRl = (config = {}) => Array.isArray(config)
|
|
263
|
+
? config.map(addRl)
|
|
264
|
+
: { ...config, plugins: [tt(), ...(config.plugins ?? [])] }
|
|
265
|
+
|
|
266
|
+
export default typeof base === 'function'
|
|
267
|
+
? async (...args) => addRl(await base(...args))
|
|
268
|
+
: Promise.resolve(base).then(addRl)
|
|
269
|
+
`
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function tsconfig() {
|
|
273
|
+
return {
|
|
274
|
+
compilerOptions: {
|
|
275
|
+
target: 'ES2022',
|
|
276
|
+
module: 'Preserve',
|
|
277
|
+
moduleResolution: 'Bundler',
|
|
278
|
+
strict: true,
|
|
279
|
+
noEmit: true,
|
|
280
|
+
skipLibCheck: true,
|
|
281
|
+
},
|
|
282
|
+
include: ['src', 'vite.config.ts'],
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function packageName(value) {
|
|
287
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '')
|
|
288
|
+
return normalized || 'my-tt-app'
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function indentation(source) {
|
|
292
|
+
return source.match(/\n([ \t]+)\S/)?.[1] ?? ' '
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function writeJson(path, value, space = ' ') {
|
|
296
|
+
await writeFile(path, `${JSON.stringify(value, null, space)}\n`)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const viteConfig = `import { defineConfig } from 'vite'
|
|
300
|
+
import tt from '@openload28/unplugin-tt/vite'
|
|
301
|
+
|
|
302
|
+
export default defineConfig({
|
|
303
|
+
plugins: [tt()],
|
|
304
|
+
})
|
|
305
|
+
`
|
|
306
|
+
|
|
307
|
+
const indexHtml = `<!doctype html>
|
|
308
|
+
<html lang="en">
|
|
309
|
+
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>tt app</title></head>
|
|
310
|
+
<body><main id="app"></main><script type="module" src="/src/main.ts"></script></body>
|
|
311
|
+
</html>
|
|
312
|
+
`
|
|
313
|
+
|
|
314
|
+
const starterSource = `variant Greeting {
|
|
315
|
+
Hello(name: string),
|
|
316
|
+
Welcome,
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const greeting = Greeting.Hello('tt')
|
|
320
|
+
document.querySelector<HTMLDivElement>('#app')!.textContent = match (greeting) {
|
|
321
|
+
Hello(name) => \`Hello, \${name}!\`,
|
|
322
|
+
Welcome => 'Welcome!',
|
|
323
|
+
}
|
|
324
|
+
`
|
|
325
|
+
|
|
326
|
+
const help = `Create a new tt project or add tt to an existing TypeScript project.
|
|
327
|
+
|
|
328
|
+
Usage:
|
|
329
|
+
bunx @openload28/create-tt@next [directory] [--no-install]
|
|
330
|
+
bunx @openload28/create-tt@next init [directory] [--bundler vite]
|
|
331
|
+
|
|
332
|
+
Options:
|
|
333
|
+
--bundler <auto|none|vite|rollup|rolldown|webpack|rspack|esbuild|farm>
|
|
334
|
+
--package-manager <npm|pnpm|yarn|bun>
|
|
335
|
+
--registry <url> install from an npm-compatible private/local registry
|
|
336
|
+
--no-install
|
|
337
|
+
-h, --help`
|