@devicecloud.dev/dcd 3.7.0 → 3.7.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/commands/cloud.js +25 -15
- package/dist/commands/status.js +3 -9
- package/dist/gateways/ApiGateway.d.ts +35 -0
- package/dist/gateways/ApiGateway.js +122 -0
- package/dist/gateways/SupabaseGateway.d.ts +11 -0
- package/dist/gateways/SupabaseGateway.js +29 -0
- package/dist/methods.d.ts +6 -25
- package/dist/methods.js +22 -117
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/dist/commands/cloud.js
CHANGED
|
@@ -12,6 +12,7 @@ const StreamZip = require("node-stream-zip");
|
|
|
12
12
|
const constants_1 = require("../constants");
|
|
13
13
|
const methods_1 = require("../methods");
|
|
14
14
|
const plan_1 = require("../plan");
|
|
15
|
+
const ApiGateway_1 = require("../gateways/ApiGateway");
|
|
15
16
|
exports.mimeTypeLookupByExtension = {
|
|
16
17
|
apk: 'application/vnd.android.package-archive',
|
|
17
18
|
yaml: 'application/x-yaml',
|
|
@@ -518,11 +519,7 @@ class Cloud extends core_1.Command {
|
|
|
518
519
|
if (debug) {
|
|
519
520
|
this.log(`DEBUG: Submitting flow upload request to ${apiUrl}/uploads/flow`);
|
|
520
521
|
}
|
|
521
|
-
const
|
|
522
|
-
body: testFormData,
|
|
523
|
-
headers: { 'x-app-api-key': apiKey },
|
|
524
|
-
};
|
|
525
|
-
const { message, results } = await (0, methods_1.typeSafePost)(apiUrl, '/uploads/flow', options);
|
|
522
|
+
const { message, results } = await ApiGateway_1.ApiGateway.uploadFlow(apiUrl, apiKey, testFormData);
|
|
526
523
|
if (debug) {
|
|
527
524
|
this.log(`DEBUG: Flow upload response received`);
|
|
528
525
|
this.log(`DEBUG: Message: ${message}`);
|
|
@@ -581,9 +578,7 @@ class Cloud extends core_1.Command {
|
|
|
581
578
|
if (debug) {
|
|
582
579
|
this.log(`DEBUG: Polling for results: ${results[0].test_upload_id}`);
|
|
583
580
|
}
|
|
584
|
-
const { results: updatedResults } = await
|
|
585
|
-
headers: { 'x-app-api-key': apiKey },
|
|
586
|
-
});
|
|
581
|
+
const { results: updatedResults } = await ApiGateway_1.ApiGateway.getResultsForUpload(apiUrl, apiKey, results[0].test_upload_id);
|
|
587
582
|
if (!updatedResults) {
|
|
588
583
|
throw new Error('no results');
|
|
589
584
|
}
|
|
@@ -607,11 +602,24 @@ class Cloud extends core_1.Command {
|
|
|
607
602
|
if (!json) {
|
|
608
603
|
core_1.ux.action.stop('completed');
|
|
609
604
|
this.log('\n');
|
|
605
|
+
const hasFailedTests = updatedResults.some((result) => result.status === 'FAILED');
|
|
610
606
|
(0, cli_ux_1.table)(updatedResults, {
|
|
611
607
|
status: { get: (row) => row.status },
|
|
612
608
|
test: {
|
|
613
609
|
get: (row) => `${row.test_file_name} ${row.retry_of ? '(retry)' : ''}`,
|
|
614
610
|
},
|
|
611
|
+
duration: {
|
|
612
|
+
get: (row) => row.duration_seconds
|
|
613
|
+
? (0, methods_1.formatDurationSeconds)(row.duration_seconds)
|
|
614
|
+
: '',
|
|
615
|
+
},
|
|
616
|
+
...(hasFailedTests && {
|
|
617
|
+
fail_reason: {
|
|
618
|
+
get: (row) => row.status === 'FAILED' && row.fail_reason
|
|
619
|
+
? row.fail_reason
|
|
620
|
+
: '',
|
|
621
|
+
},
|
|
622
|
+
}),
|
|
615
623
|
}, { printLine: this.log.bind(this) });
|
|
616
624
|
this.log('\n');
|
|
617
625
|
this.log('Run completed, you can access the results at:');
|
|
@@ -624,13 +632,7 @@ class Cloud extends core_1.Command {
|
|
|
624
632
|
if (debug) {
|
|
625
633
|
this.log(`DEBUG: Downloading artifacts: ${downloadArtifacts}`);
|
|
626
634
|
}
|
|
627
|
-
await
|
|
628
|
-
body: JSON.stringify({ results: downloadArtifacts }),
|
|
629
|
-
headers: {
|
|
630
|
-
'content-type': 'application/json',
|
|
631
|
-
'x-app-api-key': apiKey,
|
|
632
|
-
},
|
|
633
|
-
}, artifactsPath);
|
|
635
|
+
await ApiGateway_1.ApiGateway.downloadArtifactsZip(apiUrl, apiKey, results[0].test_upload_id, downloadArtifacts, artifactsPath);
|
|
634
636
|
this.log('\n');
|
|
635
637
|
this.log(`Test artifacts have been downloaded to ${artifactsPath || './artifacts.zip'}`);
|
|
636
638
|
}
|
|
@@ -657,6 +659,10 @@ class Cloud extends core_1.Command {
|
|
|
657
659
|
tests: resultsWithoutEarlierTries.map((r) => ({
|
|
658
660
|
name: r.test_file_name,
|
|
659
661
|
status: r.status,
|
|
662
|
+
duration_seconds: r.duration_seconds,
|
|
663
|
+
fail_reason: r.status === 'FAILED'
|
|
664
|
+
? r.fail_reason || 'No reason provided'
|
|
665
|
+
: undefined,
|
|
660
666
|
})),
|
|
661
667
|
};
|
|
662
668
|
if (flags['json-file']) {
|
|
@@ -679,6 +685,10 @@ class Cloud extends core_1.Command {
|
|
|
679
685
|
tests: resultsWithoutEarlierTries.map((r) => ({
|
|
680
686
|
name: r.test_file_name,
|
|
681
687
|
status: r.status,
|
|
688
|
+
duration_seconds: r.duration_seconds,
|
|
689
|
+
fail_reason: r.status === 'FAILED'
|
|
690
|
+
? r.fail_reason || 'No reason provided'
|
|
691
|
+
: undefined,
|
|
682
692
|
})),
|
|
683
693
|
};
|
|
684
694
|
if (flags['json-file']) {
|
package/dist/commands/status.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_1 = require("@oclif/core");
|
|
4
4
|
const constants_1 = require("../constants");
|
|
5
|
+
const ApiGateway_1 = require("../gateways/ApiGateway");
|
|
5
6
|
const methods_1 = require("../methods");
|
|
6
7
|
class Status extends core_1.Command {
|
|
7
8
|
static description = 'Get the status of an upload by name or upload ID';
|
|
@@ -52,7 +53,7 @@ class Status extends core_1.Command {
|
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
55
|
try {
|
|
55
|
-
const status = (await
|
|
56
|
+
const status = (await ApiGateway_1.ApiGateway.getUploadStatus(apiUrl, apiKey, {
|
|
56
57
|
name,
|
|
57
58
|
uploadId,
|
|
58
59
|
}));
|
|
@@ -85,14 +86,7 @@ class Status extends core_1.Command {
|
|
|
85
86
|
this.log(` Fail reason: ${item.failReason}`);
|
|
86
87
|
}
|
|
87
88
|
if (item.durationSeconds) {
|
|
88
|
-
|
|
89
|
-
const seconds = item.durationSeconds % 60;
|
|
90
|
-
if (minutes > 0) {
|
|
91
|
-
this.log(` Duration: ${minutes}m ${seconds}s`);
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
this.log(` Duration: ${item.durationSeconds}s`);
|
|
95
|
-
}
|
|
89
|
+
this.log(` Duration: ${(0, methods_1.formatDurationSeconds)(item.durationSeconds)}`);
|
|
96
90
|
}
|
|
97
91
|
this.log('');
|
|
98
92
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { TAppMetadata } from '../types';
|
|
2
|
+
export declare class ApiGateway {
|
|
3
|
+
static getResultsForUpload: (baseUrl: string, apiKey: string, uploadId: string) => Promise<{
|
|
4
|
+
statusCode?: number;
|
|
5
|
+
results?: import("../../../api/schema.types").components["schemas"]["TResultResponse"][];
|
|
6
|
+
}>;
|
|
7
|
+
static getUploadStatus: (baseUrl: string, apiKey: string, options: {
|
|
8
|
+
uploadId?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
}) => Promise<{
|
|
11
|
+
status: "PASSED" | "FAILED" | "CANCELLED" | "PENDING";
|
|
12
|
+
tests: Array<{
|
|
13
|
+
name: string;
|
|
14
|
+
status: "PASSED" | "FAILED" | "CANCELLED" | "PENDING";
|
|
15
|
+
durationSeconds?: number;
|
|
16
|
+
failReason?: string;
|
|
17
|
+
}>;
|
|
18
|
+
}>;
|
|
19
|
+
static downloadArtifactsZip: (baseUrl: string, apiKey: string, uploadId: string, results: "ALL" | "FAILED", artifactsPath?: string) => Promise<void>;
|
|
20
|
+
static uploadFlow: (baseUrl: string, apiKey: string, testFormData: FormData) => Promise<{
|
|
21
|
+
message?: string;
|
|
22
|
+
results?: import("../../../api/schema.types").components["schemas"]["IDBResult"][];
|
|
23
|
+
}>;
|
|
24
|
+
static finaliseUpload: (baseUrl: string, apiKey: string, id: string, metadata: TAppMetadata, path: string, sha: string) => Promise<Record<string, never>>;
|
|
25
|
+
static getBinaryUploadUrl: (baseUrl: string, apiKey: string, platform: "android" | "ios") => Promise<{
|
|
26
|
+
message: string;
|
|
27
|
+
path: string;
|
|
28
|
+
token: string;
|
|
29
|
+
id: string;
|
|
30
|
+
}>;
|
|
31
|
+
static checkForExistingUpload: (baseUrl: string, apiKey: string, sha: string) => Promise<{
|
|
32
|
+
appBinaryId: string;
|
|
33
|
+
exists: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiGateway = void 0;
|
|
4
|
+
class ApiGateway {
|
|
5
|
+
static getResultsForUpload = async (baseUrl, apiKey, uploadId) => {
|
|
6
|
+
// todo: merge with getUploadStatus
|
|
7
|
+
const res = await fetch(`${baseUrl}/results/${uploadId}`, {
|
|
8
|
+
headers: { 'x-app-api-key': apiKey },
|
|
9
|
+
});
|
|
10
|
+
if (!res.ok) {
|
|
11
|
+
throw new Error(await res.text());
|
|
12
|
+
}
|
|
13
|
+
return res.json();
|
|
14
|
+
};
|
|
15
|
+
static getUploadStatus = async (baseUrl, apiKey, options) => {
|
|
16
|
+
const queryParams = new URLSearchParams();
|
|
17
|
+
if (options.uploadId) {
|
|
18
|
+
queryParams.append('uploadId', options.uploadId);
|
|
19
|
+
}
|
|
20
|
+
if (options.name) {
|
|
21
|
+
queryParams.append('name', options.name);
|
|
22
|
+
}
|
|
23
|
+
const response = await fetch(`${baseUrl}/uploads/status?${queryParams}`, {
|
|
24
|
+
headers: {
|
|
25
|
+
'x-app-api-key': apiKey,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(`Failed to fetch status: ${response.statusText}`);
|
|
30
|
+
}
|
|
31
|
+
return response.json();
|
|
32
|
+
};
|
|
33
|
+
static downloadArtifactsZip = async (baseUrl, apiKey, uploadId, results, artifactsPath = './artifacts.zip') => {
|
|
34
|
+
const res = await fetch(`${baseUrl}/results/${uploadId}/download`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'x-app-api-key': apiKey,
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({ results }),
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
throw new Error(await res.text());
|
|
43
|
+
}
|
|
44
|
+
// Handle tilde expansion for home directory
|
|
45
|
+
if (artifactsPath.startsWith('~/') || artifactsPath === '~') {
|
|
46
|
+
artifactsPath = artifactsPath.replace(/^~(?=$|\/|\\)/, require('os').homedir());
|
|
47
|
+
}
|
|
48
|
+
// Create directory structure if it doesn't exist
|
|
49
|
+
const { dirname } = require('node:path');
|
|
50
|
+
const { mkdirSync, createWriteStream } = require('node:fs');
|
|
51
|
+
const { finished } = require('node:stream/promises');
|
|
52
|
+
const { Readable } = require('node:stream');
|
|
53
|
+
const directory = dirname(artifactsPath);
|
|
54
|
+
if (directory !== '.') {
|
|
55
|
+
try {
|
|
56
|
+
mkdirSync(directory, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// Ignore if directory already exists
|
|
60
|
+
if (error.code !== 'EEXIST') {
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const fileStream = createWriteStream(artifactsPath, { flags: 'wx' });
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
|
+
await finished(Readable.fromWeb(res.body).pipe(fileStream));
|
|
68
|
+
};
|
|
69
|
+
static uploadFlow = async (baseUrl, apiKey, testFormData) => {
|
|
70
|
+
const res = await fetch(`${baseUrl}/uploads/flow`, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
body: testFormData,
|
|
73
|
+
headers: {
|
|
74
|
+
'x-app-api-key': apiKey,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
throw new Error(await res.text());
|
|
79
|
+
}
|
|
80
|
+
return res.json();
|
|
81
|
+
};
|
|
82
|
+
static finaliseUpload = async (baseUrl, apiKey, id, metadata, path, sha) => {
|
|
83
|
+
const res = await fetch(`${baseUrl}/uploads/finaliseUpload`, {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
body: JSON.stringify({ id, metadata, path, sha }),
|
|
86
|
+
headers: {
|
|
87
|
+
'content-type': 'application/json',
|
|
88
|
+
'x-app-api-key': apiKey,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
if (!res.ok) {
|
|
92
|
+
throw new Error(await res.text());
|
|
93
|
+
}
|
|
94
|
+
return res.json();
|
|
95
|
+
};
|
|
96
|
+
static getBinaryUploadUrl = async (baseUrl, apiKey, platform) => {
|
|
97
|
+
const res = await fetch(`${baseUrl}/uploads/getBinaryUploadUrl`, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
body: JSON.stringify({ platform }),
|
|
100
|
+
headers: {
|
|
101
|
+
'content-type': 'application/json',
|
|
102
|
+
'x-app-api-key': apiKey,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
if (!res.ok) {
|
|
106
|
+
throw new Error(await res.text());
|
|
107
|
+
}
|
|
108
|
+
return res.json();
|
|
109
|
+
};
|
|
110
|
+
static checkForExistingUpload = async (baseUrl, apiKey, sha) => {
|
|
111
|
+
const res = await fetch(`${baseUrl}/uploads/checkForExistingUpload`, {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
body: JSON.stringify({ sha }),
|
|
114
|
+
headers: {
|
|
115
|
+
'content-type': 'application/json',
|
|
116
|
+
'x-app-api-key': apiKey,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
return res.json();
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
exports.ApiGateway = ApiGateway;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class SupabaseGateway {
|
|
2
|
+
private static SB;
|
|
3
|
+
static getSupabaseKeys(env: 'prod' | 'dev'): {
|
|
4
|
+
SUPABASE_PUBLIC_KEY: string;
|
|
5
|
+
SUPABASE_URL: string;
|
|
6
|
+
} | {
|
|
7
|
+
SUPABASE_PUBLIC_KEY: string;
|
|
8
|
+
SUPABASE_URL: string;
|
|
9
|
+
};
|
|
10
|
+
static uploadToSignedUrl(env: 'prod' | 'dev', path: string, token: string, file: File): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SupabaseGateway = void 0;
|
|
4
|
+
const supabase_js_1 = require("@supabase/supabase-js");
|
|
5
|
+
class SupabaseGateway {
|
|
6
|
+
static SB = {
|
|
7
|
+
dev: {
|
|
8
|
+
SUPABASE_PUBLIC_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxibXNvd2VodGp3bnFsdXJwZW1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDkyMTg0ODcsImV4cCI6MjAyNDc5NDQ4N30.zeLTMAuZ_WwYvGdeP0kdvL_Zrs-RQee5APPyxmWq7qQ',
|
|
9
|
+
SUPABASE_URL: 'https://lbmsowehtjwnqlurpemb.supabase.co',
|
|
10
|
+
},
|
|
11
|
+
prod: {
|
|
12
|
+
SUPABASE_PUBLIC_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBneWRucGhiaW1ldGluc2dma2JvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDc1OTQzNDYsImV4cCI6MjAyMzE3MDM0Nn0.hAYOMFxxwX1exkQkY9xyQJGC_GhGnyogkj2N-kBkMI8',
|
|
13
|
+
SUPABASE_URL: 'https://pgydnphbimetinsgfkbo.supabase.co',
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
static getSupabaseKeys(env) {
|
|
17
|
+
return this.SB[env];
|
|
18
|
+
}
|
|
19
|
+
static async uploadToSignedUrl(env, path, token, file) {
|
|
20
|
+
const { SUPABASE_URL, SUPABASE_PUBLIC_KEY } = this.getSupabaseKeys(env);
|
|
21
|
+
const supabase = (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_PUBLIC_KEY);
|
|
22
|
+
const uploadToUrl = await supabase.storage
|
|
23
|
+
.from('organizations')
|
|
24
|
+
.uploadToSignedUrl(path, token, file);
|
|
25
|
+
if (uploadToUrl.error)
|
|
26
|
+
throw new Error(uploadToUrl.error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.SupabaseGateway = SupabaseGateway;
|
package/dist/methods.d.ts
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
import * as archiver from 'archiver';
|
|
2
|
-
import { paths } from '../../api/schema.types';
|
|
3
2
|
import { TAppMetadata } from './types';
|
|
4
|
-
export declare const typeSafePost: <T extends keyof paths>(baseUrl: string, path: string, init?: {
|
|
5
|
-
body?: BodyInit;
|
|
6
|
-
headers?: HeadersInit;
|
|
7
|
-
}) => Promise<paths[T]["post"]["responses"]["201"]["content"]["application/json"]>;
|
|
8
|
-
export declare const typeSafePostDownload: (baseUrl: string, path: string, init?: {
|
|
9
|
-
body?: BodyInit;
|
|
10
|
-
headers?: HeadersInit;
|
|
11
|
-
}, artifactsPath?: string) => Promise<void>;
|
|
12
|
-
export declare const typeSafeGet: <T extends keyof paths>(baseUrl: string, path: string, init?: {
|
|
13
|
-
body?: FormData;
|
|
14
|
-
headers?: HeadersInit;
|
|
15
|
-
}) => Promise<paths[T]["get"]["responses"]["200"]["content"]["application/json"]>;
|
|
16
3
|
export declare const toBuffer: (archive: archiver.Archiver) => Promise<Buffer<ArrayBuffer>>;
|
|
17
4
|
export declare const compressFolderToBlob: (sourceDir: string) => Promise<Blob>;
|
|
18
5
|
export declare const compressFilesFromRelativePath: (path: string, files: string[], commonRoot: string) => Promise<Buffer<ArrayBuffer>>;
|
|
@@ -23,18 +10,6 @@ export declare const extractAppMetadataIos: (appFolderPath: string) => Promise<T
|
|
|
23
10
|
export declare const uploadBinary: (filePath: string, apiUrl: string, apiKey: string, ignoreShaCheck?: boolean, log?: boolean) => Promise<string>;
|
|
24
11
|
export declare const uploadBinaries: (finalAppFiles: string[], apiUrl: string, apiKey: string, ignoreShaCheck?: boolean, log?: boolean) => Promise<string[]>;
|
|
25
12
|
export declare const verifyAdditionalAppFiles: (appFiles: string[] | undefined) => Promise<void>;
|
|
26
|
-
export declare const getUploadStatus: (apiUrl: string, apiKey: string, options: {
|
|
27
|
-
uploadId?: string;
|
|
28
|
-
name?: string;
|
|
29
|
-
}) => Promise<{
|
|
30
|
-
status: "PASSED" | "FAILED" | "CANCELLED" | "PENDING";
|
|
31
|
-
tests: Array<{
|
|
32
|
-
name: string;
|
|
33
|
-
status: "PASSED" | "FAILED" | "CANCELLED" | "PENDING";
|
|
34
|
-
durationSeconds?: number;
|
|
35
|
-
failReason?: string;
|
|
36
|
-
}>;
|
|
37
|
-
}>;
|
|
38
13
|
/**
|
|
39
14
|
* Writes JSON data to a file with error handling
|
|
40
15
|
* @param filePath - Path to the output JSON file
|
|
@@ -46,3 +21,9 @@ export declare const writeJSONFile: (filePath: string, data: any, logger: {
|
|
|
46
21
|
log: (message: string) => void;
|
|
47
22
|
warn: (message: string) => void;
|
|
48
23
|
}) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Formats duration in seconds into a human readable string
|
|
26
|
+
* @param durationSeconds - Duration in seconds
|
|
27
|
+
* @returns Formatted duration string (e.g. "2m 30s" or "45s")
|
|
28
|
+
*/
|
|
29
|
+
export declare const formatDurationSeconds: (durationSeconds: number) => string;
|
package/dist/methods.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.formatDurationSeconds = exports.writeJSONFile = exports.verifyAdditionalAppFiles = exports.uploadBinaries = exports.uploadBinary = exports.extractAppMetadataIos = exports.extractAppMetadataIosZip = exports.extractAppMetadataAndroid = exports.verifyAppZip = exports.compressFilesFromRelativePath = exports.compressFolderToBlob = exports.toBuffer = void 0;
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
|
-
const supabase_js_1 = require("@supabase/supabase-js");
|
|
6
5
|
// required polyfill for node 18
|
|
7
6
|
const file_1 = require("@web-std/file");
|
|
8
7
|
// @ts-ignore
|
|
@@ -13,64 +12,13 @@ const node_fs_1 = require("node:fs");
|
|
|
13
12
|
const promises_1 = require("node:fs/promises");
|
|
14
13
|
const nodePath = require("node:path");
|
|
15
14
|
const node_stream_1 = require("node:stream");
|
|
16
|
-
const promises_2 = require("node:stream/promises");
|
|
17
15
|
const StreamZip = require("node-stream-zip");
|
|
18
16
|
const plist_1 = require("plist");
|
|
19
17
|
const node_crypto_1 = require("node:crypto");
|
|
20
18
|
const path = require("path");
|
|
21
|
-
const node_path_1 = require("node:path");
|
|
22
|
-
const os = require("node:os");
|
|
23
19
|
const cloud_1 = require("./commands/cloud");
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
...init,
|
|
27
|
-
method: 'POST',
|
|
28
|
-
});
|
|
29
|
-
if (!res.ok) {
|
|
30
|
-
throw new Error(await res.text());
|
|
31
|
-
}
|
|
32
|
-
return res.json();
|
|
33
|
-
};
|
|
34
|
-
exports.typeSafePost = typeSafePost;
|
|
35
|
-
const typeSafePostDownload = async (baseUrl, path, init, artifactsPath) => {
|
|
36
|
-
const res = await fetch(baseUrl + path, {
|
|
37
|
-
...init,
|
|
38
|
-
method: 'POST',
|
|
39
|
-
});
|
|
40
|
-
if (!res.ok) {
|
|
41
|
-
throw new Error(await res.text());
|
|
42
|
-
}
|
|
43
|
-
let outputPath = artifactsPath || './artifacts.zip';
|
|
44
|
-
// Handle tilde expansion for home directory
|
|
45
|
-
if (outputPath.startsWith('~/') || outputPath === '~') {
|
|
46
|
-
outputPath = outputPath.replace(/^~(?=$|\/|\\)/, os.homedir());
|
|
47
|
-
}
|
|
48
|
-
// Create directory structure if it doesn't exist
|
|
49
|
-
const directory = (0, node_path_1.dirname)(outputPath);
|
|
50
|
-
if (directory !== '.') {
|
|
51
|
-
try {
|
|
52
|
-
(0, node_fs_1.mkdirSync)(directory, { recursive: true });
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
// Ignore if directory already exists
|
|
56
|
-
if (error.code !== 'EEXIST') {
|
|
57
|
-
throw error;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
const fileStream = (0, node_fs_1.createWriteStream)(outputPath, { flags: 'wx' });
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
|
-
await (0, promises_2.finished)(node_stream_1.Readable.fromWeb(res.body).pipe(fileStream));
|
|
64
|
-
};
|
|
65
|
-
exports.typeSafePostDownload = typeSafePostDownload;
|
|
66
|
-
const typeSafeGet = async (baseUrl, path, init) => {
|
|
67
|
-
const res = await fetch(baseUrl + path, init);
|
|
68
|
-
if (!res.ok) {
|
|
69
|
-
throw new Error(await res.text());
|
|
70
|
-
}
|
|
71
|
-
return res.json();
|
|
72
|
-
};
|
|
73
|
-
exports.typeSafeGet = typeSafeGet;
|
|
20
|
+
const SupabaseGateway_1 = require("./gateways/SupabaseGateway");
|
|
21
|
+
const ApiGateway_1 = require("./gateways/ApiGateway");
|
|
74
22
|
const toBuffer = async (archive) => {
|
|
75
23
|
const chunks = [];
|
|
76
24
|
const writable = new node_stream_1.Writable();
|
|
@@ -216,13 +164,7 @@ const uploadBinary = async (filePath, apiUrl, apiKey, ignoreShaCheck = false, lo
|
|
|
216
164
|
}
|
|
217
165
|
if (!ignoreShaCheck && sha) {
|
|
218
166
|
try {
|
|
219
|
-
const { appBinaryId, exists } = await
|
|
220
|
-
body: JSON.stringify({ sha }),
|
|
221
|
-
headers: {
|
|
222
|
-
'content-type': 'application/json',
|
|
223
|
-
'x-app-api-key': apiKey,
|
|
224
|
-
},
|
|
225
|
-
});
|
|
167
|
+
const { appBinaryId, exists } = await ApiGateway_1.ApiGateway.checkForExistingUpload(apiUrl, apiKey, sha);
|
|
226
168
|
if (exists) {
|
|
227
169
|
if (log) {
|
|
228
170
|
core_1.ux.info(`sha hash matches existing binary with id: ${appBinaryId}, skipping upload. Force upload with --ignore-sha-check`);
|
|
@@ -235,15 +177,7 @@ const uploadBinary = async (filePath, apiUrl, apiKey, ignoreShaCheck = false, lo
|
|
|
235
177
|
// ignore error
|
|
236
178
|
}
|
|
237
179
|
}
|
|
238
|
-
const { id, message, path, token } = await (
|
|
239
|
-
body: JSON.stringify({
|
|
240
|
-
platform: filePath?.endsWith('.apk') ? 'android' : 'ios',
|
|
241
|
-
}),
|
|
242
|
-
headers: {
|
|
243
|
-
'content-type': 'application/json',
|
|
244
|
-
'x-app-api-key': apiKey,
|
|
245
|
-
},
|
|
246
|
-
});
|
|
180
|
+
const { id, message, path, token } = await ApiGateway_1.ApiGateway.getBinaryUploadUrl(apiUrl, apiKey, filePath?.endsWith('.apk') ? 'android' : 'ios');
|
|
247
181
|
if (!path)
|
|
248
182
|
throw new Error(message);
|
|
249
183
|
let metadata;
|
|
@@ -259,33 +193,9 @@ const uploadBinary = async (filePath, apiUrl, apiKey, ignoreShaCheck = false, lo
|
|
|
259
193
|
core_1.ux.warn('Failed to extract app metadata, please share with support@devicecloud.dev so we can improve our parsing.');
|
|
260
194
|
}
|
|
261
195
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
SUPABASE_PUBLIC_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxibXNvd2VodGp3bnFsdXJwZW1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDkyMTg0ODcsImV4cCI6MjAyNDc5NDQ4N30.zeLTMAuZ_WwYvGdeP0kdvL_Zrs-RQee5APPyxmWq7qQ',
|
|
266
|
-
SUPABASE_URL: 'https://lbmsowehtjwnqlurpemb.supabase.co',
|
|
267
|
-
},
|
|
268
|
-
prod: {
|
|
269
|
-
SUPABASE_PUBLIC_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBneWRucGhiaW1ldGluc2dma2JvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDc1OTQzNDYsImV4cCI6MjAyMzE3MDM0Nn0.hAYOMFxxwX1exkQkY9xyQJGC_GhGnyogkj2N-kBkMI8',
|
|
270
|
-
SUPABASE_URL: 'https://pgydnphbimetinsgfkbo.supabase.co',
|
|
271
|
-
},
|
|
272
|
-
};
|
|
273
|
-
const { SUPABASE_PUBLIC_KEY, SUPABASE_URL } = SB[apiUrl === 'https://api.devicecloud.dev' ? 'prod' : 'dev'];
|
|
274
|
-
const supabase = (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_PUBLIC_KEY);
|
|
275
|
-
const uploadToUrl = await supabase.storage
|
|
276
|
-
.from('organizations')
|
|
277
|
-
.uploadToSignedUrl(path, token, file);
|
|
278
|
-
if (uploadToUrl.error)
|
|
279
|
-
throw new Error(uploadToUrl.error);
|
|
280
|
-
const { error } = await (0, exports.typeSafePost)(apiUrl, '/uploads/finaliseUpload', {
|
|
281
|
-
body: JSON.stringify({ id, metadata, path, sha }),
|
|
282
|
-
headers: {
|
|
283
|
-
'content-type': 'application/json',
|
|
284
|
-
'x-app-api-key': apiKey,
|
|
285
|
-
},
|
|
286
|
-
});
|
|
287
|
-
if (error)
|
|
288
|
-
throw new Error(error);
|
|
196
|
+
const env = apiUrl === 'https://api.devicecloud.dev' ? 'prod' : 'dev';
|
|
197
|
+
await SupabaseGateway_1.SupabaseGateway.uploadToSignedUrl(env, path, token, file);
|
|
198
|
+
await ApiGateway_1.ApiGateway.finaliseUpload(apiUrl, apiKey, id, metadata, path, sha);
|
|
289
199
|
if (log) {
|
|
290
200
|
core_1.ux.action.stop(`\nBinary uploaded with id: ${id}`);
|
|
291
201
|
}
|
|
@@ -329,25 +239,6 @@ async function getFileHashFromFile(file) {
|
|
|
329
239
|
processChunks();
|
|
330
240
|
});
|
|
331
241
|
}
|
|
332
|
-
const getUploadStatus = async (apiUrl, apiKey, options) => {
|
|
333
|
-
const queryParams = new URLSearchParams();
|
|
334
|
-
if (options.uploadId) {
|
|
335
|
-
queryParams.append('uploadId', options.uploadId);
|
|
336
|
-
}
|
|
337
|
-
if (options.name) {
|
|
338
|
-
queryParams.append('name', options.name);
|
|
339
|
-
}
|
|
340
|
-
const response = await fetch(`${apiUrl}/uploads/status?${queryParams}`, {
|
|
341
|
-
headers: {
|
|
342
|
-
'x-app-api-key': apiKey,
|
|
343
|
-
},
|
|
344
|
-
});
|
|
345
|
-
if (!response.ok) {
|
|
346
|
-
throw new Error(`Failed to fetch status: ${response.statusText}`);
|
|
347
|
-
}
|
|
348
|
-
return response.json();
|
|
349
|
-
};
|
|
350
|
-
exports.getUploadStatus = getUploadStatus;
|
|
351
242
|
/**
|
|
352
243
|
* Writes JSON data to a file with error handling
|
|
353
244
|
* @param filePath - Path to the output JSON file
|
|
@@ -367,3 +258,17 @@ const writeJSONFile = (filePath, data, logger) => {
|
|
|
367
258
|
}
|
|
368
259
|
};
|
|
369
260
|
exports.writeJSONFile = writeJSONFile;
|
|
261
|
+
/**
|
|
262
|
+
* Formats duration in seconds into a human readable string
|
|
263
|
+
* @param durationSeconds - Duration in seconds
|
|
264
|
+
* @returns Formatted duration string (e.g. "2m 30s" or "45s")
|
|
265
|
+
*/
|
|
266
|
+
const formatDurationSeconds = (durationSeconds) => {
|
|
267
|
+
const minutes = Math.floor(durationSeconds / 60);
|
|
268
|
+
const seconds = durationSeconds % 60;
|
|
269
|
+
if (minutes > 0) {
|
|
270
|
+
return `${minutes}m ${seconds}s`;
|
|
271
|
+
}
|
|
272
|
+
return `${durationSeconds}s`;
|
|
273
|
+
};
|
|
274
|
+
exports.formatDurationSeconds = formatDurationSeconds;
|
package/oclif.manifest.json
CHANGED