pantheon-opencode 1.1.1-beta.3 → 1.1.1-beta.5

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.
@@ -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
+ }
@@ -8,9 +8,10 @@
8
8
  * node scripts/install/migrate.mjs --target ~/.config/opencode --force
9
9
  */
10
10
 
11
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
11
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
12
12
  import { dirname, join } from 'node:path'
13
13
  import { fileURLToPath } from 'node:url'
14
+ import { readState, writeState, createInitialState } from './state.mjs'
14
15
 
15
16
  const __dirname = dirname(fileURLToPath(import.meta.url))
16
17
  const ROOT = join(__dirname, '..', '..')
@@ -33,16 +34,9 @@ function readPackageVersion() {
33
34
  * @returns {string|null} - version string or null for fresh install
34
35
  */
35
36
  export function detectVersion(target) {
36
- const stateFile = join(target, '.pantheon', 'install-state.json')
37
- if (existsSync(stateFile)) {
38
- try {
39
- const state = JSON.parse(readFileSync(stateFile, 'utf8'))
40
- return state.pantheon_version || state.version || 'unknown'
41
- } catch {
42
- /* corrupted state — treat as unknown */
43
- }
44
- }
45
- return null // fresh install
37
+ const state = readState(target)
38
+ if (state) return state.pantheon_version || state.version || 'unknown'
39
+ return null
46
40
  }
47
41
 
48
42
  /**
@@ -96,9 +90,42 @@ export function runMigrations(target, currentVersion, { dryRun = false } = {}) {
96
90
  semver[2] = 1
97
91
  }
98
92
 
93
+ // ── Migration: v3.19.1 → v3.19.2 ──────────────────────────────────
94
+ // Upgrade state schema from v1 to v2
95
+ if (semver && lt(semver, [3, 19, 2])) {
96
+ applied.push({
97
+ from: formatSemver(semver),
98
+ to: '3.19.2',
99
+ description: 'Upgrade install state schema to v2 with component tracking',
100
+ })
101
+ semver[0] = 3
102
+ semver[1] = 19
103
+ semver[2] = 2
104
+ }
105
+
99
106
  // ── Write updated state ───────────────────────────────────────────
100
107
  if (applied.length > 0 && !dryRun) {
101
- writeInstallState(target, formatSemver(semver))
108
+ const newVersion = formatSemver(semver)
109
+ let state = readState(target)
110
+ if (!state) state = createInitialState(newVersion)
111
+ state.pantheon_version = newVersion
112
+ for (const m of applied) {
113
+ state.applied_migrations.push({
114
+ from: m.from,
115
+ to: m.to,
116
+ applied_at: new Date().toISOString(),
117
+ status: 'completed',
118
+ })
119
+ }
120
+ if (!state.components) {
121
+ state.components = {
122
+ agents: { version: 1, status: 'installed' },
123
+ skills: { version: 1, status: 'installed' },
124
+ plugins: { version: 1, status: 'installed' },
125
+ runtime: { version: 1, status: 'installed' },
126
+ }
127
+ }
128
+ writeState(target, state)
102
129
  }
103
130
 
104
131
  return {
@@ -107,20 +134,150 @@ export function runMigrations(target, currentVersion, { dryRun = false } = {}) {
107
134
  }
108
135
  }
109
136
 
137
+ // ---------------------------------------------------------------------------
138
+ // Checkpoint system for crash recovery
139
+ // ---------------------------------------------------------------------------
140
+
141
+ const CHECKPOINT_FILE = '.migration-checkpoint'
142
+ const STATE_DIR = '.pantheon'
143
+
144
+ /**
145
+ * Read the migration checkpoint file if it exists.
146
+ * @param {string} target
147
+ * @returns {object|null}
148
+ */
149
+ export function readCheckpoint(target) {
150
+ const cpFile = join(target, STATE_DIR, CHECKPOINT_FILE)
151
+ if (!existsSync(cpFile)) return null
152
+ try {
153
+ return JSON.parse(readFileSync(cpFile, 'utf8'))
154
+ } catch {
155
+ return null
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Write a migration checkpoint before starting migrations.
161
+ * @param {string} target
162
+ * @param {string} fromVersion
163
+ * @param {string} toVersion
164
+ * @param {string[]} steps - Ordered list of step names
165
+ * @returns {object} The checkpoint object
166
+ */
167
+ export function writeCheckpoint(target, fromVersion, toVersion, steps) {
168
+ const cpDir = join(target, STATE_DIR)
169
+ if (!existsSync(cpDir)) mkdirSync(cpDir, { recursive: true })
170
+ const cpFile = join(cpDir, CHECKPOINT_FILE)
171
+ const checkpoint = {
172
+ from: fromVersion,
173
+ to: toVersion,
174
+ started_at: new Date().toISOString(),
175
+ steps_completed: [],
176
+ steps_pending: [...steps],
177
+ }
178
+ writeFileSync(cpFile, `${JSON.stringify(checkpoint, null, 2)}\n`, 'utf8')
179
+ return checkpoint
180
+ }
181
+
182
+ /**
183
+ * Advance the checkpoint: mark a step as completed and remove from pending.
184
+ * @param {string} target
185
+ * @param {string} stepName
186
+ * @returns {object|null} Updated checkpoint, or null if none exists
187
+ */
188
+ export function advanceCheckpoint(target, stepName) {
189
+ const cp = readCheckpoint(target)
190
+ if (!cp) return null
191
+ cp.steps_completed.push(stepName)
192
+ cp.steps_pending = cp.steps_pending.filter((s) => s !== stepName)
193
+ const cpFile = join(target, STATE_DIR, CHECKPOINT_FILE)
194
+ writeFileSync(cpFile, `${JSON.stringify(cp, null, 2)}\n`, 'utf8')
195
+ return cp
196
+ }
197
+
198
+ /**
199
+ * Remove the checkpoint file on successful completion.
200
+ * @param {string} target
201
+ */
202
+ export function removeCheckpoint(target) {
203
+ const cpFile = join(target, STATE_DIR, CHECKPOINT_FILE)
204
+ if (existsSync(cpFile)) {
205
+ rmSync(cpFile, { force: true })
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Resume from a pending checkpoint.
211
+ * Reads the checkpoint and reports what steps remain.
212
+ * @param {string} target
213
+ * @returns {object|null} The checkpoint, or null if none pending
214
+ */
215
+ export function resumePendingMigration(target) {
216
+ const cp = readCheckpoint(target)
217
+ if (!cp) return null
218
+ console.log(` \u{1F504} Resuming migration from checkpoint: ${cp.from} \u2192 ${cp.to}`)
219
+ console.log(` Completed: ${cp.steps_completed.join(', ') || 'none'}`)
220
+ console.log(` Pending: ${cp.steps_pending.join(', ') || 'none'}`)
221
+ return cp
222
+ }
223
+
224
+ // ---------------------------------------------------------------------------
225
+ // Rollback support
226
+ // ---------------------------------------------------------------------------
227
+
110
228
  /**
111
- * Write install state with current version to target.
229
+ * Roll back the last completed migration for a given version.
230
+ * Each migration step should define a corresponding down-migration.
231
+ * This is a stub — actual down-migration logic should be registered
232
+ * in a DOWN_MIGRATIONS registry.
233
+ *
234
+ * @param {string} target
235
+ * @param {string} version - The version to roll back from
236
+ * @returns {{ success: boolean, reason?: string, migration?: object }}
112
237
  */
113
- function writeInstallState(target, version) {
114
- const stateDir = join(target, '.pantheon')
115
- const stateFile = join(stateDir, 'install-state.json')
116
- if (!existsSync(stateDir)) {
117
- mkdirSync(stateDir, { recursive: true })
238
+ export function rollbackMigration(target, version) {
239
+ const state = readState(target)
240
+ if (!state) {
241
+ console.log(' \u26A0\uFE0F No install state found — nothing to roll back')
242
+ return { success: false, reason: 'No install state' }
118
243
  }
119
- const state = {
120
- pantheon_version: version,
121
- updated_at: new Date().toISOString(),
244
+
245
+ const migrations = state.applied_migrations || []
246
+ const targetMigration = migrations.find(
247
+ (m) => m.to === version && m.status === 'completed',
248
+ )
249
+ if (!targetMigration) {
250
+ console.log(` \u26A0\uFE0F No completed migration found for version ${version}`)
251
+ return { success: false, reason: 'Migration not found' }
252
+ }
253
+
254
+ console.log(
255
+ ` \u{1F504} Rolling back migration: ${targetMigration.from} \u2192 ${targetMigration.to}`,
256
+ )
257
+
258
+ targetMigration.status = 'rolled_back'
259
+ state.pantheon_version = targetMigration.from
260
+ writeState(target, state)
261
+
262
+ console.log(` \u2705 Rolled back to ${targetMigration.from}`)
263
+ return { success: true, migration: targetMigration }
264
+ }
265
+
266
+ /**
267
+ * List all applied migrations from the install state.
268
+ * @param {string} target
269
+ * @returns {{ currentVersion: string|null, previousVersion: string|null, migrations: object[] }}
270
+ */
271
+ export function listAppliedMigrations(target) {
272
+ const state = readState(target)
273
+ if (!state) {
274
+ return { currentVersion: null, previousVersion: null, migrations: [] }
275
+ }
276
+ return {
277
+ currentVersion: state.pantheon_version,
278
+ previousVersion: state.previous_version,
279
+ migrations: state.applied_migrations || [],
122
280
  }
123
- writeFileSync(stateFile, `${JSON.stringify(state, null, 2)}\n`, 'utf8')
124
281
  }
125
282
 
126
283
  // ---------------------------------------------------------------------------
@@ -172,15 +329,38 @@ function main() {
172
329
  const target = targetIdx !== -1 ? args[targetIdx + 1] : process.cwd()
173
330
  const dryRun = args.includes('--dry-run')
174
331
  const force = args.includes('--force')
332
+ const list = args.includes('--list')
333
+
334
+ if (list) {
335
+ const history = listAppliedMigrations(target)
336
+ if (!history.currentVersion) {
337
+ console.log(' \u2139\uFE0F No install state found in: ' + target)
338
+ return
339
+ }
340
+ console.log(` Current version: ${history.currentVersion}`)
341
+ console.log(` Previous version: ${history.previousVersion || 'N/A'}`)
342
+ console.log(` Applied migrations: ${history.migrations.length}`)
343
+ for (const m of history.migrations) {
344
+ const icon = m.status === 'completed' ? '\u2705' : m.status === 'rolled_back' ? '\u{1F504}' : '\u26A0\uFE0F'
345
+ console.log(` ${icon} ${m.from} \u2192 ${m.to} [${m.status}] (${m.applied_at})`)
346
+ }
347
+ return
348
+ }
349
+
350
+ const rollbackTo = args.find((a) => a.startsWith('--rollback='))?.split('=', 2)[1]
351
+ if (rollbackTo) {
352
+ rollbackMigration(target, rollbackTo)
353
+ return
354
+ }
175
355
 
176
356
  const currentVersion = detectVersion(target)
177
357
  if (!currentVersion) {
178
- // Fresh install — write state and exit
179
358
  if (!dryRun) {
180
- writeInstallState(target, PANTHON_VERSION)
181
- console.log(` ✅ Fresh install — state written (${PANTHON_VERSION})`)
359
+ const state = createInitialState(PANTHON_VERSION)
360
+ writeState(target, state)
361
+ console.log(` \u2705 Fresh install — state written (${PANTHON_VERSION})`)
182
362
  } else {
183
- console.log(` ✅ Fresh install — would write state (${PANTHON_VERSION})`)
363
+ console.log(` \u2705 Fresh install — would write state (${PANTHON_VERSION})`)
184
364
  }
185
365
  return
186
366
  }
@@ -188,19 +368,19 @@ function main() {
188
368
  console.log(` Current version: ${currentVersion}`)
189
369
 
190
370
  if (!force && currentVersion === PANTHON_VERSION) {
191
- console.log(' ⏭️ Already at latest version — no migrations needed')
371
+ console.log(' \u23ED\uFE0F Already at latest version — no migrations needed')
192
372
  return
193
373
  }
194
374
 
195
375
  const migration = runMigrations(target, currentVersion, { dryRun })
196
376
 
197
377
  if (migration.applied > 0) {
198
- console.log(` ✅ Applied ${migration.applied} migration(s):`)
378
+ console.log(` \u2705 Applied ${migration.applied} migration(s):`)
199
379
  for (const msg of migration.messages) {
200
- console.log(` • ${msg}`)
380
+ console.log(` \u2022 ${msg}`)
201
381
  }
202
382
  } else {
203
- console.log(' ⏭️ No applicable migrations')
383
+ console.log(' \u23ED\uFE0F No applicable migrations')
204
384
  }
205
385
  }
206
386