@akterjs/cli 0.0.0-stage → 0.1.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 (108) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +47 -2
  3. package/dist/Argument-D81dqizB.mjs +57 -0
  4. package/dist/Array-B4kuTQeW.mjs +2323 -0
  5. package/dist/Boolean-kILIvmhE.mjs +26 -0
  6. package/dist/ChildProcess-BCIwxPGc.mjs +577 -0
  7. package/dist/CliError-Cf5xqkkj.mjs +633 -0
  8. package/dist/Command-Dzq9KM-h.mjs +5135 -0
  9. package/dist/Config-B6OND9WR.mjs +854 -0
  10. package/dist/Console-BKHUkH6a.mjs +141 -0
  11. package/dist/Data-CeCULr0-.mjs +251 -0
  12. package/dist/Digest-CBd3uv5B.mjs +348 -0
  13. package/dist/Duration-EH9Vy4U6.mjs +791 -0
  14. package/dist/Effect-CKFpqbmw.mjs +12911 -0
  15. package/dist/FileSystem-4pPcEb_i.mjs +1366 -0
  16. package/dist/HttpClientError-BUeMWx70.mjs +303 -0
  17. package/dist/HttpClientResponse-D5ZKICVV.mjs +855 -0
  18. package/dist/Match-CGPLZYrV.mjs +434 -0
  19. package/dist/MutableHashMap-f8nW8AJR.mjs +376 -0
  20. package/dist/MutableHashSet-_HI572Ya.mjs +155 -0
  21. package/dist/MutableRef-D4Qv592C.mjs +107 -0
  22. package/dist/Number-BvkRkfc-.mjs +137 -0
  23. package/dist/Option-DkxSnBhp.mjs +3967 -0
  24. package/dist/Order-C1JC991I.mjs +481 -0
  25. package/dist/Path-Djdv_eF5.mjs +130 -0
  26. package/dist/Queue-CO5UQ6os.mjs +1394 -0
  27. package/dist/Redacted-RkJrG5Me.mjs +160 -0
  28. package/dist/Ref-BIPOxJNH.mjs +267 -0
  29. package/dist/Runtime-BInIAwK3.mjs +297 -0
  30. package/dist/Schema-MiKt5K1v.mjs +10999 -0
  31. package/dist/SecureRandom-B_U4ysO3.mjs +279 -0
  32. package/dist/Semaphore-DOVJ4ZGS.mjs +177 -0
  33. package/dist/Stdio-KylmOBX_.mjs +74 -0
  34. package/dist/Stream-bK9QIETN.mjs +4012 -0
  35. package/dist/String-q5dcsVe4.mjs +351 -0
  36. package/dist/TerminalCapabilities-w_44MAca.mjs +1769 -0
  37. package/dist/WebCrypto-ZI_GNVlw.mjs +33 -0
  38. package/dist/actor-call.service-CGM_vD9u.mjs +1269 -0
  39. package/dist/actor-client.adapter-Coem7gF1.mjs +3691 -0
  40. package/dist/akter-program.config-C4cXx4Fr.mjs +7034 -0
  41. package/dist/akter.cli.mjs +5 -0
  42. package/dist/app-cli.config.mjs +49 -0
  43. package/dist/app-entry.pure.mjs +31 -0
  44. package/dist/archive/akter-archive.schema.mjs +65 -0
  45. package/dist/archive/akter-archive.service.mjs +309 -0
  46. package/dist/archive/archive-cli.config.mjs +45 -0
  47. package/dist/archive/archive-invalid.errors.mjs +5 -0
  48. package/dist/archive/archived-lineage.pure.mjs +13 -0
  49. package/dist/archive/tar.pure.mjs +291 -0
  50. package/dist/archive/zstd.adapter.mjs +46 -0
  51. package/dist/bench/bench-report.pure.mjs +120 -0
  52. package/dist/bench/bench-runner.pure.mjs +89 -0
  53. package/dist/bench/bench.service.mjs +301 -0
  54. package/dist/cli-runtime.layer-BqK2mmVL.mjs +1191 -0
  55. package/dist/cli-runtime.layer.mjs +1 -0
  56. package/dist/cloud-client.adapter-Cy1BjDbG.mjs +3186 -0
  57. package/dist/cloud-client.adapter-DOVMoYAj.mjs +304 -0
  58. package/dist/command/akter-commands.config.mjs +32 -0
  59. package/dist/command/akter-program.config.mjs +1 -0
  60. package/dist/credentials/credentials.service.mjs +226 -0
  61. package/dist/credentials/keychain.adapter.mjs +92 -0
  62. package/dist/credentials/login.config.mjs +251 -0
  63. package/dist/deploy/auth/api-attempt.service.mjs +30 -0
  64. package/dist/deploy/auth/token-refresh.errors.mjs +4 -0
  65. package/dist/deploy/build-bundle.adapter.mjs +133 -0
  66. package/dist/deploy/build-manifest.pure.mjs +44 -0
  67. package/dist/deploy/cloud-client.adapter.mjs +1 -0
  68. package/dist/deploy/cloud-url.pure.mjs +17 -0
  69. package/dist/deploy/deploy-failed.errors.mjs +17 -0
  70. package/dist/deploy/deploy.service.mjs +156 -0
  71. package/dist/deploy/request-failure.pure.mjs +87 -0
  72. package/dist/dev-runner.pure.mjs +30 -0
  73. package/dist/docs/doc-topics.pure.mjs +21 -0
  74. package/dist/docs/docs.config.mjs +1 -0
  75. package/dist/docs.config-_21Xi6HU.mjs +190 -0
  76. package/dist/export/actor-export.service.mjs +267 -0
  77. package/dist/export/export-failed.errors.mjs +4 -0
  78. package/dist/export/export-failure.pure.mjs +18 -0
  79. package/dist/export/ordered-lines.pure.mjs +43 -0
  80. package/dist/export/ranged-download.pure.mjs +66 -0
  81. package/dist/export/snapshot-download.pure.mjs +86 -0
  82. package/dist/history-runner.pure.mjs +186 -0
  83. package/dist/history-since.pure.mjs +17 -0
  84. package/dist/hosted-runtime.layer-Cx3IPsEd.mjs +16358 -0
  85. package/dist/local/environment-cli.config.mjs +90 -0
  86. package/dist/local/environment-runner.pure.mjs +75 -0
  87. package/dist/local/local-cli.config.mjs +159 -0
  88. package/dist/logs/log-follow.config.mjs +21 -0
  89. package/dist/logs/log-follow.service.mjs +87 -0
  90. package/dist/logs/log-lines.pure.mjs +44 -0
  91. package/dist/migrate-runner.pure.mjs +28 -0
  92. package/dist/operation-input.pure-CS9iUhqK.mjs +1383 -0
  93. package/dist/operation-registry.pure-DVC8EDgj.mjs +283 -0
  94. package/dist/registry/actor-call.service.mjs +1 -0
  95. package/dist/registry/actor-commands.config.mjs +127 -0
  96. package/dist/registry/actor-refusal.pure.mjs +34 -0
  97. package/dist/registry/approval.service.mjs +80 -0
  98. package/dist/registry/cloud-commands.config.mjs +151 -0
  99. package/dist/registry/cloud-served.pure.mjs +31 -0
  100. package/dist/registry/operation-call.service.mjs +262 -0
  101. package/dist/registry/operation-input.pure.mjs +1 -0
  102. package/dist/registry/operation-output.pure.mjs +91 -0
  103. package/dist/registry/served-command.config.mjs +57 -0
  104. package/dist/runner-exited.errors.mjs +10 -0
  105. package/dist/terminal/cli-exit.errors.mjs +38 -0
  106. package/dist/terminal/cli-failure.pure.mjs +31 -0
  107. package/dist/terminal/confirm.adapter.mjs +27 -0
  108. package/package.json +61 -3
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { t as runAkter } from "./akter-program.config-C4cXx4Fr.mjs";
3
+ // src/akter.cli.ts
4
+ runAkter();
5
+ export {};
@@ -0,0 +1,49 @@
1
+ import { B as flatMap, Gt as scoped } from "./Effect-CKFpqbmw.mjs";
2
+ import { r as withDescription, t as String } from "./Argument-D81dqizB.mjs";
3
+ import { c as Int, f as optional, i as withDescription$2, l as String$1, m as withDescription$1, o as Boolean, p as withDefault, t as make } from "./Command-Dzq9KM-h.mjs";
4
+ import { exportArchive, importApp, importInto } from "./archive/archive-cli.config.mjs";
5
+ import { Bench } from "./bench/bench.service.mjs";
6
+ import { Credentials } from "./credentials/credentials.service.mjs";
7
+ import { Deployment } from "./deploy/deploy.service.mjs";
8
+ import { ActorExport } from "./export/actor-export.service.mjs";
9
+ // src/app-cli.config.ts
10
+ const entry = String$1("entry").pipe(withDefault("src/app.layer.ts"), withDescription$1("The module that exports app = { actors, layer }"));
11
+ const workers = 8;
12
+ const organization = String$1("organization").pipe(optional, withDescription$1("The organization; defaults to AKTER_ORGANIZATION, then the one saved by akter login"));
13
+ const url = String$1("url").pipe(optional, withDescription$1("The Cloud API's URL; defaults to AKTER_CLOUD_URL, then the one saved by akter login, then https://api.akter.dev"));
14
+ const deploy = make("deploy", {
15
+ app: String$1("app"),
16
+ domain: String$1("domain").pipe(optional, withDescription$1("Claim this customer hostname and print the CNAME to add")),
17
+ entry,
18
+ environment: String$1("environment").pipe(withDefault("prod")),
19
+ json: Boolean("json").pipe(withDefault(false), withDescription$1("Print the live deploy as one line of JSON in the API's Deploy schema; progress goes to stderr")),
20
+ organization,
21
+ url
22
+ }, (options) => flatMap(Credentials.use((credentials) => credentials.target(options)), (target) => Deployment.use((service) => service.run({
23
+ ...options,
24
+ ...target
25
+ })).pipe(scoped))).pipe(withDescription$2("Build the app, upload it and follow the deploy until it serves or fails"));
26
+ const exportApp = make("export", {
27
+ app: String$1("app").pipe(withDescription$1("The app whose actors to export")),
28
+ organization,
29
+ out: String$1("out").pipe(withDescription$1("The .akter archive to write; run again with the same path to resume")),
30
+ url
31
+ }, (options) => flatMap(Credentials.use((credentials) => credentials.target(options)), (target) => exportArchive({
32
+ out: options.out,
33
+ write: (staging) => ActorExport.use((service) => service.run({
34
+ ...options,
35
+ ...target,
36
+ out: staging
37
+ })).pipe(scoped)
38
+ }))).pipe(withDescription$2("Download every actor of an app into one .akter archive, resuming where a run stopped"));
39
+ const bench = make("bench", {
40
+ concurrency: Int("concurrency").pipe(withDefault(workers), withDescription$1("How many steps run at once, each waiting for its last to finish")),
41
+ duration: String$1("duration").pipe(withDefault("30s"), withDescription$1("How long to start new steps, like 30s or 5m; steps in flight then finish")),
42
+ entry,
43
+ json: Boolean("json").pipe(withDefault(false), withDescription$1("Print the report as one JSON object")),
44
+ stepTimeout: String$1("step-timeout").pipe(withDefault("30s"), withDescription$1("How long the workload's open and each step may take before it counts as failed, so one stuck step can't hold the run open")),
45
+ system: String$1("system").pipe(optional, withDescription$1("Another system to run the workload against for comparison, such as \"Cloudflare Durable Object\"; it needs --url, and akter bench signs in to nothing")),
46
+ url: String$1("url").pipe(optional, withDescription$1("The app to run against: a loopback akter dev, or an Akter Cloud app signed in to with AKTER_API_KEY or akter login. Without it, akter bench starts akter dev on an empty folder")),
47
+ workload: String("workload").pipe(withDescription("The module that exports workload = ({ id }) => ({ name, open, step })"))
48
+ }, (options) => Bench.use((service) => service.run(options)).pipe(scoped)).pipe(withDescription$2("Run a workload against akter dev or an Akter Cloud app and report throughput and latency"));
49
+ export { bench, deploy, entry, exportApp, importApp, importInto };
@@ -0,0 +1,31 @@
1
+ // src/app-entry.pure.ts
2
+ const typeScriptImports = [
3
+ "import * as nodeModule from \"node:module\";",
4
+ "",
5
+ "if (process.versions.bun === undefined) {",
6
+ " nodeModule.registerHooks({",
7
+ " resolve: (specifier, context, nextResolve) => {",
8
+ " try {",
9
+ " return nextResolve(specifier, context);",
10
+ " } catch (error) {",
11
+ " const typeScriptImport = /^[.#]/u.test(specifier) && specifier.endsWith(\".js\") && context.parentURL?.endsWith(\".ts\");",
12
+ " if (error?.code !== \"ERR_MODULE_NOT_FOUND\" || !typeScriptImport) throw error;",
13
+ " return nextResolve(specifier.slice(0, -\".js\".length) + \".ts\", context);",
14
+ " }",
15
+ " },",
16
+ " });",
17
+ "}"
18
+ ];
19
+ const appEntry = ({ command, entry }) => [
20
+ ...typeScriptImports,
21
+ "",
22
+ `const entry = await import(${entry});`,
23
+ "",
24
+ "if (!(\"app\" in entry)) {",
25
+ ` console.error("akter ${command}: " + ${entry} + " has no app export. Export the app as: export const app = { actors, layer };");`,
26
+ " process.exit(1);",
27
+ "}",
28
+ "",
29
+ "const { app } = entry;"
30
+ ];
31
+ export { appEntry, typeScriptImports };
@@ -0,0 +1,65 @@
1
+ import { Dt as isGreaterThanOrEqualTo, F as String, I as Struct, an as optionalKey, g as Finite, n as ArraySchema, pt as fromJsonString, qt as isPattern, v as Int, x as Literal } from "../Schema-MiKt5K1v.mjs";
2
+ // src/archive/akter-archive.schema.ts
3
+ /** The format version this akter writes. Every version it ever wrote stays readable (ADR 0019). */
4
+ const archiveFormat = 1;
5
+ const noBytes = 0;
6
+ const sha256Pattern = /^[0-9a-f]{64}$/u;
7
+ const manifestPath = "manifest.json";
8
+ /** Where an export leaves the app's computers in its staging folder; packing moves them into the manifest. */
9
+ const computersPath = "computers.json";
10
+ const Sha256 = String.check(isPattern(sha256Pattern));
11
+ /** One archive entry after the manifest, with the size and SHA-256 its bytes must have. */
12
+ const ArchiveEntry = Struct({
13
+ bytes: Int.check(isGreaterThanOrEqualTo(noBytes)),
14
+ path: String,
15
+ sha256: Sha256
16
+ });
17
+ /** An actor as export paths name it: its type and its encoded key. */
18
+ const ArchivedActor = Struct({
19
+ key: String,
20
+ type: String
21
+ });
22
+ /**
23
+ * A forked actor and the actor it was forked from: `fork` is the fork's id and `forkedAt` the parent's commit version
24
+ * at the cut. Every archive written before forks had an empty lineage, so adding `fork` kept format 1 (ADR 0019).
25
+ */
26
+ const ArchivedLineage = Struct({
27
+ actor: ArchivedActor,
28
+ fork: String,
29
+ forkedAt: Int,
30
+ parent: ArchivedActor
31
+ });
32
+ /** An Archil checkpoint: it can't leave the account, so it is never portable. */
33
+ const ArchivedCheckpoint = Struct({
34
+ id: String,
35
+ portable: Literal(false)
36
+ });
37
+ /**
38
+ * A computer an actor owns, as Akter Cloud records it. Its disk stays in the Cloud: the newest checkpoint, when it
39
+ * has one, is an Archil checkpoint, which can't leave the account, so it is recorded but never portable. `egress`
40
+ * names allowed destinations, never credentials.
41
+ */
42
+ const ArchivedComputer = Struct({
43
+ actor: ArchivedActor,
44
+ checkpoint: optionalKey(ArchivedCheckpoint),
45
+ egress: ArraySchema(String),
46
+ image: String,
47
+ name: String,
48
+ size: Struct({
49
+ memoryMib: Int,
50
+ vcpu: Finite
51
+ })
52
+ });
53
+ /** Only the version, read first, so an archive from a newer akter is refused by name instead of by shape. */
54
+ const ArchiveVersion = fromJsonString(Struct({ format: Int }));
55
+ /** `manifest.json`, the first entry of every `.akter` archive. */
56
+ const ArchiveManifest = Struct({
57
+ computers: ArraySchema(ArchivedComputer),
58
+ createdAt: Int,
59
+ entries: ArraySchema(ArchiveEntry),
60
+ format: Literal(1),
61
+ lineage: ArraySchema(ArchivedLineage)
62
+ });
63
+ const ArchiveManifestJson = fromJsonString(ArchiveManifest);
64
+ const ArchivedComputersJson = fromJsonString(ArraySchema(ArchivedComputer));
65
+ export { ArchiveEntry, ArchiveManifest, ArchiveManifestJson, ArchiveVersion, ArchivedComputer, ArchivedComputersJson, ArchivedLineage, archiveFormat, computersPath, manifestPath };
@@ -0,0 +1,309 @@
1
+ import { T as join, c as every, j as map, z as sort } from "../Array-B4kuTQeW.mjs";
2
+ import { P as currentTimeMillis } from "../HttpClientResponse-D5ZKICVV.mjs";
3
+ import { At as provideContext, B as flatMap, Gn as mergeAll, Gt as scoped, I as filter, K as forEach, Kn as provide$1, M as fail, Mr as void_, Q as fromOption, Qn as close, T as context, Vn as effect, W as fnUntraced, Wt as scope, Xt as succeed, aa as Service, dt as map$1, er as fork, et as gen, ft as mapBoth, gn as void_$1, ht as mapError, ir as provide, l as as, lt as logWarning, nn as tapCause, o as andThen, tn as tap, tt as ignore, xt as onError } from "../Effect-CKFpqbmw.mjs";
4
+ import { t as FileSystem } from "../FileSystem-4pPcEb_i.mjs";
5
+ import { a as BlobHashing, d as get, f as isEmpty, l as empty, n as ArchivedActors, p as remove, r as blobHashing, t as ArchivedStore, u as fromIterable } from "../hosted-runtime.layer-Cx3IPsEd.mjs";
6
+ import { S as none, _ as isSome, h as isNone, w as some, x as match } from "../Option-DkxSnBhp.mjs";
7
+ import { a as String$1 } from "../Order-C1JC991I.mjs";
8
+ import { t as Path } from "../Path-Djdv_eF5.mjs";
9
+ import { mt as is, st as encodeEffect, tt as decodeEffect } from "../Schema-MiKt5K1v.mjs";
10
+ import { D as mapAccum, O as mapEffect, T as make, U as runFoldEffect, W as runForEach, k as mapError$1, m as flatMap$1, nt as unwrap, r as concat$1, y as fromIterable$1, z as run } from "../Stream-bK9QIETN.mjs";
11
+ import { h as startsWith, i as includes, m as split, r as endsWith } from "../String-q5dcsVe4.mjs";
12
+ import { ArchiveInvalidError } from "./archive-invalid.errors.mjs";
13
+ import { ArchiveManifestJson, ArchiveVersion, ArchivedComputersJson, computersPath, manifestPath } from "./akter-archive.schema.mjs";
14
+ import { parentOf } from "./archived-lineage.pure.mjs";
15
+ import { tarEnd, tarEntry, untar } from "./tar.pure.mjs";
16
+ import { Zstd } from "./zstd.adapter.mjs";
17
+ // src/archive/akter-archive.service.ts
18
+ /** The manifest of an archive holding thousands of actors stays far below this; a larger one is not ours. */
19
+ const largestManifest = 67108864;
20
+ const readBytes = 65536;
21
+ const noBytes = 0;
22
+ const ownerOnlyFile = 384;
23
+ const ownerOnlyFolder = 448;
24
+ const encoder = new globalThis.TextEncoder();
25
+ const invalid = (message) => ArchiveInvalidError.make({ message });
26
+ const failed = (doing) => (cause) => invalid(`${doing}: ${cause.message}`);
27
+ /** Partial downloads of the staging folder and its computers, which the manifest holds, are never archived as entries. */
28
+ const isStaging = (name) => name === "computers.json" || endsWith(".export")(name) || endsWith(".export.json")(name);
29
+ /** A safe relative path inside the folder: no absolute path, no `..`, no backslash, no empty segment. */
30
+ const isSafe = (path) => !startsWith("/")(path) && !includes("\\")(path) && every(split("/")(path), (part) => part !== "" && part !== "..");
31
+ const hashed = fnUntraced(function* hashed(folder, name) {
32
+ const fileSystem = yield* FileSystem;
33
+ const path = yield* Path;
34
+ const hash = yield* BlobHashing.use((hashing) => hashing.sha256);
35
+ const file = path.join(folder, name);
36
+ const stats = yield* fileSystem.stat(file);
37
+ yield* runForEach(fileSystem.stream(file, { chunkSize: readBytes }), hash.update);
38
+ const sha256 = yield* hash.digest;
39
+ return {
40
+ bytes: Number(stats.size),
41
+ path: join(split(path.sep)(name), "/"),
42
+ sha256
43
+ };
44
+ });
45
+ const filesIn = fnUntraced(function* filesIn(folder) {
46
+ const fileSystem = yield* FileSystem;
47
+ const path = yield* Path;
48
+ const names = yield* fileSystem.readDirectory(folder, { recursive: true });
49
+ const kept = (name) => !isStaging(name) && !ArchivedStore.isEnvironmentEntry(join(split(path.sep)(name), "/"));
50
+ const files = yield* filter(names, (name) => map$1(fileSystem.stat(path.join(folder, name)), (stats) => stats.type === "File" && kept(name)));
51
+ return sort(files, String$1);
52
+ });
53
+ /** An entry's content, refused if the file changed size after it was hashed, so a header never lies about a size. */
54
+ const exactly = (entry, content) => content.pipe(mapAccum(() => noBytes, (total, chunk) => [total + chunk.length, [{
55
+ chunk,
56
+ final: false,
57
+ total: total + chunk.length
58
+ }]], { onHalt: (total) => [{
59
+ chunk: /* @__PURE__ */ new Uint8Array(),
60
+ final: true,
61
+ total
62
+ }] }), mapEffect(({ chunk, final, total }) => {
63
+ if (total > entry.bytes || final && total !== entry.bytes) return fail(invalid(`${entry.path} changed size while it was archived; archive again`));
64
+ return succeed(chunk);
65
+ }));
66
+ const contentOf = fnUntraced(function* contentOf(folder, entry) {
67
+ const fileSystem = yield* FileSystem;
68
+ const path = yield* Path;
69
+ const read = fileSystem.stream(path.join(folder, entry.path), { chunkSize: readBytes });
70
+ const content = exactly(entry, mapError$1(read, failed(entry.path)));
71
+ return tarEntry({
72
+ content,
73
+ path: entry.path,
74
+ size: entry.bytes
75
+ });
76
+ });
77
+ /** The computers the export left in the staging folder, or none when the app has none or the export recorded none. */
78
+ const computersIn = fnUntraced(function* computersIn(folder) {
79
+ const fileSystem = yield* FileSystem;
80
+ const file = (yield* Path).join(folder, computersPath);
81
+ if (!(yield* fileSystem.exists(file))) return [];
82
+ const text = yield* fileSystem.readFileString(file);
83
+ return yield* mapError(decodeEffect(ArchivedComputersJson)(text), failed(`${computersPath} is invalid`));
84
+ });
85
+ const lineageEntry = ({ actor, fork, forkedAt, origin }) => match(parentOf(origin), {
86
+ onNone: () => fail(invalid(`${actor.type}/${actor.key} names the actor it was forked from as ${origin}, not as <Actor>/<key>`)),
87
+ onSome: (parent) => succeed({
88
+ actor,
89
+ fork,
90
+ forkedAt,
91
+ parent
92
+ })
93
+ });
94
+ /** Each forked actor's parent, the fork's id and the version at the cut, as the actors' databases record them. */
95
+ const lineageIn = fnUntraced(function* lineageIn(folder) {
96
+ const forks = yield* ArchivedActors.lineage(folder).pipe(tapCause(logWarning), mapError(() => invalid("its actors' fork lineage could not be read")));
97
+ return yield* forEach(forks, lineageEntry);
98
+ });
99
+ const storeOf = (folder) => mapError(ArchivedStore.served(folder), failed("its environments record could not be read"));
100
+ const manifestFor = fnUntraced(function* manifestFor(folder) {
101
+ const store = yield* storeOf(folder);
102
+ const names = yield* filesIn(store);
103
+ const entries = yield* forEach(names, (name) => hashed(store, name));
104
+ const computers = yield* computersIn(store);
105
+ const lineage = yield* lineageIn(store);
106
+ const manifest = {
107
+ computers,
108
+ createdAt: yield* currentTimeMillis,
109
+ entries,
110
+ format: 1,
111
+ lineage
112
+ };
113
+ const manifestText = yield* encodeEffect(ArchiveManifestJson)(manifest);
114
+ return {
115
+ header: encoder.encode(manifestText),
116
+ manifest,
117
+ store
118
+ };
119
+ });
120
+ const packed = fnUntraced(function* packed({ archive, folder }) {
121
+ const fileSystem = yield* FileSystem;
122
+ const zstd = yield* Zstd;
123
+ const { header, manifest, store } = yield* manifestFor(folder);
124
+ const first = tarEntry({
125
+ content: make(header),
126
+ path: manifestPath,
127
+ size: header.length
128
+ });
129
+ const rest = flatMap$1(fromIterable$1(manifest.entries), (entry) => unwrap(contentOf(store, entry)));
130
+ const tar = concat$1(concat$1(first, rest), tarEnd);
131
+ yield* run(zstd.compress(tar), fileSystem.sink(`${archive}.partial`, { mode: ownerOnlyFile }));
132
+ yield* fileSystem.rename(`${archive}.partial`, archive);
133
+ return manifest;
134
+ });
135
+ /**
136
+ * Writes `folder` (the staging layout: `shards/<shard>/<Actor>/<key>/actor.db` and each actor's files) as one `.akter`
137
+ * archive. A local data directory is packed as the one environment it serves: the folder itself for `dev`, or a
138
+ * promoted branch's store, and never its record, its snapshots or another environment. It is a zstd-compressed tar
139
+ * whose first entry is `manifest.json` with every entry's size and SHA-256. The archive is written beside its final
140
+ * name and renamed only when complete, so a cut run never leaves a file that looks done.
141
+ */
142
+ const pack = ({ archive, folder }) => mapError(packed({
143
+ archive,
144
+ folder
145
+ }), (cause) => {
146
+ if (is(ArchiveInvalidError)(cause)) return cause;
147
+ return invalid(`could not write ${archive}: ${cause.message}`);
148
+ });
149
+ const concat = (left, right) => {
150
+ const joined = new Uint8Array(left.length + right.length);
151
+ joined.set(left);
152
+ joined.set(right, left.length);
153
+ return joined;
154
+ };
155
+ const finishWriting = (writing) => flatMap(tap(writing.hash.digest, close(writing.scope, void_)), (sha256) => {
156
+ if (writing.written !== writing.entry.bytes || sha256 !== writing.entry.sha256) return fail(invalid(`${writing.entry.path} does not match its checksum in the manifest`));
157
+ return void_$1;
158
+ });
159
+ const finishEntry = (state) => match(state.writing, {
160
+ onNone: () => succeed(state),
161
+ onSome: (writing) => as(finishWriting(writing), {
162
+ ...state,
163
+ writing: none()
164
+ })
165
+ });
166
+ const manifestOf = fnUntraced(function* manifestOf(bytes) {
167
+ const text = new globalThis.TextDecoder().decode(bytes);
168
+ const { format } = yield* mapError(decodeEffect(ArchiveVersion)(text), () => invalid("its manifest.json is not JSON"));
169
+ if (format > 1) return yield* invalid(`it is format ${String(format)}, written by a newer akter; upgrade akter to read it`);
170
+ return yield* mapError(decodeEffect(ArchiveManifestJson)(text), failed("its manifest.json is invalid"));
171
+ });
172
+ const settleManifest = (state) => match(state.collected, {
173
+ onNone: () => succeed(state),
174
+ onSome: (bytes) => map$1(manifestOf(bytes), (manifest) => ({
175
+ ...state,
176
+ collected: none(),
177
+ expected: fromIterable(map(manifest.entries, (entry) => [entry.path, entry])),
178
+ manifest: some(manifest)
179
+ }))
180
+ });
181
+ const startWriting = fnUntraced(function* startWriting(folder, entry) {
182
+ const fileSystem = yield* FileSystem;
183
+ const path = yield* Path;
184
+ const target = path.join(folder, entry.path);
185
+ const scope$1 = yield* flatMap(scope, (parent) => fork(parent, "sequential"));
186
+ yield* fileSystem.makeDirectory(path.dirname(target), {
187
+ mode: ownerOnlyFolder,
188
+ recursive: true
189
+ });
190
+ return {
191
+ entry,
192
+ file: yield* provide(fileSystem.open(target, {
193
+ flag: "wx",
194
+ mode: ownerOnlyFile
195
+ }), scope$1),
196
+ hash: yield* BlobHashing.use((hashing) => hashing.sha256),
197
+ scope: scope$1,
198
+ written: noBytes
199
+ };
200
+ });
201
+ const onEntry = (state, { folder, path, size }) => {
202
+ if (isNone(state.manifest)) {
203
+ if (path !== "manifest.json" || size > largestManifest) return fail(invalid("its first entry is not manifest.json"));
204
+ return succeed({
205
+ ...state,
206
+ collected: some(/* @__PURE__ */ new Uint8Array())
207
+ });
208
+ }
209
+ const expected = get(state.expected, path);
210
+ if (isNone(expected) || !isSafe(path) || expected.value.bytes !== size) return fail(invalid(`${path} is not an entry its manifest lists`));
211
+ return mapBoth(startWriting(folder, expected.value), {
212
+ onFailure: failed(path),
213
+ onSuccess: (writing) => ({
214
+ ...state,
215
+ expected: remove(state.expected, path),
216
+ writing: some(writing)
217
+ })
218
+ });
219
+ };
220
+ const onData = (state, bytes) => {
221
+ if (isSome(state.collected)) return succeed({
222
+ ...state,
223
+ collected: some(concat(state.collected.value, bytes))
224
+ });
225
+ return match(state.writing, {
226
+ onNone: () => fail(invalid("it holds bytes outside any entry")),
227
+ onSome: (writing) => {
228
+ const written = andThen(mapError(writing.file.writeAll(bytes), failed(writing.entry.path)), writing.hash.update(bytes));
229
+ return as(written, {
230
+ ...state,
231
+ writing: some({
232
+ ...writing,
233
+ written: writing.written + bytes.length
234
+ })
235
+ });
236
+ }
237
+ });
238
+ };
239
+ const onEnd = (state) => {
240
+ if (isNone(state.manifest) || !isEmpty(state.expected)) return fail(invalid("it ends before every entry its manifest lists"));
241
+ return succeed(state);
242
+ };
243
+ const step = (folder) => (state, event) => {
244
+ if (event.kind === "data") return onData(state, event.bytes);
245
+ if (event.kind === "truncated") return fail(invalid("it is cut short"));
246
+ if (event.kind === "invalid") return fail(invalid(event.reason));
247
+ const settled = flatMap(finishEntry(state), settleManifest);
248
+ if (event.kind === "end") return flatMap(settled, onEnd);
249
+ return flatMap(settled, (next) => onEntry(next, {
250
+ folder,
251
+ path: event.path,
252
+ size: event.size
253
+ }));
254
+ };
255
+ const emptyState = {
256
+ collected: none(),
257
+ expected: empty(),
258
+ manifest: none(),
259
+ writing: none()
260
+ };
261
+ /** Reads every entry of `archive` into `folder`, checking each against the manifest. */
262
+ const entriesInto = fnUntraced(function* entriesInto({ archive, folder }) {
263
+ const fileSystem = yield* FileSystem;
264
+ const zstd = yield* Zstd;
265
+ const read = mapError$1(fileSystem.stream(archive, { chunkSize: readBytes }), failed(archive));
266
+ return yield* runFoldEffect(untar(zstd.decompress(read)), () => emptyState, step(folder));
267
+ });
268
+ const unpacked = fnUntraced(function* unpacked({ archive, folder }) {
269
+ const fileSystem = yield* FileSystem;
270
+ const staging = `${folder}.importing`;
271
+ if (yield* fileSystem.exists(folder)) return yield* invalid(`${folder} already exists; import into a new folder`);
272
+ const removeStaging = ignore(fileSystem.remove(staging, {
273
+ force: true,
274
+ recursive: true
275
+ }));
276
+ yield* removeStaging;
277
+ const adopted = gen(function* adoptedEntries() {
278
+ const done = yield* entriesInto({
279
+ archive,
280
+ folder: staging
281
+ });
282
+ const manifest = yield* mapError(fromOption(done.manifest), () => invalid("it holds no manifest.json"));
283
+ yield* ArchivedActors.adopt(staging).pipe(tapCause(logWarning), mapError(() => invalid("its actors' files could not be taken into the folder's file home")));
284
+ return manifest;
285
+ });
286
+ const manifest = yield* onError(adopted, () => removeStaging);
287
+ return yield* as(fileSystem.rename(staging, folder), manifest);
288
+ });
289
+ /**
290
+ * Reads an `.akter` archive into a new folder in the staging layout, which `akter dev` serves. Every entry is checked
291
+ * against the size and SHA-256 its manifest records, and an entry the manifest doesn't list is refused. Each actor's
292
+ * `files/<sha256>` is then adopted into the folder's file home under the actor's local identity. The folder appears
293
+ * only once every entry has passed and every file is adopted, so a cut or altered archive never leaves a folder that
294
+ * looks imported.
295
+ */
296
+ const unpack = ({ archive, folder }) => mapError(unpacked({
297
+ archive,
298
+ folder
299
+ }), (cause) => {
300
+ if (is(ArchiveInvalidError)(cause)) return ArchiveInvalidError.make({ message: `${archive} can't be imported: ${cause.message}` });
301
+ return invalid(`${archive} can't be imported: ${cause.message}`);
302
+ });
303
+ var AkterArchive = class extends Service()("@akterjs/cli/archive/akter-archive.service/AkterArchive", { make: map$1(context(), (context) => ({
304
+ pack: (packing) => provideContext(scoped(pack(packing)), context),
305
+ unpack: (packing) => provideContext(scoped(unpack(packing)), context)
306
+ })) }) {
307
+ static layer = effect(this, this.make).pipe(provide$1(mergeAll(Zstd.layer, blobHashing)));
308
+ };
309
+ export { AkterArchive };
@@ -0,0 +1,45 @@
1
+ import { l as filter } from "../Array-B4kuTQeW.mjs";
2
+ import { n as log } from "../Console-BKHUkH6a.mjs";
3
+ import { R as filterOrFail, U as fn, W as fnUntraced, Xt as succeed } from "../Effect-CKFpqbmw.mjs";
4
+ import { t as FileSystem } from "../FileSystem-4pPcEb_i.mjs";
5
+ import { t as Path } from "../Path-Djdv_eF5.mjs";
6
+ import { r as withDescription, t as String$1 } from "../Argument-D81dqizB.mjs";
7
+ import { i as withDescription$2, l as String$2, m as withDescription$1, p as withDefault, t as make } from "../Command-Dzq9KM-h.mjs";
8
+ import { AkterArchive } from "./akter-archive.service.mjs";
9
+ import { ArchiveInvalidError } from "./archive-invalid.errors.mjs";
10
+ // src/archive/archive-cli.config.ts
11
+ const actorDatabase = /^shards\/\d{3}\/[^/]+\/[^/]+\/actor\.db$/u;
12
+ /** Reads an `.akter` archive into a new folder that `akter dev`, `akter history` and `akter migrate` read. */
13
+ const importInto = fn("importInto")(function* importInto({ archive, dir }) {
14
+ const path = yield* Path;
15
+ const folder = path.resolve(dir);
16
+ const manifest = yield* AkterArchive.use((archives) => archives.unpack({
17
+ archive: path.resolve(archive),
18
+ folder
19
+ }));
20
+ const actors = filter(manifest.entries, (entry) => actorDatabase.test(entry.path)).length;
21
+ yield* log(`Imported ${String(actors)} actors (${String(manifest.entries.length)} entries) into ${folder}`);
22
+ return manifest;
23
+ });
24
+ /**
25
+ * Writes an app's actors into `<out>.staging` with `write`, the folder layout a rerun resumes, then packs that folder
26
+ * into the archive at `out` and removes it. The archive appears only once it is complete.
27
+ */
28
+ const exportArchive = fnUntraced(function* exportArchive({ out, write }) {
29
+ const fileSystem = yield* FileSystem;
30
+ const archive = yield* Path.use((path) => succeed(path.resolve(out)));
31
+ const staging = `${archive}.staging`;
32
+ yield* filterOrFail(fileSystem.exists(archive), (exists) => !exists, () => ArchiveInvalidError.make({ message: `${archive} already exists; export to a new file` }));
33
+ yield* write(staging);
34
+ const manifest = yield* AkterArchive.use((archives) => archives.pack({
35
+ archive,
36
+ folder: staging
37
+ }));
38
+ yield* fileSystem.remove(staging, { recursive: true });
39
+ yield* log(`Wrote ${archive} with ${String(manifest.entries.length)} entries`);
40
+ });
41
+ const importApp = make("import", {
42
+ archive: String$1("archive").pipe(withDescription("The .akter archive akter export wrote")),
43
+ dir: String$2("dir").pipe(withDefault(".akter"), withDescription$1("A new folder to read the actors into; it must not exist yet"))
44
+ }, importInto).pipe(withDescription$2("Read an .akter archive into a folder, checking every entry against its manifest"));
45
+ export { exportArchive, importApp, importInto };
@@ -0,0 +1,5 @@
1
+ import { F as String, z as TaggedError } from "../Schema-MiKt5K1v.mjs";
2
+ // src/archive/archive-invalid.errors.ts
3
+ /** An `.akter` archive that can't be written or read as one: cut short, corrupt, mismatched or from a newer akter. */
4
+ var ArchiveInvalidError = class extends TaggedError()("ArchiveInvalidError", { message: String }) {};
5
+ export { ArchiveInvalidError };
@@ -0,0 +1,13 @@
1
+ import { t as all, u as fromNullishOr } from "../Option-DkxSnBhp.mjs";
2
+ // src/archive/archived-lineage.pure.ts
3
+ /** Akter Cloud names a fork's parent `<Actor>/<keySegment>`, with the key encoded as its folder name. */
4
+ const cloudIdentity = /^(?<type>[^/:]+)\/(?<key>[^/]+)$/u;
5
+ /** The parent a Cloud actor's `fork.origin` names, or none when it is not in Cloud's `<Actor>/<keySegment>` form. */
6
+ const parentOf = (origin) => {
7
+ const groups = cloudIdentity.exec(origin)?.groups;
8
+ return all({
9
+ key: fromNullishOr(groups?.key),
10
+ type: fromNullishOr(groups?.type)
11
+ });
12
+ };
13
+ export { parentOf };