swarm-deploy 0.0.0 → 0.2.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 (148) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/LICENSE.md +201 -0
  3. package/NOTICE.md +13 -0
  4. package/README.md +1283 -0
  5. package/RELEASING.md +71 -0
  6. package/SECURITY.md +40 -0
  7. package/dist/abort.d.ts +16 -0
  8. package/dist/abort.js +52 -0
  9. package/dist/abort.js.map +1 -0
  10. package/dist/allowlist.d.ts +2 -0
  11. package/dist/allowlist.js +21 -0
  12. package/dist/allowlist.js.map +1 -0
  13. package/dist/bin/swarm-deploy.d.ts +2 -0
  14. package/dist/bin/swarm-deploy.js +41 -0
  15. package/dist/bin/swarm-deploy.js.map +1 -0
  16. package/dist/cli.d.ts +27 -0
  17. package/dist/cli.js +751 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/client.d.ts +111 -0
  20. package/dist/client.js +276 -0
  21. package/dist/client.js.map +1 -0
  22. package/dist/direct-dht.d.ts +81 -0
  23. package/dist/direct-dht.js +254 -0
  24. package/dist/direct-dht.js.map +1 -0
  25. package/dist/error-code.d.ts +15 -0
  26. package/dist/error-code.js +30 -0
  27. package/dist/error-code.js.map +1 -0
  28. package/dist/errors.d.ts +31 -0
  29. package/dist/errors.js +37 -0
  30. package/dist/errors.js.map +1 -0
  31. package/dist/events-node.d.ts +2 -0
  32. package/dist/events-node.js +7 -0
  33. package/dist/events-node.js.map +1 -0
  34. package/dist/files.d.ts +32 -0
  35. package/dist/files.js +85 -0
  36. package/dist/files.js.map +1 -0
  37. package/dist/fs-node.d.ts +2 -0
  38. package/dist/fs-node.js +7 -0
  39. package/dist/fs-node.js.map +1 -0
  40. package/dist/hooks-module.d.ts +9 -0
  41. package/dist/hooks-module.js +118 -0
  42. package/dist/hooks-module.js.map +1 -0
  43. package/dist/hooks.d.ts +69 -0
  44. package/dist/hooks.js +88 -0
  45. package/dist/hooks.js.map +1 -0
  46. package/dist/identity.d.ts +7 -0
  47. package/dist/identity.js +50 -0
  48. package/dist/identity.js.map +1 -0
  49. package/dist/index.d.ts +12 -0
  50. package/dist/index.js +21 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/os-node.d.ts +2 -0
  53. package/dist/os-node.js +7 -0
  54. package/dist/os-node.js.map +1 -0
  55. package/dist/path-node.d.ts +2 -0
  56. package/dist/path-node.js +7 -0
  57. package/dist/path-node.js.map +1 -0
  58. package/dist/process-node.d.ts +2 -0
  59. package/dist/process-node.js +7 -0
  60. package/dist/process-node.js.map +1 -0
  61. package/dist/release.d.ts +26 -0
  62. package/dist/release.js +419 -0
  63. package/dist/release.js.map +1 -0
  64. package/dist/server.d.ts +239 -0
  65. package/dist/server.js +928 -0
  66. package/dist/server.js.map +1 -0
  67. package/dist/storage/atomic-file.d.ts +15 -0
  68. package/dist/storage/atomic-file.js +172 -0
  69. package/dist/storage/atomic-file.js.map +1 -0
  70. package/dist/storage/commit-journal.d.ts +120 -0
  71. package/dist/storage/commit-journal.js +417 -0
  72. package/dist/storage/commit-journal.js.map +1 -0
  73. package/dist/storage/commit-store.d.ts +307 -0
  74. package/dist/storage/commit-store.js +1691 -0
  75. package/dist/storage/commit-store.js.map +1 -0
  76. package/dist/storage/layout.d.ts +16 -0
  77. package/dist/storage/layout.js +409 -0
  78. package/dist/storage/layout.js.map +1 -0
  79. package/dist/storage/link-store.d.ts +70 -0
  80. package/dist/storage/link-store.js +363 -0
  81. package/dist/storage/link-store.js.map +1 -0
  82. package/dist/storage/recovery.d.ts +100 -0
  83. package/dist/storage/recovery.js +260 -0
  84. package/dist/storage/recovery.js.map +1 -0
  85. package/dist/storage/retention.d.ts +183 -0
  86. package/dist/storage/retention.js +619 -0
  87. package/dist/storage/retention.js.map +1 -0
  88. package/dist/storage/root-coordinator.d.ts +7 -0
  89. package/dist/storage/root-coordinator.js +27 -0
  90. package/dist/storage/root-coordinator.js.map +1 -0
  91. package/dist/storage/session-store.d.ts +2 -0
  92. package/dist/storage/session-store.js +7 -0
  93. package/dist/storage/session-store.js.map +1 -0
  94. package/dist/storage/tar-session-store.d.ts +147 -0
  95. package/dist/storage/tar-session-store.js +1044 -0
  96. package/dist/storage/tar-session-store.js.map +1 -0
  97. package/dist/storage/tree-fs.d.ts +44 -0
  98. package/dist/storage/tree-fs.js +580 -0
  99. package/dist/storage/tree-fs.js.map +1 -0
  100. package/dist/storage/tree-staging.d.ts +9 -0
  101. package/dist/storage/tree-staging.js +64 -0
  102. package/dist/storage/tree-staging.js.map +1 -0
  103. package/dist/storage/types.d.ts +77 -0
  104. package/dist/storage/types.js +3 -0
  105. package/dist/storage/types.js.map +1 -0
  106. package/dist/symlinks.d.ts +36 -0
  107. package/dist/symlinks.js +128 -0
  108. package/dist/symlinks.js.map +1 -0
  109. package/dist/tar-protocol/controls.d.ts +73 -0
  110. package/dist/tar-protocol/controls.js +291 -0
  111. package/dist/tar-protocol/controls.js.map +1 -0
  112. package/dist/tar-protocol/direct-wire.d.ts +43 -0
  113. package/dist/tar-protocol/direct-wire.js +194 -0
  114. package/dist/tar-protocol/direct-wire.js.map +1 -0
  115. package/dist/tar-protocol/extract.d.ts +18 -0
  116. package/dist/tar-protocol/extract.js +124 -0
  117. package/dist/tar-protocol/extract.js.map +1 -0
  118. package/dist/tar-protocol/hash.d.ts +10 -0
  119. package/dist/tar-protocol/hash.js +44 -0
  120. package/dist/tar-protocol/hash.js.map +1 -0
  121. package/dist/tar-protocol/lifecycle.d.ts +13 -0
  122. package/dist/tar-protocol/lifecycle.js +90 -0
  123. package/dist/tar-protocol/lifecycle.js.map +1 -0
  124. package/dist/tar-protocol/manifest.d.ts +58 -0
  125. package/dist/tar-protocol/manifest.js +312 -0
  126. package/dist/tar-protocol/manifest.js.map +1 -0
  127. package/dist/tar-protocol/tree-extract.d.ts +37 -0
  128. package/dist/tar-protocol/tree-extract.js +240 -0
  129. package/dist/tar-protocol/tree-extract.js.map +1 -0
  130. package/dist/tar-protocol/tree-manifest.d.ts +45 -0
  131. package/dist/tar-protocol/tree-manifest.js +277 -0
  132. package/dist/tar-protocol/tree-manifest.js.map +1 -0
  133. package/dist/tar-protocol/tree-path.d.ts +15 -0
  134. package/dist/tar-protocol/tree-path.js +68 -0
  135. package/dist/tar-protocol/tree-path.js.map +1 -0
  136. package/dist/tar-protocol/tree.d.ts +68 -0
  137. package/dist/tar-protocol/tree.js +269 -0
  138. package/dist/tar-protocol/tree.js.map +1 -0
  139. package/dist/tar-protocol/ustar.d.ts +36 -0
  140. package/dist/tar-protocol/ustar.js +167 -0
  141. package/dist/tar-protocol/ustar.js.map +1 -0
  142. package/dist/types.d.ts +61 -0
  143. package/dist/types.js +3 -0
  144. package/dist/types.js.map +1 -0
  145. package/dist/validation.d.ts +4 -0
  146. package/dist/validation.js +26 -0
  147. package/dist/validation.js.map +1 -0
  148. package/package.json +103 -1
package/dist/cli.js ADDED
@@ -0,0 +1,751 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.USAGE = void 0;
7
+ exports.main = main;
8
+ const b4a_1 = __importDefault(require("b4a"));
9
+ const error_code_js_1 = require("./error-code.js");
10
+ const _fs_1 = __importDefault(require("#fs"));
11
+ const _os_1 = __importDefault(require("#os"));
12
+ const _path_1 = __importDefault(require("#path"));
13
+ const _process_1 = __importDefault(require("#process"));
14
+ const errors_js_1 = require("./errors.js");
15
+ const identity_js_1 = require("./identity.js");
16
+ const files_js_1 = require("./files.js");
17
+ const symlinks_js_1 = require("./symlinks.js");
18
+ const hooks_module_js_1 = require("./hooks-module.js");
19
+ const server_js_1 = require("./server.js");
20
+ const client_js_1 = require("./client.js");
21
+ const COMMANDS = new Set(['keygen', 'public-key', 'server', 'upload']);
22
+ const HELP_ARGS = new Set(['--help', '-h']);
23
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
24
+ const MAX_SEED_FILE_BYTES = 65;
25
+ const SERVER_SEED_ENV = 'SWARM_DEPLOY_SERVER_SEED';
26
+ const CLIENT_SEED_ENV = 'SWARM_DEPLOY_CLIENT_SEED';
27
+ const USAGE = [
28
+ 'Usage:',
29
+ ' swarm-deploy keygen --out <seed-file>',
30
+ ' swarm-deploy public-key (--seed-file <seed-file> | --seed <64-lower-hex>)',
31
+ ' swarm-deploy server (--seed-file <seed-file> | --seed <64-lower-hex>) --storage <dir> --allow-key <64-lower-hex> --max-file-bytes <bytes> --max-staging-bytes <bytes> [--allow-key <64-lower-hex>]... [--max-storage-bytes <bytes>] [--max-age-days <days>] [--replace-name <safe-basename>]... [--artifact-pattern <template>]... [--max-count <count>] [--max-versions <count> --version-granularity <major|minor>] [--symlink <selector> <link-name>]... [--hooks <module>]',
32
+ ' swarm-deploy upload (--seed-file <seed-file> | --seed <64-lower-hex>) --server-key <64-lower-hex> [--idle-timeout <milliseconds>] [--no-source-parent] <file-or-directory>',
33
+ '',
34
+ 'Use exactly one seed source. A command-specific environment variable is also accepted.'
35
+ ].join('\n');
36
+ exports.USAGE = USAGE;
37
+ class CliError extends Error {
38
+ exitCode;
39
+ constructor(message, exitCode = 2) {
40
+ super(message);
41
+ this.name = 'CliError';
42
+ this.exitCode = exitCode;
43
+ }
44
+ }
45
+ function usageError(message) {
46
+ return new CliError(`${message}\n${USAGE}`, 2);
47
+ }
48
+ function configError(message) {
49
+ return new CliError(message, 2);
50
+ }
51
+ function basenameOf(value) {
52
+ try {
53
+ return _path_1.default.basename(String(value));
54
+ }
55
+ catch {
56
+ return String(value);
57
+ }
58
+ }
59
+ function isRuntime(value) {
60
+ const base = basenameOf(value);
61
+ return base === 'node' || base === 'node.exe' || base === 'bare' || base === 'bare.exe';
62
+ }
63
+ function isScript(value) {
64
+ const base = basenameOf(value);
65
+ return base === 'swarm-deploy' || base === 'swarm-deploy.js';
66
+ }
67
+ function normalizeArgv(argv) {
68
+ const args = Array.from(argv || []);
69
+ if (args.length >= 2 && isRuntime(args[0]) && isScript(args[1]))
70
+ return args.slice(2);
71
+ if (args.length >= 1 && isScript(args[0]))
72
+ return args.slice(1);
73
+ return args;
74
+ }
75
+ function platformName() {
76
+ return _os_1.default.platform();
77
+ }
78
+ function noFollowFlag() {
79
+ if (_fs_1.default.constants?.O_NOFOLLOW !== undefined)
80
+ return _fs_1.default.constants.O_NOFOLLOW;
81
+ const platform = platformName();
82
+ if (platform === 'darwin')
83
+ return 0x100;
84
+ if (platform === 'linux')
85
+ return 0x20000;
86
+ throw configError('Safe file open is unsupported on this platform');
87
+ }
88
+ function exclusiveCreateFlag() {
89
+ if (_fs_1.default.constants?.O_EXCL !== undefined)
90
+ return _fs_1.default.constants.O_EXCL;
91
+ const platform = platformName();
92
+ if (platform === 'darwin')
93
+ return 0x800;
94
+ if (platform === 'linux')
95
+ return 0x80;
96
+ throw configError('Safe exclusive create is unsupported on this platform');
97
+ }
98
+ function createFileFlag() {
99
+ if (_fs_1.default.constants?.O_CREAT !== undefined)
100
+ return _fs_1.default.constants.O_CREAT;
101
+ const platform = platformName();
102
+ if (platform === 'darwin')
103
+ return 0x200;
104
+ if (platform === 'linux')
105
+ return 0x40;
106
+ throw configError('Safe exclusive create is unsupported on this platform');
107
+ }
108
+ function createSeedFlags() {
109
+ const constants = _fs_1.default.constants || {};
110
+ return (constants.O_WRONLY ?? 1) | createFileFlag() | exclusiveCreateFlag() | noFollowFlag();
111
+ }
112
+ function readFlags() {
113
+ return (_fs_1.default.constants?.O_RDONLY ?? 0) | noFollowFlag();
114
+ }
115
+ function parsePositiveSafeInteger(value, name) {
116
+ if (typeof value !== 'string' || !/^[1-9][0-9]*$/.test(value) || value.length > 16) {
117
+ throw usageError(`Invalid ${name}`);
118
+ }
119
+ const number = Number(value);
120
+ if (!Number.isSafeInteger(number) || number <= 0)
121
+ throw usageError(`Invalid ${name}`);
122
+ return number;
123
+ }
124
+ function parseMaxAgeDays(value) {
125
+ const days = parsePositiveSafeInteger(value, 'max-age-days');
126
+ if (days > Math.floor(Number.MAX_SAFE_INTEGER / MS_PER_DAY)) {
127
+ throw usageError('Invalid max-age-days');
128
+ }
129
+ return days * MS_PER_DAY;
130
+ }
131
+ function envHas(env, name) {
132
+ return !!env && Object.prototype.hasOwnProperty.call(env, name) && env[name] !== undefined;
133
+ }
134
+ function parseSeedText(text, label) {
135
+ if (typeof text !== 'string')
136
+ throw configError(`Invalid ${label}`);
137
+ const body = text.endsWith('\n') ? text.slice(0, -1) : text;
138
+ if (text !== body && text !== `${body}\n`)
139
+ throw configError(`Invalid ${label}`);
140
+ if (!/^[0-9a-f]{64}$/.test(body))
141
+ throw configError(`Invalid ${label}`);
142
+ return (0, identity_js_1.parseSeed)(body);
143
+ }
144
+ async function writeAll(handle, bytes) {
145
+ let offset = 0;
146
+ while (offset < bytes.byteLength) {
147
+ const written = await handle.write(bytes, offset, bytes.byteLength - offset, offset);
148
+ const count = typeof written === 'number' ? written : written.bytesWritten;
149
+ if (!Number.isSafeInteger(count) || count <= 0)
150
+ throw configError('Unable to write seed file');
151
+ offset += count;
152
+ }
153
+ }
154
+ async function readAll(handle, size) {
155
+ const bytes = b4a_1.default.alloc(size);
156
+ let offset = 0;
157
+ while (offset < size) {
158
+ const read = await handle.read(bytes, offset, size - offset, offset);
159
+ const count = typeof read === 'number' ? read : read.bytesRead;
160
+ if (!Number.isSafeInteger(count) || count <= 0)
161
+ throw configError('Unable to read file');
162
+ offset += count;
163
+ }
164
+ return bytes;
165
+ }
166
+ async function syncDirectory(directory) {
167
+ const handle = await _fs_1.default.promises.open(directory, 'r');
168
+ try {
169
+ if (typeof handle.sync === 'function')
170
+ await handle.sync();
171
+ }
172
+ finally {
173
+ await handle.close();
174
+ }
175
+ }
176
+ async function openNoFollowRead(filePath, label) {
177
+ try {
178
+ return await _fs_1.default.promises.open(filePath, readFlags());
179
+ }
180
+ catch (err) {
181
+ if ((0, error_code_js_1.errorCode)(err) === 'ELOOP' || (0, error_code_js_1.errorCode)(err) === 'EISDIR') {
182
+ throw configError(`${label} must be a regular file`);
183
+ }
184
+ throw configError(`Unable to read ${label}`);
185
+ }
186
+ }
187
+ async function readBoundedRegularFile(filePath, maxBytes, label) {
188
+ let stat;
189
+ try {
190
+ stat = await _fs_1.default.promises.lstat(filePath);
191
+ }
192
+ catch {
193
+ throw configError(`Unable to read ${label}`);
194
+ }
195
+ if (stat.isSymbolicLink() || !stat.isFile())
196
+ throw configError(`${label} must be a regular file`);
197
+ if (!Number.isSafeInteger(stat.size) || stat.size < 0 || stat.size > maxBytes) {
198
+ throw configError(`Invalid ${label}`);
199
+ }
200
+ const handle = await openNoFollowRead(filePath, label);
201
+ try {
202
+ const opened = await handle.stat();
203
+ if (!opened.isFile() || opened.size !== stat.size)
204
+ throw configError(`Invalid ${label}`);
205
+ return b4a_1.default.toString(await readAll(handle, stat.size), 'utf8');
206
+ }
207
+ finally {
208
+ await handle.close().catch(() => { });
209
+ }
210
+ }
211
+ async function readSeedFile(filePath) {
212
+ const text = await readBoundedRegularFile(filePath, MAX_SEED_FILE_BYTES, 'seed file');
213
+ if (text.length < 64)
214
+ throw configError('Invalid seed file');
215
+ return parseSeedText(text, 'seed file');
216
+ }
217
+ async function removeSeedTemporary(filePath) {
218
+ try {
219
+ await _fs_1.default.promises.unlink(filePath);
220
+ }
221
+ catch (err) {
222
+ if ((0, error_code_js_1.errorCode)(err) === 'ENOENT')
223
+ return;
224
+ throw configError('Unable to remove temporary seed file');
225
+ }
226
+ try {
227
+ await syncDirectory(_path_1.default.dirname(filePath));
228
+ }
229
+ catch { }
230
+ }
231
+ async function writeSeedFile(filePath, seedHex) {
232
+ if (typeof filePath !== 'string' || filePath.length === 0)
233
+ throw usageError('Missing --out');
234
+ const directory = _path_1.default.dirname(filePath);
235
+ const temporary = _path_1.default.join(directory, `.${_path_1.default.basename(filePath)}.${b4a_1.default.toString((0, identity_js_1.generateSeed)(), 'hex')}.tmp`);
236
+ try {
237
+ await _fs_1.default.promises.lstat(filePath);
238
+ throw configError('Refusing to overwrite existing seed file');
239
+ }
240
+ catch (err) {
241
+ if (err instanceof CliError)
242
+ throw err;
243
+ if ((0, error_code_js_1.errorCode)(err) !== 'ENOENT')
244
+ throw configError('Unable to create seed file');
245
+ }
246
+ let handle = null;
247
+ try {
248
+ handle = await _fs_1.default.promises.open(temporary, createSeedFlags(), 0o600);
249
+ }
250
+ catch (err) {
251
+ throw configError('Unable to create seed file');
252
+ }
253
+ try {
254
+ const opened = await handle.stat();
255
+ if (!opened.isFile())
256
+ throw configError('Unable to create seed file');
257
+ if (typeof handle.chmod === 'function')
258
+ await handle.chmod(0o600);
259
+ await writeAll(handle, b4a_1.default.from(`${seedHex}\n`));
260
+ await handle.sync();
261
+ }
262
+ catch (err) {
263
+ await handle.close().catch(() => { });
264
+ handle = null;
265
+ await removeSeedTemporary(temporary);
266
+ if (err instanceof CliError)
267
+ throw err;
268
+ throw configError('Unable to write seed file');
269
+ }
270
+ await handle.close();
271
+ handle = null;
272
+ try {
273
+ await _fs_1.default.promises.link(temporary, filePath);
274
+ await syncDirectory(directory);
275
+ }
276
+ catch (err) {
277
+ await removeSeedTemporary(temporary);
278
+ if ((0, error_code_js_1.errorCode)(err) === 'EEXIST')
279
+ throw configError('Refusing to overwrite existing seed file');
280
+ throw configError('Unable to create seed file');
281
+ }
282
+ await removeSeedTemporary(temporary);
283
+ }
284
+ function isCanonicalHexToken(value) {
285
+ return typeof value === 'string' && /^[0-9a-f]{64}$/.test(value);
286
+ }
287
+ function rejectUnexpectedSeed() {
288
+ throw usageError('Seed strings must be passed with --seed');
289
+ }
290
+ function parseOptions(args, allowed, repeatable = new Set(), keyValues = new Set(), flags = new Set(), pairs = new Set()) {
291
+ const options = Object.create(null);
292
+ const repeatedOptions = Object.create(null);
293
+ const pairOptions = Object.create(null);
294
+ const flagOptions = Object.create(null);
295
+ const positionals = [];
296
+ for (let i = 0; i < args.length; i++) {
297
+ const arg = args[i];
298
+ if (isCanonicalHexToken(arg) && !keyValues.has(args[i - 1])) {
299
+ rejectUnexpectedSeed();
300
+ }
301
+ if (arg.startsWith('-')) {
302
+ if (arg.includes('=') || !allowed.has(arg))
303
+ throw usageError('Unknown option');
304
+ if (!repeatable.has(arg) &&
305
+ !pairs.has(arg) &&
306
+ (Object.prototype.hasOwnProperty.call(options, arg) ||
307
+ Object.prototype.hasOwnProperty.call(repeatedOptions, arg) ||
308
+ Object.prototype.hasOwnProperty.call(flagOptions, arg))) {
309
+ throw usageError('Duplicate option');
310
+ }
311
+ // A flag takes no value, so the next argument keeps its own meaning.
312
+ if (flags.has(arg)) {
313
+ flagOptions[arg] = true;
314
+ continue;
315
+ }
316
+ if (pairs.has(arg)) {
317
+ const first = args[i + 1];
318
+ const second = args[i + 2];
319
+ if (first === undefined ||
320
+ first.startsWith('-') ||
321
+ second === undefined ||
322
+ second.startsWith('-')) {
323
+ throw usageError('Missing option value');
324
+ }
325
+ if (isCanonicalHexToken(second) && !keyValues.has(arg))
326
+ rejectUnexpectedSeed();
327
+ const values = pairOptions[arg] || [];
328
+ values.push([first, second]);
329
+ pairOptions[arg] = values;
330
+ i += 2;
331
+ continue;
332
+ }
333
+ const value = args[i + 1];
334
+ if (value === undefined || value.startsWith('-')) {
335
+ throw usageError('Missing option value');
336
+ }
337
+ if (isCanonicalHexToken(value) && !keyValues.has(arg))
338
+ rejectUnexpectedSeed();
339
+ if (repeatable.has(arg)) {
340
+ const values = repeatedOptions[arg] || [];
341
+ values.push(value);
342
+ repeatedOptions[arg] = values;
343
+ }
344
+ else {
345
+ options[arg] = value;
346
+ }
347
+ i++;
348
+ continue;
349
+ }
350
+ positionals.push(arg);
351
+ }
352
+ return { options, repeatedOptions, pairOptions, flagOptions, positionals };
353
+ }
354
+ function requireOption(options, name) {
355
+ if (typeof options[name] !== 'string' || options[name].length === 0) {
356
+ throw usageError(`Missing ${name}`);
357
+ }
358
+ return options[name];
359
+ }
360
+ function requirePositionals(positionals, count, label) {
361
+ if (positionals.length !== count)
362
+ throw usageError(label);
363
+ return positionals;
364
+ }
365
+ async function resolveSeed({ seedFile, seedText, env, envName, role }) {
366
+ const hasFile = seedFile !== undefined;
367
+ const hasText = seedText !== undefined;
368
+ const hasEnv = envHas(env, envName);
369
+ if (Number(hasFile) + Number(hasText) + Number(hasEnv) > 1) {
370
+ throw configError(`${role} seed must be provided by exactly one source`);
371
+ }
372
+ if (hasFile)
373
+ return await readSeedFile(seedFile);
374
+ if (hasText)
375
+ return parseSeedText(seedText, `${role} seed`);
376
+ if (hasEnv)
377
+ return parseSeedText(String(env[envName]), `${role} seed`);
378
+ throw usageError(`${role} seed is required`);
379
+ }
380
+ function writeLine(stream, line) {
381
+ if (!stream || typeof stream.write !== 'function')
382
+ return;
383
+ stream.write(line.endsWith('\n') ? line : `${line}\n`);
384
+ }
385
+ function createLogger(io) {
386
+ const write = (level, message, details) => {
387
+ try {
388
+ if (!io.stderr || typeof io.stderr.write !== 'function')
389
+ return;
390
+ const suffix = details && typeof details === 'object' ? ` ${JSON.stringify(details)}` : '';
391
+ io.stderr.write(`${level}: ${message}${suffix}\n`);
392
+ }
393
+ catch { }
394
+ };
395
+ return {
396
+ info(message, details) {
397
+ write('info', message, details);
398
+ },
399
+ warn(message, details) {
400
+ write('warn', message, details);
401
+ },
402
+ error(message, details) {
403
+ write('error', message, details);
404
+ }
405
+ };
406
+ }
407
+ function attachSignals(proc, handler) {
408
+ if (!proc || typeof proc.on !== 'function')
409
+ return () => { };
410
+ proc.on('SIGINT', handler);
411
+ proc.on('SIGTERM', handler);
412
+ return () => {
413
+ if (typeof proc.off === 'function') {
414
+ proc.off('SIGINT', handler);
415
+ proc.off('SIGTERM', handler);
416
+ return;
417
+ }
418
+ if (typeof proc.removeListener === 'function') {
419
+ proc.removeListener('SIGINT', handler);
420
+ proc.removeListener('SIGTERM', handler);
421
+ }
422
+ };
423
+ }
424
+ function onceClose(resource) {
425
+ let pending = null;
426
+ return () => {
427
+ if (!pending) {
428
+ pending = Promise.resolve().then(() => resource && typeof resource.close === 'function' ? resource.close() : undefined);
429
+ }
430
+ return pending;
431
+ };
432
+ }
433
+ function isConfigFailure(err) {
434
+ if (err instanceof CliError)
435
+ return err.exitCode === 2;
436
+ if (!(err instanceof errors_js_1.SwarmDeployError))
437
+ return false;
438
+ return (err.code === errors_js_1.ERRORS.INVALID_SEED ||
439
+ err.code === errors_js_1.ERRORS.INVALID_PUBLIC_KEY ||
440
+ err.code === errors_js_1.ERRORS.PROTOCOL_INVALID ||
441
+ err.code === errors_js_1.ERRORS.SERVER_KEY_MISMATCH);
442
+ }
443
+ function exitCodeFor(err) {
444
+ if (typeof err === 'object' &&
445
+ err !== null &&
446
+ 'exitCode' in err &&
447
+ typeof err.exitCode === 'number') {
448
+ return err.exitCode;
449
+ }
450
+ return isConfigFailure(err) ? 2 : 1;
451
+ }
452
+ function errorMessage(err) {
453
+ if (!err)
454
+ return 'Unknown error';
455
+ if (typeof err === 'object') {
456
+ if ('message' in err && typeof err.message === 'string' && err.message.length > 0) {
457
+ return err.message;
458
+ }
459
+ if ('name' in err && typeof err.name === 'string' && err.name.length > 0)
460
+ return err.name;
461
+ }
462
+ return 'Error';
463
+ }
464
+ function writeError(io, err) {
465
+ try {
466
+ if (!io.stderr || typeof io.stderr.write !== 'function')
467
+ return;
468
+ const message = errorMessage(err);
469
+ io.stderr.write(message.endsWith('\n') ? message : `${message}\n`);
470
+ }
471
+ catch { }
472
+ }
473
+ async function runKeygen(args) {
474
+ const { options, positionals } = parseOptions(args, new Set(['--out']));
475
+ requirePositionals(positionals, 0, 'keygen does not accept positional arguments');
476
+ const out = requireOption(options, '--out');
477
+ const seed = (0, identity_js_1.generateSeed)();
478
+ const seedHex = b4a_1.default.toString(seed, 'hex');
479
+ await writeSeedFile(out, seedHex);
480
+ return b4a_1.default.toString((0, identity_js_1.publicKeyFromSeed)(seed), 'hex');
481
+ }
482
+ async function runPublicKey(args) {
483
+ const { options, positionals } = parseOptions(args, new Set(['--seed-file', '--seed']), new Set(), new Set(['--seed']));
484
+ requirePositionals(positionals, 0, 'public-key does not accept positional arguments');
485
+ const hasFile = options['--seed-file'] !== undefined;
486
+ const hasText = options['--seed'] !== undefined;
487
+ if (hasFile === hasText)
488
+ throw usageError('public-key requires exactly one seed source');
489
+ const seed = hasFile
490
+ ? await readSeedFile(requireOption(options, '--seed-file'))
491
+ : parseSeedText(requireOption(options, '--seed'), 'seed');
492
+ return b4a_1.default.toString((0, identity_js_1.publicKeyFromSeed)(seed), 'hex');
493
+ }
494
+ async function runServer(args, env, io) {
495
+ const { options, repeatedOptions, pairOptions, positionals } = parseOptions(args, new Set([
496
+ '--seed-file',
497
+ '--seed',
498
+ '--storage',
499
+ '--allow-key',
500
+ '--max-file-bytes',
501
+ '--max-staging-bytes',
502
+ '--max-storage-bytes',
503
+ '--max-age-days',
504
+ '--replace-name',
505
+ '--artifact-pattern',
506
+ '--max-count',
507
+ '--max-versions',
508
+ '--version-granularity',
509
+ '--symlink',
510
+ '--hooks'
511
+ ]), new Set(['--replace-name', '--allow-key', '--artifact-pattern', '--symlink']), new Set(['--allow-key', '--seed']), new Set(), new Set(['--symlink']));
512
+ requirePositionals(positionals, 0, 'server does not accept positional arguments');
513
+ const storageDir = requireOption(options, '--storage');
514
+ const maxFileBytes = parsePositiveSafeInteger(requireOption(options, '--max-file-bytes'), 'max-file-bytes');
515
+ const maxStagingBytes = parsePositiveSafeInteger(requireOption(options, '--max-staging-bytes'), 'max-staging-bytes');
516
+ const maxStorageBytes = options['--max-storage-bytes'] === undefined
517
+ ? undefined
518
+ : parsePositiveSafeInteger(options['--max-storage-bytes'], 'max-storage-bytes');
519
+ const maxAge = options['--max-age-days'] === undefined ? undefined : parseMaxAgeDays(options['--max-age-days']);
520
+ let replaceNames;
521
+ try {
522
+ replaceNames = (0, files_js_1.validateReplaceNames)(repeatedOptions['--replace-name'] || []);
523
+ }
524
+ catch {
525
+ throw usageError('Invalid --replace-name');
526
+ }
527
+ const artifactPatterns = repeatedOptions['--artifact-pattern'];
528
+ const maxCount = options['--max-count'] === undefined
529
+ ? undefined
530
+ : parsePositiveSafeInteger(options['--max-count'], 'max-count');
531
+ const maxVersions = options['--max-versions'] === undefined
532
+ ? undefined
533
+ : parsePositiveSafeInteger(options['--max-versions'], 'max-versions');
534
+ const granularity = options['--version-granularity'];
535
+ if (granularity !== undefined && granularity !== 'major' && granularity !== 'minor') {
536
+ throw usageError('Invalid --version-granularity');
537
+ }
538
+ const seed = await resolveSeed({
539
+ seedFile: options['--seed-file'],
540
+ seedText: options['--seed'],
541
+ env,
542
+ envName: SERVER_SEED_ENV,
543
+ role: 'server'
544
+ });
545
+ const allowedKeyValues = repeatedOptions['--allow-key'] || [];
546
+ if (allowedKeyValues.length === 0)
547
+ throw usageError('Missing --allow-key');
548
+ const allowedKeys = new Set();
549
+ try {
550
+ for (const value of allowedKeyValues) {
551
+ const parsed = (0, identity_js_1.parsePublicKey)(value);
552
+ const encoded = b4a_1.default.toString(parsed, 'hex');
553
+ if ([...allowedKeys].some((candidate) => b4a_1.default.toString(candidate, 'hex') === encoded)) {
554
+ throw new Error('duplicate');
555
+ }
556
+ allowedKeys.add(parsed);
557
+ }
558
+ }
559
+ catch {
560
+ throw usageError('Invalid --allow-key');
561
+ }
562
+ let hooks;
563
+ if (options['--hooks'] !== undefined) {
564
+ try {
565
+ hooks = await (0, hooks_module_js_1.loadHooksModule)(options['--hooks'], io.cwd ?? _process_1.default.cwd());
566
+ }
567
+ catch (err) {
568
+ throw configError(errorMessage(err));
569
+ }
570
+ }
571
+ const symlinkPairs = pairOptions['--symlink'] || [];
572
+ const symlinks = symlinkPairs.map(([selector, name]) => ({ selector, name }));
573
+ try {
574
+ (0, symlinks_js_1.compileSymlinkRules)(symlinks);
575
+ }
576
+ catch {
577
+ throw usageError('Invalid --symlink');
578
+ }
579
+ const ServerImpl = io.Server || server_js_1.Server;
580
+ let server;
581
+ try {
582
+ server = new ServerImpl({
583
+ seed,
584
+ storageDir,
585
+ allowedKeys,
586
+ maxFileBytes,
587
+ maxStagingBytes,
588
+ maxStorageBytes,
589
+ maxAge,
590
+ replaceNames,
591
+ artifactPatterns,
592
+ maxCount,
593
+ maxVersions,
594
+ versionGranularity: granularity,
595
+ ...(symlinks.length > 0 ? { symlinks } : {}),
596
+ hooks,
597
+ dht: io.dht,
598
+ logger: createLogger(io)
599
+ });
600
+ }
601
+ catch (err) {
602
+ throw new CliError(errorMessage(err), 2);
603
+ }
604
+ const proc = io.process || _process_1.default;
605
+ const close = onceClose(server);
606
+ let resolveStopped;
607
+ const stopped = new Promise((resolve) => {
608
+ resolveStopped = resolve;
609
+ });
610
+ const onSignal = () => {
611
+ close().then(resolveStopped, resolveStopped);
612
+ };
613
+ const detach = attachSignals(proc, onSignal);
614
+ let runError = null;
615
+ try {
616
+ try {
617
+ await server.listen();
618
+ }
619
+ catch (err) {
620
+ runError = new CliError(errorMessage(err), 1);
621
+ }
622
+ if (!runError) {
623
+ writeLine(io.stdout, b4a_1.default.toString(server.publicKey, 'hex'));
624
+ writeLine(io.stdout, 'ready');
625
+ await stopped;
626
+ }
627
+ }
628
+ finally {
629
+ detach();
630
+ try {
631
+ await close();
632
+ }
633
+ catch {
634
+ if (!runError)
635
+ runError = new CliError('Cleanup failed', 1);
636
+ }
637
+ }
638
+ if (runError)
639
+ throw runError;
640
+ return 0;
641
+ }
642
+ function printUploadResult(result, io) {
643
+ writeLine(io.stdout, `${result.name} ${result.kind} ${result.status}`);
644
+ return result.status === 'COMMITTED' || result.status === 'ALREADY_COMMITTED' ? 0 : 1;
645
+ }
646
+ async function runUpload(args, env, io) {
647
+ const { options, flagOptions, positionals } = parseOptions(args, new Set(['--seed-file', '--seed', '--server-key', '--idle-timeout', '--no-source-parent']), new Set(), new Set(['--server-key', '--seed']), new Set(['--no-source-parent']));
648
+ const [inputPath] = requirePositionals(positionals, 1, 'upload requires a file or directory');
649
+ const seed = await resolveSeed({
650
+ seedFile: options['--seed-file'],
651
+ seedText: options['--seed'],
652
+ env,
653
+ envName: CLIENT_SEED_ENV,
654
+ role: 'client'
655
+ });
656
+ let serverPublicKey;
657
+ try {
658
+ serverPublicKey = (0, identity_js_1.parsePublicKey)(requireOption(options, '--server-key'));
659
+ }
660
+ catch {
661
+ throw usageError('Invalid --server-key');
662
+ }
663
+ const idleTimeout = options['--idle-timeout'] === undefined
664
+ ? io.idleTimeout
665
+ : parsePositiveSafeInteger(options['--idle-timeout'], 'idle-timeout');
666
+ const ClientImpl = io.Client || client_js_1.Client;
667
+ let client;
668
+ try {
669
+ client = new ClientImpl({
670
+ seed,
671
+ serverPublicKey,
672
+ dht: io.dht,
673
+ connectTimeout: io.connectTimeout,
674
+ idleTimeout,
675
+ ...(flagOptions['--no-source-parent'] ? { includeSourceParent: false } : {}),
676
+ logger: createLogger(io)
677
+ });
678
+ }
679
+ catch (err) {
680
+ throw new CliError(errorMessage(err), 2);
681
+ }
682
+ const proc = io.process || _process_1.default;
683
+ const close = onceClose(client);
684
+ const detach = attachSignals(proc, () => {
685
+ close().then(() => { }, () => { });
686
+ });
687
+ let runError = null;
688
+ let code = 0;
689
+ try {
690
+ try {
691
+ const result = await client.upload(inputPath);
692
+ code = printUploadResult(result, io);
693
+ }
694
+ catch (err) {
695
+ runError = new CliError(errorMessage(err), 1);
696
+ }
697
+ }
698
+ finally {
699
+ detach();
700
+ try {
701
+ await close();
702
+ }
703
+ catch {
704
+ if (!runError && code === 0)
705
+ runError = new CliError('Cleanup failed', 1);
706
+ }
707
+ }
708
+ if (runError)
709
+ throw runError;
710
+ return code;
711
+ }
712
+ async function dispatch(argv, env, io) {
713
+ if (argv.length === 1 && HELP_ARGS.has(argv[0])) {
714
+ writeLine(io.stdout, USAGE);
715
+ return 0;
716
+ }
717
+ if (argv.length === 0)
718
+ throw usageError('Missing command');
719
+ const command = argv[0];
720
+ if (isCanonicalHexToken(command))
721
+ rejectUnexpectedSeed();
722
+ if (!COMMANDS.has(command))
723
+ throw usageError('Unknown command');
724
+ const args = argv.slice(1);
725
+ if (command === 'keygen') {
726
+ writeLine(io.stdout, await runKeygen(args));
727
+ return 0;
728
+ }
729
+ if (command === 'public-key') {
730
+ writeLine(io.stdout, await runPublicKey(args));
731
+ return 0;
732
+ }
733
+ if (command === 'server')
734
+ return runServer(args, env, io);
735
+ return runUpload(args, env, io);
736
+ }
737
+ async function main(argv, env, io = {}) {
738
+ const streams = {
739
+ stdout: io.stdout || _process_1.default.stdout,
740
+ stderr: io.stderr || _process_1.default.stderr
741
+ };
742
+ const context = { ...io, ...streams };
743
+ try {
744
+ return await dispatch(normalizeArgv(argv), env || {}, context);
745
+ }
746
+ catch (err) {
747
+ writeError(context, err);
748
+ return exitCodeFor(err);
749
+ }
750
+ }
751
+ //# sourceMappingURL=cli.js.map