@abarcenas/ai-workflow-template 1.29.0 → 1.33.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/.agents/instructions/learned-knowledge.instructions.md +46 -0
- package/.github/workflows/npm-publish.yml +3 -0
- package/.github/workflows/pr-test.yml +23 -0
- package/.husky/post-merge +1 -0
- package/.husky/pre-commit +2 -0
- package/README.md +48 -1
- package/bin/setup.js +23 -0
- package/package.json +7 -2
- package/scripts/setup/constants.js +226 -0
- package/scripts/setup/discover.js +261 -0
- package/scripts/setup/discover.test.js +408 -0
- package/scripts/setup/hooks.js +280 -0
- package/scripts/setup/hooks.test.js +527 -0
- package/scripts/setup/husky-init.js +127 -0
- package/scripts/setup/index.js +354 -0
- package/scripts/setup/index.test.js +664 -0
- package/scripts/setup/prepare.js +127 -0
- package/scripts/setup/prepare.test.js +718 -0
- package/scripts/setup/sync-phase.js +179 -0
- package/scripts/setup/sync-phase.test.js +195 -0
- package/scripts/setup/ui.js +521 -0
- package/scripts/setup/utils.js +371 -0
- package/scripts/sync.js +9 -0
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// ui.js — User interface / output formatting for the setup command pipeline
|
|
3
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from 'fs'
|
|
6
|
+
import { join } from 'path'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
UNICODE_CHARS,
|
|
10
|
+
PACKAGE_NAME,
|
|
11
|
+
BIN_NAME,
|
|
12
|
+
SUPPORTED_FLAGS,
|
|
13
|
+
} from './constants.js'
|
|
14
|
+
import { resolvePackageRoot } from './utils.js'
|
|
15
|
+
|
|
16
|
+
// ── Colour support ──────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
/** `true` when stdout is a TTY — ANSI escape codes are emitted. */
|
|
19
|
+
const useColor = process.stdout.isTTY
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Builds an ANSI SGR escape sequence.
|
|
23
|
+
*
|
|
24
|
+
* Returns an empty string when colours are disabled (piped / non-TTY output)
|
|
25
|
+
* so that log files stay clean.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} code - SGR parameter(s), e.g. `'1'`, `'32m'`, `'1;31m'`.
|
|
28
|
+
* @returns {string} Escape sequence, or `''` when colours are off.
|
|
29
|
+
*/
|
|
30
|
+
function ansi(code) {
|
|
31
|
+
return useColor ? `\x1b[${code}` : ''
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** ANSI reset. */
|
|
35
|
+
const RST = ansi('0m')
|
|
36
|
+
/** ANSI bold / increased intensity. */
|
|
37
|
+
const BLD = ansi('1m')
|
|
38
|
+
/** ANSI dim / decreased intensity. */
|
|
39
|
+
const DIM = ansi('2m')
|
|
40
|
+
/** ANSI green foreground. */
|
|
41
|
+
const GRN = ansi('32m')
|
|
42
|
+
/** ANSI yellow foreground. */
|
|
43
|
+
const YLW = ansi('33m')
|
|
44
|
+
/** ANSI red foreground. */
|
|
45
|
+
const RED = ansi('31m')
|
|
46
|
+
/** ANSI cyan foreground. */
|
|
47
|
+
const CYN = ansi('36m')
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Read-only colour palette for use by downstream consumers.
|
|
51
|
+
*
|
|
52
|
+
* @type {{ reset: string, bold: string, dim: string, green: string,
|
|
53
|
+
* yellow: string, red: string, cyan: string }}
|
|
54
|
+
*/
|
|
55
|
+
export const COLORS = Object.freeze({
|
|
56
|
+
reset: RST,
|
|
57
|
+
bold: BLD,
|
|
58
|
+
dim: DIM,
|
|
59
|
+
green: GRN,
|
|
60
|
+
yellow: YLW,
|
|
61
|
+
red: RED,
|
|
62
|
+
cyan: CYN,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Strips ANSI escape sequences from a string.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} str - Input that may contain ANSI codes.
|
|
71
|
+
* @returns {string} Plain text without escape sequences.
|
|
72
|
+
*/
|
|
73
|
+
function stripAnsi(str) {
|
|
74
|
+
return str.replace(/\x1b\[[0-9;]*m/g, '')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Returns the visible display width of a string, treating ANSI codes as
|
|
79
|
+
* zero-width.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} str - Input that may contain ANSI codes.
|
|
82
|
+
* @returns {number} Number of visible characters.
|
|
83
|
+
*/
|
|
84
|
+
function visibleWidth(str) {
|
|
85
|
+
return stripAnsi(str).length
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Returns a horizontal rule of the given character, bounded by the terminal
|
|
90
|
+
* width.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} [char='─'] - Character to repeat.
|
|
93
|
+
* @param {number} [maxWidth] - Maximum available width (defaults to terminal
|
|
94
|
+
* columns, capped at 72).
|
|
95
|
+
* @returns {string} Rule string.
|
|
96
|
+
*/
|
|
97
|
+
function rule(char = '\u2500', maxWidth) {
|
|
98
|
+
const width = maxWidth ?? Math.min(process.stdout.columns || 72, 72)
|
|
99
|
+
return char.repeat(width)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Version caching ─────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/** @type {string|null} */
|
|
105
|
+
let _version = null
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Reads the package version from the project's `package.json`.
|
|
109
|
+
*
|
|
110
|
+
* The result is cached after the first successful read so repeated calls
|
|
111
|
+
* (e.g. `header()` then `help()`) don't re-read the filesystem.
|
|
112
|
+
*
|
|
113
|
+
* @returns {string} Semver string (e.g. `'1.29.0'`). Falls back to `'0.0.0'`.
|
|
114
|
+
*/
|
|
115
|
+
function getVersion() {
|
|
116
|
+
if (_version === null) {
|
|
117
|
+
try {
|
|
118
|
+
const pkgRoot = resolvePackageRoot()
|
|
119
|
+
const pkgPath = join(pkgRoot, 'package.json')
|
|
120
|
+
const raw = readFileSync(pkgPath, 'utf-8')
|
|
121
|
+
_version = JSON.parse(raw).version || '0.0.0'
|
|
122
|
+
} catch {
|
|
123
|
+
_version = '0.0.0'
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return _version
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Write helpers ───────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Writes one or more lines to stdout.
|
|
133
|
+
*
|
|
134
|
+
* @param {...string} lines - Lines to print.
|
|
135
|
+
*/
|
|
136
|
+
function write(...lines) {
|
|
137
|
+
for (const line of lines) {
|
|
138
|
+
console.log(line)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Prints a dimmed verbose/debug message to stdout when verbose mode is
|
|
144
|
+
* enabled.
|
|
145
|
+
*
|
|
146
|
+
* When `enabled` is falsy the function is a no-op, making it safe to call
|
|
147
|
+
* unconditionally in hot paths without an extra `if` guard.
|
|
148
|
+
*
|
|
149
|
+
* @param {*} enabled - Truthy to print, falsy to skip.
|
|
150
|
+
* @param {string} message - Debug message to display.
|
|
151
|
+
* @example
|
|
152
|
+
* verbose(true, 'Resolving consumer root from INIT_CWD\u2026')
|
|
153
|
+
* // \u2026 Resolving consumer root from INIT_CWD\u2026
|
|
154
|
+
*/
|
|
155
|
+
export function verbose(enabled, message) {
|
|
156
|
+
if (!enabled) return
|
|
157
|
+
write(` ${DIM}\u2026 ${message}${RST}`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── Public output API ───────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Prints the setup banner showing the package name and version.
|
|
164
|
+
*
|
|
165
|
+
* Example:
|
|
166
|
+
*
|
|
167
|
+
* ────────────────────────────────────────────────────────────
|
|
168
|
+
* @abarcenas/ai-workflow-template v1.29.0 — Setup
|
|
169
|
+
* ────────────────────────────────────────────────────────────
|
|
170
|
+
*/
|
|
171
|
+
export function header() {
|
|
172
|
+
const version = getVersion()
|
|
173
|
+
const width = Math.min(process.stdout.columns || 72, 72)
|
|
174
|
+
const sep = DIM + rule() + RST
|
|
175
|
+
const title =
|
|
176
|
+
` ${BLD}${PACKAGE_NAME}${RST}` +
|
|
177
|
+
` ${GRN}v${version}${RST}` +
|
|
178
|
+
` ${DIM}\u2014${RST}` +
|
|
179
|
+
` ${BLD}Setup${RST}`
|
|
180
|
+
|
|
181
|
+
write()
|
|
182
|
+
write(sep)
|
|
183
|
+
write(title)
|
|
184
|
+
write(sep)
|
|
185
|
+
write()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Prints a progress step indicator with a rightwards arrow.
|
|
190
|
+
*
|
|
191
|
+
* @param {string} message - Description of the step being performed.
|
|
192
|
+
* @example
|
|
193
|
+
* step('Checking environment…')
|
|
194
|
+
* // → Checking environment…
|
|
195
|
+
*/
|
|
196
|
+
export function step(message) {
|
|
197
|
+
write(` ${UNICODE_CHARS.ARROW} ${message}`)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Prints a step that completed successfully (checkmark).
|
|
202
|
+
*
|
|
203
|
+
* @param {string} message - Description of the completed step.
|
|
204
|
+
* @example
|
|
205
|
+
* stepSuccess('Environment ready')
|
|
206
|
+
* // ✓ Environment ready
|
|
207
|
+
*/
|
|
208
|
+
export function stepSuccess(message) {
|
|
209
|
+
write(` ${GRN}${UNICODE_CHARS.CHECK}${RST} ${message}`)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Prints a step that completed with a warning.
|
|
214
|
+
*
|
|
215
|
+
* @param {string} message - Warning description.
|
|
216
|
+
* @example
|
|
217
|
+
* stepWarn('Husky version is newer than expected')
|
|
218
|
+
* // ⚠ Husky version is newer than expected
|
|
219
|
+
*/
|
|
220
|
+
export function stepWarn(message) {
|
|
221
|
+
write(` ${YLW}${UNICODE_CHARS.WARN}${RST} ${message}`)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Prints a step that failed.
|
|
226
|
+
*
|
|
227
|
+
* @param {string} message - Error description.
|
|
228
|
+
* @example
|
|
229
|
+
* stepError('Git repository not found')
|
|
230
|
+
* // ✗ Git repository not found
|
|
231
|
+
*/
|
|
232
|
+
export function stepError(message) {
|
|
233
|
+
write(` ${RED}${UNICODE_CHARS.CROSS}${RST} ${message}`)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Prints an informational message (default colour, stdout).
|
|
238
|
+
*
|
|
239
|
+
* @param {string} message - Message to display.
|
|
240
|
+
*/
|
|
241
|
+
export function info(message) {
|
|
242
|
+
write(message)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Prints a warning message (yellow, bold) to stderr.
|
|
247
|
+
*
|
|
248
|
+
* @param {string} message - Warning message.
|
|
249
|
+
*/
|
|
250
|
+
export function warn(message) {
|
|
251
|
+
console.error(`${YLW}${BLD}warning${RST} ${message}`)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Prints an error message (red, bold) to stderr.
|
|
256
|
+
*
|
|
257
|
+
* @param {string} message - Error message.
|
|
258
|
+
*/
|
|
259
|
+
export function error(message) {
|
|
260
|
+
console.error(`${RED}${BLD}error${RST} ${message}`)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Prints a success message (green) to stdout.
|
|
265
|
+
*
|
|
266
|
+
* @param {string} message - Success message.
|
|
267
|
+
*/
|
|
268
|
+
export function success(message) {
|
|
269
|
+
write(`${GRN}${UNICODE_CHARS.CHECK}${RST} ${message}`)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Prints a bold section header.
|
|
274
|
+
*
|
|
275
|
+
* @param {string} title - Section title.
|
|
276
|
+
* @example
|
|
277
|
+
* section('Environment Discovery')
|
|
278
|
+
* // Environment Discovery
|
|
279
|
+
*/
|
|
280
|
+
export function section(title) {
|
|
281
|
+
write(`${BLD}${title}${RST}`)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Prints a formatted summary table from phase results.
|
|
286
|
+
*
|
|
287
|
+
* Each result entry is rendered as a single row in a text table with three
|
|
288
|
+
* columns: **Phase**, **Status**, and **Message**. The table is followed by
|
|
289
|
+
* an overall result line (success / warnings / errors).
|
|
290
|
+
*
|
|
291
|
+
* @param {Array<{ phase: string, status: string, message: string }>} results
|
|
292
|
+
* Phase results to display.
|
|
293
|
+
* - `phase` — Short phase name (e.g. `'discover'`, `'hooks'`)
|
|
294
|
+
* - `status` — One of `'success'`, `'warn'`, `'error'`, or a custom string
|
|
295
|
+
* - `message` — Human-readable result description
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* summary([
|
|
299
|
+
* { phase: 'discover', status: 'success', message: 'Git repo found' },
|
|
300
|
+
* { phase: 'hooks', status: 'error', message: 'Permission denied' },
|
|
301
|
+
* ])
|
|
302
|
+
*/
|
|
303
|
+
export function summary(results) {
|
|
304
|
+
if (!results || results.length === 0) return
|
|
305
|
+
|
|
306
|
+
const headerPhase = 'Phase'
|
|
307
|
+
const headerStatus = 'Status'
|
|
308
|
+
const headerMessage = 'Message'
|
|
309
|
+
|
|
310
|
+
// ── Column widths ───────────────────────────────────────────────────
|
|
311
|
+
const phases = results.map((r) => r.phase)
|
|
312
|
+
const statuses = results.map((r) => r.status)
|
|
313
|
+
const colPhase = Math.max(headerPhase.length, ...phases.map((s) => s.length))
|
|
314
|
+
const colStatus = Math.max(headerStatus.length, ...statuses.map((s) => s.length))
|
|
315
|
+
const colMsg = Math.max(
|
|
316
|
+
headerMessage.length,
|
|
317
|
+
...results.map((r) => visibleWidth(r.message || '')),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
// ── Status rendering ────────────────────────────────────────────────
|
|
321
|
+
/**
|
|
322
|
+
* Formats a status string with a colour-coded symbol.
|
|
323
|
+
*
|
|
324
|
+
* @param {string} s - Status key.
|
|
325
|
+
* @returns {string} Formatted status (may contain ANSI codes).
|
|
326
|
+
*/
|
|
327
|
+
const fmtStatus = (s) => {
|
|
328
|
+
switch (s) {
|
|
329
|
+
case 'success':
|
|
330
|
+
return `${GRN}${UNICODE_CHARS.CHECK}${RST} success`
|
|
331
|
+
case 'warn':
|
|
332
|
+
return `${YLW}${UNICODE_CHARS.WARN}${RST} warn`
|
|
333
|
+
case 'error':
|
|
334
|
+
return `${RED}${UNICODE_CHARS.CROSS}${RST} error`
|
|
335
|
+
default:
|
|
336
|
+
return s
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Number of extra invisible chars added by the colour wrapper in fmtStatus
|
|
341
|
+
const statusAnsiExtra =
|
|
342
|
+
useColor && ['success', 'warn', 'error'].includes(statuses[0])
|
|
343
|
+
? // Each coloured status wraps the symbol in open + close = 2 escapes
|
|
344
|
+
// e.g. `\x1b[32m✓\x1b[0m success` = 5+5+8 = 18 visible, but 28 chars
|
|
345
|
+
// The extra is the ANSI codes length per status
|
|
346
|
+
10
|
|
347
|
+
: 0
|
|
348
|
+
|
|
349
|
+
// ── Print ───────────────────────────────────────────────────────────
|
|
350
|
+
write()
|
|
351
|
+
section('Setup Summary')
|
|
352
|
+
divider()
|
|
353
|
+
|
|
354
|
+
// Header
|
|
355
|
+
write(
|
|
356
|
+
` ${headerPhase.padEnd(colPhase)} ` +
|
|
357
|
+
`${headerStatus.padEnd(colStatus + statusAnsiExtra)} ` +
|
|
358
|
+
`${headerMessage}`,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
// Separator
|
|
362
|
+
write(
|
|
363
|
+
` ${DIM}${'\u2500'.repeat(colPhase)}` +
|
|
364
|
+
` ${'\u2500'.repeat(Math.max(colStatus, 6))}` +
|
|
365
|
+
` ${'\u2500'.repeat(colMsg)}${RST}`,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
// Rows
|
|
369
|
+
for (const r of results) {
|
|
370
|
+
const p = r.phase.padEnd(colPhase)
|
|
371
|
+
const s = fmtStatus(r.status).padEnd(colStatus + statusAnsiExtra)
|
|
372
|
+
write(` ${p} ${s} ${r.message || ''}`)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
divider()
|
|
376
|
+
|
|
377
|
+
// Overall result
|
|
378
|
+
const hasErrors = results.some((r) => r.status === 'error')
|
|
379
|
+
const hasWarnings = results.some((r) => r.status === 'warn')
|
|
380
|
+
if (hasErrors) {
|
|
381
|
+
error('Setup completed with errors.')
|
|
382
|
+
} else if (hasWarnings) {
|
|
383
|
+
warn('Setup completed with warnings.')
|
|
384
|
+
} else {
|
|
385
|
+
success('Setup completed successfully.')
|
|
386
|
+
}
|
|
387
|
+
write()
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Prints usage / help information showing all available CLI flags and their
|
|
392
|
+
* descriptions.
|
|
393
|
+
*/
|
|
394
|
+
export function help() {
|
|
395
|
+
const version = getVersion()
|
|
396
|
+
|
|
397
|
+
// ── Build flag display groups ───────────────────────────────────────
|
|
398
|
+
// Invert SUPPORTED_FLAGS to group short + long forms per canonical key.
|
|
399
|
+
/** @type {Record<string, string[]>} */
|
|
400
|
+
const groups = {}
|
|
401
|
+
for (const [flag, canonical] of Object.entries(SUPPORTED_FLAGS)) {
|
|
402
|
+
;(groups[canonical] || (groups[canonical] = [])).push(flag)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Sort each group so shorter flags come first, then build display text.
|
|
406
|
+
/** @type {Record<string, string>} */
|
|
407
|
+
const display = {}
|
|
408
|
+
let maxLen = 0
|
|
409
|
+
for (const [canonical, flags] of Object.entries(groups)) {
|
|
410
|
+
flags.sort((a, b) => a.length - b.length)
|
|
411
|
+
const text = flags.join(', ')
|
|
412
|
+
display[canonical] = text
|
|
413
|
+
maxLen = Math.max(maxLen, text.length)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Ordered flag descriptions.
|
|
417
|
+
/** @type {Record<string, string>} */
|
|
418
|
+
const descriptions = {
|
|
419
|
+
force: 'Overwrite existing hooks (a .bak backup is created)',
|
|
420
|
+
dryRun: 'Show what would be done without making any changes',
|
|
421
|
+
yes: 'Skip confirmation prompts (non-interactive mode)',
|
|
422
|
+
skipHooks: 'Skip git hook installation phase',
|
|
423
|
+
skipPrepare: 'Skip prepare script modification phase',
|
|
424
|
+
skipSync: 'Skip file sync phase',
|
|
425
|
+
help: 'Show this help message and exit',
|
|
426
|
+
version: 'Show the version number and exit',
|
|
427
|
+
quiet: 'Suppress non-error output (quiet mode)',
|
|
428
|
+
verbose: 'Show detailed debug information',
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const order = [
|
|
432
|
+
'force',
|
|
433
|
+
'dryRun',
|
|
434
|
+
'yes',
|
|
435
|
+
'skipHooks',
|
|
436
|
+
'skipPrepare',
|
|
437
|
+
'skipSync',
|
|
438
|
+
'help',
|
|
439
|
+
'version',
|
|
440
|
+
'quiet',
|
|
441
|
+
'verbose',
|
|
442
|
+
]
|
|
443
|
+
|
|
444
|
+
// ── Print help text ─────────────────────────────────────────────────
|
|
445
|
+
write()
|
|
446
|
+
write(`${BLD}${PACKAGE_NAME}${RST} ${GRN}v${version}${RST}`)
|
|
447
|
+
write()
|
|
448
|
+
write(` ${BLD}Usage:${RST}`)
|
|
449
|
+
write(` npx ${PACKAGE_NAME} setup [options]`)
|
|
450
|
+
write(` ${BIN_NAME} [options]`)
|
|
451
|
+
write()
|
|
452
|
+
write(` ${BLD}Description:${RST}`)
|
|
453
|
+
write(
|
|
454
|
+
` Sets up a consumer project with git hooks, memory-bank scaffolding,` +
|
|
455
|
+
` and AI workflow`,
|
|
456
|
+
)
|
|
457
|
+
write(` integration for the ${PACKAGE_NAME} framework.`)
|
|
458
|
+
write()
|
|
459
|
+
write(` ${BLD}Options:${RST}`)
|
|
460
|
+
|
|
461
|
+
for (const key of order) {
|
|
462
|
+
const d = display[key]
|
|
463
|
+
const desc = descriptions[key] || ''
|
|
464
|
+
if (d) {
|
|
465
|
+
write(` ${d.padEnd(maxLen)} ${desc}`)
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
write()
|
|
470
|
+
write(` ${BLD}Examples:${RST}`)
|
|
471
|
+
write(` npx ${PACKAGE_NAME} setup`)
|
|
472
|
+
write(` npx ${PACKAGE_NAME} setup --dry-run --verbose`)
|
|
473
|
+
write(` npx ${PACKAGE_NAME} setup --force --skip-hooks`)
|
|
474
|
+
write(` ${BIN_NAME} --help`)
|
|
475
|
+
write()
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Prints a horizontal rule for visual separation.
|
|
480
|
+
*/
|
|
481
|
+
export function divider() {
|
|
482
|
+
write(` ${DIM}${rule()}${RST}`)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Prints a prominent banner indicating that `--dry-run` mode is active and no
|
|
487
|
+
* files will be modified.
|
|
488
|
+
*/
|
|
489
|
+
export function dryRunBanner() {
|
|
490
|
+
const width = Math.min(process.stdout.columns || 72, 72)
|
|
491
|
+
const sep = YLW + rule() + RST
|
|
492
|
+
write()
|
|
493
|
+
write(sep)
|
|
494
|
+
write(
|
|
495
|
+
` ${YLW}${UNICODE_CHARS.WARN}${RST}` +
|
|
496
|
+
` ${BLD}DRY RUN MODE${RST}` +
|
|
497
|
+
` ${DIM}\u2014${RST} No files will be modified.` +
|
|
498
|
+
` ${YLW}${UNICODE_CHARS.WARN}${RST}`,
|
|
499
|
+
)
|
|
500
|
+
write(sep)
|
|
501
|
+
write()
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Prints a banner indicating CI mode is detected and git hooks will be
|
|
506
|
+
* skipped.
|
|
507
|
+
*/
|
|
508
|
+
export function ciModeBanner() {
|
|
509
|
+
const width = Math.min(process.stdout.columns || 72, 72)
|
|
510
|
+
const sep = CYN + rule() + RST
|
|
511
|
+
write()
|
|
512
|
+
write(sep)
|
|
513
|
+
write(
|
|
514
|
+
` ${CYN}${UNICODE_CHARS.BULLET}${RST}` +
|
|
515
|
+
` ${BLD}CI MODE DETECTED${RST}` +
|
|
516
|
+
` ${DIM}\u2014${RST} Git hooks will be skipped.` +
|
|
517
|
+
` Use ${CYN}--skip-hooks${RST} to suppress this message.`,
|
|
518
|
+
)
|
|
519
|
+
write(sep)
|
|
520
|
+
write()
|
|
521
|
+
}
|