@daytonaio/sdk 0.17.0-alpha.2 → 0.18.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Image.js ADDED
@@ -0,0 +1,557 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.Image = void 0;
40
+ const fs = __importStar(require("fs"));
41
+ const fg = __importStar(require("fast-glob"));
42
+ const _path = __importStar(require("path"));
43
+ const shell_quote_1 = require("shell-quote");
44
+ const untildify_1 = __importDefault(require("untildify"));
45
+ const DaytonaError_1 = require("./errors/DaytonaError");
46
+ const ObjectStorage_1 = require("./ObjectStorage");
47
+ const toml_1 = require("@iarna/toml");
48
+ const SUPPORTED_PYTHON_SERIES = ['3.9', '3.10', '3.11', '3.12', '3.13'];
49
+ const LATEST_PYTHON_MICRO_VERSIONS = ['3.9.22', '3.10.17', '3.11.12', '3.12.10', '3.13.3'];
50
+ /**
51
+ * Represents an image definition for a Daytona sandbox.
52
+ * Do not construct this class directly. Instead use one of its static factory methods,
53
+ * such as `Image.base()`, `Image.debianSlim()` or `Image.fromDockerfile()`.
54
+ *
55
+ * @class
56
+ * @property {string} dockerfile - The Dockerfile content.
57
+ * @property {Context[]} contextList - The list of context files to be added to the image.
58
+ */
59
+ class Image {
60
+ constructor() {
61
+ this._dockerfile = '';
62
+ this._contextList = [];
63
+ }
64
+ get dockerfile() {
65
+ return this._dockerfile;
66
+ }
67
+ get contextList() {
68
+ return this._contextList;
69
+ }
70
+ /**
71
+ * Adds commands to install packages using pip.
72
+ *
73
+ * @param {string | string[]} packages - The packages to install.
74
+ * @param {Object} options - The options for the pip install command.
75
+ * @param {string[]} options.findLinks - The find-links to use for the pip install command.
76
+ * @returns {Image} The Image instance.
77
+ *
78
+ * @example
79
+ * const image = Image.debianSlim('3.12').pipInstall('numpy', { findLinks: ['https://pypi.org/simple'] })
80
+ */
81
+ pipInstall(packages, options) {
82
+ const pkgs = this.flattenStringArgs('pipInstall', 'packages', packages);
83
+ if (!pkgs.length)
84
+ return this;
85
+ const extraArgs = this.formatPipInstallArgs(options);
86
+ this._dockerfile += `RUN python -m pip install ${(0, shell_quote_1.quote)(pkgs.sort())}${extraArgs}\n`;
87
+ return this;
88
+ }
89
+ /**
90
+ * Installs dependencies from a requirements.txt file.
91
+ *
92
+ * @param {string} requirementsTxt - The path to the requirements.txt file.
93
+ * @param {PipInstallOptions} options - The options for the pip install command.
94
+ * @returns {Image} The Image instance.
95
+ *
96
+ * @example
97
+ * const image = Image.debianSlim('3.12')
98
+ * image.pipInstallFromRequirements('requirements.txt', { findLinks: ['https://pypi.org/simple'] })
99
+ */
100
+ pipInstallFromRequirements(requirementsTxt, options) {
101
+ const expandedPath = (0, untildify_1.default)(requirementsTxt);
102
+ if (!fs.existsSync(expandedPath)) {
103
+ throw new Error(`Requirements file ${requirementsTxt} does not exist`);
104
+ }
105
+ const extraArgs = this.formatPipInstallArgs(options);
106
+ const archivePath = ObjectStorage_1.ObjectStorage.computeArchiveBasePath(expandedPath);
107
+ this._contextList.push({ sourcePath: expandedPath, archivePath });
108
+ this._dockerfile += `COPY ${archivePath} /.requirements.txt\n`;
109
+ this._dockerfile += `RUN python -m pip install -r /.requirements.txt${extraArgs}\n`;
110
+ return this;
111
+ }
112
+ /**
113
+ * Installs dependencies from a pyproject.toml file.
114
+ *
115
+ * @param {string} pyprojectToml - The path to the pyproject.toml file.
116
+ * @param {PyprojectOptions} options - The options for the pip install command.
117
+ * @returns {Image} The Image instance.
118
+ *
119
+ * @example
120
+ * const image = Image.debianSlim('3.12')
121
+ * image.pipInstallFromPyproject('pyproject.toml', { optionalDependencies: ['dev'] })
122
+ */
123
+ pipInstallFromPyproject(pyprojectToml, options) {
124
+ const tomlData = (0, toml_1.parse)(fs.readFileSync((0, untildify_1.default)(pyprojectToml), 'utf-8'));
125
+ const dependencies = [];
126
+ if (!tomlData || !tomlData.project || !Array.isArray(tomlData.project.dependencies)) {
127
+ const msg = 'No [project.dependencies] section in pyproject.toml file. ' +
128
+ 'See https://packaging.python.org/en/latest/guides/writing-pyproject-toml ' +
129
+ 'for further file format guidelines.';
130
+ throw new DaytonaError_1.DaytonaError(msg);
131
+ }
132
+ dependencies.push(...tomlData.project.dependencies);
133
+ if ((options === null || options === void 0 ? void 0 : options.optionalDependencies) && tomlData.project['optional-dependencies']) {
134
+ const optionalGroups = tomlData.project['optional-dependencies'];
135
+ for (const group of options.optionalDependencies) {
136
+ const deps = optionalGroups[group];
137
+ if (Array.isArray(deps)) {
138
+ dependencies.push(...deps);
139
+ }
140
+ }
141
+ }
142
+ return this.pipInstall(dependencies, options);
143
+ }
144
+ /**
145
+ * Adds a local file to the image.
146
+ *
147
+ * @param {string} localPath - The path to the local file.
148
+ * @param {string} remotePath - The path of the file in the image.
149
+ * @returns {Image} The Image instance.
150
+ *
151
+ * @example
152
+ * const image = Image
153
+ * .debianSlim('3.12')
154
+ * .addLocalFile('requirements.txt', '/home/daytona/requirements.txt')
155
+ */
156
+ addLocalFile(localPath, remotePath) {
157
+ if (remotePath.endsWith('/')) {
158
+ remotePath = remotePath + _path.basename(localPath);
159
+ }
160
+ const expandedPath = (0, untildify_1.default)(localPath);
161
+ const archivePath = ObjectStorage_1.ObjectStorage.computeArchiveBasePath(expandedPath);
162
+ this._contextList.push({ sourcePath: expandedPath, archivePath });
163
+ this._dockerfile += `COPY ${archivePath} ${remotePath}\n`;
164
+ return this;
165
+ }
166
+ /**
167
+ * Adds a local directory to the image.
168
+ *
169
+ * @param {string} localPath - The path to the local directory.
170
+ * @param {string} remotePath - The path of the directory in the image.
171
+ * @returns {Image} The Image instance.
172
+ *
173
+ * @example
174
+ * const image = Image
175
+ * .debianSlim('3.12')
176
+ * .addLocalDir('src', '/home/daytona/src')
177
+ */
178
+ addLocalDir(localPath, remotePath) {
179
+ const expandedPath = (0, untildify_1.default)(localPath);
180
+ const archivePath = ObjectStorage_1.ObjectStorage.computeArchiveBasePath(expandedPath);
181
+ this._contextList.push({ sourcePath: expandedPath, archivePath });
182
+ this._dockerfile += `COPY ${archivePath} ${remotePath}\n`;
183
+ return this;
184
+ }
185
+ /**
186
+ * Runs commands in the image.
187
+ *
188
+ * @param {string | string[]} commands - The commands to run.
189
+ * @returns {Image} The Image instance.
190
+ *
191
+ * @example
192
+ * const image = Image
193
+ * .debianSlim('3.12')
194
+ * .runCommands('echo "Hello, world!"')
195
+ */
196
+ runCommands(...commands) {
197
+ const cmds = this.flattenStringArgs('runCommands', 'commands', commands);
198
+ if (!cmds.length)
199
+ return this;
200
+ for (const cmd of cmds) {
201
+ this._dockerfile += `RUN ${cmd}\n`;
202
+ }
203
+ return this;
204
+ }
205
+ /**
206
+ * Sets environment variables in the image.
207
+ *
208
+ * @param {Record<string, string>} envVars - The environment variables to set.
209
+ * @returns {Image} The Image instance.
210
+ *
211
+ * @example
212
+ * const image = Image
213
+ * .debianSlim('3.12')
214
+ * .env({ FOO: 'bar' })
215
+ */
216
+ env(envVars) {
217
+ const nonStringKeys = Object.entries(envVars)
218
+ .filter(([, value]) => typeof value !== 'string')
219
+ .map(([key]) => key);
220
+ if (nonStringKeys.length) {
221
+ throw new Error(`Image ENV variables must be strings. Invalid keys: ${nonStringKeys}`);
222
+ }
223
+ for (const [key, val] of Object.entries(envVars)) {
224
+ this._dockerfile += `ENV ${key}=${(0, shell_quote_1.quote)([val])}\n`;
225
+ }
226
+ return this;
227
+ }
228
+ /**
229
+ * Sets the working directory in the image.
230
+ *
231
+ * @param {string} dirPath - The path to the working directory.
232
+ * @returns {Image} The Image instance.
233
+ *
234
+ * @example
235
+ * const image = Image
236
+ * .debianSlim('3.12')
237
+ * .workdir('/home/daytona')
238
+ */
239
+ workdir(dirPath) {
240
+ this._dockerfile += `WORKDIR ${(0, shell_quote_1.quote)([dirPath])}\n`;
241
+ return this;
242
+ }
243
+ /**
244
+ * Sets the entrypoint for the image.
245
+ *
246
+ * @param {string[]} entrypointCommands - The commands to set as the entrypoint.
247
+ * @returns {Image} The Image instance.
248
+ *
249
+ * @example
250
+ * const image = Image
251
+ * .debianSlim('3.12')
252
+ * .entrypoint(['/bin/bash'])
253
+ */
254
+ entrypoint(entrypointCommands) {
255
+ if (!Array.isArray(entrypointCommands) || !entrypointCommands.every((x) => typeof x === 'string')) {
256
+ throw new Error('entrypoint_commands must be a list of strings');
257
+ }
258
+ const argsStr = entrypointCommands.map((arg) => `"${arg}"`).join(', ');
259
+ this._dockerfile += `ENTRYPOINT [${argsStr}]\n`;
260
+ return this;
261
+ }
262
+ /**
263
+ * Sets the default command for the image.
264
+ *
265
+ * @param {string[]} cmd - The command to set as the default command.
266
+ * @returns {Image} The Image instance.
267
+ *
268
+ * @example
269
+ * const image = Image
270
+ * .debianSlim('3.12')
271
+ * .cmd(['/bin/bash'])
272
+ */
273
+ cmd(cmd) {
274
+ if (!Array.isArray(cmd) || !cmd.every((x) => typeof x === 'string')) {
275
+ throw new Error('Image CMD must be a list of strings');
276
+ }
277
+ const cmdStr = cmd.map((arg) => `"${arg}"`).join(', ');
278
+ this._dockerfile += `CMD [${cmdStr}]\n`;
279
+ return this;
280
+ }
281
+ /**
282
+ * Extends an image with arbitrary Dockerfile-like commands.
283
+ *
284
+ * @param {string | string[]} dockerfileCommands - The commands to add to the Dockerfile.
285
+ * @param {string} contextDir - The path to the context directory.
286
+ * @returns {Image} The Image instance.
287
+ *
288
+ * @example
289
+ * const image = Image
290
+ * .debianSlim('3.12')
291
+ * .dockerfileCommands(['RUN echo "Hello, world!"'])
292
+ */
293
+ dockerfileCommands(dockerfileCommands, contextDir) {
294
+ const cmds = this.flattenStringArgs('dockerfileCommands', 'dockerfileCommands', dockerfileCommands);
295
+ if (!cmds.length)
296
+ return this;
297
+ if (contextDir) {
298
+ const expandedPath = (0, untildify_1.default)(contextDir);
299
+ if (!fs.existsSync(expandedPath) || !fs.statSync(expandedPath).isDirectory()) {
300
+ throw new Error(`Context directory ${contextDir} does not exist`);
301
+ }
302
+ }
303
+ for (const [contextPath, originalPath] of Image.extractCopySources(cmds.join('\n'), contextDir || '')) {
304
+ let archiveBasePath = contextPath;
305
+ if (contextDir && !originalPath.startsWith(contextDir)) {
306
+ archiveBasePath = contextPath.substring(contextDir.length);
307
+ // Remove leading separators
308
+ archiveBasePath = archiveBasePath.replace(/^[\/\\]+/, '');
309
+ }
310
+ this._contextList.push({ sourcePath: contextPath, archivePath: archiveBasePath });
311
+ }
312
+ this._dockerfile += cmds.join('\n') + '\n';
313
+ return this;
314
+ }
315
+ /**
316
+ * Creates an Image from an existing Dockerfile.
317
+ *
318
+ * @param {string} path - The path to the Dockerfile.
319
+ * @returns {Image} The Image instance.
320
+ *
321
+ * @example
322
+ * const image = Image.fromDockerfile('Dockerfile')
323
+ */
324
+ static fromDockerfile(path) {
325
+ const expandedPath = _path.resolve((0, untildify_1.default)(path));
326
+ if (!fs.existsSync(expandedPath)) {
327
+ throw new Error(`Dockerfile ${path} does not exist`);
328
+ }
329
+ const dockerfileContent = fs.readFileSync(expandedPath, 'utf-8');
330
+ const img = new Image();
331
+ img._dockerfile = dockerfileContent;
332
+ // Remove dockerfile filename from path to get the path prefix
333
+ const pathPrefix = _path.dirname(expandedPath) + _path.sep;
334
+ for (const [contextPath, originalPath] of Image.extractCopySources(dockerfileContent, pathPrefix)) {
335
+ let archiveBasePath = contextPath;
336
+ if (!originalPath.startsWith(pathPrefix)) {
337
+ // Remove the path prefix from the context path to get the archive path
338
+ archiveBasePath = contextPath.substring(pathPrefix.length);
339
+ // Remove leading separators
340
+ archiveBasePath = archiveBasePath.replace(/^[\/\\]+/, '');
341
+ }
342
+ img._contextList.push({ sourcePath: contextPath, archivePath: archiveBasePath });
343
+ }
344
+ return img;
345
+ }
346
+ /**
347
+ * Creates an Image from an existing base image.
348
+ *
349
+ * @param {string} image - The base image to use.
350
+ * @returns {Image} The Image instance.
351
+ *
352
+ * @example
353
+ * const image = Image.base('python:3.12-slim-bookworm')
354
+ */
355
+ static base(image) {
356
+ const img = new Image();
357
+ img._dockerfile = `FROM ${image}\n`;
358
+ return img;
359
+ }
360
+ /**
361
+ * Creates a Debian slim image based on the official Python Docker image.
362
+ *
363
+ * @param {string} pythonVersion - The Python version to use.
364
+ * @returns {Image} The Image instance.
365
+ *
366
+ * @example
367
+ * const image = Image.debianSlim('3.12')
368
+ */
369
+ static debianSlim(pythonVersion) {
370
+ const version = Image.processPythonVersion(pythonVersion);
371
+ const img = new Image();
372
+ const commands = [
373
+ `FROM python:${version}-slim-bookworm`,
374
+ 'RUN apt-get update',
375
+ 'RUN apt-get install -y gcc gfortran build-essential',
376
+ 'RUN pip install --upgrade pip',
377
+ // Set debian front-end to non-interactive to avoid users getting stuck with input prompts.
378
+ // eslint-disable-next-line quotes
379
+ "RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections",
380
+ ];
381
+ img._dockerfile = commands.join('\n') + '\n';
382
+ return img;
383
+ }
384
+ /**
385
+ * Formats pip install arguments in a single string.
386
+ *
387
+ * @param {PipInstallOptions} options - The options for the pip install command.
388
+ * @returns {string} The formatted pip install arguments.
389
+ */
390
+ formatPipInstallArgs(options) {
391
+ if (!options)
392
+ return '';
393
+ let extraArgs = '';
394
+ if (options.findLinks) {
395
+ for (const findLink of options.findLinks) {
396
+ extraArgs += ` --find-links ${(0, shell_quote_1.quote)([findLink])}`;
397
+ }
398
+ }
399
+ if (options.indexUrl) {
400
+ extraArgs += ` --index-url ${(0, shell_quote_1.quote)([options.indexUrl])}`;
401
+ }
402
+ if (options.extraIndexUrls) {
403
+ for (const extraIndexUrl of options.extraIndexUrls) {
404
+ extraArgs += ` --extra-index-url ${(0, shell_quote_1.quote)([extraIndexUrl])}`;
405
+ }
406
+ }
407
+ if (options.pre) {
408
+ extraArgs += ' --pre';
409
+ }
410
+ if (options.extraOptions) {
411
+ extraArgs += ` ${options.extraOptions.trim()}`;
412
+ }
413
+ return extraArgs;
414
+ }
415
+ /**
416
+ * Flattens a string argument.
417
+ *
418
+ * @param {string} functionName - The name of the function.
419
+ * @param {string} argName - The name of the argument.
420
+ * @param {any} args - The argument to flatten.
421
+ * @returns {string[]} The flattened argument.
422
+ */
423
+ flattenStringArgs(functionName, argName, args) {
424
+ const result = [];
425
+ const flatten = (arg) => {
426
+ if (typeof arg === 'string') {
427
+ result.push(arg);
428
+ }
429
+ else if (Array.isArray(arg)) {
430
+ for (const item of arg) {
431
+ flatten(item);
432
+ }
433
+ }
434
+ else {
435
+ throw new Error(`${functionName}: ${argName} must only contain strings`);
436
+ }
437
+ };
438
+ flatten(args);
439
+ return result;
440
+ }
441
+ /**
442
+ * Processes the Python version.
443
+ *
444
+ * @param {string} pythonVersion - The Python version to use.
445
+ * @returns {string} The processed Python version.
446
+ */
447
+ static processPythonVersion(pythonVersion) {
448
+ if (!pythonVersion) {
449
+ // Default to latest
450
+ pythonVersion = SUPPORTED_PYTHON_SERIES[SUPPORTED_PYTHON_SERIES.length - 1];
451
+ }
452
+ if (!SUPPORTED_PYTHON_SERIES.includes(pythonVersion)) {
453
+ throw new Error(`Unsupported Python version: ${pythonVersion}. ` +
454
+ `Daytona supports the following series: ${SUPPORTED_PYTHON_SERIES.join(', ')}`);
455
+ }
456
+ // Map series to latest micro version
457
+ const seriesMap = Object.fromEntries(LATEST_PYTHON_MICRO_VERSIONS.map((v) => {
458
+ const [major, minor, micro] = v.split('.');
459
+ return [`${major}.${minor}`, micro];
460
+ }));
461
+ const micro = seriesMap[pythonVersion];
462
+ return `${pythonVersion}.${micro}`;
463
+ }
464
+ /**
465
+ * Extracts source files from COPY commands in a Dockerfile.
466
+ *
467
+ * @param {string} dockerfileContent - The content of the Dockerfile.
468
+ * @param {string} pathPrefix - The path prefix to use for the sources.
469
+ * @returns {Array<[string, string]>} The list of the actual file path and its corresponding COPY-command source path.
470
+ */
471
+ static extractCopySources(dockerfileContent, pathPrefix = '') {
472
+ const sources = [];
473
+ const lines = dockerfileContent.split('\n');
474
+ for (const line of lines) {
475
+ // Skip empty lines and comments
476
+ if (!line.trim() || line.trim().startsWith('#')) {
477
+ continue;
478
+ }
479
+ // Check if the line contains a COPY command
480
+ if (/^\s*COPY\s/.test(line)) {
481
+ const commandParts = this.parseCopyCommand(line);
482
+ if (commandParts) {
483
+ // Get source paths from the parsed command parts
484
+ for (const source of commandParts.sources) {
485
+ // Handle absolute and relative paths differently
486
+ const fullPathPattern = _path.isAbsolute(source) ? source : _path.join(pathPrefix, source);
487
+ const matchingFiles = fg.sync([fullPathPattern], { dot: true });
488
+ if (matchingFiles.length > 0) {
489
+ for (const matchingFile of matchingFiles) {
490
+ sources.push([matchingFile, source]);
491
+ }
492
+ }
493
+ else {
494
+ sources.push([fullPathPattern, source]);
495
+ }
496
+ }
497
+ }
498
+ }
499
+ }
500
+ return sources;
501
+ }
502
+ /**
503
+ * Parses a COPY command to extract sources and destination.
504
+ *
505
+ * @param {string} line - The line to parse.
506
+ * @returns {Object} The parsed sources and destination.
507
+ */
508
+ static parseCopyCommand(line) {
509
+ // Remove initial "COPY" and strip whitespace
510
+ const parts = line.trim().substring(4).trim();
511
+ // Handle JSON array format: COPY ["src1", "src2", "dest"]
512
+ if (parts.startsWith('[')) {
513
+ try {
514
+ // Parse the JSON-like array format
515
+ const elements = (0, shell_quote_1.parse)(parts.replace('[', '').replace(']', '')).filter((x) => typeof x === 'string');
516
+ if (elements.length < 2) {
517
+ return null;
518
+ }
519
+ return {
520
+ sources: elements.slice(0, -1),
521
+ dest: elements[elements.length - 1],
522
+ };
523
+ }
524
+ catch (_a) {
525
+ return null;
526
+ }
527
+ }
528
+ // Handle regular format with possible flags
529
+ const splitParts = (0, shell_quote_1.parse)(parts).filter((x) => typeof x === 'string');
530
+ // Extract flags like --chown, --chmod, --from
531
+ let sourcesStartIdx = 0;
532
+ for (let i = 0; i < splitParts.length; i++) {
533
+ const part = splitParts[i];
534
+ if (part.startsWith('--')) {
535
+ // Skip the flag and its value if it has one
536
+ if (!part.includes('=') && i + 1 < splitParts.length && !splitParts[i + 1].startsWith('--')) {
537
+ sourcesStartIdx = i + 2;
538
+ }
539
+ else {
540
+ sourcesStartIdx = i + 1;
541
+ }
542
+ }
543
+ else {
544
+ break;
545
+ }
546
+ }
547
+ // After skipping flags, we need at least one source and one destination
548
+ if (splitParts.length - sourcesStartIdx < 2) {
549
+ return null;
550
+ }
551
+ return {
552
+ sources: splitParts.slice(sourcesStartIdx, -1),
553
+ dest: splitParts[splitParts.length - 1],
554
+ };
555
+ }
556
+ }
557
+ exports.Image = Image;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Configuration for the ObjectStorage class.
3
+ *
4
+ * @interface
5
+ * @property {string} endpointUrl - The endpoint URL for the object storage service.
6
+ * @property {string} accessKeyId - The access key ID for the object storage service.
7
+ * @property {string} secretAccessKey - The secret access key for the object storage service.
8
+ * @property {string} [sessionToken] - The session token for the object storage service. Used for temporary credentials.
9
+ * @property {string} [bucketName] - The name of the bucket to use.
10
+ */
11
+ export interface ObjectStorageConfig {
12
+ endpointUrl: string;
13
+ accessKeyId: string;
14
+ secretAccessKey: string;
15
+ sessionToken?: string;
16
+ bucketName?: string;
17
+ }
18
+ /**
19
+ * ObjectStorage class for interacting with object storage services.
20
+ *
21
+ * @class
22
+ * @param {ObjectStorageConfig} config - The configuration for the object storage service.
23
+ */
24
+ export declare class ObjectStorage {
25
+ private bucketName;
26
+ private s3Client;
27
+ constructor(config: ObjectStorageConfig);
28
+ /**
29
+ * Upload a file or directory to object storage.
30
+ *
31
+ * @param {string} path - The path to the file or directory to upload.
32
+ * @param {string} organizationId - The organization ID to use for the upload.
33
+ * @param {string} [archiveBasePath] - The base path to use for the archive.
34
+ * @returns {Promise<string>} The hash of the uploaded file or directory.
35
+ */
36
+ upload(path: string, organizationId: string, archiveBasePath?: string): Promise<string>;
37
+ /**
38
+ * Compute a hash for a file or directory.
39
+ *
40
+ * @param {string} pathStr - The path to the file or directory to hash.
41
+ * @param {string} [archiveBasePath] - The base path to use for the archive.
42
+ * @returns {Promise<string>} The hash of the file or directory.
43
+ */
44
+ private computeHashForPathMd5;
45
+ /**
46
+ * Recursively hash a directory and its contents.
47
+ *
48
+ * @param {string} dirPath - The path to the directory to hash.
49
+ * @param {string} basePath - The base path to use for the hash.
50
+ * @param {crypto.Hash} hasher - The hasher to use for the hash.
51
+ * @returns {Promise<void>} A promise that resolves when the directory has been hashed.
52
+ */
53
+ private hashDirectory;
54
+ /**
55
+ * Hash a file.
56
+ *
57
+ * @param {string} filePath - The path to the file to hash.
58
+ * @param {crypto.Hash} hasher - The hasher to use for the hash.
59
+ * @returns {Promise<void>} A promise that resolves when the file has been hashed.
60
+ */
61
+ private hashFile;
62
+ /**
63
+ * Check if a prefix (folder) exists in S3.
64
+ *
65
+ * @param {string} prefix - The prefix to check.
66
+ * @returns {Promise<boolean>} True if the prefix exists, false otherwise.
67
+ */
68
+ private folderExistsInS3;
69
+ /**
70
+ * Create a tar archive of the specified path and upload it to S3.
71
+ *
72
+ * @param {string} s3Key - The key to use for the uploaded file.
73
+ * @param {string} sourcePath - The path to the file or directory to upload.
74
+ * @param {string} [archiveBasePath] - The base path to use for the archive.
75
+ */
76
+ private uploadAsTar;
77
+ private extractAwsRegion;
78
+ /**
79
+ * Compute the base path for an archive. Returns normalized path without the root (drive letter or leading slash).
80
+ *
81
+ * @param {string} pathStr - The path to compute the base path for.
82
+ * @returns {string} The base path for the archive.
83
+ */
84
+ static computeArchiveBasePath(pathStr: string): string;
85
+ }