@shrkcrft/migrate 0.1.0-alpha.10
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/dist/engine/define-migration.d.ts +8 -0
- package/dist/engine/define-migration.d.ts.map +1 -0
- package/dist/engine/define-migration.js +16 -0
- package/dist/engine/plan-migration.d.ts +32 -0
- package/dist/engine/plan-migration.d.ts.map +1 -0
- package/dist/engine/plan-migration.js +49 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/runner/apply-migration.d.ts +37 -0
- package/dist/runner/apply-migration.d.ts.map +1 -0
- package/dist/runner/apply-migration.js +169 -0
- package/dist/runner/prune-migrations.d.ts +43 -0
- package/dist/runner/prune-migrations.d.ts.map +1 -0
- package/dist/runner/prune-migrations.js +115 -0
- package/dist/runner/resume-migration.d.ts +28 -0
- package/dist/runner/resume-migration.d.ts.map +1 -0
- package/dist/runner/resume-migration.js +42 -0
- package/dist/runner/state-store.d.ts +26 -0
- package/dist/runner/state-store.d.ts.map +1 -0
- package/dist/runner/state-store.js +59 -0
- package/dist/schema/migration.d.ts +90 -0
- package/dist/schema/migration.d.ts.map +1 -0
- package/dist/schema/migration.js +2 -0
- package/package.json +51 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type IMigration } from '../schema/migration.js';
|
|
2
|
+
/**
|
|
3
|
+
* Type-safe builder for migration definitions. Validates basic shape
|
|
4
|
+
* (no empty id / title / steps); deeper validation happens at plan /
|
|
5
|
+
* apply time when the steps are evaluated.
|
|
6
|
+
*/
|
|
7
|
+
export declare function defineMigration(input: Omit<IMigration, 'schema'>): IMigration;
|
|
8
|
+
//# sourceMappingURL=define-migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-migration.d.ts","sourceRoot":"","sources":["../../src/engine/define-migration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAE3E;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,GAAG,UAAU,CAO7E"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MIGRATION_SCHEMA } from "../schema/migration.js";
|
|
2
|
+
/**
|
|
3
|
+
* Type-safe builder for migration definitions. Validates basic shape
|
|
4
|
+
* (no empty id / title / steps); deeper validation happens at plan /
|
|
5
|
+
* apply time when the steps are evaluated.
|
|
6
|
+
*/
|
|
7
|
+
export function defineMigration(input) {
|
|
8
|
+
if (!input.id)
|
|
9
|
+
throw new Error('defineMigration: id is required');
|
|
10
|
+
if (!input.title)
|
|
11
|
+
throw new Error('defineMigration: title is required');
|
|
12
|
+
if (!input.steps || input.steps.length === 0) {
|
|
13
|
+
throw new Error('defineMigration: steps must be non-empty');
|
|
14
|
+
}
|
|
15
|
+
return { schema: MIGRATION_SCHEMA, ...input };
|
|
16
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type IRewritePlan } from '@shrkcrft/structural-search';
|
|
2
|
+
import type { IMigration, MigrationStep } from '../schema/migration.js';
|
|
3
|
+
export interface IPlannedStep {
|
|
4
|
+
index: number;
|
|
5
|
+
id: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
step: MigrationStep;
|
|
8
|
+
/** For structural-rewrite steps, the computed plan (dry). */
|
|
9
|
+
rewritePlan?: IRewritePlan;
|
|
10
|
+
}
|
|
11
|
+
export interface IMigrationPlan {
|
|
12
|
+
schema: 'sharkcraft.migration-plan/v1';
|
|
13
|
+
migration: {
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
};
|
|
17
|
+
plannedSteps: readonly IPlannedStep[];
|
|
18
|
+
/** Sum of (rewritePlan.totalEdits) across all rewrite steps. */
|
|
19
|
+
totalEdits: number;
|
|
20
|
+
/** Sum of (rewritePlan.files.length) across all rewrite steps. */
|
|
21
|
+
totalFiles: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compute the migration's plan without writing anything.
|
|
25
|
+
*
|
|
26
|
+
* `structural-rewrite` steps are pre-computed via `planRewrite` so the
|
|
27
|
+
* caller can preview file-level edits before deciding to apply.
|
|
28
|
+
* `shell` / `check` steps are listed as-is — they're executed only
|
|
29
|
+
* during `applyMigration`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function planMigration(migration: IMigration, projectRoot: string): IMigrationPlan;
|
|
32
|
+
//# sourceMappingURL=plan-migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-migration.d.ts","sourceRoot":"","sources":["../../src/engine/plan-migration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAExE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,aAAa,CAAC;IACpB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,YAAY,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,8BAA8B,CAAC;IACvC,SAAS,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,YAAY,EAAE,SAAS,YAAY,EAAE,CAAC;IACtC,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc,CAsCxF"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { planRewrite } from '@shrkcrft/structural-search';
|
|
2
|
+
/**
|
|
3
|
+
* Compute the migration's plan without writing anything.
|
|
4
|
+
*
|
|
5
|
+
* `structural-rewrite` steps are pre-computed via `planRewrite` so the
|
|
6
|
+
* caller can preview file-level edits before deciding to apply.
|
|
7
|
+
* `shell` / `check` steps are listed as-is — they're executed only
|
|
8
|
+
* during `applyMigration`.
|
|
9
|
+
*/
|
|
10
|
+
export function planMigration(migration, projectRoot) {
|
|
11
|
+
const plannedSteps = [];
|
|
12
|
+
let totalEdits = 0;
|
|
13
|
+
let totalFiles = 0;
|
|
14
|
+
for (let i = 0; i < migration.steps.length; i += 1) {
|
|
15
|
+
const step = migration.steps[i];
|
|
16
|
+
const id = step.id ?? `step-${i + 1}`;
|
|
17
|
+
if (step.kind === 'structural-rewrite') {
|
|
18
|
+
const rewritePlan = planRewrite({
|
|
19
|
+
projectRoot,
|
|
20
|
+
pattern: step.pattern,
|
|
21
|
+
recipe: step.recipe,
|
|
22
|
+
});
|
|
23
|
+
totalEdits += rewritePlan.totalEdits;
|
|
24
|
+
totalFiles += rewritePlan.files.length;
|
|
25
|
+
plannedSteps.push({
|
|
26
|
+
index: i,
|
|
27
|
+
id,
|
|
28
|
+
...(step.description ? { description: step.description } : {}),
|
|
29
|
+
step,
|
|
30
|
+
rewritePlan,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
plannedSteps.push({
|
|
35
|
+
index: i,
|
|
36
|
+
id,
|
|
37
|
+
...(step.description ? { description: step.description } : {}),
|
|
38
|
+
step,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
schema: 'sharkcraft.migration-plan/v1',
|
|
44
|
+
migration: { id: migration.id, title: migration.title },
|
|
45
|
+
plannedSteps,
|
|
46
|
+
totalEdits,
|
|
47
|
+
totalFiles,
|
|
48
|
+
};
|
|
49
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './schema/migration.js';
|
|
2
|
+
export * from './engine/define-migration.js';
|
|
3
|
+
export * from './engine/plan-migration.js';
|
|
4
|
+
export * from './runner/apply-migration.js';
|
|
5
|
+
export * from './runner/state-store.js';
|
|
6
|
+
export * from './runner/resume-migration.js';
|
|
7
|
+
export * from './runner/prune-migrations.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from "./schema/migration.js";
|
|
2
|
+
export * from "./engine/define-migration.js";
|
|
3
|
+
export * from "./engine/plan-migration.js";
|
|
4
|
+
export * from "./runner/apply-migration.js";
|
|
5
|
+
export * from "./runner/state-store.js";
|
|
6
|
+
export * from "./runner/resume-migration.js";
|
|
7
|
+
export * from "./runner/prune-migrations.js";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type IMigration, type IMigrationRunReport, type IStepRunResult } from '../schema/migration.js';
|
|
2
|
+
export interface IApplyMigrationOptions {
|
|
3
|
+
projectRoot: string;
|
|
4
|
+
/** When true, structural rewrites compute but don't write. Shell and
|
|
5
|
+
* check steps are still skipped (not executed). Default false. */
|
|
6
|
+
dryRun?: boolean;
|
|
7
|
+
/** When true, the runner stops at the first failed step. Default true. */
|
|
8
|
+
stopOnFailure?: boolean;
|
|
9
|
+
/** Per-shell timeout in ms. Default 5 * 60 * 1000. */
|
|
10
|
+
shellTimeoutMs?: number;
|
|
11
|
+
/**
|
|
12
|
+
* When true, persist a checkpoint after each step (and the final
|
|
13
|
+
* report) to `.sharkcraft/migrations/<id>.state.json` so
|
|
14
|
+
* `resumeMigration` can pick up where `apply` left off. Default true
|
|
15
|
+
* for non-dry-run, false for dry-run.
|
|
16
|
+
*/
|
|
17
|
+
persistCheckpoints?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Index of the step to start at. Steps before this index are
|
|
20
|
+
* carried over from `priorSteps` with status `applied`. Used by
|
|
21
|
+
* `resumeMigration`; callers don't typically set this directly.
|
|
22
|
+
*/
|
|
23
|
+
resumeFromIndex?: number;
|
|
24
|
+
/** Step results carried over from a prior run (when resuming). */
|
|
25
|
+
priorSteps?: readonly IStepRunResult[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Execute a migration end-to-end. Returns a structured run report;
|
|
29
|
+
* never throws (errors are captured in per-step `status: 'failed'`).
|
|
30
|
+
*
|
|
31
|
+
* `structural-rewrite` steps go through `planRewrite` + `applyRewritePlan`
|
|
32
|
+
* (or stop at plan when `dryRun: true`). `shell` / `check` steps run
|
|
33
|
+
* via `spawnSync(bash -c, ...)`. A `check` step that exits non-zero
|
|
34
|
+
* marks the step as `failed` and (by default) halts the run.
|
|
35
|
+
*/
|
|
36
|
+
export declare function applyMigration(migration: IMigration, options: IApplyMigrationOptions): IMigrationRunReport;
|
|
37
|
+
//# sourceMappingURL=apply-migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-migration.d.ts","sourceRoot":"","sources":["../../src/runner/apply-migration.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACpB,MAAM,wBAAwB,CAAC;AAGhC,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB;sEACkE;IAClE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sDAAsD;IACtD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,UAAU,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CACxC;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,UAAU,EACrB,OAAO,EAAE,sBAAsB,GAC9B,mBAAmB,CAqIrB"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
import { applyRewritePlan, planRewrite } from '@shrkcrft/structural-search';
|
|
4
|
+
import { MIGRATION_RUN_SCHEMA, } from "../schema/migration.js";
|
|
5
|
+
import { MigrationStateStore } from "./state-store.js";
|
|
6
|
+
/**
|
|
7
|
+
* Execute a migration end-to-end. Returns a structured run report;
|
|
8
|
+
* never throws (errors are captured in per-step `status: 'failed'`).
|
|
9
|
+
*
|
|
10
|
+
* `structural-rewrite` steps go through `planRewrite` + `applyRewritePlan`
|
|
11
|
+
* (or stop at plan when `dryRun: true`). `shell` / `check` steps run
|
|
12
|
+
* via `spawnSync(bash -c, ...)`. A `check` step that exits non-zero
|
|
13
|
+
* marks the step as `failed` and (by default) halts the run.
|
|
14
|
+
*/
|
|
15
|
+
export function applyMigration(migration, options) {
|
|
16
|
+
const startedAt = new Date().toISOString();
|
|
17
|
+
const start = Date.now();
|
|
18
|
+
const dryRun = options.dryRun ?? false;
|
|
19
|
+
const stopOnFailure = options.stopOnFailure ?? true;
|
|
20
|
+
const shellTimeoutMs = options.shellTimeoutMs ?? 5 * 60 * 1000;
|
|
21
|
+
const persist = options.persistCheckpoints ?? !dryRun;
|
|
22
|
+
const resumeFromIndex = options.resumeFromIndex ?? 0;
|
|
23
|
+
const priorSteps = options.priorSteps ?? [];
|
|
24
|
+
const store = persist ? new MigrationStateStore(options.projectRoot) : undefined;
|
|
25
|
+
const results = [];
|
|
26
|
+
let halted = false;
|
|
27
|
+
// Carry forward prior successful steps when resuming.
|
|
28
|
+
if (resumeFromIndex > 0 && priorSteps.length > 0) {
|
|
29
|
+
for (const p of priorSteps) {
|
|
30
|
+
if (p.index >= resumeFromIndex)
|
|
31
|
+
break;
|
|
32
|
+
// Re-stamp status as `applied` (in case the prior run halted
|
|
33
|
+
// BEFORE this step's status was updated — shouldn't happen with
|
|
34
|
+
// checkpoints but be defensive).
|
|
35
|
+
results.push(p.status === 'applied' || p.status === 'planned' || p.status === 'skipped'
|
|
36
|
+
? p
|
|
37
|
+
: { ...p, status: 'applied' });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (let i = resumeFromIndex; i < migration.steps.length; i += 1) {
|
|
41
|
+
const step = migration.steps[i];
|
|
42
|
+
const id = step.id ?? `step-${i + 1}`;
|
|
43
|
+
if (halted) {
|
|
44
|
+
results.push({
|
|
45
|
+
index: i,
|
|
46
|
+
id,
|
|
47
|
+
kind: step.kind,
|
|
48
|
+
status: 'skipped',
|
|
49
|
+
message: 'skipped after previous failure',
|
|
50
|
+
durationMs: 0,
|
|
51
|
+
diagnostics: [],
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const stepStart = Date.now();
|
|
56
|
+
if (step.kind === 'structural-rewrite') {
|
|
57
|
+
const plan = planRewrite({
|
|
58
|
+
projectRoot: options.projectRoot,
|
|
59
|
+
pattern: step.pattern,
|
|
60
|
+
recipe: step.recipe,
|
|
61
|
+
});
|
|
62
|
+
const apply = applyRewritePlan(plan, { projectRoot: options.projectRoot, dryRun });
|
|
63
|
+
const failed = apply.conflicts.length > 0;
|
|
64
|
+
results.push({
|
|
65
|
+
index: i,
|
|
66
|
+
id,
|
|
67
|
+
kind: step.kind,
|
|
68
|
+
status: failed ? 'failed' : (dryRun ? 'planned' : 'applied'),
|
|
69
|
+
message: failed
|
|
70
|
+
? `${apply.conflicts.length} conflict(s) — file content drifted`
|
|
71
|
+
: `${apply.filesChanged} file(s) ${dryRun ? 'would be ' : ''}changed, ${plan.totalEdits} edit(s)`,
|
|
72
|
+
durationMs: Date.now() - stepStart,
|
|
73
|
+
rewriteStats: {
|
|
74
|
+
filesScanned: plan.filesScanned,
|
|
75
|
+
filesAttempted: apply.filesAttempted,
|
|
76
|
+
filesChanged: apply.filesChanged,
|
|
77
|
+
totalEdits: plan.totalEdits,
|
|
78
|
+
conflicts: apply.conflicts,
|
|
79
|
+
},
|
|
80
|
+
diagnostics: [...plan.diagnostics, ...apply.diagnostics],
|
|
81
|
+
});
|
|
82
|
+
if (failed && stopOnFailure)
|
|
83
|
+
halted = true;
|
|
84
|
+
}
|
|
85
|
+
else if (step.kind === 'shell' || step.kind === 'check') {
|
|
86
|
+
if (dryRun) {
|
|
87
|
+
results.push({
|
|
88
|
+
index: i,
|
|
89
|
+
id,
|
|
90
|
+
kind: step.kind,
|
|
91
|
+
status: 'planned',
|
|
92
|
+
message: `${step.kind} (dry-run): ${step.command}`,
|
|
93
|
+
durationMs: Date.now() - stepStart,
|
|
94
|
+
diagnostics: [],
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const cwd = step.cwd
|
|
99
|
+
? nodePath.resolve(options.projectRoot, step.cwd)
|
|
100
|
+
: options.projectRoot;
|
|
101
|
+
const res = spawnSync('bash', ['-c', step.command], {
|
|
102
|
+
cwd,
|
|
103
|
+
encoding: 'utf8',
|
|
104
|
+
timeout: shellTimeoutMs,
|
|
105
|
+
});
|
|
106
|
+
const exitCode = res.status ?? -1;
|
|
107
|
+
const failed = exitCode !== 0;
|
|
108
|
+
results.push({
|
|
109
|
+
index: i,
|
|
110
|
+
id,
|
|
111
|
+
kind: step.kind,
|
|
112
|
+
status: failed ? 'failed' : 'applied',
|
|
113
|
+
message: failed
|
|
114
|
+
? `exit ${exitCode}: ${oneLine(res.stderr ?? '') || step.command}`
|
|
115
|
+
: `exit 0: ${step.command}`,
|
|
116
|
+
durationMs: Date.now() - stepStart,
|
|
117
|
+
shellOutput: {
|
|
118
|
+
exitCode,
|
|
119
|
+
stdout: (res.stdout ?? '').slice(0, 8000),
|
|
120
|
+
stderr: (res.stderr ?? '').slice(0, 8000),
|
|
121
|
+
},
|
|
122
|
+
diagnostics: [],
|
|
123
|
+
});
|
|
124
|
+
if (step.kind === 'check' && failed && stopOnFailure)
|
|
125
|
+
halted = true;
|
|
126
|
+
}
|
|
127
|
+
if (store) {
|
|
128
|
+
// Per-step checkpoint so a crashing runner / killed process can
|
|
129
|
+
// still be resumed from the right point.
|
|
130
|
+
store.write(migration.id, buildPartialReport(migration, dryRun, startedAt, start, results));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const overall = results.some((r) => r.status === 'failed')
|
|
134
|
+
? 'fail'
|
|
135
|
+
: results.some((r) => r.status === 'applied' || r.status === 'planned')
|
|
136
|
+
? 'pass'
|
|
137
|
+
: 'skipped';
|
|
138
|
+
const report = {
|
|
139
|
+
schema: MIGRATION_RUN_SCHEMA,
|
|
140
|
+
migration: { id: migration.id, title: migration.title },
|
|
141
|
+
dryRun,
|
|
142
|
+
startedAt,
|
|
143
|
+
totalDurationMs: Date.now() - start,
|
|
144
|
+
overall,
|
|
145
|
+
steps: results,
|
|
146
|
+
};
|
|
147
|
+
if (store)
|
|
148
|
+
store.write(migration.id, report);
|
|
149
|
+
return report;
|
|
150
|
+
}
|
|
151
|
+
function oneLine(s) {
|
|
152
|
+
return s.replace(/\s+/g, ' ').trim().slice(0, 160);
|
|
153
|
+
}
|
|
154
|
+
function buildPartialReport(migration, dryRun, startedAt, start, results) {
|
|
155
|
+
const overall = results.some((r) => r.status === 'failed')
|
|
156
|
+
? 'fail'
|
|
157
|
+
: results.some((r) => r.status === 'applied' || r.status === 'planned')
|
|
158
|
+
? 'pass'
|
|
159
|
+
: 'skipped';
|
|
160
|
+
return {
|
|
161
|
+
schema: MIGRATION_RUN_SCHEMA,
|
|
162
|
+
migration: { id: migration.id, title: migration.title },
|
|
163
|
+
dryRun,
|
|
164
|
+
startedAt,
|
|
165
|
+
totalDurationMs: Date.now() - start,
|
|
166
|
+
overall,
|
|
167
|
+
steps: results,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface IPruneOptions {
|
|
2
|
+
projectRoot: string;
|
|
3
|
+
/** Minimum age in days for a state file to be eligible. Default 30. */
|
|
4
|
+
olderThanDays?: number;
|
|
5
|
+
/**
|
|
6
|
+
* When true, also prune state files where the last recorded run is
|
|
7
|
+
* a `fail`. Default false — failed migrations are typically what the
|
|
8
|
+
* user wants to keep so they can `resume`.
|
|
9
|
+
*/
|
|
10
|
+
includeFailed?: boolean;
|
|
11
|
+
/** When true, list what would be deleted but don't touch disk. */
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface IPrunedEntry {
|
|
15
|
+
id: string;
|
|
16
|
+
startedAt: string;
|
|
17
|
+
overall: 'pass' | 'fail' | 'skipped';
|
|
18
|
+
ageDays: number;
|
|
19
|
+
reason: 'older-than' | 'failed-included';
|
|
20
|
+
}
|
|
21
|
+
export interface IPruneResult {
|
|
22
|
+
schema: 'sharkcraft.migration-prune/v1';
|
|
23
|
+
/** Total state files scanned. */
|
|
24
|
+
scanned: number;
|
|
25
|
+
/** Files matching the eligibility criteria. */
|
|
26
|
+
eligible: number;
|
|
27
|
+
/** Files actually removed (0 in dry-run). */
|
|
28
|
+
removed: number;
|
|
29
|
+
dryRun: boolean;
|
|
30
|
+
entries: readonly IPrunedEntry[];
|
|
31
|
+
diagnostics: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Prune `.sharkcraft/migrations/*.state.json` files older than
|
|
35
|
+
* `olderThanDays`. Used to keep the dashboard's Migrations panel
|
|
36
|
+
* focused on recent activity.
|
|
37
|
+
*
|
|
38
|
+
* By default skips `overall: 'fail'` entries — those are kept so the
|
|
39
|
+
* user can `shrk migrate resume`. Pass `includeFailed: true` to clear
|
|
40
|
+
* those too (typical after a project-wide cleanup).
|
|
41
|
+
*/
|
|
42
|
+
export declare function pruneMigrations(options: IPruneOptions): IPruneResult;
|
|
43
|
+
//# sourceMappingURL=prune-migrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prune-migrations.d.ts","sourceRoot":"","sources":["../../src/runner/prune-migrations.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kEAAkE;IAClE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,YAAY,GAAG,iBAAiB,CAAC;CAC1C;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,+BAA+B,CAAC;IACxC,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IACjC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CA4FpE"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { existsSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
import { MigrationStateStore } from "./state-store.js";
|
|
4
|
+
/**
|
|
5
|
+
* Prune `.sharkcraft/migrations/*.state.json` files older than
|
|
6
|
+
* `olderThanDays`. Used to keep the dashboard's Migrations panel
|
|
7
|
+
* focused on recent activity.
|
|
8
|
+
*
|
|
9
|
+
* By default skips `overall: 'fail'` entries — those are kept so the
|
|
10
|
+
* user can `shrk migrate resume`. Pass `includeFailed: true` to clear
|
|
11
|
+
* those too (typical after a project-wide cleanup).
|
|
12
|
+
*/
|
|
13
|
+
export function pruneMigrations(options) {
|
|
14
|
+
const olderThanDays = options.olderThanDays ?? 30;
|
|
15
|
+
const includeFailed = options.includeFailed ?? false;
|
|
16
|
+
const dryRun = options.dryRun ?? false;
|
|
17
|
+
const diagnostics = [];
|
|
18
|
+
const dir = nodePath.join(options.projectRoot, '.sharkcraft', 'migrations');
|
|
19
|
+
if (!existsSync(dir)) {
|
|
20
|
+
return {
|
|
21
|
+
schema: 'sharkcraft.migration-prune/v1',
|
|
22
|
+
scanned: 0,
|
|
23
|
+
eligible: 0,
|
|
24
|
+
removed: 0,
|
|
25
|
+
dryRun,
|
|
26
|
+
entries: [],
|
|
27
|
+
diagnostics: ['no .sharkcraft/migrations/ directory'],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const store = new MigrationStateStore(options.projectRoot);
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
let scanned = 0;
|
|
33
|
+
const eligibleEntries = [];
|
|
34
|
+
let entries;
|
|
35
|
+
try {
|
|
36
|
+
entries = readdirSync(dir);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
diagnostics.push(`readdir failed: ${e.message}`);
|
|
40
|
+
entries = [];
|
|
41
|
+
}
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (!entry.endsWith('.state.json'))
|
|
44
|
+
continue;
|
|
45
|
+
scanned += 1;
|
|
46
|
+
const abs = nodePath.join(dir, entry);
|
|
47
|
+
let report;
|
|
48
|
+
try {
|
|
49
|
+
report = store.read(entry.replace(/\.state\.json$/, ''));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
report = undefined;
|
|
53
|
+
}
|
|
54
|
+
let startedAt;
|
|
55
|
+
let ageMs;
|
|
56
|
+
if (report) {
|
|
57
|
+
startedAt = report.startedAt;
|
|
58
|
+
ageMs = now - Date.parse(report.startedAt);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Corrupted state file — fall back to mtime.
|
|
62
|
+
try {
|
|
63
|
+
const st = statSync(abs);
|
|
64
|
+
startedAt = new Date(st.mtimeMs).toISOString();
|
|
65
|
+
ageMs = now - st.mtimeMs;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
diagnostics.push(`could not stat ${abs}`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (Number.isNaN(ageMs))
|
|
73
|
+
continue;
|
|
74
|
+
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
75
|
+
const isFailed = report?.overall === 'fail';
|
|
76
|
+
let reason;
|
|
77
|
+
if (ageDays >= olderThanDays) {
|
|
78
|
+
if (isFailed && !includeFailed)
|
|
79
|
+
continue;
|
|
80
|
+
reason = ageDays >= olderThanDays ? 'older-than' : undefined;
|
|
81
|
+
if (isFailed && includeFailed)
|
|
82
|
+
reason = 'failed-included';
|
|
83
|
+
}
|
|
84
|
+
if (!reason)
|
|
85
|
+
continue;
|
|
86
|
+
eligibleEntries.push({
|
|
87
|
+
id: report?.migration.id ?? entry.replace(/\.state\.json$/, ''),
|
|
88
|
+
startedAt,
|
|
89
|
+
overall: report?.overall ?? 'skipped',
|
|
90
|
+
ageDays: Math.floor(ageDays * 10) / 10,
|
|
91
|
+
reason,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
let removed = 0;
|
|
95
|
+
if (!dryRun) {
|
|
96
|
+
for (const e of eligibleEntries) {
|
|
97
|
+
try {
|
|
98
|
+
rmSync(nodePath.join(dir, `${e.id}.state.json`));
|
|
99
|
+
removed += 1;
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
diagnostics.push(`rm failed for ${e.id}: ${err.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
schema: 'sharkcraft.migration-prune/v1',
|
|
108
|
+
scanned,
|
|
109
|
+
eligible: eligibleEntries.length,
|
|
110
|
+
removed,
|
|
111
|
+
dryRun,
|
|
112
|
+
entries: eligibleEntries,
|
|
113
|
+
diagnostics,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { IMigration, IMigrationRunReport } from '../schema/migration.js';
|
|
2
|
+
export interface IResumeMigrationOptions {
|
|
3
|
+
projectRoot: string;
|
|
4
|
+
/** Forwarded to applyMigration. */
|
|
5
|
+
dryRun?: boolean;
|
|
6
|
+
stopOnFailure?: boolean;
|
|
7
|
+
shellTimeoutMs?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface IResumeMigrationResult {
|
|
10
|
+
/** Final run report after the resume. */
|
|
11
|
+
report: IMigrationRunReport;
|
|
12
|
+
/** Step index the resume started at. */
|
|
13
|
+
resumedFromIndex: number;
|
|
14
|
+
/** Free-form diagnostics about the resume decision. */
|
|
15
|
+
diagnostics: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Pick up a previously-failed migration from the step that failed and
|
|
19
|
+
* continue forward. Reads the saved state at
|
|
20
|
+
* `.sharkcraft/migrations/<id>.state.json` (written by `applyMigration`
|
|
21
|
+
* after each step) and dispatches a fresh `applyMigration` call with
|
|
22
|
+
* `resumeFromIndex` set.
|
|
23
|
+
*
|
|
24
|
+
* Returns a diagnostic and skips re-running when no resume point is
|
|
25
|
+
* found (everything already applied, or no saved state at all).
|
|
26
|
+
*/
|
|
27
|
+
export declare function resumeMigration(migration: IMigration, options: IResumeMigrationOptions): IResumeMigrationResult;
|
|
28
|
+
//# sourceMappingURL=resume-migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resume-migration.d.ts","sourceRoot":"","sources":["../../src/runner/resume-migration.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE9E,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,yCAAyC;IACzC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,wCAAwC;IACxC,gBAAgB,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,UAAU,EACrB,OAAO,EAAE,uBAAuB,GAC/B,sBAAsB,CAqBxB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { applyMigration } from "./apply-migration.js";
|
|
2
|
+
import { findResumePoint, MigrationStateStore } from "./state-store.js";
|
|
3
|
+
/**
|
|
4
|
+
* Pick up a previously-failed migration from the step that failed and
|
|
5
|
+
* continue forward. Reads the saved state at
|
|
6
|
+
* `.sharkcraft/migrations/<id>.state.json` (written by `applyMigration`
|
|
7
|
+
* after each step) and dispatches a fresh `applyMigration` call with
|
|
8
|
+
* `resumeFromIndex` set.
|
|
9
|
+
*
|
|
10
|
+
* Returns a diagnostic and skips re-running when no resume point is
|
|
11
|
+
* found (everything already applied, or no saved state at all).
|
|
12
|
+
*/
|
|
13
|
+
export function resumeMigration(migration, options) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
const store = new MigrationStateStore(options.projectRoot);
|
|
16
|
+
const prior = store.read(migration.id);
|
|
17
|
+
if (!prior) {
|
|
18
|
+
diagnostics.push(`no saved state for migration "${migration.id}" — running from the beginning.`);
|
|
19
|
+
const fresh = applyMigration(migration, applyOpts(options));
|
|
20
|
+
return { report: fresh, resumedFromIndex: 0, diagnostics };
|
|
21
|
+
}
|
|
22
|
+
const resumePoint = findResumePoint(prior);
|
|
23
|
+
if (resumePoint === undefined) {
|
|
24
|
+
diagnostics.push(`migration "${migration.id}" already complete; nothing to resume.`);
|
|
25
|
+
return { report: prior, resumedFromIndex: prior.steps.length, diagnostics };
|
|
26
|
+
}
|
|
27
|
+
diagnostics.push(`resuming from step ${resumePoint + 1} (${migration.steps[resumePoint]?.id ?? 'unknown'}).`);
|
|
28
|
+
const report = applyMigration(migration, {
|
|
29
|
+
...applyOpts(options),
|
|
30
|
+
resumeFromIndex: resumePoint,
|
|
31
|
+
priorSteps: prior.steps.slice(0, resumePoint),
|
|
32
|
+
});
|
|
33
|
+
return { report, resumedFromIndex: resumePoint, diagnostics };
|
|
34
|
+
}
|
|
35
|
+
function applyOpts(options) {
|
|
36
|
+
return {
|
|
37
|
+
projectRoot: options.projectRoot,
|
|
38
|
+
...(options.dryRun !== undefined ? { dryRun: options.dryRun } : {}),
|
|
39
|
+
...(options.stopOnFailure !== undefined ? { stopOnFailure: options.stopOnFailure } : {}),
|
|
40
|
+
...(options.shellTimeoutMs !== undefined ? { shellTimeoutMs: options.shellTimeoutMs } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IMigrationRunReport } from '../schema/migration.js';
|
|
2
|
+
/**
|
|
3
|
+
* Persist per-migration run state under `.sharkcraft/migrations/`. The
|
|
4
|
+
* checkpoint is the full `IMigrationRunReport` so far — `resumeMigration`
|
|
5
|
+
* reads it back, finds the last failed step, and continues from there.
|
|
6
|
+
*/
|
|
7
|
+
export declare class MigrationStateStore {
|
|
8
|
+
private readonly projectRoot;
|
|
9
|
+
readonly dir: string;
|
|
10
|
+
constructor(projectRoot: string);
|
|
11
|
+
pathFor(migrationId: string): string;
|
|
12
|
+
exists(migrationId: string): boolean;
|
|
13
|
+
read(migrationId: string): IMigrationRunReport | undefined;
|
|
14
|
+
write(migrationId: string, report: IMigrationRunReport): void;
|
|
15
|
+
clear(migrationId: string): void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Find the index of the first step the resume runner should re-execute.
|
|
19
|
+
*
|
|
20
|
+
* - First failed step → resume from there.
|
|
21
|
+
* - No failures + steps remaining → resume from the first skipped /
|
|
22
|
+
* pending step.
|
|
23
|
+
* - All steps applied → undefined (nothing to resume).
|
|
24
|
+
*/
|
|
25
|
+
export declare function findResumePoint(report: IMigrationRunReport): number | undefined;
|
|
26
|
+
//# sourceMappingURL=state-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-store.d.ts","sourceRoot":"","sources":["../../src/runner/state-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAIlE;;;;GAIG;AACH,qBAAa,mBAAmB;IAGlB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAFxC,SAAgB,GAAG,EAAE,MAAM,CAAC;gBAEC,WAAW,EAAE,MAAM;IAIhD,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAIpC,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAIpC,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAS1D,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAK7D,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;CAGjC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS,CAQ/E"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
const DIR = '.sharkcraft/migrations';
|
|
4
|
+
/**
|
|
5
|
+
* Persist per-migration run state under `.sharkcraft/migrations/`. The
|
|
6
|
+
* checkpoint is the full `IMigrationRunReport` so far — `resumeMigration`
|
|
7
|
+
* reads it back, finds the last failed step, and continues from there.
|
|
8
|
+
*/
|
|
9
|
+
export class MigrationStateStore {
|
|
10
|
+
projectRoot;
|
|
11
|
+
dir;
|
|
12
|
+
constructor(projectRoot) {
|
|
13
|
+
this.projectRoot = projectRoot;
|
|
14
|
+
this.dir = nodePath.join(projectRoot, DIR);
|
|
15
|
+
}
|
|
16
|
+
pathFor(migrationId) {
|
|
17
|
+
return nodePath.join(this.dir, `${migrationId}.state.json`);
|
|
18
|
+
}
|
|
19
|
+
exists(migrationId) {
|
|
20
|
+
return existsSync(this.pathFor(migrationId));
|
|
21
|
+
}
|
|
22
|
+
read(migrationId) {
|
|
23
|
+
if (!this.exists(migrationId))
|
|
24
|
+
return undefined;
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(readFileSync(this.pathFor(migrationId), 'utf8'));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
write(migrationId, report) {
|
|
33
|
+
mkdirSync(this.dir, { recursive: true });
|
|
34
|
+
writeFileSync(this.pathFor(migrationId), JSON.stringify(report, null, 2), 'utf8');
|
|
35
|
+
}
|
|
36
|
+
clear(migrationId) {
|
|
37
|
+
if (this.exists(migrationId))
|
|
38
|
+
rmSync(this.pathFor(migrationId));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Find the index of the first step the resume runner should re-execute.
|
|
43
|
+
*
|
|
44
|
+
* - First failed step → resume from there.
|
|
45
|
+
* - No failures + steps remaining → resume from the first skipped /
|
|
46
|
+
* pending step.
|
|
47
|
+
* - All steps applied → undefined (nothing to resume).
|
|
48
|
+
*/
|
|
49
|
+
export function findResumePoint(report) {
|
|
50
|
+
for (const s of report.steps) {
|
|
51
|
+
if (s.status === 'failed')
|
|
52
|
+
return s.index;
|
|
53
|
+
}
|
|
54
|
+
for (const s of report.steps) {
|
|
55
|
+
if (s.status === 'skipped' || s.status === 'pending')
|
|
56
|
+
return s.index;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { RewriteRecipe, StructuralPattern } from '@shrkcrft/structural-search';
|
|
2
|
+
export declare const MIGRATION_SCHEMA: "sharkcraft.migration/v1";
|
|
3
|
+
export declare const MIGRATION_RUN_SCHEMA: "sharkcraft.migration-run/v1";
|
|
4
|
+
/**
|
|
5
|
+
* Ordered list of steps. Each step is one of:
|
|
6
|
+
*
|
|
7
|
+
* - `structural-rewrite`: pair of (pattern, recipe) consumed by
|
|
8
|
+
* `@shrkcrft/structural-search`. The most common step kind.
|
|
9
|
+
* - `shell`: a literal shell command. Useful for `bun install`,
|
|
10
|
+
* `bun run build`, version bumps, etc.
|
|
11
|
+
* - `check`: a CLI command whose exit code gates the migration.
|
|
12
|
+
* Same as `shell` but the runner records a pass/fail per step.
|
|
13
|
+
*/
|
|
14
|
+
export type MigrationStep = {
|
|
15
|
+
kind: 'structural-rewrite';
|
|
16
|
+
id?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
pattern: StructuralPattern;
|
|
19
|
+
recipe: RewriteRecipe;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'shell';
|
|
22
|
+
id?: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
/** Shell command line. Run via bash -c. */
|
|
25
|
+
command: string;
|
|
26
|
+
/** Working directory relative to project root. */
|
|
27
|
+
cwd?: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: 'check';
|
|
30
|
+
id?: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
command: string;
|
|
33
|
+
cwd?: string;
|
|
34
|
+
};
|
|
35
|
+
export interface IMigration {
|
|
36
|
+
schema: typeof MIGRATION_SCHEMA;
|
|
37
|
+
/** Stable migration id (used to look it up on disk). */
|
|
38
|
+
id: string;
|
|
39
|
+
/** Display title. */
|
|
40
|
+
title: string;
|
|
41
|
+
/** Optional human description. */
|
|
42
|
+
description?: string;
|
|
43
|
+
/** Steps run in this order. */
|
|
44
|
+
steps: readonly MigrationStep[];
|
|
45
|
+
}
|
|
46
|
+
export type StepStatus = 'pending' | 'planned' | 'applied' | 'failed' | 'skipped';
|
|
47
|
+
export interface IStepRunResult {
|
|
48
|
+
/** Index in the migration's steps array. */
|
|
49
|
+
index: number;
|
|
50
|
+
/** Step id (defaults to `step-<index>`). */
|
|
51
|
+
id: string;
|
|
52
|
+
kind: MigrationStep['kind'];
|
|
53
|
+
status: StepStatus;
|
|
54
|
+
/** Human-readable headline. */
|
|
55
|
+
message: string;
|
|
56
|
+
/** Wall-clock duration of the step, in ms. */
|
|
57
|
+
durationMs: number;
|
|
58
|
+
/** For structural-rewrite steps: counts of files / edits. */
|
|
59
|
+
rewriteStats?: {
|
|
60
|
+
filesScanned: number;
|
|
61
|
+
filesAttempted: number;
|
|
62
|
+
filesChanged: number;
|
|
63
|
+
totalEdits: number;
|
|
64
|
+
conflicts: readonly string[];
|
|
65
|
+
};
|
|
66
|
+
/** For shell / check steps: captured stdout + exit code. */
|
|
67
|
+
shellOutput?: {
|
|
68
|
+
exitCode: number;
|
|
69
|
+
stdout: string;
|
|
70
|
+
stderr: string;
|
|
71
|
+
};
|
|
72
|
+
/** Free-form diagnostics from the step. */
|
|
73
|
+
diagnostics: readonly string[];
|
|
74
|
+
}
|
|
75
|
+
export interface IMigrationRunReport {
|
|
76
|
+
schema: typeof MIGRATION_RUN_SCHEMA;
|
|
77
|
+
migration: {
|
|
78
|
+
id: string;
|
|
79
|
+
title: string;
|
|
80
|
+
};
|
|
81
|
+
/** True for the `plan` flow (no fs writes); false for `apply`. */
|
|
82
|
+
dryRun: boolean;
|
|
83
|
+
startedAt: string;
|
|
84
|
+
totalDurationMs: number;
|
|
85
|
+
/** Overall status: `pass` if every step is `applied` (or `skipped`);
|
|
86
|
+
* `fail` if any step failed. */
|
|
87
|
+
overall: 'pass' | 'fail' | 'skipped';
|
|
88
|
+
steps: readonly IStepRunResult[];
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../../src/schema/migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEpF,eAAO,MAAM,gBAAgB,EAAG,yBAAkC,CAAC;AACnE,eAAO,MAAM,oBAAoB,EAAG,6BAAsC,CAAC;AAE3E;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GACrB;IACE,IAAI,EAAE,oBAAoB,CAAC;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,MAAM,EAAE,aAAa,CAAC;CACvB,GACD;IACE,IAAI,EAAE,OAAO,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GACD;IACE,IAAI,EAAE,OAAO,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEN,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,OAAO,gBAAgB,CAAC;IAChC,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;CACjC;AAED,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAElF,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5B,MAAM,EAAE,UAAU,CAAC;IACnB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,YAAY,CAAC,EAAE;QACb,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;KAC9B,CAAC;IACF,4DAA4D;IAC5D,WAAW,CAAC,EAAE;QACZ,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,2CAA2C;IAC3C,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,OAAO,oBAAoB,CAAC;IACpC,SAAS,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,kEAAkE;IAClE,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB;oCACgC;IAChC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;CAClC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shrkcrft/migrate",
|
|
3
|
+
"version": "0.1.0-alpha.10",
|
|
4
|
+
"description": "SharkCraft migrations: orchestrate multi-step refactors (structural rewrites + shell + checkpoints) as one replayable migration. The safe path for big cross-cutting refactors.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "SharkCraft contributors",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/sharkcraft/sharkcraft.git",
|
|
25
|
+
"directory": "packages/migrate"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/sharkcraft/sharkcraft",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/sharkcraft/sharkcraft/issues"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"sharkcraft",
|
|
33
|
+
"migration",
|
|
34
|
+
"codemod",
|
|
35
|
+
"orchestration"
|
|
36
|
+
],
|
|
37
|
+
"engines": {
|
|
38
|
+
"bun": ">=1.1.0",
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@shrkcrft/core": "^0.1.0-alpha.10",
|
|
46
|
+
"@shrkcrft/structural-search": "^0.1.0-alpha.10"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
51
|
+
}
|