dsh-config-manager 0.1.12 → 0.1.13
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/README.md +30 -0
- package/README.zh-CN.md +30 -0
- package/lib/adapters/test-helpers.d.ts +4 -2
- package/lib/adapters/test-helpers.js +11 -3
- package/lib/adapters/test-helpers.js.map +1 -1
- package/lib/cli/index.d.ts +44 -0
- package/lib/cli/index.js +230 -0
- package/lib/cli/index.js.map +1 -0
- package/lib/client.d.ts +260 -5
- package/lib/client.js +2519 -820
- package/lib/client.js.map +1 -1
- package/lib/core/analyzer.d.ts +18 -1
- package/lib/core/analyzer.js +29 -0
- package/lib/core/analyzer.js.map +1 -1
- package/lib/core/backup.d.ts +3 -1
- package/lib/core/backup.js +70 -0
- package/lib/core/backup.js.map +1 -1
- package/lib/core/exporter.d.ts +11 -0
- package/lib/core/exporter.js +11 -0
- package/lib/core/exporter.js.map +1 -1
- package/lib/core/importer.d.ts +3 -0
- package/lib/core/importer.js +1 -0
- package/lib/core/importer.js.map +1 -1
- package/lib/core/index.d.ts +1 -0
- package/lib/core/index.js +1 -0
- package/lib/core/index.js.map +1 -1
- package/lib/core/restore.d.ts +92 -0
- package/lib/core/restore.js +505 -0
- package/lib/core/restore.js.map +1 -0
- package/lib/core/run-registry.d.ts +65 -0
- package/lib/core/run-registry.js +120 -0
- package/lib/core/run-registry.js.map +1 -0
- package/lib/core/types.d.ts +20 -0
- package/lib/core/types.js.map +1 -1
- package/lib/index.d.ts +45 -0
- package/lib/index.js +442 -8
- package/lib/index.js.map +1 -1
- package/lib/sync/fs.d.ts +18 -0
- package/lib/sync/fs.js +47 -0
- package/lib/sync/fs.js.map +1 -0
- package/lib/sync/git/git-transport.d.ts +87 -0
- package/lib/sync/git/git-transport.js +333 -0
- package/lib/sync/git/git-transport.js.map +1 -0
- package/lib/sync/layout.d.ts +29 -0
- package/lib/sync/layout.js +166 -0
- package/lib/sync/layout.js.map +1 -0
- package/lib/sync/sync-config.d.ts +16 -0
- package/lib/sync/sync-config.js +67 -0
- package/lib/sync/sync-config.js.map +1 -0
- package/lib/sync/sync-engine.d.ts +96 -0
- package/lib/sync/sync-engine.js +236 -0
- package/lib/sync/sync-engine.js.map +1 -0
- package/lib/sync/sync-state.d.ts +31 -0
- package/lib/sync/sync-state.js +103 -0
- package/lib/sync/sync-state.js.map +1 -0
- package/lib/sync/transport.d.ts +50 -0
- package/lib/sync/transport.js +22 -0
- package/lib/sync/transport.js.map +1 -0
- package/package.json +4 -1
- package/src/adapters/test-helpers.ts +10 -3
- package/src/cli/index.ts +278 -0
- package/src/client/ConfigManagerSection.tsx +49 -14
- package/src/client/api.ts +53 -5
- package/src/client/client-types.ts +0 -6
- package/src/client/common/ProgressBar.tsx +31 -19
- package/src/client/common/progress-view-edge.test.ts +60 -0
- package/src/client/common/progress-view.test.ts +79 -0
- package/src/client/common/progress-view.ts +94 -0
- package/src/client/config-manager.module.css +99 -20
- package/src/client/export/ExportView.tsx +63 -49
- package/src/client/import/ImportWizardView.tsx +107 -66
- package/src/client/index.ts +22 -0
- package/src/client/locales.ts +53 -0
- package/src/client/run-store.test.ts +500 -0
- package/src/client/run-store.ts +772 -0
- package/src/client/snapshots/SnapshotsPanel.tsx +240 -0
- package/src/client/sync/SyncSettingsView.tsx +261 -0
- package/src/client/sync/sync-api.test.ts +117 -0
- package/src/client/sync/sync-api.ts +132 -0
- package/src/client/sync/sync-locales.ts +77 -0
- package/src/client/sync/sync-view.test.ts +211 -0
- package/src/client/sync/sync-view.ts +202 -0
- package/src/core/analyzer.ts +50 -1
- package/src/core/backup.ts +78 -2
- package/src/core/exporter.ts +21 -0
- package/src/core/importer.ts +4 -0
- package/src/core/index.ts +5 -0
- package/src/core/restore.ts +587 -0
- package/src/core/run-progress.test.ts +237 -0
- package/src/core/run-registry.test.ts +139 -0
- package/src/core/run-registry.ts +166 -0
- package/src/core/smoke.test.ts +5 -0
- package/src/core/types.ts +22 -0
- package/src/index.ts +476 -8
- package/src/security/security.test.ts +5 -0
- package/src/sync/fs.ts +46 -0
- package/src/sync/git/git-transport.test.ts +456 -0
- package/src/sync/git/git-transport.ts +390 -0
- package/src/sync/layout.test.ts +226 -0
- package/src/sync/layout.ts +183 -0
- package/src/sync/sync-config.ts +72 -0
- package/src/sync/sync-engine.test.ts +299 -0
- package/src/sync/sync-engine.ts +318 -0
- package/src/sync/sync-state.test.ts +118 -0
- package/src/sync/sync-state.ts +123 -0
- package/src/sync/transport.test.ts +110 -0
- package/src/sync/transport.ts +73 -0
package/src/core/analyzer.ts
CHANGED
|
@@ -67,6 +67,19 @@ interface AnalyzedBundle extends Bundle {
|
|
|
67
67
|
adapterIssues: string[];
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/** m1:每完成一个计划项的进度回调信息(Host 侧 run 状态更新用) */
|
|
71
|
+
export interface PlanItemProgress {
|
|
72
|
+
adapter: SectionId;
|
|
73
|
+
/** 已处理计划项序号(1 起,含 skip/warning 信息项) */
|
|
74
|
+
index: number;
|
|
75
|
+
/** 将实际执行的计划项总数(APPLY_ORDER 内各项合计) */
|
|
76
|
+
total: number;
|
|
77
|
+
/** 该项最终状态(ok/skipped/warning/failed) */
|
|
78
|
+
status?: ExecutedItem['status'];
|
|
79
|
+
/** 当前计划项 id(非敏感) */
|
|
80
|
+
detail?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
export class Analyzer {
|
|
71
84
|
private readonly ctx: HostContext;
|
|
72
85
|
private readonly adapters: ConfigAdapter[];
|
|
@@ -361,7 +374,14 @@ export class Analyzer {
|
|
|
361
374
|
async executeImportPlan(
|
|
362
375
|
zipPath: string,
|
|
363
376
|
plan: ImportPlan,
|
|
364
|
-
opts: {
|
|
377
|
+
opts: {
|
|
378
|
+
confirm?: boolean;
|
|
379
|
+
secretInputs?: Record<string, string>;
|
|
380
|
+
decryptedCredentials?: Map<string, string>;
|
|
381
|
+
rollbackOnError?: boolean;
|
|
382
|
+
/** m1:每完成一个计划项调用(真实进度埋点;不传则无埋点) */
|
|
383
|
+
onItem?: (info: PlanItemProgress) => void;
|
|
384
|
+
} = {},
|
|
365
385
|
): Promise<ImportResult> {
|
|
366
386
|
const bundle = await this.loadBundle(zipPath);
|
|
367
387
|
const analyzed = await this.analyzeBundle(bundle);
|
|
@@ -405,12 +425,27 @@ export class Analyzer {
|
|
|
405
425
|
list.push(item);
|
|
406
426
|
byAdapter.set(item.adapter, list);
|
|
407
427
|
}
|
|
428
|
+
const totalItems = APPLY_ORDER.reduce((sum, id) => sum + (byAdapter.get(id)?.length ?? 0), 0);
|
|
429
|
+
let itemIndex = 0;
|
|
408
430
|
|
|
409
431
|
for (const adapterId of APPLY_ORDER) {
|
|
410
432
|
const adapter = this.adapters.find((a) => a.id === adapterId);
|
|
411
433
|
if (!adapter) continue;
|
|
412
434
|
for (const item of byAdapter.get(adapterId) ?? []) {
|
|
413
435
|
const outcome = await this.applyOne(adapter, item, importCtx);
|
|
436
|
+
// m1 埋点:每完成一个计划项上报(真实进度;onItem 抛错不得中断导入)
|
|
437
|
+
itemIndex += 1;
|
|
438
|
+
try {
|
|
439
|
+
opts.onItem?.({
|
|
440
|
+
adapter: item.adapter,
|
|
441
|
+
index: itemIndex,
|
|
442
|
+
total: totalItems,
|
|
443
|
+
status: outcome.executed.status,
|
|
444
|
+
detail: item.id,
|
|
445
|
+
});
|
|
446
|
+
} catch {
|
|
447
|
+
// 埋点回调失败不影响导入执行(进度是尽力而为)
|
|
448
|
+
}
|
|
414
449
|
executed.push(outcome.executed);
|
|
415
450
|
// 仅硬失败计入 anyFailed(warning 属非致命:目标不可达等,不触发回滚,§34.17)
|
|
416
451
|
if (outcome.executed.status === 'failed') anyFailed = true;
|
|
@@ -429,6 +464,8 @@ export class Analyzer {
|
|
|
429
464
|
store: this.snapshotStore,
|
|
430
465
|
adapters: this.adapters,
|
|
431
466
|
});
|
|
467
|
+
// M1:回滚完成 → 快照标记 rolled-back(元数据写失败只告警,不影响回滚结论)
|
|
468
|
+
await this.markSnapshotStatus(snapshot.id, 'rolled-back');
|
|
432
469
|
this.ctx.log.warn(`导入失败,已回滚(${rollbackReport.full ? '完整' : '部分'})`, {
|
|
433
470
|
failed: executed.filter((e) => e.status === 'failed').map((e) => e.itemId),
|
|
434
471
|
});
|
|
@@ -462,6 +499,9 @@ export class Analyzer {
|
|
|
462
499
|
.filter((s) => !importCtx.decryptedCredentials?.has(s.ref) && !importCtx.secretInputs[s.ref])
|
|
463
500
|
.map((s) => s.ref);
|
|
464
501
|
|
|
502
|
+
// M1:导入成功 → 快照标记 done(元数据写失败只告警,不改变导入结论)
|
|
503
|
+
await this.markSnapshotStatus(snapshot.id, 'done');
|
|
504
|
+
|
|
465
505
|
return {
|
|
466
506
|
ok: true, // 单项失败已如实记录在 executed;无未捕获异常即完成
|
|
467
507
|
executed,
|
|
@@ -473,6 +513,15 @@ export class Analyzer {
|
|
|
473
513
|
};
|
|
474
514
|
}
|
|
475
515
|
|
|
516
|
+
/** 快照状态标记(M1):成功→done / 失败回滚→rolled-back。元数据写失败只告警不抛错。 */
|
|
517
|
+
private async markSnapshotStatus(id: string, status: 'done' | 'rolled-back'): Promise<void> {
|
|
518
|
+
try {
|
|
519
|
+
await this.snapshotStore.updateStatus(id, status);
|
|
520
|
+
} catch (err) {
|
|
521
|
+
this.ctx.log.warn(`快照 ${id} 状态标记 ${status} 失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
476
525
|
private async applyOne(
|
|
477
526
|
adapter: ConfigAdapter,
|
|
478
527
|
item: PlanItem,
|
package/src/core/backup.ts
CHANGED
|
@@ -12,13 +12,67 @@ import { parseJsonSafe } from '../utils/json.ts';
|
|
|
12
12
|
import { sha256Hex } from '../utils/hashing.ts';
|
|
13
13
|
import type { SectionId } from '../schema/types.ts';
|
|
14
14
|
import type {
|
|
15
|
-
ConfigAdapter, HostContext, ImportPlan, PlanItem, Snapshot,
|
|
16
|
-
SnapshotEntry, SnapshotStore, SnapshotTarget,
|
|
15
|
+
ConfigAdapter, HostContext, HostFileBackup, ImportPlan, PlanItem, Snapshot,
|
|
16
|
+
SnapshotEntry, SnapshotStatus, SnapshotStore, SnapshotTarget,
|
|
17
17
|
} from './types.ts';
|
|
18
18
|
|
|
19
19
|
/** 导入将实际写入的目标 kinds(这些项才需要快照) */
|
|
20
20
|
const EXECUTABLE_KINDS = new Set(['Create', 'Update', 'Install', 'MissingSecret', 'MissingDependency']);
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* 需要整文件备份的宿主关键文件(相对 $DSH_HOME):
|
|
24
|
+
* - settings.yaml(DSH 配置主存储;不存在时探测 settings.json)
|
|
25
|
+
* - cordis.patch.yml(用户 patch 层)
|
|
26
|
+
* - profiles/<profile>/cordis.patch.yml(profile patch 层,宿主暴露 profile 时)
|
|
27
|
+
*/
|
|
28
|
+
const HOST_FILE_CANDIDATES: ReadonlyArray<{ relPath: string }> = [
|
|
29
|
+
{ relPath: 'settings.yaml' },
|
|
30
|
+
{ relPath: 'settings.json' },
|
|
31
|
+
{ relPath: 'cordis.patch.yml' },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 宿主整文件备份(M1):探测存在性,存在的文件字节进 blobs Map 由 store.save 落盘,
|
|
36
|
+
* 全部候选(含 existed:false)登记进 hostFileBackups,供 M2 restore 整文件还原。
|
|
37
|
+
*/
|
|
38
|
+
async function backupHostFiles(
|
|
39
|
+
ctx: HostContext,
|
|
40
|
+
blobs: Map<string, Uint8Array>,
|
|
41
|
+
): Promise<HostFileBackup[]> {
|
|
42
|
+
const candidates = [...HOST_FILE_CANDIDATES];
|
|
43
|
+
if (ctx.profile !== undefined && ctx.profile !== '') {
|
|
44
|
+
candidates.push({ relPath: `profiles/${ctx.profile}/cordis.patch.yml` });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const backups: HostFileBackup[] = [];
|
|
48
|
+
for (const { relPath } of candidates) {
|
|
49
|
+
// settings.yaml 与 settings.json 互斥:主存储存在时不再探测 json 备选
|
|
50
|
+
if (relPath === 'settings.json' && backups.some((b) => b.relPath === 'settings.yaml' && b.existed)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
let existed = false;
|
|
54
|
+
try {
|
|
55
|
+
existed = await ctx.fs.exists(relPath);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
ctx.log.warn(`快照探测宿主文件失败 ${relPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
58
|
+
existed = false;
|
|
59
|
+
}
|
|
60
|
+
if (!existed) {
|
|
61
|
+
backups.push({ relPath, blobPath: '', existed: false });
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const blobPath = `blobs/host/${crypto.randomUUID()}`;
|
|
65
|
+
try {
|
|
66
|
+
blobs.set(blobPath, await ctx.fs.readFile(relPath));
|
|
67
|
+
backups.push({ relPath, blobPath, existed: true });
|
|
68
|
+
} catch (err) {
|
|
69
|
+
ctx.log.warn(`快照读取宿主文件失败 ${relPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
70
|
+
backups.push({ relPath, blobPath: '', existed: false });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return backups;
|
|
74
|
+
}
|
|
75
|
+
|
|
22
76
|
/** 文件类分区的目标基准目录(相对 homeDir;pluginFiles 的 ref 已是完整相对路径) */
|
|
23
77
|
const FILE_BASES: Partial<Record<SectionId, string>> = {
|
|
24
78
|
skills: 'skills',
|
|
@@ -152,11 +206,25 @@ export async function createSnapshot(opts: CreateSnapshotOptions): Promise<Snaps
|
|
|
152
206
|
}
|
|
153
207
|
}
|
|
154
208
|
|
|
209
|
+
// 3) 宿主整文件备份(M1):settings.yaml/settings.json + 用户/ profile 层 cordis.patch.yml
|
|
210
|
+
const hostFileBackups = await backupHostFiles(ctx, blobs);
|
|
211
|
+
|
|
212
|
+
// 4) 导入前插件清单(M2 restore 撤销插件对比基准;读取失败不阻断快照)
|
|
213
|
+
let beforePlugins: Snapshot['beforePlugins'] = [];
|
|
214
|
+
try {
|
|
215
|
+
beforePlugins = await ctx.plugins.listInstalled();
|
|
216
|
+
} catch (err) {
|
|
217
|
+
ctx.log.warn(`快照登记插件清单失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
155
220
|
const snapshot: Snapshot = {
|
|
156
221
|
id: crypto.randomUUID(),
|
|
157
222
|
createdAt: new Date().toISOString(),
|
|
158
223
|
sourceZip,
|
|
159
224
|
entries,
|
|
225
|
+
status: 'pending',
|
|
226
|
+
beforePlugins,
|
|
227
|
+
hostFileBackups,
|
|
160
228
|
};
|
|
161
229
|
// file 条目登记 snapshotId,回滚读 blob 时定位快照目录
|
|
162
230
|
for (const entry of snapshot.entries) {
|
|
@@ -209,4 +277,12 @@ export class FileSnapshotStore implements SnapshotStore {
|
|
|
209
277
|
if (!target.startsWith(dir)) throw new Error(`快照 blob 路径越界: ${blobPath}`);
|
|
210
278
|
return fs.readFile(target);
|
|
211
279
|
}
|
|
280
|
+
|
|
281
|
+
/** 标记快照生命周期状态:重写 <dir>/<id>/snapshot.json(保留其余字段)。 */
|
|
282
|
+
async updateStatus(id: string, status: SnapshotStatus): Promise<void> {
|
|
283
|
+
const file = path.join(this.snapshotDir(id), 'snapshot.json');
|
|
284
|
+
const snapshot = parseJsonSafe(await fs.readFile(file, 'utf8')) as Snapshot;
|
|
285
|
+
snapshot.status = status;
|
|
286
|
+
await fs.writeFile(file, JSON.stringify(snapshot, null, 2));
|
|
287
|
+
}
|
|
212
288
|
}
|
package/src/core/exporter.ts
CHANGED
|
@@ -20,6 +20,15 @@ import type {
|
|
|
20
20
|
ExportSection, HostContext, SecretScanner, SensitiveHit,
|
|
21
21
|
} from './types.ts';
|
|
22
22
|
|
|
23
|
+
/** m1:每导出一个分区前的进度回调信息(Host 侧 run 状态更新用;section = adapter id) */
|
|
24
|
+
export interface SectionProgress {
|
|
25
|
+
section: string;
|
|
26
|
+
/** 当前分区序号(1 起) */
|
|
27
|
+
index: number;
|
|
28
|
+
/** 选中分区总数 */
|
|
29
|
+
total: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
export interface ExporterOptions {
|
|
24
33
|
ctx: HostContext;
|
|
25
34
|
adapters: ConfigAdapter[];
|
|
@@ -30,6 +39,8 @@ export interface ExporterOptions {
|
|
|
30
39
|
/** 插件自身版本(manifest.exporter.version) */
|
|
31
40
|
exporterVersion?: string;
|
|
32
41
|
now?: () => Date;
|
|
42
|
+
/** m1:每导出一个分区前调用(真实进度埋点;不传则无埋点) */
|
|
43
|
+
onSection?: (info: SectionProgress) => void;
|
|
33
44
|
}
|
|
34
45
|
|
|
35
46
|
/** 缺省 SecretScanner:递归黑名单字段剥离(字段名大小写不敏感;二进制/Uint8Array 原样跳过) */
|
|
@@ -76,6 +87,7 @@ export class Exporter {
|
|
|
76
87
|
private readonly encryption: EncryptionProvider | null;
|
|
77
88
|
private readonly exporterVersion: string;
|
|
78
89
|
private readonly now: () => Date;
|
|
90
|
+
private readonly onSection: ((info: SectionProgress) => void) | undefined;
|
|
79
91
|
|
|
80
92
|
constructor(opts: ExporterOptions) {
|
|
81
93
|
this.ctx = opts.ctx;
|
|
@@ -84,6 +96,7 @@ export class Exporter {
|
|
|
84
96
|
this.encryption = opts.encryption ?? null;
|
|
85
97
|
this.exporterVersion = opts.exporterVersion ?? '0.1.0';
|
|
86
98
|
this.now = opts.now ?? (() => new Date());
|
|
99
|
+
this.onSection = opts.onSection;
|
|
87
100
|
}
|
|
88
101
|
|
|
89
102
|
/**
|
|
@@ -108,8 +121,16 @@ export class Exporter {
|
|
|
108
121
|
const included: ExportReport['included'] = [];
|
|
109
122
|
const excluded: SectionId[] = this.adapters.filter((a) => !selected.includes(a.id)).map((a) => a.id);
|
|
110
123
|
|
|
124
|
+
// m1 埋点:每导出一个分区前上报真实进度(onSection 抛错不得中断导出)
|
|
125
|
+
let sectionIndex = 0;
|
|
111
126
|
for (const adapter of this.adapters) {
|
|
112
127
|
if (!selected.includes(adapter.id)) continue;
|
|
128
|
+
sectionIndex += 1;
|
|
129
|
+
try {
|
|
130
|
+
this.onSection?.({ section: adapter.id, index: sectionIndex, total: selected.length });
|
|
131
|
+
} catch {
|
|
132
|
+
// 埋点回调失败不影响导出本身(进度是尽力而为)
|
|
133
|
+
}
|
|
113
134
|
let section: ExportSection;
|
|
114
135
|
try {
|
|
115
136
|
section = await adapter.export(this.ctx, options);
|
package/src/core/importer.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { ZipArchive, ZipSafetyLimits } from '../utils/zip.ts';
|
|
12
12
|
import { Analyzer } from './analyzer.ts';
|
|
13
|
+
import type { PlanItemProgress } from './analyzer.ts';
|
|
13
14
|
import type {
|
|
14
15
|
ConfigAdapter, HostContext, ImportAnalysis, ImportDecisions, ImportPlan,
|
|
15
16
|
ImportResult, SnapshotStore,
|
|
@@ -35,6 +36,8 @@ export interface ExecuteOptions {
|
|
|
35
36
|
decryptedCredentials?: Map<string, string>;
|
|
36
37
|
/** 任一项失败立即整体回滚(默认 false:单项失败如实记录并继续其余项) */
|
|
37
38
|
rollbackOnError?: boolean;
|
|
39
|
+
/** m1:每完成一个计划项的进度回调(透传给 Analyzer;不传则无埋点) */
|
|
40
|
+
onItem?: (info: PlanItemProgress) => void;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
export class Importer {
|
|
@@ -61,6 +64,7 @@ export class Importer {
|
|
|
61
64
|
secretInputs: opts.secretInputs,
|
|
62
65
|
decryptedCredentials: opts.decryptedCredentials,
|
|
63
66
|
rollbackOnError: opts.rollbackOnError,
|
|
67
|
+
onItem: opts.onItem,
|
|
64
68
|
});
|
|
65
69
|
}
|
|
66
70
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -16,6 +16,11 @@ export {
|
|
|
16
16
|
type CreateSnapshotOptions, type FileSnapshotStoreOptions,
|
|
17
17
|
} from './backup.ts';
|
|
18
18
|
export { rollback, type RollbackOptions } from './rollback.ts';
|
|
19
|
+
export {
|
|
20
|
+
planRestore, restore, listSnapshots,
|
|
21
|
+
type RestoreAction, type RestoreActionKind, type RestoreOptions,
|
|
22
|
+
type RestorePlan, type RestoreReport, type SnapshotMeta,
|
|
23
|
+
} from './restore.ts';
|
|
19
24
|
export {
|
|
20
25
|
computeCompatibility, describeCompatibility, describeSchemaStatus,
|
|
21
26
|
validateSections,
|