rightsize 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +36 -26
  2. package/dist/backend-docker/backend.d.ts +41 -4
  3. package/dist/backend-docker/backend.d.ts.map +1 -1
  4. package/dist/backend-docker/backend.js +84 -6
  5. package/dist/backend-docker/backend.js.map +1 -1
  6. package/dist/backend-docker/cli.d.ts +29 -0
  7. package/dist/backend-docker/cli.d.ts.map +1 -0
  8. package/dist/backend-docker/cli.js +72 -0
  9. package/dist/backend-docker/cli.js.map +1 -0
  10. package/dist/backend-msb/backend.d.ts +91 -7
  11. package/dist/backend-msb/backend.d.ts.map +1 -1
  12. package/dist/backend-msb/backend.js +185 -11
  13. package/dist/backend-msb/backend.js.map +1 -1
  14. package/dist/backend-msb/commands.d.ts +16 -0
  15. package/dist/backend-msb/commands.d.ts.map +1 -1
  16. package/dist/backend-msb/commands.js +41 -1
  17. package/dist/backend-msb/commands.js.map +1 -1
  18. package/dist/backend-msb/snapshot-import.d.ts +33 -0
  19. package/dist/backend-msb/snapshot-import.d.ts.map +1 -0
  20. package/dist/backend-msb/snapshot-import.js +48 -0
  21. package/dist/backend-msb/snapshot-import.js.map +1 -0
  22. package/dist/backend-msb/snapshot-list.d.ts +43 -0
  23. package/dist/backend-msb/snapshot-list.d.ts.map +1 -0
  24. package/dist/backend-msb/snapshot-list.js +53 -0
  25. package/dist/backend-msb/snapshot-list.js.map +1 -0
  26. package/dist/backend-msb/snapshot-not-found.d.ts +25 -0
  27. package/dist/backend-msb/snapshot-not-found.d.ts.map +1 -0
  28. package/dist/backend-msb/snapshot-not-found.js +27 -0
  29. package/dist/backend-msb/snapshot-not-found.js.map +1 -0
  30. package/dist/core/backend.d.ts +86 -6
  31. package/dist/core/backend.d.ts.map +1 -1
  32. package/dist/core/checkpoint/api.d.ts +129 -0
  33. package/dist/core/checkpoint/api.d.ts.map +1 -0
  34. package/dist/core/checkpoint/api.js +261 -0
  35. package/dist/core/checkpoint/api.js.map +1 -0
  36. package/dist/core/checkpoint/archive.d.ts +52 -0
  37. package/dist/core/checkpoint/archive.d.ts.map +1 -0
  38. package/dist/core/checkpoint/archive.js +134 -0
  39. package/dist/core/checkpoint/archive.js.map +1 -0
  40. package/dist/core/checkpoint/name.d.ts +12 -0
  41. package/dist/core/checkpoint/name.d.ts.map +1 -0
  42. package/dist/core/checkpoint/name.js +17 -0
  43. package/dist/core/checkpoint/name.js.map +1 -0
  44. package/dist/core/checkpoint/ref.d.ts +12 -0
  45. package/dist/core/checkpoint/ref.d.ts.map +1 -0
  46. package/dist/core/checkpoint/ref.js +16 -0
  47. package/dist/core/checkpoint/ref.js.map +1 -0
  48. package/dist/core/checkpoint/registry.d.ts +109 -0
  49. package/dist/core/checkpoint/registry.d.ts.map +1 -0
  50. package/dist/core/checkpoint/registry.js +181 -0
  51. package/dist/core/checkpoint/registry.js.map +1 -0
  52. package/dist/core/checkpoint/tar-cli.d.ts +45 -0
  53. package/dist/core/checkpoint/tar-cli.d.ts.map +1 -0
  54. package/dist/core/checkpoint/tar-cli.js +86 -0
  55. package/dist/core/checkpoint/tar-cli.js.map +1 -0
  56. package/dist/core/errors.d.ts +97 -4
  57. package/dist/core/errors.d.ts.map +1 -1
  58. package/dist/core/errors.js +124 -7
  59. package/dist/core/errors.js.map +1 -1
  60. package/dist/core/generic-container.d.ts +87 -18
  61. package/dist/core/generic-container.d.ts.map +1 -1
  62. package/dist/core/generic-container.js +171 -22
  63. package/dist/core/generic-container.js.map +1 -1
  64. package/dist/core/model.d.ts +25 -8
  65. package/dist/core/model.d.ts.map +1 -1
  66. package/dist/index.d.ts +4 -2
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +4 -2
  69. package/dist/index.js.map +1 -1
  70. package/dist/modules/keycloak.js +1 -1
  71. package/dist/modules/mysql.d.ts.map +1 -1
  72. package/dist/modules/mysql.js +4 -3
  73. package/dist/modules/mysql.js.map +1 -1
  74. package/package.json +1 -1
@@ -0,0 +1,261 @@
1
+ import { cacheDir } from "../cache-dir.js";
2
+ import { Backends } from "../backends.js";
3
+ import { CheckpointArtifactMissingError, CheckpointBackendMismatchError } from "../errors.js";
4
+ import { requireValidCheckpointName } from "./name.js";
5
+ import { readCheckpointRegistry, removeCheckpointRegistryFile, writeCheckpointRegistryAtomic, listCheckpointNames, fromCheckpointRegistryEntry, toCheckpointRegistrySpec, } from "./registry.js";
6
+ import { writeCheckpointArchive, readCheckpointArchive, CHECKPOINT_ARCHIVE_VERSION } from "./archive.js";
7
+ /** Every field of a `Checkpoint` comes straight from the registry entry except `spec`, which is reconstructed via `fromCheckpointRegistryEntry` — see that function's own doc for what is and isn't meaningful in the result. */
8
+ function toCheckpoint(entry) {
9
+ return { ref: entry.ref, backend: entry.backend, spec: fromCheckpointRegistryEntry(entry) };
10
+ }
11
+ /**
12
+ * Rediscovers a named checkpoint written by an earlier `checkpoint(name)`
13
+ * call — in this process or an entirely different one, since the registry
14
+ * lives on disk under the shared rightsize cache directory. No entry for
15
+ * `name` resolves to `undefined`. A corrupt entry is treated the same as
16
+ * absent, with a best-effort delete of the bad file.
17
+ *
18
+ * When the entry's own recorded backend matches the CURRENTLY active
19
+ * backend, the underlying artifact is probed via the backend's
20
+ * `hasCheckpoint` SPI before this resolves — an artifact that's gone (removed
21
+ * by hand, or by something outside this library) makes the entry stale: it's
22
+ * best-effort deleted and this resolves to `undefined`, the same as if it
23
+ * had never existed. A probe FAILURE (the backend call itself throws) is
24
+ * never swallowed into a `false` — only a confirmed "does not exist"
25
+ * resolves that way, so a probe failure propagates out of this call.
26
+ *
27
+ * When the entry's recorded backend DIFFERS from the active one, this
28
+ * returns the entry unprobed — an msb ref means nothing to a docker probe
29
+ * and vice versa. `GenericContainer.fromCheckpoint(cp).start()`'s own
30
+ * `CheckpointBackendMismatchError` gate stays the sole authority for that
31
+ * mismatch; this function must not force-resolve a backend the host may not
32
+ * even have.
33
+ *
34
+ * `name` is validated against `CHECKPOINT_NAME_PATTERN` before anything else
35
+ * — including before the registry file is even looked up — so a `name`
36
+ * carrying `../` segments can never reach path construction; an invalid name
37
+ * throws `InvalidCheckpointNameError` and touches no file.
38
+ */
39
+ export async function find(name) {
40
+ requireValidCheckpointName(name);
41
+ const dir = cacheDir();
42
+ const read = await readCheckpointRegistry(dir, name);
43
+ if (read.kind === "missing") {
44
+ return undefined;
45
+ }
46
+ if (read.kind === "corrupt") {
47
+ await removeCheckpointRegistryFile(dir, name);
48
+ return undefined;
49
+ }
50
+ const entry = read.entry;
51
+ const active = Backends.active();
52
+ if (entry.backend !== active.name) {
53
+ return toCheckpoint(entry);
54
+ }
55
+ const exists = await active.hasCheckpoint(entry.ref);
56
+ if (!exists) {
57
+ await removeCheckpointRegistryFile(dir, name);
58
+ return undefined;
59
+ }
60
+ return toCheckpoint(entry);
61
+ }
62
+ /**
63
+ * Every named checkpoint currently in the registry — registry contents
64
+ * only, never probed against a backend (unlike `find`), so a stale entry
65
+ * whose artifact is gone still appears here until something calls `find` or
66
+ * `remove` on it. A corrupt entry is silently skipped, never removed (only
67
+ * `find`/`remove` clean those up, since `list` never resolves a single name
68
+ * the caller could target for a retry).
69
+ */
70
+ export async function list() {
71
+ const dir = cacheDir();
72
+ const names = await listCheckpointNames(dir);
73
+ const checkpoints = [];
74
+ for (const name of names) {
75
+ const read = await readCheckpointRegistry(dir, name);
76
+ if (read.kind === "found") {
77
+ checkpoints.push(toCheckpoint(read.entry));
78
+ }
79
+ }
80
+ return checkpoints;
81
+ }
82
+ /**
83
+ * Deletes a named checkpoint: best-effort removal of the backend artifact
84
+ * (only when the entry's recorded backend matches the currently active
85
+ * one — this call never touches a backend that isn't active) plus the
86
+ * registry file, regardless of order of failure in either. Idempotent and
87
+ * always best-effort: "not found" anywhere (no registry entry at all) is
88
+ * success, reported as `false`; an existing entry — valid or corrupt —
89
+ * reports `true` once its registry file is gone.
90
+ *
91
+ * When the entry's recorded backend DIFFERS from the currently active one,
92
+ * only the registry record is deleted — the underlying artifact (the docker
93
+ * image, or the microsandbox snapshot) is left on disk PERMANENTLY, since
94
+ * this call never touches a backend that isn't active. Nothing in this
95
+ * library reclaims it automatically — and once the record is gone, a later
96
+ * `remove(name)` finds nothing to act on. Remove a checkpoint under its
97
+ * creating backend in the first place, or clean the leftover artifact
98
+ * directly with that backend's own CLI one-liner (see the
99
+ * [checkpoints guide](/guide/checkpoints#cleanup-checkpoints-are-not-auto-reaped)).
100
+ *
101
+ * `name` is validated against `CHECKPOINT_NAME_PATTERN` before anything else
102
+ * — including before the registry file is even looked up — so a `name`
103
+ * carrying `../` segments can never reach path construction; an invalid name
104
+ * throws `InvalidCheckpointNameError` and touches no file.
105
+ */
106
+ export async function remove(name) {
107
+ requireValidCheckpointName(name);
108
+ const dir = cacheDir();
109
+ const read = await readCheckpointRegistry(dir, name);
110
+ if (read.kind === "missing") {
111
+ return false;
112
+ }
113
+ if (read.kind === "corrupt") {
114
+ await removeCheckpointRegistryFile(dir, name);
115
+ return true;
116
+ }
117
+ const entry = read.entry;
118
+ const active = Backends.active();
119
+ if (entry.backend === active.name) {
120
+ await active.removeCheckpoint(entry.ref).catch(() => { });
121
+ }
122
+ await removeCheckpointRegistryFile(dir, name);
123
+ return true;
124
+ }
125
+ /**
126
+ * Reverse lookup: the registry entry (if any) whose `ref`/`backend` match a
127
+ * `Checkpoint`'s own — `exportTo`'s only way to learn whether the checkpoint
128
+ * it's given was ever named, and under what name, since a `Checkpoint`
129
+ * object itself never carries one (see `toCheckpoint` above). Scans every
130
+ * registered name the same way `list()` does; a corrupt entry is silently
131
+ * skipped, matching `list()`'s own tolerance.
132
+ */
133
+ async function findRegistryEntryByRef(dir, ref, backend) {
134
+ const names = await listCheckpointNames(dir);
135
+ for (const name of names) {
136
+ const read = await readCheckpointRegistry(dir, name);
137
+ if (read.kind === "found" && read.entry.ref === ref && read.entry.backend === backend) {
138
+ return read.entry;
139
+ }
140
+ }
141
+ return undefined;
142
+ }
143
+ /**
144
+ * Bundles `cp` into a self-describing archive at `destPath`: `checkpoint.json`
145
+ * (the pinned metadata — format version, this checkpoint's name if it was
146
+ * ever registered under one, ref, backend, creation time, and spec) plus an
147
+ * `artifact` member holding the backend's own payload, byte-for-byte what
148
+ * `SandboxBackend.exportCheckpoint` produces. See the
149
+ * [checkpoints guide](/guide/checkpoints#moving-checkpoints-between-machines)
150
+ * for the full CI-cache pattern this exists for.
151
+ *
152
+ * Requires the ACTIVE backend to equal `cp.backend` — the same
153
+ * `CheckpointBackendMismatchError` `fromCheckpoint(cp).start()` throws —
154
+ * before any backend or filesystem work. Then probes the artifact still
155
+ * exists via `hasCheckpoint`: exporting a stale checkpoint throws
156
+ * `CheckpointArtifactMissingError` rather than producing a broken archive.
157
+ * Only once both checks pass does this stage the export in a fresh unique
158
+ * temp directory (removed in a `finally` regardless of outcome) and tar it
159
+ * into `destPath` (parent directories created; a pre-existing file there is
160
+ * overwritten). Works on an ephemeral (unnamed) checkpoint too — the
161
+ * resulting archive just carries `name: null`.
162
+ */
163
+ export async function exportTo(cp, destPath) {
164
+ const active = Backends.active();
165
+ if (cp.backend !== active.name) {
166
+ throw new CheckpointBackendMismatchError(cp.backend, active.name);
167
+ }
168
+ const exists = await active.hasCheckpoint(cp.ref);
169
+ if (!exists) {
170
+ throw new CheckpointArtifactMissingError(cp.ref, cp.backend);
171
+ }
172
+ const dir = cacheDir();
173
+ const registryEntry = await findRegistryEntryByRef(dir, cp.ref, cp.backend);
174
+ const metadata = {
175
+ rightsizeArchive: CHECKPOINT_ARCHIVE_VERSION,
176
+ name: registryEntry?.name ?? null,
177
+ ref: cp.ref,
178
+ backend: cp.backend,
179
+ createdIso: registryEntry?.createdIso ?? new Date().toISOString(),
180
+ spec: toCheckpointRegistrySpec(cp.spec),
181
+ };
182
+ await writeCheckpointArchive(destPath, metadata, (artifactPath) => active.exportCheckpoint(cp.ref, artifactPath));
183
+ }
184
+ /**
185
+ * The inverse of `exportTo`: extracts `srcPath`, validates its
186
+ * `checkpoint.json` (format version, `name` against
187
+ * `CHECKPOINT_NAME_PATTERN` when non-null, backend against the ACTIVE
188
+ * backend — a `MalformedCheckpointArchiveError` or `CheckpointBackendMismatchError`
189
+ * either way, before any backend call or registry write), then hands the
190
+ * extracted `artifact` to `SandboxBackend.importCheckpoint`, which
191
+ * materializes it and returns the EFFECTIVE ref (docker: the same `ref` the
192
+ * archive recorded; microsandbox: the digest `snapshot import` actually
193
+ * assigned it — never necessarily the archive's own `ref`).
194
+ *
195
+ * A NAMED archive (`name` non-null) gets replace semantics matching
196
+ * `checkpoint(name)`: if a registry entry already exists for that name under
197
+ * a DIFFERENT ref and its recorded backend matches the active one, its old
198
+ * artifact is best-effort removed first (never a foreign-backend
199
+ * `removeCheckpoint` call — the same gate `remove()` applies); the registry
200
+ * entry is then written (or overwritten) with the effective ref. A NAMELESS
201
+ * archive writes no registry entry at all — the returned `Checkpoint` is
202
+ * purely ephemeral, the same as an unnamed `checkpoint()` call's result.
203
+ * Either way, the returned `Checkpoint` restores via the existing
204
+ * `fromCheckpoint()` path with zero changes — refs are opaque throughout
205
+ * this library.
206
+ */
207
+ export async function importFrom(srcPath) {
208
+ const active = Backends.active();
209
+ return readCheckpointArchive(srcPath, async (metadata, artifactPath) => {
210
+ if (metadata.name !== null) {
211
+ requireValidCheckpointName(metadata.name);
212
+ }
213
+ if (metadata.backend !== active.name) {
214
+ throw new CheckpointBackendMismatchError(metadata.backend, active.name);
215
+ }
216
+ const effectiveRef = await active.importCheckpoint(artifactPath, metadata.ref);
217
+ const spec = fromCheckpointRegistryEntry({
218
+ name: metadata.name ?? "",
219
+ ref: effectiveRef,
220
+ backend: active.name,
221
+ createdIso: metadata.createdIso,
222
+ spec: metadata.spec,
223
+ });
224
+ if (metadata.name !== null) {
225
+ const dir = cacheDir();
226
+ const existing = await readCheckpointRegistry(dir, metadata.name);
227
+ if (existing.kind === "found" && existing.entry.ref !== effectiveRef && existing.entry.backend === active.name) {
228
+ await active.removeCheckpoint(existing.entry.ref).catch(() => { });
229
+ }
230
+ const entry = {
231
+ name: metadata.name,
232
+ ref: effectiveRef,
233
+ backend: active.name,
234
+ createdIso: metadata.createdIso,
235
+ spec: metadata.spec,
236
+ };
237
+ await writeCheckpointRegistryAtomic(dir, metadata.name, entry);
238
+ }
239
+ return { ref: effectiveRef, backend: active.name, spec };
240
+ });
241
+ }
242
+ /**
243
+ * The library's entry point for rediscovering NAMED checkpoints across
244
+ * processes — see the [checkpoints guide](/guide/checkpoints#reusing-checkpoints-across-runs)
245
+ * for the `find(...) ?? seed()` first-run/later-run pattern this exists to
246
+ * support. Unnamed `checkpoint()` calls never appear here; only a
247
+ * `checkpoint(name)` call writes a registry entry these functions can find.
248
+ */
249
+ export const Checkpoints = {
250
+ /** Rediscovers a named checkpoint — see `find` above. */
251
+ find,
252
+ /** Every named checkpoint currently in the registry — see `list` above. */
253
+ list,
254
+ /** Deletes a named checkpoint — see `remove` above. */
255
+ remove,
256
+ /** Bundles a checkpoint into a portable archive — see `exportTo` above. */
257
+ exportTo,
258
+ /** Materializes a portable archive on this machine — see `importFrom` above. */
259
+ importFrom,
260
+ };
261
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/core/checkpoint/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import type { CheckpointRegistrySpec } from "./registry.js";
2
+ /** The one archive format version this library understands — pinned identically across every rightsize language implementation. */
3
+ export declare const CHECKPOINT_ARCHIVE_VERSION = 1;
4
+ /**
5
+ * `checkpoint.json`'s exact shape — pinned identically in every rightsize
6
+ * language implementation. `name` is `null` for an archive built from an
7
+ * unnamed (ephemeral) checkpoint; every other field mirrors
8
+ * `CheckpointRegistryEntry` plus the format version.
9
+ */
10
+ export interface CheckpointArchiveMetadata {
11
+ readonly rightsizeArchive: 1;
12
+ readonly name: string | null;
13
+ readonly ref: string;
14
+ readonly backend: string;
15
+ readonly createdIso: string;
16
+ readonly spec: CheckpointRegistrySpec;
17
+ }
18
+ /**
19
+ * Parses and validates `text` (the extracted archive's `checkpoint.json`
20
+ * content) into a `CheckpointArchiveMetadata`. Every failure — invalid JSON,
21
+ * a `rightsizeArchive` value other than `CHECKPOINT_ARCHIVE_VERSION` (named
22
+ * in the thrown error, per the pinned format's own contract), a malformed
23
+ * `name`/`ref`/`backend`/`createdIso`/`spec` — throws
24
+ * `MalformedCheckpointArchiveError` naming `archivePath` (the ORIGINAL
25
+ * archive the caller passed to `importFrom`, not the temp-extracted json
26
+ * path, so the error is actionable). Never touches a backend or the
27
+ * registry — pure parsing.
28
+ */
29
+ export declare function parseCheckpointArchiveMetadata(text: string, archivePath: string): CheckpointArchiveMetadata;
30
+ /**
31
+ * Builds a checkpoint archive at `destPath`: stages `checkpoint.json` and an
32
+ * `artifact` file (written by `exportArtifact`, the backend's own
33
+ * `exportCheckpoint` call) in a fresh temp directory, then tars exactly
34
+ * those two members at the archive's root — never nested under the temp
35
+ * dir's own path. `destPath`'s parent directories are created first; a
36
+ * pre-existing file at `destPath` is overwritten (tar's own default
37
+ * behavior). The temp directory is removed in a `finally`, on success and on
38
+ * failure alike.
39
+ */
40
+ export declare function writeCheckpointArchive(destPath: string, metadata: CheckpointArchiveMetadata, exportArtifact: (artifactPath: string) => Promise<void>): Promise<void>;
41
+ /**
42
+ * Extracts `srcPath` into a fresh temp directory, parses and validates
43
+ * `checkpoint.json` (see `parseCheckpointArchiveMetadata`), confirms the
44
+ * `artifact` member is present, then hands both to `importArtifact` — the
45
+ * caller's own backend/registry validation and `importCheckpoint` call —
46
+ * before the temp directory is removed in a `finally`. `srcPath` not
47
+ * existing, or not being a valid tar, or missing its `checkpoint.json`
48
+ * member each throw `MalformedCheckpointArchiveError` naming `srcPath`
49
+ * itself, before any backend call.
50
+ */
51
+ export declare function readCheckpointArchive<T>(srcPath: string, importArtifact: (metadata: CheckpointArchiveMetadata, artifactPath: string) => Promise<T>): Promise<T>;
52
+ //# sourceMappingURL=archive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../../src/core/checkpoint/archive.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAG5D,mIAAmI;AACnI,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAU5C;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;CACvC;AAWD;;;;;;;;;;GAUG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,yBAAyB,CAwC3G;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,yBAAyB,EACnC,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GACtD,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAC3C,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,CAAC,QAAQ,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GACxF,OAAO,CAAC,CAAC,CAAC,CAsCZ"}
@@ -0,0 +1,134 @@
1
+ import * as fsp from "node:fs/promises";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { BackendError, MalformedCheckpointArchiveError } from "../errors.js";
5
+ import { isCheckpointRegistrySpec } from "./registry.js";
6
+ import { TarCli, runTar } from "./tar-cli.js";
7
+ /** The one archive format version this library understands — pinned identically across every rightsize language implementation. */
8
+ export const CHECKPOINT_ARCHIVE_VERSION = 1;
9
+ /**
10
+ * An archive bundles a full backend artifact (a zstd-compressed msb snapshot
11
+ * or a saved docker image) — generous relative to a plain `msb copy`/`docker
12
+ * cp`, matching the backend's own export/import budget for the payload
13
+ * itself.
14
+ */
15
+ const ARCHIVE_TAR_TIMEOUT_MS = 300_000;
16
+ async function withTempDir(prefix, fn) {
17
+ const dir = await fsp.mkdtemp(path.join(os.tmpdir(), prefix));
18
+ try {
19
+ return await fn(dir);
20
+ }
21
+ finally {
22
+ await fsp.rm(dir, { recursive: true, force: true });
23
+ }
24
+ }
25
+ /**
26
+ * Parses and validates `text` (the extracted archive's `checkpoint.json`
27
+ * content) into a `CheckpointArchiveMetadata`. Every failure — invalid JSON,
28
+ * a `rightsizeArchive` value other than `CHECKPOINT_ARCHIVE_VERSION` (named
29
+ * in the thrown error, per the pinned format's own contract), a malformed
30
+ * `name`/`ref`/`backend`/`createdIso`/`spec` — throws
31
+ * `MalformedCheckpointArchiveError` naming `archivePath` (the ORIGINAL
32
+ * archive the caller passed to `importFrom`, not the temp-extracted json
33
+ * path, so the error is actionable). Never touches a backend or the
34
+ * registry — pure parsing.
35
+ */
36
+ export function parseCheckpointArchiveMetadata(text, archivePath) {
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(text);
40
+ }
41
+ catch {
42
+ throw new MalformedCheckpointArchiveError(archivePath, "checkpoint.json is not valid JSON");
43
+ }
44
+ if (typeof parsed !== "object" || parsed === null) {
45
+ throw new MalformedCheckpointArchiveError(archivePath, "checkpoint.json is not a JSON object");
46
+ }
47
+ const rec = parsed;
48
+ if (rec["rightsizeArchive"] !== CHECKPOINT_ARCHIVE_VERSION) {
49
+ throw new MalformedCheckpointArchiveError(archivePath, `unsupported rightsizeArchive version ${JSON.stringify(rec["rightsizeArchive"])} (this library reads version ${CHECKPOINT_ARCHIVE_VERSION})`);
50
+ }
51
+ const name = rec["name"];
52
+ if (name !== null && typeof name !== "string") {
53
+ throw new MalformedCheckpointArchiveError(archivePath, "checkpoint.json's 'name' field must be a string or null");
54
+ }
55
+ if (typeof rec["ref"] !== "string" || typeof rec["backend"] !== "string" || typeof rec["createdIso"] !== "string") {
56
+ throw new MalformedCheckpointArchiveError(archivePath, "checkpoint.json is missing one of the required string fields 'ref', 'backend', 'createdIso'");
57
+ }
58
+ if (!isCheckpointRegistrySpec(rec["spec"])) {
59
+ throw new MalformedCheckpointArchiveError(archivePath, "checkpoint.json's 'spec' field is missing or malformed");
60
+ }
61
+ return {
62
+ rightsizeArchive: 1,
63
+ name,
64
+ ref: rec["ref"],
65
+ backend: rec["backend"],
66
+ createdIso: rec["createdIso"],
67
+ spec: rec["spec"],
68
+ };
69
+ }
70
+ /**
71
+ * Builds a checkpoint archive at `destPath`: stages `checkpoint.json` and an
72
+ * `artifact` file (written by `exportArtifact`, the backend's own
73
+ * `exportCheckpoint` call) in a fresh temp directory, then tars exactly
74
+ * those two members at the archive's root — never nested under the temp
75
+ * dir's own path. `destPath`'s parent directories are created first; a
76
+ * pre-existing file at `destPath` is overwritten (tar's own default
77
+ * behavior). The temp directory is removed in a `finally`, on success and on
78
+ * failure alike.
79
+ */
80
+ export async function writeCheckpointArchive(destPath, metadata, exportArtifact) {
81
+ await fsp.mkdir(path.dirname(destPath), { recursive: true });
82
+ await withTempDir("rightsize-checkpoint-export-", async (workDir) => {
83
+ const artifactPath = path.join(workDir, "artifact");
84
+ await exportArtifact(artifactPath);
85
+ await fsp.writeFile(path.join(workDir, "checkpoint.json"), JSON.stringify(metadata));
86
+ const result = await runTar(TarCli.create(path.basename(destPath), workDir, ["checkpoint.json", "artifact"]), ARCHIVE_TAR_TIMEOUT_MS, path.dirname(destPath));
87
+ if (result.exitCode !== 0) {
88
+ throw new BackendError(`tar could not create checkpoint archive '${destPath}' (exit ${result.exitCode}): ${result.stderr.trim()}`);
89
+ }
90
+ });
91
+ }
92
+ /**
93
+ * Extracts `srcPath` into a fresh temp directory, parses and validates
94
+ * `checkpoint.json` (see `parseCheckpointArchiveMetadata`), confirms the
95
+ * `artifact` member is present, then hands both to `importArtifact` — the
96
+ * caller's own backend/registry validation and `importCheckpoint` call —
97
+ * before the temp directory is removed in a `finally`. `srcPath` not
98
+ * existing, or not being a valid tar, or missing its `checkpoint.json`
99
+ * member each throw `MalformedCheckpointArchiveError` naming `srcPath`
100
+ * itself, before any backend call.
101
+ */
102
+ export async function readCheckpointArchive(srcPath, importArtifact) {
103
+ const exists = await fsp
104
+ .stat(srcPath)
105
+ .then((s) => s.isFile())
106
+ .catch(() => false);
107
+ if (!exists) {
108
+ throw new MalformedCheckpointArchiveError(srcPath, "the file does not exist");
109
+ }
110
+ return withTempDir("rightsize-checkpoint-import-", async (workDir) => {
111
+ const extracted = await runTar(TarCli.extract(path.basename(srcPath), workDir), ARCHIVE_TAR_TIMEOUT_MS, path.dirname(srcPath));
112
+ if (extracted.exitCode !== 0) {
113
+ throw new MalformedCheckpointArchiveError(srcPath, `not a valid tar archive: ${extracted.stderr.trim()}`);
114
+ }
115
+ let text;
116
+ try {
117
+ text = await fsp.readFile(path.join(workDir, "checkpoint.json"), "utf8");
118
+ }
119
+ catch {
120
+ throw new MalformedCheckpointArchiveError(srcPath, "missing checkpoint.json");
121
+ }
122
+ const metadata = parseCheckpointArchiveMetadata(text, srcPath);
123
+ const artifactPath = path.join(workDir, "artifact");
124
+ const hasArtifact = await fsp
125
+ .stat(artifactPath)
126
+ .then(() => true)
127
+ .catch(() => false);
128
+ if (!hasArtifact) {
129
+ throw new MalformedCheckpointArchiveError(srcPath, "missing artifact");
130
+ }
131
+ return importArtifact(metadata, artifactPath);
132
+ });
133
+ }
134
+ //# sourceMappingURL=archive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.js","sourceRoot":"","sources":["../../../src/core/checkpoint/archive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Pinned identically across every rightsize language implementation (see the
3
+ * named-checkpoints spec's own "Names and refs" section): lowercase letters,
4
+ * digits, and hyphens only, starting with a letter or digit, at most 41
5
+ * characters. This is also exactly the alphabet both backends' ref formats
6
+ * embed verbatim (`rz-ckpt-<name>`, `rightsize/checkpoint:<name>`), so a
7
+ * valid name is guaranteed to produce a valid ref on either backend.
8
+ */
9
+ export declare const CHECKPOINT_NAME_PATTERN: RegExp;
10
+ /** Fails fast — before any backend or filesystem call — on a `name` that doesn't match `CHECKPOINT_NAME_PATTERN`. */
11
+ export declare function requireValidCheckpointName(name: string): void;
12
+ //# sourceMappingURL=name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"name.d.ts","sourceRoot":"","sources":["../../../src/core/checkpoint/name.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,QAA8B,CAAC;AAEnE,qHAAqH;AACrH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI7D"}
@@ -0,0 +1,17 @@
1
+ import { InvalidCheckpointNameError } from "../errors.js";
2
+ /**
3
+ * Pinned identically across every rightsize language implementation (see the
4
+ * named-checkpoints spec's own "Names and refs" section): lowercase letters,
5
+ * digits, and hyphens only, starting with a letter or digit, at most 41
6
+ * characters. This is also exactly the alphabet both backends' ref formats
7
+ * embed verbatim (`rz-ckpt-<name>`, `rightsize/checkpoint:<name>`), so a
8
+ * valid name is guaranteed to produce a valid ref on either backend.
9
+ */
10
+ export const CHECKPOINT_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,40}$/;
11
+ /** Fails fast — before any backend or filesystem call — on a `name` that doesn't match `CHECKPOINT_NAME_PATTERN`. */
12
+ export function requireValidCheckpointName(name) {
13
+ if (!CHECKPOINT_NAME_PATTERN.test(name)) {
14
+ throw new InvalidCheckpointNameError(name);
15
+ }
16
+ }
17
+ //# sourceMappingURL=name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"name.js","sourceRoot":"","sources":["../../../src/core/checkpoint/name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,2BAA2B,CAAC;AAEnE,qHAAqH;AACrH,MAAM,UAAU,0BAA0B,CAAC,IAAY;IACrD,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,0BAA0B,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Mints the backend-specific checkpoint ref: `rz-ckpt-<suffix>` on
3
+ * microsandbox, `rightsize/checkpoint:<suffix>` elsewhere. `name` present
4
+ * (a NAMED checkpoint) makes the suffix — and therefore the whole ref —
5
+ * deterministic: re-checkpointing the same name reproduces the exact same
6
+ * ref, which is what makes the registry's replace semantics (remove the old
7
+ * artifact under this ref, then create the new one) correct. `name`
8
+ * `undefined` mints a fresh random 12-hex suffix instead, byte-for-byte the
9
+ * pre-named-checkpoints behavior.
10
+ */
11
+ export declare function checkpointRef(backendName: string, name: string | undefined): string;
12
+ //# sourceMappingURL=ref.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref.d.ts","sourceRoot":"","sources":["../../../src/core/checkpoint/ref.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGnF"}
@@ -0,0 +1,16 @@
1
+ import { randomBytes } from "node:crypto";
2
+ /**
3
+ * Mints the backend-specific checkpoint ref: `rz-ckpt-<suffix>` on
4
+ * microsandbox, `rightsize/checkpoint:<suffix>` elsewhere. `name` present
5
+ * (a NAMED checkpoint) makes the suffix — and therefore the whole ref —
6
+ * deterministic: re-checkpointing the same name reproduces the exact same
7
+ * ref, which is what makes the registry's replace semantics (remove the old
8
+ * artifact under this ref, then create the new one) correct. `name`
9
+ * `undefined` mints a fresh random 12-hex suffix instead, byte-for-byte the
10
+ * pre-named-checkpoints behavior.
11
+ */
12
+ export function checkpointRef(backendName, name) {
13
+ const suffix = name ?? randomBytes(6).toString("hex");
14
+ return backendName === "microsandbox" ? `rz-ckpt-${suffix}` : `rightsize/checkpoint:${suffix}`;
15
+ }
16
+ //# sourceMappingURL=ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref.js","sourceRoot":"","sources":["../../../src/core/checkpoint/ref.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,WAAmB,EAAE,IAAwB;IACzE,MAAM,MAAM,GAAG,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,OAAO,WAAW,KAAK,cAAc,CAAC,CAAC,CAAC,WAAW,MAAM,EAAE,CAAC,CAAC,CAAC,wBAAwB,MAAM,EAAE,CAAC;AACjG,CAAC"}