itty-packager 1.2.0 โ†’ 1.6.1

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.
@@ -3,7 +3,8 @@
3
3
  "allow": [
4
4
  "Bash(bun bin/itty.js lint:*)",
5
5
  "Bash(bun bin/itty.js prepare:*)",
6
- "Bash(bun bin/itty.js publish:*)"
6
+ "Bash(bun bin/itty.js publish:*)",
7
+ "Bash(cat:*)"
7
8
  ],
8
9
  "deny": []
9
10
  }
@@ -2,7 +2,6 @@ import { parseArgs } from 'node:util'
2
2
  import { spawn } from 'node:child_process'
3
3
  import fs from 'fs-extra'
4
4
  import path from 'node:path'
5
- import readline from 'node:readline'
6
5
  import { prepareCommand } from './prepare.js'
7
6
 
8
7
  const SEMVER_TYPES = ['major', 'minor', 'patch']
@@ -79,41 +78,122 @@ async function getCommitMessage(newVersion, silent = false) {
79
78
  return `released v${newVersion}`
80
79
  }
81
80
 
82
- return new Promise((resolve) => {
83
- const rl = readline.createInterface({
84
- input: process.stdin,
85
- output: process.stdout
86
- })
87
-
88
- console.log('\nEnter optional commit message (empty submission skips):')
89
- console.log('\x1b[90mPress Enter to finish, Ctrl+C to skip\x1b[0m')
90
- process.stdout.write('\n')
91
-
81
+ return new Promise((resolve, reject) => {
82
+ // Show placeholder with hidden cursor
83
+ const placeholderText = '\x1b[90mpress enter to skip\x1b[0m'
84
+ process.stdout.write(`๐Ÿ’ฌ Commit message: ${placeholderText}\x1b[?25l`) // Hide cursor
85
+
92
86
  let inputLines = []
93
87
  let firstInput = true
88
+ let placeholderCleared = false
89
+ let inputBuffer = ''
90
+
91
+ // Set up raw mode for immediate key detection
92
+ process.stdin.setRawMode(true)
93
+ process.stdin.resume()
94
+
95
+ const clearPlaceholder = () => {
96
+ if (!placeholderCleared) {
97
+ // Clear line and show prompt with cursor
98
+ process.stdout.write('\r\x1b[K๐Ÿ’ฌ Commit message: \x1b[?25h') // Show cursor
99
+ placeholderCleared = true
100
+ }
101
+ }
94
102
 
95
- rl.on('line', (input) => {
96
- if (firstInput && input.trim() === '') {
97
- // First line is empty, skip custom message
98
- rl.close()
99
- resolve(`released v${newVersion}`)
103
+ const handleInput = (chunk) => {
104
+ const key = chunk.toString()
105
+
106
+ // Check for escape sequences
107
+ if (key === '\x1b') {
108
+ // Wait for potential escape sequence completion
109
+ setTimeout(() => {
110
+ process.stdout.write('\r\x1b[K๐Ÿ’ฌ Commit message: cancelled\x1b[?25h\n')
111
+ cleanup()
112
+ reject(new Error('User cancelled with Escape key'))
113
+ }, 10)
100
114
  return
101
115
  }
102
116
 
103
- firstInput = false
117
+ // Handle Ctrl+C
118
+ if (key === '\x03') {
119
+ process.stdout.write('\r\x1b[K๐Ÿ’ฌ Commit message: cancelled\x1b[?25h\n')
120
+ cleanup()
121
+ reject(new Error('User cancelled with Ctrl+C'))
122
+ return
123
+ }
124
+
125
+ // Handle Enter
126
+ if (key === '\r' || key === '\n') {
127
+ if (!placeholderCleared && inputBuffer === '') {
128
+ // Empty input, skip
129
+ process.stdout.write('\r\x1b[K๐Ÿ’ฌ Commit message: \x1b[?25h\n')
130
+ cleanup()
131
+ resolve(`released v${newVersion}`)
132
+ return
133
+ }
134
+
135
+ // Single line input - finish immediately
136
+ if (firstInput) {
137
+ const customMessage = inputBuffer.trim()
138
+ process.stdout.write('\x1b[?25h\n')
139
+ cleanup()
140
+
141
+ if (!customMessage) {
142
+ resolve(`released v${newVersion}`)
143
+ } else {
144
+ const escapedMessage = customMessage.replace(/"/g, '\\"')
145
+ resolve(`released v${newVersion} - ${escapedMessage}`)
146
+ }
147
+ return
148
+ }
149
+
150
+ // Multi-line: empty line finishes input
151
+ if (inputBuffer.trim() === '') {
152
+ finishInput()
153
+ return
154
+ }
155
+
156
+ // Add line and continue
157
+ inputLines.push(inputBuffer)
158
+ inputBuffer = ''
159
+ firstInput = false
160
+ process.stdout.write('\n')
161
+ return
162
+ }
104
163
 
105
- if (input.trim() === '' && inputLines.length > 0) {
106
- // Empty line after content - finish input
107
- finishInput()
164
+ // Handle backspace
165
+ if (key === '\x7f' || key === '\x08') {
166
+ if (!placeholderCleared) return // Can't backspace in placeholder
167
+
168
+ if (inputBuffer.length > 0) {
169
+ inputBuffer = inputBuffer.slice(0, -1)
170
+ process.stdout.write('\b \b')
171
+ }
108
172
  return
109
173
  }
110
174
 
111
- inputLines.push(input)
112
- })
175
+ // Handle printable characters
176
+ if (key.length === 1 && key >= ' ' && key <= '~') {
177
+ if (!placeholderCleared) {
178
+ clearPlaceholder()
179
+ }
180
+
181
+ inputBuffer += key
182
+ process.stdout.write(key)
183
+ return
184
+ }
185
+ }
186
+
187
+ const cleanup = () => {
188
+ process.stdin.setRawMode(false)
189
+ process.stdin.pause()
190
+ process.stdin.removeListener('data', handleInput)
191
+ }
113
192
 
114
193
  const finishInput = () => {
115
- rl.close()
116
194
  const customMessage = inputLines.join('\n').trim()
195
+ process.stdout.write('\x1b[?25h\n') // Show cursor and newline
196
+ cleanup()
117
197
 
118
198
  if (!customMessage) {
119
199
  resolve(`released v${newVersion}`)
@@ -124,18 +204,7 @@ async function getCommitMessage(newVersion, silent = false) {
124
204
  }
125
205
  }
126
206
 
127
- rl.on('SIGINT', () => {
128
- console.log('\nSkipped. Using default commit message.')
129
- rl.close()
130
- resolve(`released v${newVersion}`)
131
- })
132
-
133
- // Handle Ctrl+D (EOF) as completion
134
- rl.on('close', () => {
135
- if (!firstInput && inputLines.length > 0) {
136
- finishInput()
137
- }
138
- })
207
+ process.stdin.on('data', handleInput)
139
208
  })
140
209
  }
141
210
 
@@ -283,10 +352,14 @@ Publish Options:
283
352
 
284
353
  Git Options:
285
354
  --tag Create git tag for release
286
- --push Push changes and tags to git remote
355
+ --push Push changes and tags to git remote (prompts for commit message)
287
356
  --no-git Skip all git operations
288
357
  -h, --help Show help
289
358
 
359
+ Interactive Options:
360
+ When using --push, you'll be prompted for an optional commit message.
361
+ Press Enter to skip, Escape or Ctrl+C to cancel and revert version.
362
+
290
363
  Examples:
291
364
  itty publish # Patch version bump and publish from dist/ (default)
292
365
  itty publish --minor --tag # Minor bump, publish, and create git tag
@@ -327,6 +400,12 @@ This creates a clean, flat package structure in node_modules.
327
400
  const silent = publishArgs.silent
328
401
  const verbose = publishArgs.verbose
329
402
 
403
+ // Read package.json and store original version for potential revert
404
+ const pkgPath = path.join(rootPath, 'package.json')
405
+ const originalPkg = await fs.readJSON(pkgPath)
406
+ const originalVersion = originalPkg.version
407
+ const newVersion = versionBump(originalVersion, releaseType)
408
+
330
409
  try {
331
410
  // Run prepare if requested
332
411
  if (shouldPrepare) {
@@ -334,12 +413,8 @@ This creates a clean, flat package structure in node_modules.
334
413
  await prepareCommand(verbose ? ['--verbose'] : [])
335
414
  console.log('โœ… Prepare completed successfully\n')
336
415
  }
337
- // Read package.json
338
- const pkgPath = path.join(rootPath, 'package.json')
339
- const pkg = await fs.readJSON(pkgPath)
340
- const newVersion = versionBump(pkg.version, releaseType)
341
416
 
342
- console.log(`๐Ÿ“ฆ Publishing ${pkg.name} v${pkg.version} โ†’ v${newVersion}`)
417
+ console.log(`๐Ÿ“ฆ Publishing ${originalPkg.name} v${originalVersion} โ†’ v${newVersion}`)
343
418
  if (verbose) console.log(`๐Ÿ“ Source: ${publishArgs.src}/`)
344
419
 
345
420
  // Check if source directory exists
@@ -396,43 +471,40 @@ This creates a clean, flat package structure in node_modules.
396
471
 
397
472
  // Update package.json in temp directory with transformed paths
398
473
  const updatedPkg = isRootPublish
399
- ? { ...pkg, version: newVersion } // No path transformation for root publishing
400
- : transformPackageExports({ ...pkg, version: newVersion }, publishArgs.src)
474
+ ? { ...originalPkg, version: newVersion } // No path transformation for root publishing
475
+ : transformPackageExports({ ...originalPkg, version: newVersion }, publishArgs.src)
401
476
  const tempPkgPath = path.join(tempDir, 'package.json')
402
477
 
403
478
  const transformMessage = isRootPublish ? '' : ' (transforming paths)'
404
479
  if (verbose) console.log(`๐Ÿ“ Updating package.json to v${newVersion}${transformMessage}`)
405
480
  await fs.writeJSON(tempPkgPath, updatedPkg, { spaces: 2 })
406
481
 
407
- if (dryRun) {
408
- console.log('๐Ÿงช Dry run - skipping publish')
409
- } else {
410
- // Publish from temp directory
411
- console.log(`๐Ÿš€ Publishing to npm...`)
412
-
413
- const publishCmd = [
414
- 'npm publish',
415
- publicAccess ? '--access=public' : '',
416
- SEMVER_TYPES.includes(releaseType) ? '' : `--tag=${releaseType}`
417
- ].filter(Boolean).join(' ')
418
-
419
- if (verbose) console.log(`Running: ${publishCmd}`)
420
- await runCommand(publishCmd, tempDir, verbose)
421
-
422
- // Update root package.json
482
+ // Update root package.json first (before git operations)
483
+ if (!dryRun) {
423
484
  if (verbose) console.log(`๐Ÿ“ Updating root package.json`)
424
485
  await fs.writeJSON(pkgPath, updatedPkg, { spaces: 2 })
425
486
  }
426
487
 
427
- // Git operations
488
+ // Git operations (before publishing)
428
489
  if (!noGit && !dryRun) {
429
490
  if (shouldPush || shouldTag) {
430
- // Get commit message (interactive or default)
431
- const commitMessage = await getCommitMessage(newVersion, silent)
432
-
433
- if (verbose) console.log(`๐Ÿ“‹ Committing changes...`)
434
- await runCommand('git add .', rootPath, verbose)
435
- await runCommand(`git commit -m "${commitMessage}"`, rootPath, verbose)
491
+ try {
492
+ // Get commit message (interactive or default)
493
+ const commitMessage = await getCommitMessage(newVersion, silent)
494
+
495
+ if (verbose) console.log(`๐Ÿ“‹ Committing changes...`)
496
+ await runCommand('git add .', rootPath, verbose)
497
+ await runCommand(`git commit -m "${commitMessage}"`, rootPath, verbose)
498
+ } catch (error) {
499
+ if (error.message.includes('cancelled')) {
500
+ console.log('๐Ÿ“‹ Commit cancelled - reverting version and exiting')
501
+ // Revert the version we just updated
502
+ await fs.writeJSON(pkgPath, originalPkg, { spaces: 2 })
503
+ // Don't rethrow - exit cleanly since this is user-initiated
504
+ process.exit(0)
505
+ }
506
+ throw error
507
+ }
436
508
  }
437
509
 
438
510
  if (shouldTag) {
@@ -451,17 +523,48 @@ This creates a clean, flat package structure in node_modules.
451
523
  }
452
524
  }
453
525
 
526
+ // NPM publish as final step
527
+ if (dryRun) {
528
+ console.log('๐Ÿงช Dry run - skipping publish')
529
+ } else {
530
+ // Publish from temp directory
531
+ console.log(`๐Ÿš€ Publishing to npm...`)
532
+
533
+ const publishCmd = [
534
+ 'npm publish',
535
+ publicAccess ? '--access=public' : '',
536
+ SEMVER_TYPES.includes(releaseType) ? '' : `--tag=${releaseType}`
537
+ ].filter(Boolean).join(' ')
538
+
539
+ if (verbose) console.log(`Running: ${publishCmd}`)
540
+ await runCommand(publishCmd, tempDir, verbose)
541
+ }
542
+
454
543
  // Cleanup
455
544
  if (!noCleanup) {
456
545
  if (verbose) console.log(`๐Ÿงน Cleaning up ${publishArgs.dest}/`)
457
546
  await fs.remove(tempDir)
458
547
  }
459
548
 
460
- console.log(`โœ… Successfully published ${pkg.name}@${newVersion}`)
549
+ console.log(`โœ… Successfully published ${originalPkg.name}@${newVersion}`)
461
550
 
462
551
  } catch (error) {
463
552
  console.error(`โŒ Publish failed: ${error.message}`)
464
553
 
554
+ // Revert version in root package.json if it was changed
555
+ if (!dryRun) {
556
+ try {
557
+ const currentPkg = await fs.readJSON(pkgPath)
558
+ if (currentPkg.version !== originalVersion) {
559
+ if (verbose) console.log(`๐Ÿ”„ Reverting version from v${currentPkg.version} to v${originalVersion}`)
560
+ await fs.writeJSON(pkgPath, { ...currentPkg, version: originalVersion }, { spaces: 2 })
561
+ console.log(`โœ… Version reverted to v${originalVersion}`)
562
+ }
563
+ } catch (revertError) {
564
+ console.error(`โŒ Failed to revert version: ${revertError.message}`)
565
+ }
566
+ }
567
+
465
568
  // Cleanup on error
466
569
  if (await fs.pathExists(tempDir) && !noCleanup) {
467
570
  await fs.remove(tempDir)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itty-packager",
3
- "version": "1.2.0",
3
+ "version": "1.6.1",
4
4
  "description": "Universal build tool for itty libraries",
5
5
  "type": "module",
6
6
  "bin": {