Skip to content

Commit 86f527e

Browse files
balzssclaude
andcommitted
chore(scripts): improve clean script and add bootstrap summary table
Clean script improvements: - Add 'lib' to DIRS_TO_DELETE for cleaning legacy Babel builds - Preserve both 'lib' and 'es' directories in tooling packages - Handles branch switching between Babel and SWC builds Bootstrap script improvements: - Add summary table showing phase timings and status - Track individual phases: Clean, Icon build, SWC compilation, Token generation, TypeScript declarations - Calculate total time using wall-clock time to properly account for parallel execution - Display errors with detailed messages if any phase fails 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent b828eca commit 86f527e

File tree

2 files changed

+156
-42
lines changed

2 files changed

+156
-42
lines changed

scripts/bootstrap.js

Lines changed: 152 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -29,57 +29,170 @@ const path = require('path')
2929

3030
const opts = { stdio: 'inherit' }
3131

32-
function runInParallel(commands) {
33-
return Promise.all(
34-
commands.map(({ name, command }) => {
35-
return new Promise((resolve, reject) => {
36-
console.info(`${name}...`)
37-
const child = spawn('pnpm', ['run', command], {
38-
stdio: 'inherit',
39-
shell: true
40-
})
41-
child.on('close', (code) => {
42-
if (code !== 0) {
43-
reject(new Error(`'${command}' failed with exit code ${code}`))
44-
} else {
45-
resolve()
46-
}
47-
})
48-
child.on('error', reject)
49-
})
50-
})
51-
)
52-
}
32+
// Track phases for summary
33+
const phases = []
5334

54-
function buildProject() {
55-
execSync('pnpm --filter @instructure/ui-icons prepare-build', opts)
35+
function trackPhase(name, fn) {
36+
const start = Date.now()
37+
const phase = { name, start, end: null, duration: null, status: 'running', error: null }
38+
phases.push(phase)
39+
40+
const finish = (error = null) => {
41+
phase.end = Date.now()
42+
phase.duration = ((phase.end - phase.start) / 1000).toFixed(1)
43+
phase.status = error ? 'failed' : 'success'
44+
phase.error = error ? error.message : null
45+
}
5646

57-
console.info('Building packages with SWC...')
5847
try {
59-
execSync('pnpm run build', opts)
48+
const result = fn()
49+
if (result && typeof result.then === 'function') {
50+
return result.then(
51+
() => {
52+
finish()
53+
return Promise.resolve()
54+
},
55+
(error) => {
56+
finish(error)
57+
return Promise.reject(error)
58+
}
59+
)
60+
}
61+
finish()
62+
return result
6063
} catch (error) {
61-
console.error("'pnpm run build' failed", error)
62-
process.exit(1)
64+
finish(error)
65+
throw error
6366
}
67+
}
6468

65-
console.info('Running token generation and TypeScript compilation in parallel...')
66-
return runInParallel([
67-
{ name: 'Generating tokens', command: 'build:tokens' },
68-
{ name: 'Building TypeScript declarations', command: 'build:types' }
69-
]).catch((error) => {
70-
console.error('Parallel build failed:', error)
71-
process.exit(1)
69+
function printSummary(actualTotalDuration) {
70+
const summedDuration = phases.reduce((sum, p) => sum + parseFloat(p.duration), 0).toFixed(1)
71+
const totalDuration = actualTotalDuration || summedDuration
72+
const hasErrors = phases.some(p => p.status === 'failed')
73+
74+
console.log('\n' + '═'.repeat(80))
75+
console.log('Bootstrap Summary')
76+
console.log('═'.repeat(80))
77+
console.log(
78+
'│ ' +
79+
'Phase'.padEnd(30) +
80+
'│ ' +
81+
'Duration'.padEnd(10) +
82+
'│ ' +
83+
'Status'.padEnd(10) +
84+
'│'
85+
)
86+
console.log('├' + '─'.repeat(31) + '┼' + '─'.repeat(11) + '┼' + '─'.repeat(11) + '┤')
87+
88+
phases.forEach(phase => {
89+
const status = phase.status === 'success' ? '✓ Success' : '✗ Failed'
90+
console.log(
91+
'│ ' +
92+
phase.name.padEnd(30) +
93+
'│ ' +
94+
(phase.duration + 's').padEnd(10) +
95+
'│ ' +
96+
status.padEnd(10) +
97+
'│'
98+
)
99+
if (phase.error) {
100+
console.log('│ ' + 'Error: '.padEnd(30) + '│ ' + phase.error.padEnd(21) + '│')
101+
}
72102
})
103+
104+
console.log('├' + '─'.repeat(31) + '┼' + '─'.repeat(11) + '┼' + '─'.repeat(11) + '┤')
105+
console.log(
106+
'│ ' +
107+
'TOTAL'.padEnd(30) +
108+
'│ ' +
109+
(totalDuration + 's').padEnd(10) +
110+
'│ ' +
111+
(hasErrors ? '✗ Failed' : '✓ Success').padEnd(10) +
112+
'│'
113+
)
114+
console.log('└' + '─'.repeat(31) + '┴' + '─'.repeat(11) + '┴' + '─'.repeat(11) + '┘')
115+
116+
if (hasErrors) {
117+
console.log('\n⚠️ Bootstrap completed with errors. See details above.')
118+
} else {
119+
console.log('\n✓ Bootstrap completed successfully!')
120+
}
121+
}
122+
123+
async function buildProject() {
124+
await trackPhase('Icon build', () => {
125+
execSync('pnpm --filter @instructure/ui-icons prepare-build', opts)
126+
})
127+
128+
await trackPhase('SWC compilation', () => {
129+
console.info('Building packages with SWC...')
130+
execSync('pnpm run build', opts)
131+
})
132+
133+
console.info('Running token generation and TypeScript compilation in parallel...')
134+
135+
const parallelPhases = [
136+
{ trackName: 'Token generation', name: 'Generating tokens', command: 'build:tokens' },
137+
{ trackName: 'TypeScript declarations', name: 'Building TypeScript declarations', command: 'build:types' }
138+
]
139+
140+
await Promise.all(
141+
parallelPhases.map(({ trackName, name, command }) => {
142+
return trackPhase(trackName, () => {
143+
return new Promise((resolve, reject) => {
144+
console.info(`${name}...`)
145+
const child = spawn('pnpm', ['run', command], {
146+
stdio: 'inherit',
147+
shell: true
148+
})
149+
child.on('close', (code) => {
150+
if (code !== 0) {
151+
reject(new Error(`'${command}' failed with exit code ${code}`))
152+
} else {
153+
resolve()
154+
}
155+
})
156+
child.on('error', reject)
157+
})
158+
})
159+
})
160+
)
73161
}
74162
async function bootstrap() {
163+
const bootstrapStart = Date.now()
164+
165+
await trackPhase('Clean', () => {
166+
return new Promise((resolve, reject) => {
167+
const child = fork(path.resolve('scripts/clean.js'), opts)
168+
child.on('exit', (code) => {
169+
if (code !== 0) {
170+
reject(new Error(`clean script failed with exit code ${code}`))
171+
} else {
172+
resolve()
173+
}
174+
})
175+
child.on('error', reject)
176+
})
177+
})
178+
75179
try {
76-
fork(path.resolve('scripts/clean.js'), opts)
180+
await buildProject()
77181
} catch (error) {
78-
console.error('clean failed with error:', error)
79-
process.exit(1)
182+
// Error already tracked, just print summary
80183
}
81184

82-
await buildProject()
185+
const actualTotalDuration = ((Date.now() - bootstrapStart) / 1000).toFixed(1)
186+
printSummary(actualTotalDuration)
187+
188+
const hasErrors = phases.some(p => p.status === 'failed')
189+
if (hasErrors) {
190+
process.exit(1)
191+
}
83192
}
84193

85-
bootstrap()
194+
bootstrap().catch((error) => {
195+
console.error('Bootstrap failed:', error)
196+
printSummary() // No actual time available in error case
197+
process.exit(1)
198+
})

scripts/clean.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ const DIRS_TO_DELETE = [
4747
'tokens',
4848
'.babel-cache',
4949
'.cache',
50-
'es'
50+
'es',
51+
'lib'
5152
]
5253

5354
async function deleteDirs(dirs = []) {
@@ -75,8 +76,8 @@ async function clean() {
7576
)
7677

7778
if (NODE_PACKAGES.includes(packageDir.name)) {
78-
// For tooling packages, don't delete 'es' directory (preserve pre-built code)
79-
rmDirs = rmDirs.filter(dir => !dir.endsWith('/es'))
79+
// For tooling packages, don't delete 'es' or 'lib' directories (preserve pre-built code)
80+
rmDirs = rmDirs.filter(dir => !dir.endsWith('/es') && !dir.endsWith('/lib'))
8081
}
8182
// Note: Component packages build to 'es/' which is included in DIRS_TO_DELETE
8283

0 commit comments

Comments
 (0)