@tanstack/cli 0.60.1 → 0.62.0
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/dist/cli.js +266 -11
- package/dist/command-line.js +103 -8
- package/dist/discovery.js +144 -0
- package/dist/options.js +35 -2
- package/dist/types/command-line.d.ts +7 -0
- package/dist/types/{mcp/types.d.ts → discovery.d.ts} +23 -75
- package/dist/types/types.d.ts +1 -2
- package/dist/types/ui-prompts.d.ts +5 -0
- package/dist/ui-prompts.js +26 -0
- package/package.json +6 -5
- package/skills/CHANGELOG.md +18 -0
- package/skills/add-addons-existing-app/SKILL.md +113 -0
- package/skills/choose-ecosystem-integrations/SKILL.md +140 -0
- package/skills/choose-ecosystem-integrations/references/authentication-providers.md +19 -0
- package/skills/choose-ecosystem-integrations/references/data-layer-providers.md +20 -0
- package/skills/choose-ecosystem-integrations/references/deployment-targets.md +19 -0
- package/skills/create-app-scaffold/SKILL.md +132 -0
- package/skills/create-app-scaffold/references/create-flag-compatibility-matrix.md +34 -0
- package/skills/create-app-scaffold/references/deployment-providers.md +19 -0
- package/skills/create-app-scaffold/references/framework-adapters.md +17 -0
- package/skills/create-app-scaffold/references/toolchains.md +17 -0
- package/skills/maintain-custom-addons-dev-watch/SKILL.md +118 -0
- package/skills/query-docs-library-metadata/SKILL.md +85 -0
- package/skills/query-docs-library-metadata/references/discovery-command-output-schemas.md +70 -0
- package/CHANGELOG.md +0 -787
- package/dist/mcp/api.js +0 -31
- package/dist/mcp/tools.js +0 -250
- package/dist/mcp/types.js +0 -37
- package/dist/mcp.js +0 -181
- package/dist/types/mcp/api.d.ts +0 -4
- package/dist/types/mcp/tools.d.ts +0 -2
- package/dist/types/mcp.d.ts +0 -5
- package/playwright-report/index.html +0 -85
- package/playwright.config.ts +0 -21
- package/src/bin.ts +0 -15
- package/src/cli.ts +0 -767
- package/src/command-line.ts +0 -473
- package/src/dev-watch.ts +0 -564
- package/src/file-syncer.ts +0 -263
- package/src/index.ts +0 -21
- package/src/mcp/api.ts +0 -42
- package/src/mcp/tools.ts +0 -323
- package/src/mcp/types.ts +0 -46
- package/src/mcp.ts +0 -263
- package/src/options.ts +0 -234
- package/src/types.ts +0 -28
- package/src/ui-environment.ts +0 -74
- package/src/ui-prompts.ts +0 -355
- package/src/utils.ts +0 -30
- package/test-results/.last-run.json +0 -4
- package/tests/command-line.test.ts +0 -622
- package/tests/index.test.ts +0 -9
- package/tests/mcp.test.ts +0 -225
- package/tests/options.test.ts +0 -216
- package/tests/setupVitest.ts +0 -6
- package/tests/ui-environment.test.ts +0 -97
- package/tests/ui-prompts.test.ts +0 -205
- package/tests-e2e/addons-smoke.spec.ts +0 -31
- package/tests-e2e/create-smoke.spec.ts +0 -39
- package/tests-e2e/helpers.ts +0 -526
- package/tests-e2e/matrix-opportunistic.spec.ts +0 -142
- package/tests-e2e/router-only-smoke.spec.ts +0 -68
- package/tests-e2e/solid-smoke.spec.ts +0 -25
- package/tests-e2e/templates-smoke.spec.ts +0 -52
- package/tsconfig.json +0 -17
- package/vitest.config.js +0 -8
package/src/dev-watch.ts
DELETED
|
@@ -1,564 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import { spawn } from 'node:child_process'
|
|
4
|
-
|
|
5
|
-
import chokidar from 'chokidar'
|
|
6
|
-
import chalk from 'chalk'
|
|
7
|
-
import { temporaryDirectory } from 'tempy'
|
|
8
|
-
import {
|
|
9
|
-
createApp,
|
|
10
|
-
finalizeAddOns,
|
|
11
|
-
getFrameworkById,
|
|
12
|
-
registerFramework,
|
|
13
|
-
scanAddOnDirectories,
|
|
14
|
-
scanProjectDirectory,
|
|
15
|
-
} from '@tanstack/create'
|
|
16
|
-
import { FileSyncer } from './file-syncer.js'
|
|
17
|
-
import { createUIEnvironment } from './ui-environment.js'
|
|
18
|
-
import type {
|
|
19
|
-
Environment,
|
|
20
|
-
Framework,
|
|
21
|
-
FrameworkDefinition,
|
|
22
|
-
Options,
|
|
23
|
-
} from '@tanstack/create'
|
|
24
|
-
import type { FSWatcher } from 'chokidar'
|
|
25
|
-
|
|
26
|
-
export interface DevWatchOptions {
|
|
27
|
-
watchPath: string
|
|
28
|
-
targetDir: string
|
|
29
|
-
framework: Framework
|
|
30
|
-
cliOptions: Options
|
|
31
|
-
packageManager: string
|
|
32
|
-
runDevCommand?: boolean
|
|
33
|
-
environment: Environment
|
|
34
|
-
frameworkDefinitionInitializers?: Array<() => FrameworkDefinition>
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface ChangeEvent {
|
|
38
|
-
type: 'add' | 'change' | 'unlink'
|
|
39
|
-
path: string
|
|
40
|
-
relativePath: string
|
|
41
|
-
timestamp: number
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
class DebounceQueue {
|
|
45
|
-
private timer: NodeJS.Timeout | null = null
|
|
46
|
-
private changes: Set<string> = new Set()
|
|
47
|
-
private callback: (changes: Set<string>) => void
|
|
48
|
-
|
|
49
|
-
constructor(
|
|
50
|
-
callback: (changes: Set<string>) => void,
|
|
51
|
-
private delay: number = 1000,
|
|
52
|
-
) {
|
|
53
|
-
this.callback = callback
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
add(path: string): void {
|
|
57
|
-
this.changes.add(path)
|
|
58
|
-
|
|
59
|
-
if (this.timer) {
|
|
60
|
-
clearTimeout(this.timer)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
this.timer = setTimeout(() => {
|
|
64
|
-
const currentChanges = new Set(this.changes)
|
|
65
|
-
this.callback(currentChanges)
|
|
66
|
-
this.changes.clear()
|
|
67
|
-
}, this.delay)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
size(): number {
|
|
71
|
-
return this.changes.size
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
clear(): void {
|
|
75
|
-
if (this.timer) {
|
|
76
|
-
clearTimeout(this.timer)
|
|
77
|
-
this.timer = null
|
|
78
|
-
}
|
|
79
|
-
this.changes.clear()
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export class DevWatchManager {
|
|
84
|
-
private watcher: FSWatcher | null = null
|
|
85
|
-
private debounceQueue: DebounceQueue
|
|
86
|
-
private syncer: FileSyncer
|
|
87
|
-
private tempDir: string | null = null
|
|
88
|
-
private isBuilding = false
|
|
89
|
-
private buildCount = 0
|
|
90
|
-
private appDevProcess: ReturnType<typeof spawn> | null = null
|
|
91
|
-
private lastSyncedSourceFiles: Set<string> | null = null
|
|
92
|
-
|
|
93
|
-
constructor(private options: DevWatchOptions) {
|
|
94
|
-
this.syncer = new FileSyncer()
|
|
95
|
-
this.debounceQueue = new DebounceQueue((changes) => this.rebuild(changes))
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async start(): Promise<void> {
|
|
99
|
-
// Validate watch path
|
|
100
|
-
if (!fs.existsSync(this.options.watchPath)) {
|
|
101
|
-
throw new Error(`Watch path does not exist: ${this.options.watchPath}`)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Validate target directory exists (should have been created by createApp)
|
|
105
|
-
if (!fs.existsSync(this.options.targetDir)) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
`Target directory does not exist: ${this.options.targetDir}`,
|
|
108
|
-
)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (this.options.cliOptions.install === false) {
|
|
112
|
-
throw new Error('Cannot use the --no-install flag when using --dev-watch')
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Log startup with tree style
|
|
116
|
-
console.log()
|
|
117
|
-
console.log(chalk.bold('dev-watch'))
|
|
118
|
-
this.log.tree('', `watching: ${chalk.cyan(this.options.watchPath)}`)
|
|
119
|
-
this.log.tree('', `target: ${chalk.cyan(this.options.targetDir)}`)
|
|
120
|
-
if (this.options.runDevCommand) {
|
|
121
|
-
this.log.tree('', `app dev server: ${chalk.cyan('enabled')}`)
|
|
122
|
-
}
|
|
123
|
-
this.log.tree('', 'ready', true)
|
|
124
|
-
|
|
125
|
-
// Setup signal handlers
|
|
126
|
-
process.on('SIGINT', () => this.cleanup())
|
|
127
|
-
process.on('SIGTERM', () => this.cleanup())
|
|
128
|
-
|
|
129
|
-
// Start watching
|
|
130
|
-
this.startWatcher()
|
|
131
|
-
|
|
132
|
-
if (this.options.runDevCommand) {
|
|
133
|
-
this.startAppDevServer()
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async stop(): Promise<void> {
|
|
138
|
-
console.log()
|
|
139
|
-
this.log.info('Stopping dev watch mode...')
|
|
140
|
-
|
|
141
|
-
if (this.watcher) {
|
|
142
|
-
await this.watcher.close()
|
|
143
|
-
this.watcher = null
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
this.debounceQueue.clear()
|
|
147
|
-
this.cleanup()
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
private startWatcher(): void {
|
|
151
|
-
const watcherConfig = {
|
|
152
|
-
ignored: [
|
|
153
|
-
'**/node_modules/**',
|
|
154
|
-
'**/.git/**',
|
|
155
|
-
'**/dist/**',
|
|
156
|
-
'**/build/**',
|
|
157
|
-
'**/.DS_Store',
|
|
158
|
-
'**/*.log',
|
|
159
|
-
this.tempDir!,
|
|
160
|
-
],
|
|
161
|
-
persistent: true,
|
|
162
|
-
ignoreInitial: true,
|
|
163
|
-
awaitWriteFinish: {
|
|
164
|
-
stabilityThreshold: 100,
|
|
165
|
-
pollInterval: 100,
|
|
166
|
-
},
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
this.watcher = chokidar.watch(this.options.watchPath, watcherConfig)
|
|
170
|
-
|
|
171
|
-
this.watcher.on('add', (filePath) => this.handleChange('add', filePath))
|
|
172
|
-
this.watcher.on('change', (filePath) =>
|
|
173
|
-
this.handleChange('change', filePath),
|
|
174
|
-
)
|
|
175
|
-
this.watcher.on('unlink', (filePath) =>
|
|
176
|
-
this.handleChange('unlink', filePath),
|
|
177
|
-
)
|
|
178
|
-
this.watcher.on('error', (error) =>
|
|
179
|
-
this.log.error(`Watcher error: ${error.message}`),
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
this.watcher.on('ready', () => {
|
|
183
|
-
// Already shown in startup, no need to repeat
|
|
184
|
-
})
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private handleChange(_type: ChangeEvent['type'], filePath: string): void {
|
|
188
|
-
const relativePath = path.relative(this.options.watchPath, filePath)
|
|
189
|
-
// Log change only once for the first file in debounce queue
|
|
190
|
-
if (this.debounceQueue.size() === 0) {
|
|
191
|
-
this.log.section('change detected')
|
|
192
|
-
this.log.subsection(`└─ ${relativePath}`)
|
|
193
|
-
} else {
|
|
194
|
-
this.log.subsection(`└─ ${relativePath}`)
|
|
195
|
-
}
|
|
196
|
-
this.debounceQueue.add(filePath)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
private async rebuild(changes: Set<string>): Promise<void> {
|
|
200
|
-
if (this.isBuilding) {
|
|
201
|
-
this.log.warning('Build already in progress, skipping...')
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
this.isBuilding = true
|
|
206
|
-
this.buildCount++
|
|
207
|
-
const buildId = this.buildCount
|
|
208
|
-
|
|
209
|
-
try {
|
|
210
|
-
this.log.section(`build #${buildId}`)
|
|
211
|
-
const startTime = Date.now()
|
|
212
|
-
|
|
213
|
-
let refreshedFramework: FrameworkDefinition | null | undefined =
|
|
214
|
-
this.createFrameworkDefinitionFromWatchPath()
|
|
215
|
-
|
|
216
|
-
if (!refreshedFramework && this.options.frameworkDefinitionInitializers) {
|
|
217
|
-
const refreshedFrameworks =
|
|
218
|
-
this.options.frameworkDefinitionInitializers.map(
|
|
219
|
-
(frameworkInitalizer) => frameworkInitalizer(),
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
refreshedFramework = refreshedFrameworks.find(
|
|
223
|
-
(f) => f.id === this.options.framework.id,
|
|
224
|
-
)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (!refreshedFramework) {
|
|
228
|
-
throw new Error(
|
|
229
|
-
'Could not refresh framework from watch path or framework initializers',
|
|
230
|
-
)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Update the chosen addons to use the latest code
|
|
234
|
-
const chosenAddonIds = this.options.cliOptions.chosenAddOns.map(
|
|
235
|
-
(m) => m.id,
|
|
236
|
-
)
|
|
237
|
-
// Create temp directory for this build using tempy
|
|
238
|
-
this.tempDir = temporaryDirectory()
|
|
239
|
-
|
|
240
|
-
// Register the scanned framework
|
|
241
|
-
registerFramework({
|
|
242
|
-
...refreshedFramework,
|
|
243
|
-
id: `${refreshedFramework.id}-updated`,
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
// Get the registered framework
|
|
247
|
-
const registeredFramework = getFrameworkById(
|
|
248
|
-
`${refreshedFramework.id}-updated`,
|
|
249
|
-
)
|
|
250
|
-
if (!registeredFramework) {
|
|
251
|
-
throw new Error(
|
|
252
|
-
`Failed to register framework: ${this.options.framework.id}`,
|
|
253
|
-
)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const updatedChosenAddons = await finalizeAddOns(
|
|
257
|
-
registeredFramework,
|
|
258
|
-
this.options.cliOptions.mode,
|
|
259
|
-
chosenAddonIds,
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
// Check if package metadata was modified
|
|
263
|
-
const packageMetadataChanged = Array.from(changes).some((filePath) => {
|
|
264
|
-
const normalized = filePath.replace(/\\/g, '/')
|
|
265
|
-
return /(^|\/)package\.json(\.ejs)?$/.test(normalized)
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
const updatedOptions: Options = {
|
|
269
|
-
...this.options.cliOptions,
|
|
270
|
-
chosenAddOns: updatedChosenAddons,
|
|
271
|
-
framework: registeredFramework,
|
|
272
|
-
targetDir: this.tempDir,
|
|
273
|
-
git: false,
|
|
274
|
-
install: packageMetadataChanged,
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Show package installation indicator if needed
|
|
278
|
-
if (packageMetadataChanged) {
|
|
279
|
-
this.log.tree(' ', `${chalk.yellow('⟳')} installing packages...`)
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Create app in temp directory with silent environment
|
|
283
|
-
const silentEnvironment = createUIEnvironment(
|
|
284
|
-
this.options.environment.appName,
|
|
285
|
-
true,
|
|
286
|
-
)
|
|
287
|
-
await createApp(silentEnvironment, updatedOptions)
|
|
288
|
-
|
|
289
|
-
// Sync files to target directory
|
|
290
|
-
const syncResult = await this.syncer.sync(this.tempDir, this.options.targetDir, {
|
|
291
|
-
deleteRemoved: this.lastSyncedSourceFiles !== null,
|
|
292
|
-
previousSourceFiles: this.lastSyncedSourceFiles ?? undefined,
|
|
293
|
-
})
|
|
294
|
-
this.lastSyncedSourceFiles = new Set(syncResult.sourceFiles)
|
|
295
|
-
|
|
296
|
-
// Clean up temp directory after sync is complete
|
|
297
|
-
try {
|
|
298
|
-
await fs.promises.rm(this.tempDir, { recursive: true, force: true })
|
|
299
|
-
} catch (cleanupError) {
|
|
300
|
-
this.log.warning(
|
|
301
|
-
`Failed to clean up temp directory: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`,
|
|
302
|
-
)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const elapsed = Date.now() - startTime
|
|
306
|
-
|
|
307
|
-
// Build tree-style summary
|
|
308
|
-
this.log.tree(' ', `duration: ${chalk.cyan(elapsed + 'ms')}`)
|
|
309
|
-
|
|
310
|
-
if (packageMetadataChanged) {
|
|
311
|
-
this.log.tree(' ', `packages: ${chalk.green('✓ installed')}`)
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Always show the last item in tree without checking for files to show
|
|
315
|
-
const noMoreTreeItems =
|
|
316
|
-
syncResult.updated.length === 0 &&
|
|
317
|
-
syncResult.created.length === 0 &&
|
|
318
|
-
syncResult.deleted.length === 0 &&
|
|
319
|
-
syncResult.errors.length === 0
|
|
320
|
-
|
|
321
|
-
if (syncResult.updated.length > 0) {
|
|
322
|
-
this.log.tree(
|
|
323
|
-
' ',
|
|
324
|
-
`updated: ${chalk.green(syncResult.updated.length + ' file' + (syncResult.updated.length > 1 ? 's' : ''))}`,
|
|
325
|
-
syncResult.created.length === 0 &&
|
|
326
|
-
syncResult.deleted.length === 0 &&
|
|
327
|
-
syncResult.errors.length === 0,
|
|
328
|
-
)
|
|
329
|
-
}
|
|
330
|
-
if (syncResult.created.length > 0) {
|
|
331
|
-
this.log.tree(
|
|
332
|
-
' ',
|
|
333
|
-
`created: ${chalk.green(syncResult.created.length + ' file' + (syncResult.created.length > 1 ? 's' : ''))}`,
|
|
334
|
-
syncResult.deleted.length === 0 && syncResult.errors.length === 0,
|
|
335
|
-
)
|
|
336
|
-
}
|
|
337
|
-
if (syncResult.deleted.length > 0) {
|
|
338
|
-
this.log.tree(
|
|
339
|
-
' ',
|
|
340
|
-
`deleted: ${chalk.green(syncResult.deleted.length + ' file' + (syncResult.deleted.length > 1 ? 's' : ''))}`,
|
|
341
|
-
syncResult.errors.length === 0,
|
|
342
|
-
)
|
|
343
|
-
}
|
|
344
|
-
if (syncResult.errors.length > 0) {
|
|
345
|
-
this.log.tree(
|
|
346
|
-
' ',
|
|
347
|
-
`failed: ${chalk.red(syncResult.errors.length + ' file' + (syncResult.errors.length > 1 ? 's' : ''))}`,
|
|
348
|
-
true,
|
|
349
|
-
)
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// If nothing changed, show that
|
|
353
|
-
if (noMoreTreeItems) {
|
|
354
|
-
this.log.tree(' ', `no changes`, true)
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Always show changed files with diffs
|
|
358
|
-
if (syncResult.updated.length > 0) {
|
|
359
|
-
syncResult.updated.forEach((update, index) => {
|
|
360
|
-
const isLastFile =
|
|
361
|
-
index === syncResult.updated.length - 1 &&
|
|
362
|
-
syncResult.created.length === 0
|
|
363
|
-
|
|
364
|
-
// For files with diffs, always use ├─
|
|
365
|
-
const fileIsLast = isLastFile && !update.diff
|
|
366
|
-
this.log.treeItem(' ', update.path, fileIsLast)
|
|
367
|
-
|
|
368
|
-
// Always show diff if available
|
|
369
|
-
if (update.diff) {
|
|
370
|
-
const diffLines = update.diff.split('\n')
|
|
371
|
-
const relevantLines = diffLines
|
|
372
|
-
.slice(4)
|
|
373
|
-
.filter(
|
|
374
|
-
(line) =>
|
|
375
|
-
line.startsWith('+') ||
|
|
376
|
-
line.startsWith('-') ||
|
|
377
|
-
line.startsWith('@'),
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
if (relevantLines.length > 0) {
|
|
381
|
-
// Always use │ to continue the tree line through the diff
|
|
382
|
-
const prefix = ' │ '
|
|
383
|
-
relevantLines.forEach((line) => {
|
|
384
|
-
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
385
|
-
console.log(chalk.gray(prefix) + ' ' + chalk.green(line))
|
|
386
|
-
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
387
|
-
console.log(chalk.gray(prefix) + ' ' + chalk.red(line))
|
|
388
|
-
} else if (line.startsWith('@')) {
|
|
389
|
-
console.log(chalk.gray(prefix) + ' ' + chalk.cyan(line))
|
|
390
|
-
}
|
|
391
|
-
})
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
})
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Show created files
|
|
398
|
-
if (syncResult.created.length > 0) {
|
|
399
|
-
syncResult.created.forEach((file, index) => {
|
|
400
|
-
const isLast =
|
|
401
|
-
index === syncResult.created.length - 1 && syncResult.deleted.length === 0
|
|
402
|
-
this.log.treeItem(' ', `${chalk.green('+')} ${file}`, isLast)
|
|
403
|
-
})
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
if (syncResult.deleted.length > 0) {
|
|
407
|
-
syncResult.deleted.forEach((file, index) => {
|
|
408
|
-
const isLast = index === syncResult.deleted.length - 1
|
|
409
|
-
this.log.treeItem(' ', `${chalk.red('-')} ${file}`, isLast)
|
|
410
|
-
})
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Always show errors
|
|
414
|
-
if (syncResult.errors.length > 0) {
|
|
415
|
-
console.log() // Add spacing
|
|
416
|
-
syncResult.errors.forEach((err, index) => {
|
|
417
|
-
this.log.tree(
|
|
418
|
-
' ',
|
|
419
|
-
`${chalk.red('error:')} ${err}`,
|
|
420
|
-
index === syncResult.errors.length - 1,
|
|
421
|
-
)
|
|
422
|
-
})
|
|
423
|
-
}
|
|
424
|
-
} catch (error) {
|
|
425
|
-
this.log.error(
|
|
426
|
-
`Build #${buildId} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
427
|
-
)
|
|
428
|
-
} finally {
|
|
429
|
-
this.isBuilding = false
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
private createFrameworkDefinitionFromWatchPath(): FrameworkDefinition | null {
|
|
434
|
-
const frameworkRoot = this.options.watchPath
|
|
435
|
-
const projectDirectory = path.join(frameworkRoot, 'project')
|
|
436
|
-
const baseDirectory = path.join(projectDirectory, 'base')
|
|
437
|
-
|
|
438
|
-
if (!fs.existsSync(projectDirectory) || !fs.existsSync(baseDirectory)) {
|
|
439
|
-
return null
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
const addOnDirectoryCandidates = [
|
|
443
|
-
path.join(frameworkRoot, 'add-ons'),
|
|
444
|
-
path.join(frameworkRoot, 'toolchains'),
|
|
445
|
-
path.join(frameworkRoot, 'examples'),
|
|
446
|
-
path.join(frameworkRoot, 'hosts'),
|
|
447
|
-
]
|
|
448
|
-
|
|
449
|
-
const addOnDirectories = addOnDirectoryCandidates.filter((dir) =>
|
|
450
|
-
fs.existsSync(dir),
|
|
451
|
-
)
|
|
452
|
-
|
|
453
|
-
const addOns =
|
|
454
|
-
addOnDirectories.length > 0 ? scanAddOnDirectories(addOnDirectories) : []
|
|
455
|
-
const { files, basePackageJSON, optionalPackages } = scanProjectDirectory(
|
|
456
|
-
projectDirectory,
|
|
457
|
-
baseDirectory,
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
return {
|
|
461
|
-
id: this.options.framework.id,
|
|
462
|
-
name: this.options.framework.name,
|
|
463
|
-
description: this.options.framework.description,
|
|
464
|
-
version: this.options.framework.version,
|
|
465
|
-
base: files,
|
|
466
|
-
addOns,
|
|
467
|
-
basePackageJSON,
|
|
468
|
-
optionalPackages,
|
|
469
|
-
supportedModes: this.options.framework.supportedModes,
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
private cleanup(): void {
|
|
474
|
-
console.log()
|
|
475
|
-
console.log('Cleaning up...')
|
|
476
|
-
|
|
477
|
-
// Clean up temp directory
|
|
478
|
-
if (this.tempDir && fs.existsSync(this.tempDir)) {
|
|
479
|
-
try {
|
|
480
|
-
fs.rmSync(this.tempDir, { recursive: true, force: true })
|
|
481
|
-
} catch (error) {
|
|
482
|
-
this.log.error(
|
|
483
|
-
`Failed to clean up temp directory: ${error instanceof Error ? error.message : String(error)}`,
|
|
484
|
-
)
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (this.appDevProcess && !this.appDevProcess.killed) {
|
|
489
|
-
this.appDevProcess.kill('SIGTERM')
|
|
490
|
-
this.appDevProcess = null
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
process.exit(0)
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
private startAppDevServer(): void {
|
|
497
|
-
if (this.appDevProcess) {
|
|
498
|
-
return
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const { command, args } = this.getDevCommandForPackageManager(
|
|
502
|
-
this.options.packageManager,
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
this.log.section('app dev server')
|
|
506
|
-
this.log.tree(' ', `starting: ${chalk.cyan([command, ...args].join(' '))}`)
|
|
507
|
-
|
|
508
|
-
this.appDevProcess = spawn(command, args, {
|
|
509
|
-
cwd: this.options.targetDir,
|
|
510
|
-
stdio: 'inherit',
|
|
511
|
-
shell: process.platform === 'win32',
|
|
512
|
-
env: process.env,
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
this.appDevProcess.on('exit', (code, signal) => {
|
|
516
|
-
if (signal) {
|
|
517
|
-
this.log.warning(`app dev server exited via signal ${signal}`)
|
|
518
|
-
} else if (code && code !== 0) {
|
|
519
|
-
this.log.warning(`app dev server exited with code ${code}`)
|
|
520
|
-
}
|
|
521
|
-
this.appDevProcess = null
|
|
522
|
-
})
|
|
523
|
-
|
|
524
|
-
this.appDevProcess.on('error', (error) => {
|
|
525
|
-
this.log.error(`Failed to start app dev server: ${error.message}`)
|
|
526
|
-
this.appDevProcess = null
|
|
527
|
-
})
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
private getDevCommandForPackageManager(packageManager: string): {
|
|
531
|
-
command: string
|
|
532
|
-
args: Array<string>
|
|
533
|
-
} {
|
|
534
|
-
switch (packageManager) {
|
|
535
|
-
case 'npm':
|
|
536
|
-
return { command: 'npm', args: ['run', 'dev'] }
|
|
537
|
-
case 'yarn':
|
|
538
|
-
return { command: 'yarn', args: ['dev'] }
|
|
539
|
-
case 'bun':
|
|
540
|
-
return { command: 'bun', args: ['run', 'dev'] }
|
|
541
|
-
case 'deno':
|
|
542
|
-
return { command: 'deno', args: ['task', 'dev'] }
|
|
543
|
-
default:
|
|
544
|
-
return { command: 'pnpm', args: ['dev'] }
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
private log = {
|
|
549
|
-
tree: (prefix: string, msg: string, isLast = false) => {
|
|
550
|
-
const connector = isLast ? '└─' : '├─'
|
|
551
|
-
console.log(chalk.gray(prefix + connector) + ' ' + msg)
|
|
552
|
-
},
|
|
553
|
-
treeItem: (prefix: string, msg: string, isLast = false) => {
|
|
554
|
-
const connector = isLast ? '└─' : '├─'
|
|
555
|
-
console.log(chalk.gray(prefix + ' ' + connector) + ' ' + msg)
|
|
556
|
-
},
|
|
557
|
-
info: (msg: string) => console.log(msg),
|
|
558
|
-
error: (msg: string) => console.error(chalk.red('✗') + ' ' + msg),
|
|
559
|
-
success: (msg: string) => console.log(chalk.green('✓') + ' ' + msg),
|
|
560
|
-
warning: (msg: string) => console.log(chalk.yellow('⚠') + ' ' + msg),
|
|
561
|
-
section: (title: string) => console.log('\n' + chalk.bold('▸ ' + title)),
|
|
562
|
-
subsection: (msg: string) => console.log(' ' + msg),
|
|
563
|
-
}
|
|
564
|
-
}
|