pantheon-opencode 1.1.1-beta.3 → 1.1.1-beta.4
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/package.json +1 -1
- package/scripts/install/cli-ui.mjs +160 -0
- package/scripts/install/component.mjs +24 -0
- package/scripts/install/migrate-config.mjs +308 -0
- package/scripts/install/migrate.mjs +210 -30
- package/scripts/install/opencode.mjs +43 -77
- package/scripts/install/plugin.mjs +139 -0
- package/scripts/install/state.mjs +194 -0
package/package.json
CHANGED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
export const icons = {
|
|
2
|
+
success: '\u2705',
|
|
3
|
+
warning: '\u26a0\ufe0f ',
|
|
4
|
+
error: '\u274c',
|
|
5
|
+
info: '\u2139\ufe0f ',
|
|
6
|
+
progress: '\u23f3',
|
|
7
|
+
done: '\u2705',
|
|
8
|
+
arrow: '\u2192',
|
|
9
|
+
bullet: '\u2022',
|
|
10
|
+
star: '\u2b50',
|
|
11
|
+
wrench: '\U0001f527',
|
|
12
|
+
rocket: '\U0001f680',
|
|
13
|
+
package: '\U0001f4e6',
|
|
14
|
+
gear: '\u2699\ufe0f',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const colors = {
|
|
18
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
19
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
20
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
21
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
22
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
23
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let _quiet = false
|
|
27
|
+
let _dryRun = false
|
|
28
|
+
|
|
29
|
+
export function configure(opts = {}) {
|
|
30
|
+
if (opts.quiet !== undefined) _quiet = opts.quiet
|
|
31
|
+
if (opts.dryRun !== undefined) _dryRun = opts.dryRun
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function tag(msg) {
|
|
35
|
+
return _dryRun ? `[DRY-RUN] ${msg}` : msg
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function tty() {
|
|
39
|
+
return process.stdout.isTTY
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function section(title) {
|
|
43
|
+
if (_quiet) return
|
|
44
|
+
const width = Math.min(process.stdout.columns || 60, 72)
|
|
45
|
+
const text = `\u2500\u2500 ${title} `
|
|
46
|
+
const line = text + '\u2500'.repeat(Math.max(0, width - text.length + 1))
|
|
47
|
+
console.log(tag(line))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function step(message) {
|
|
51
|
+
const text = tag(`${icons.progress} ${message}...`)
|
|
52
|
+
if (!_quiet) process.stdout.write(text)
|
|
53
|
+
return (ok = true) => {
|
|
54
|
+
const icon = ok ? icons.success : icons.error
|
|
55
|
+
const doneText = tag(`${icon} ${message}`)
|
|
56
|
+
if (_quiet && !ok) {
|
|
57
|
+
console.warn(doneText)
|
|
58
|
+
} else if (!_quiet) {
|
|
59
|
+
process.stdout.write(`\r${doneText}\n`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function success(message) {
|
|
65
|
+
console.log(tag(`${icons.success} ${message}`))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function warning(message) {
|
|
69
|
+
console.warn(tag(`${icons.warning}${message}`))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function error(message) {
|
|
73
|
+
console.error(tag(`${icons.error} ${message}`))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function info(message) {
|
|
77
|
+
if (_quiet) return
|
|
78
|
+
console.log(tag(`${icons.info}${message}`))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function bullet(text, indent = 0) {
|
|
82
|
+
if (_quiet) return
|
|
83
|
+
const pad = ' '.repeat(indent)
|
|
84
|
+
console.log(tag(`${pad}${icons.bullet} ${text}`))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function summaryTable(items) {
|
|
88
|
+
if (_quiet || items.length === 0) return
|
|
89
|
+
|
|
90
|
+
const nameWidth = Math.max(...items.map((i) => i.name.length), 15)
|
|
91
|
+
const statusWidth = Math.max(...items.map((i) => String(i.status).length), 8)
|
|
92
|
+
|
|
93
|
+
const top = `\u250c${'\u2500'.repeat(nameWidth + 2)}\u252c${'\u2500'.repeat(statusWidth + 2)}\u2510`
|
|
94
|
+
const sep = `\u251c${'\u2500'.repeat(nameWidth + 2)}\u253c${'\u2500'.repeat(statusWidth + 2)}\u2524`
|
|
95
|
+
const bot = `\u2514${'\u2500'.repeat(nameWidth + 2)}\u2534${'\u2500'.repeat(statusWidth + 2)}\u2518`
|
|
96
|
+
|
|
97
|
+
console.log(tag(top))
|
|
98
|
+
console.log(tag(`\u2502 ${'Componente'.padEnd(nameWidth)} \u2502 ${'Status'.padEnd(statusWidth)} \u2502`))
|
|
99
|
+
console.log(tag(sep))
|
|
100
|
+
for (const item of items) {
|
|
101
|
+
console.log(tag(`\u2502 ${item.name.padEnd(nameWidth)} \u2502 ${String(item.status).padEnd(statusWidth)} \u2502`))
|
|
102
|
+
}
|
|
103
|
+
console.log(tag(bot))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function spinner(message) {
|
|
107
|
+
const frames = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f']
|
|
108
|
+
let i = 0
|
|
109
|
+
let running = true
|
|
110
|
+
|
|
111
|
+
if (tty() && !_quiet) {
|
|
112
|
+
const interval = setInterval(() => {
|
|
113
|
+
if (!running) { clearInterval(interval); return }
|
|
114
|
+
process.stdout.write(`\r${frames[i % frames.length]} ${tag(message)}...`)
|
|
115
|
+
i++
|
|
116
|
+
}, 80)
|
|
117
|
+
process.stdout.write(`\r${frames[0]} ${tag(message)}...`)
|
|
118
|
+
|
|
119
|
+
return (ok = true) => {
|
|
120
|
+
running = false
|
|
121
|
+
clearInterval(interval)
|
|
122
|
+
const icon = ok ? icons.success : icons.error
|
|
123
|
+
process.stdout.write(`\r${icon} ${tag(message)}\n`)
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
const text = tag(`${icons.progress} ${message}...`)
|
|
127
|
+
if (!_quiet) process.stdout.write(text)
|
|
128
|
+
return (ok = true) => {
|
|
129
|
+
const icon = ok ? icons.success : icons.error
|
|
130
|
+
const doneText = tag(`${icon} ${message}`)
|
|
131
|
+
if (_quiet && !ok) {
|
|
132
|
+
console.warn(doneText)
|
|
133
|
+
} else if (!_quiet) {
|
|
134
|
+
process.stdout.write(`\r${doneText}\n`)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function printSummary(target, platforms, stats) {
|
|
141
|
+
if (_quiet) return
|
|
142
|
+
|
|
143
|
+
const line = '\u2500'.repeat(Math.min(process.stdout.columns || 60, 60))
|
|
144
|
+
console.log(tag(`${icons.rocket} OpenCode instalado em ${target}`))
|
|
145
|
+
console.log(tag(line))
|
|
146
|
+
console.log('')
|
|
147
|
+
|
|
148
|
+
if (stats) {
|
|
149
|
+
console.log(tag(` Componentes:\t${stats.created} instalados, ${stats.skipped} pulados`))
|
|
150
|
+
if (stats.errors > 0) {
|
|
151
|
+
console.warn(tag(` ${icons.warning}${stats.errors} erro(s) encontrados`))
|
|
152
|
+
}
|
|
153
|
+
console.log('')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log(tag(` ${icons.star} Próximos passos:`))
|
|
157
|
+
console.log(tag(` ${icons.bullet} Configure seus agentes em opencode.json`))
|
|
158
|
+
console.log(tag(` ${icons.bullet} Adicione MCP servers em mcp.json`))
|
|
159
|
+
console.log(tag(` ${icons.bullet} Rode 'opencode doctor' para verificar a instalação`))
|
|
160
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* component.mjs — Component resolution utilities for the OpenCode installer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Available component identifiers */
|
|
6
|
+
export const COMPONENT_NAMES = [
|
|
7
|
+
'agents',
|
|
8
|
+
'skills',
|
|
9
|
+
'instructions',
|
|
10
|
+
'prompts',
|
|
11
|
+
'commands',
|
|
12
|
+
'plugins',
|
|
13
|
+
'runtime',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse --components flag into a Set.
|
|
18
|
+
* @param {string|string[]} spec - Comma-separated string or array of component names
|
|
19
|
+
* @returns {Set<string>}
|
|
20
|
+
*/
|
|
21
|
+
export function resolveComponents(spec) {
|
|
22
|
+
if (Array.isArray(spec)) return new Set(spec)
|
|
23
|
+
return new Set(spec.split(',').map((s) => s.trim()))
|
|
24
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* migrate-config.mjs — Standalone migration CLI with checkpoint crash recovery
|
|
4
|
+
*
|
|
5
|
+
* Runs configuration migrations with an atomic checkpoint system.
|
|
6
|
+
* If the process crashes mid-migration, the checkpoint file persists
|
|
7
|
+
* so that --resume picks up where it left off.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/install/migrate-config.mjs --from=1.1.0 --to=1.1.1 --target=~/.config/opencode
|
|
11
|
+
* node scripts/install/migrate-config.mjs --resume --target=~/.config/opencode
|
|
12
|
+
* node scripts/install/migrate-config.mjs --list --target=~/.config/opencode
|
|
13
|
+
* node scripts/install/migrate-config.mjs --dry-run --from=1.1.0 --to=1.1.1
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
17
|
+
import { join } from 'node:path'
|
|
18
|
+
import {
|
|
19
|
+
readState,
|
|
20
|
+
writeState,
|
|
21
|
+
createInitialState,
|
|
22
|
+
addMigrationRecord,
|
|
23
|
+
updateVersion,
|
|
24
|
+
} from './state.mjs'
|
|
25
|
+
import {
|
|
26
|
+
readCheckpoint,
|
|
27
|
+
writeCheckpoint,
|
|
28
|
+
advanceCheckpoint,
|
|
29
|
+
removeCheckpoint,
|
|
30
|
+
resumePendingMigration,
|
|
31
|
+
listAppliedMigrations,
|
|
32
|
+
} from './migrate.mjs'
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Migration step definitions
|
|
36
|
+
// Each step is an idempotent function(target, state, { dryRun }) => void
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
const MIGRATION_STEPS = {
|
|
40
|
+
/**
|
|
41
|
+
* migrate_state_v2: Upgrade install-state.json from schema v1 to v2.
|
|
42
|
+
* Adds schema_version field, components tracking, and migration history.
|
|
43
|
+
*/
|
|
44
|
+
migrate_state_v2: (target, state, { dryRun }) => {
|
|
45
|
+
if (state.schema_version && state.schema_version >= 2) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
if (!dryRun) {
|
|
49
|
+
state.schema_version = 2
|
|
50
|
+
state.components = state.components || {
|
|
51
|
+
agents: { version: 1, status: 'pending' },
|
|
52
|
+
skills: { version: 1, status: 'pending' },
|
|
53
|
+
plugins: { version: 1, status: 'pending' },
|
|
54
|
+
runtime: { version: 1, status: 'pending' },
|
|
55
|
+
}
|
|
56
|
+
state.applied_migrations = state.applied_migrations || []
|
|
57
|
+
state.migration_checkpoint = null
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* update_schema: Ensure all v2 schema fields exist with defaults.
|
|
63
|
+
*/
|
|
64
|
+
update_schema: (target, state, { dryRun }) => {
|
|
65
|
+
if (!dryRun) {
|
|
66
|
+
if (!state.components) {
|
|
67
|
+
state.components = {
|
|
68
|
+
agents: { version: 1, status: 'pending' },
|
|
69
|
+
skills: { version: 1, status: 'pending' },
|
|
70
|
+
plugins: { version: 1, status: 'pending' },
|
|
71
|
+
runtime: { version: 1, status: 'pending' },
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const key of ['agents', 'skills', 'plugins', 'runtime']) {
|
|
75
|
+
if (!state.components[key]) {
|
|
76
|
+
state.components[key] = { version: 1, status: 'pending' }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
state.applied_migrations = state.applied_migrations || []
|
|
80
|
+
if (state.migration_checkpoint === undefined) {
|
|
81
|
+
state.migration_checkpoint = null
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* migrate_plugins: Normalize plugin component tracking.
|
|
88
|
+
*/
|
|
89
|
+
migrate_plugins: (target, state, { dryRun }) => {
|
|
90
|
+
if (!dryRun) {
|
|
91
|
+
if (!state.components) state.components = {}
|
|
92
|
+
const p = state.components.plugins || { version: 1, status: 'pending' }
|
|
93
|
+
p.version = Math.max(p.version || 0, 1)
|
|
94
|
+
state.components.plugins = p
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Migration step ordering (a step runs ONLY after all predecessors complete)
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
const STEP_ORDER = Object.keys(MIGRATION_STEPS)
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Resolve which steps to run based on from/to version comparison.
|
|
107
|
+
* For now, runs all steps. In the future this can be gated by version.
|
|
108
|
+
* @param {string} fromVersion
|
|
109
|
+
* @param {string} toVersion
|
|
110
|
+
* @returns {string[]} Ordered list of step names
|
|
111
|
+
*/
|
|
112
|
+
function resolveSteps(fromVersion, toVersion) {
|
|
113
|
+
return [...STEP_ORDER]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// CLI
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
function parseArgs() {
|
|
121
|
+
const args = process.argv.slice(2)
|
|
122
|
+
const result = {
|
|
123
|
+
from: null,
|
|
124
|
+
to: null,
|
|
125
|
+
target: process.cwd(),
|
|
126
|
+
dryRun: false,
|
|
127
|
+
resume: false,
|
|
128
|
+
list: false,
|
|
129
|
+
help: false,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i < args.length; i++) {
|
|
133
|
+
const arg = args[i]
|
|
134
|
+
if (arg === '--dry-run') result.dryRun = true
|
|
135
|
+
else if (arg === '--resume') result.resume = true
|
|
136
|
+
else if (arg === '--list') result.list = true
|
|
137
|
+
else if (arg === '--help' || arg === '-h') result.help = true
|
|
138
|
+
else if (arg.startsWith('--from=')) result.from = arg.split('=', 2)[1]
|
|
139
|
+
else if (arg.startsWith('--to=')) result.to = arg.split('=', 2)[1]
|
|
140
|
+
else if (arg.startsWith('--target=')) result.target = arg.split('=', 2)[1]
|
|
141
|
+
else if (arg === '--from') result.from = args[++i]
|
|
142
|
+
else if (arg === '--to') result.to = args[++i]
|
|
143
|
+
else if (arg === '--target') result.target = args[++i]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return result
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function showHelp() {
|
|
150
|
+
console.log(`
|
|
151
|
+
migrate-config.mjs — Pantheon configuration migration CLI
|
|
152
|
+
|
|
153
|
+
Usage:
|
|
154
|
+
node scripts/install/migrate-config.mjs --from=1.1.0 --to=1.1.1 --target=~/.config/opencode
|
|
155
|
+
node scripts/install/migrate-config.mjs --resume --target=~/.config/opencode
|
|
156
|
+
node scripts/install/migrate-config.mjs --list --target=~/.config/opencode
|
|
157
|
+
node scripts/install/migrate-config.mjs --dry-run --from=1.1.0 --to=1.1.1
|
|
158
|
+
node scripts/install/migrate-config.mjs --help
|
|
159
|
+
|
|
160
|
+
Options:
|
|
161
|
+
--from=<version> Source version (required for new migrations)
|
|
162
|
+
--to=<version> Target version (required for new migrations)
|
|
163
|
+
--target=<path> Installation directory (default: cwd)
|
|
164
|
+
--dry-run Preview without writing
|
|
165
|
+
--resume Resume from pending checkpoint (crash recovery)
|
|
166
|
+
--list Show applied migration history
|
|
167
|
+
--help Show this help
|
|
168
|
+
|
|
169
|
+
Crash Recovery:
|
|
170
|
+
If the process is interrupted mid-migration, a checkpoint file is left
|
|
171
|
+
at .pantheon/.migration-checkpoint. Run with --resume to continue.
|
|
172
|
+
`)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function main() {
|
|
176
|
+
const config = parseArgs()
|
|
177
|
+
|
|
178
|
+
if (config.help) {
|
|
179
|
+
showHelp()
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── List mode ──
|
|
184
|
+
if (config.list) {
|
|
185
|
+
const history = listAppliedMigrations(config.target)
|
|
186
|
+
if (!history.currentVersion) {
|
|
187
|
+
console.log(' \u2139\uFE0F No install state found in: ' + config.target)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
console.log(` Current version: ${history.currentVersion}`)
|
|
191
|
+
console.log(` Previous version: ${history.previousVersion || 'N/A'}`)
|
|
192
|
+
console.log(` Applied migrations: ${history.migrations.length}`)
|
|
193
|
+
for (const m of history.migrations) {
|
|
194
|
+
const icon =
|
|
195
|
+
m.status === 'completed'
|
|
196
|
+
? '\u2705'
|
|
197
|
+
: m.status === 'rolled_back'
|
|
198
|
+
? '\u{1F504}'
|
|
199
|
+
: '\u26A0\uFE0F'
|
|
200
|
+
console.log(` ${icon} ${m.from} \u2192 ${m.to} [${m.status}] (${m.applied_at})`)
|
|
201
|
+
}
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ── Resume mode ──
|
|
206
|
+
if (config.resume) {
|
|
207
|
+
const cp = resumePendingMigration(config.target)
|
|
208
|
+
if (!cp) {
|
|
209
|
+
console.log(' \u23ED\uFE0F No pending checkpoint found — nothing to resume')
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
config.from = cp.from
|
|
213
|
+
config.to = cp.to
|
|
214
|
+
} else {
|
|
215
|
+
if (!config.from || !config.to) {
|
|
216
|
+
console.error('\u274C --from and --to are required (or use --resume)')
|
|
217
|
+
showHelp()
|
|
218
|
+
process.exit(1)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── Read state ──
|
|
223
|
+
let state = readState(config.target)
|
|
224
|
+
if (!state) {
|
|
225
|
+
state = createInitialState(config.from)
|
|
226
|
+
console.log(` \u{1F4DD} Creating new install state (v${state.schema_version})`)
|
|
227
|
+
if (!config.dryRun) writeState(config.target, state)
|
|
228
|
+
} else {
|
|
229
|
+
console.log(
|
|
230
|
+
` \u{1F4CB} Current state: v${state.schema_version}, version ${state.pantheon_version}`,
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ── Resolve checkpoint ──
|
|
235
|
+
let cp = readCheckpoint(config.target)
|
|
236
|
+
|
|
237
|
+
if (!cp) {
|
|
238
|
+
const steps = resolveSteps(config.from, config.to)
|
|
239
|
+
cp = writeCheckpoint(config.target, config.from, config.to, steps)
|
|
240
|
+
console.log(` \u{1F4DD} Checkpoint written: ${config.from} \u2192 ${config.to}`)
|
|
241
|
+
} else {
|
|
242
|
+
console.log(
|
|
243
|
+
` \u{1F504} Resuming from checkpoint (${cp.steps_completed.length} steps done, ${cp.steps_pending.length} remaining)`,
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ── Run pending steps ──
|
|
248
|
+
const stepsToRun = [...cp.steps_pending]
|
|
249
|
+
let failed = false
|
|
250
|
+
|
|
251
|
+
for (const step of stepsToRun) {
|
|
252
|
+
const stepFn = MIGRATION_STEPS[step]
|
|
253
|
+
if (!stepFn) {
|
|
254
|
+
console.warn(` \u26A0\uFE0F Unknown migration step: ${step} — skipping`)
|
|
255
|
+
if (!config.dryRun) {
|
|
256
|
+
advanceCheckpoint(config.target, step)
|
|
257
|
+
}
|
|
258
|
+
continue
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
console.log(` \u25B6\uFE0F Running: ${step}`)
|
|
262
|
+
try {
|
|
263
|
+
stepFn(config.target, state, { dryRun: config.dryRun })
|
|
264
|
+
|
|
265
|
+
if (!config.dryRun) {
|
|
266
|
+
advanceCheckpoint(config.target, step)
|
|
267
|
+
writeState(config.target, state)
|
|
268
|
+
console.log(` \u2705 ${step} completed`)
|
|
269
|
+
} else {
|
|
270
|
+
console.log(` \u2705 ${step} would complete (dry-run)`)
|
|
271
|
+
}
|
|
272
|
+
} catch (err) {
|
|
273
|
+
console.error(` \u274C ${step} failed: ${err.message}`)
|
|
274
|
+
failed = true
|
|
275
|
+
break
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ── Finalize ──
|
|
280
|
+
if (failed) {
|
|
281
|
+
console.error(`\n \u274C Migration failed at checkpoint.`)
|
|
282
|
+
console.error(
|
|
283
|
+
` Resume with: node scripts/install/migrate-config.mjs --resume --target=${config.target}`,
|
|
284
|
+
)
|
|
285
|
+
process.exit(1)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!config.dryRun) {
|
|
289
|
+
updateVersion(state, config.to)
|
|
290
|
+
addMigrationRecord(state, config.from, config.to, 'completed')
|
|
291
|
+
removeCheckpoint(config.target)
|
|
292
|
+
writeState(config.target, state)
|
|
293
|
+
console.log(
|
|
294
|
+
`\n \u2705 Migration complete: ${config.from} \u2192 ${config.to}`,
|
|
295
|
+
)
|
|
296
|
+
console.log(
|
|
297
|
+
` State written to: ${join(config.target, '.pantheon', 'install-state.json')}`,
|
|
298
|
+
)
|
|
299
|
+
} else {
|
|
300
|
+
console.log(
|
|
301
|
+
`\n \u2705 Migration would complete (dry-run): ${config.from} \u2192 ${config.to}`,
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
|
|
307
|
+
main()
|
|
308
|
+
}
|