@capgo/cli 4.12.12 → 4.12.14-beta.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.
- package/.github/workflows/autofix.yml +25 -0
- package/CHANGELOG.md +9 -0
- package/bun.lockb +0 -0
- package/bunfig.toml +2 -0
- package/dist/index.js +40559 -50832
- package/package.json +22 -24
- package/src/api/app.ts +5 -5
- package/src/api/channels.ts +12 -12
- package/src/api/devices_override.ts +8 -8
- package/src/api/update.ts +2 -2
- package/src/api/versions.ts +10 -9
- package/src/app/add.ts +21 -22
- package/src/app/debug.ts +53 -54
- package/src/app/delete.ts +20 -20
- package/src/app/info.ts +34 -24
- package/src/app/list.ts +11 -11
- package/src/app/set.ts +16 -16
- package/src/bundle/check.ts +10 -10
- package/src/bundle/cleanup.ts +27 -27
- package/src/bundle/compatibility.ts +8 -8
- package/src/bundle/decrypt.ts +8 -8
- package/src/bundle/delete.ts +14 -15
- package/src/bundle/encrypt.ts +13 -13
- package/src/bundle/list.ts +11 -12
- package/src/bundle/unlink.ts +15 -13
- package/src/bundle/upload.ts +93 -86
- package/src/bundle/zip.ts +23 -21
- package/src/channel/add.ts +14 -14
- package/src/channel/currentBundle.ts +13 -13
- package/src/channel/delete.ts +13 -13
- package/src/channel/list.ts +11 -11
- package/src/channel/set.ts +28 -26
- package/src/config/index.ts +156 -0
- package/src/index.ts +6 -3
- package/src/init.ts +22 -22
- package/src/key.ts +45 -46
- package/src/login.ts +10 -10
- package/src/user/account.ts +3 -3
- package/src/utils.ts +119 -150
- package/tsconfig.json +1 -1
- package/vercel-ncc.js +18 -0
package/src/bundle/upload.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs'
|
|
3
|
+
import { exit } from 'node:process'
|
|
3
4
|
import type { Buffer } from 'node:buffer'
|
|
4
|
-
import process from 'node:process'
|
|
5
|
-
import * as p from '@clack/prompts'
|
|
6
5
|
import { program } from 'commander'
|
|
7
6
|
import { checksum as getChecksum } from '@tomasklaen/checksum'
|
|
8
7
|
import ciDetect from 'ci-info'
|
|
9
8
|
import type LogSnag from 'logsnag'
|
|
9
|
+
import { S3Client } from '@bradenmacdonald/s3-lite-client'
|
|
10
10
|
import ky, { HTTPError } from 'ky'
|
|
11
|
+
import { confirm as confirmC, intro, log, outro, spinner as spinnerC } from '@clack/prompts'
|
|
11
12
|
import { encryptSource } from '../api/crypto'
|
|
12
|
-
import { type OptionsBase, OrganizationPerm, baseKeyPub, checkChecksum, checkCompatibility, checkPlanValid, convertAppName, createSupabaseClient, deletedFailedVersion, findSavedKey, formatError, getConfig, getLocalConfig, getLocalDepenencies, getOrganizationId, getPMAndCommand, hasOrganizationPerm, regexSemver, updateOrCreateChannel, updateOrCreateVersion, uploadMultipart, uploadUrl, useLogSnag, verifyUser, zipFile } from '../utils'
|
|
13
|
+
import { type OptionsBase, OrganizationPerm, baseKeyPub, checkChecksum, checkCompatibility, checkPlanValid, convertAppName, createSupabaseClient, deletedFailedVersion, findSavedKey, formatError, getConfig, getLocalConfig, getLocalDepenencies, getOrganizationId, getPMAndCommand, hasOrganizationPerm, readPackageJson, regexSemver, updateOrCreateChannel, updateOrCreateVersion, uploadMultipart, uploadUrl, useLogSnag, verifyUser, zipFile } from '../utils'
|
|
13
14
|
import { checkAppExistsAndHasPermissionOrgErr } from '../api/app'
|
|
14
15
|
import { checkLatest } from '../api/update'
|
|
16
|
+
import type { CapacitorConfig } from '../config'
|
|
15
17
|
import { checkIndexPosition, searchInDirectory } from './check'
|
|
16
18
|
|
|
17
19
|
interface Options extends OptionsBase {
|
|
@@ -27,6 +29,9 @@ interface Options extends OptionsBase {
|
|
|
27
29
|
s3Apikey?: string
|
|
28
30
|
s3Apisecret?: string
|
|
29
31
|
s3BucketName?: string
|
|
32
|
+
s3Port?: number
|
|
33
|
+
s3SSL?: boolean
|
|
34
|
+
s3Endpoint?: string
|
|
30
35
|
bundleUrl?: boolean
|
|
31
36
|
codeCheck?: boolean
|
|
32
37
|
minUpdateVersion?: string
|
|
@@ -40,20 +45,20 @@ interface Options extends OptionsBase {
|
|
|
40
45
|
const alertMb = 20
|
|
41
46
|
const UPLOAD_TIMEOUT = 120000
|
|
42
47
|
|
|
43
|
-
type ConfigType = Awaited<ReturnType<typeof getConfig>>
|
|
44
48
|
type SupabaseType = Awaited<ReturnType<typeof createSupabaseClient>>
|
|
45
49
|
type pmType = ReturnType<typeof getPMAndCommand>
|
|
46
50
|
type localConfigType = Awaited<ReturnType<typeof getLocalConfig>>
|
|
47
51
|
|
|
48
|
-
function getBundle(config:
|
|
52
|
+
async function getBundle(config: CapacitorConfig, options: Options) {
|
|
53
|
+
const pkg = await readPackageJson()
|
|
49
54
|
// create bundle name format : 1.0.0-beta.x where x is a uuid
|
|
50
55
|
const bundle = options.bundle
|
|
51
|
-
|| config?.
|
|
52
|
-
||
|
|
56
|
+
|| config?.plugins?.CapacitorUpdater?.version
|
|
57
|
+
|| pkg?.version
|
|
53
58
|
|| `0.0.1-beta.${randomUUID().split('-')[0]}`
|
|
54
59
|
|
|
55
60
|
if (!regexSemver.test(bundle)) {
|
|
56
|
-
|
|
61
|
+
log.error(`Your bundle name ${bundle}, is not valid it should follow semver convention : https://semver.org/`)
|
|
57
62
|
program.error('')
|
|
58
63
|
}
|
|
59
64
|
|
|
@@ -63,24 +68,24 @@ function getBundle(config: ConfigType, options: Options) {
|
|
|
63
68
|
function getApikey(options: Options) {
|
|
64
69
|
const apikey = options.apikey || findSavedKey()
|
|
65
70
|
if (!apikey) {
|
|
66
|
-
|
|
71
|
+
log.error(`Missing API key, you need to provide a API key to upload your bundle`)
|
|
67
72
|
program.error('')
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
return apikey
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
function getAppIdAndPath(appId: string | undefined, options: Options, config:
|
|
74
|
-
const finalAppId = appId || config?.
|
|
75
|
-
const path = options.path || config?.
|
|
78
|
+
function getAppIdAndPath(appId: string | undefined, options: Options, config: CapacitorConfig) {
|
|
79
|
+
const finalAppId = appId || config?.appId
|
|
80
|
+
const path = options.path || config?.webDir
|
|
76
81
|
|
|
77
82
|
if (!finalAppId || !path) {
|
|
78
|
-
|
|
83
|
+
log.error('Missing argument, you need to provide a appid and a path (--path), or be in a capacitor project')
|
|
79
84
|
program.error('')
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
if (!existsSync(path)) {
|
|
83
|
-
|
|
88
|
+
log.error(`Path ${path} does not exist, build your app first, or provide a valid path`)
|
|
84
89
|
program.error('')
|
|
85
90
|
}
|
|
86
91
|
|
|
@@ -93,12 +98,12 @@ function checkNotifyAppReady(options: Options, path: string) {
|
|
|
93
98
|
if (typeof checkNotifyAppReady === 'undefined' || checkNotifyAppReady) {
|
|
94
99
|
const isPluginConfigured = searchInDirectory(path, 'notifyAppReady')
|
|
95
100
|
if (!isPluginConfigured) {
|
|
96
|
-
|
|
101
|
+
log.error(`notifyAppReady() is missing in the source code. see: https://capgo.app/docs/plugin/api/#notifyappready`)
|
|
97
102
|
program.error('')
|
|
98
103
|
}
|
|
99
104
|
const foundIndex = checkIndexPosition(path)
|
|
100
105
|
if (!foundIndex) {
|
|
101
|
-
|
|
106
|
+
log.error(`index.html is missing in the root folder or in the only folder in the root folder`)
|
|
102
107
|
program.error('')
|
|
103
108
|
}
|
|
104
109
|
}
|
|
@@ -125,7 +130,7 @@ async function verifyCompatibility(supabase: SupabaseType, pm: pmType, options:
|
|
|
125
130
|
|
|
126
131
|
// We only check compatibility IF the channel exists
|
|
127
132
|
if (!channelError && channelData && channelData.version && (channelData.version as any).native_packages && !ignoreMetadataCheck) {
|
|
128
|
-
const spinner =
|
|
133
|
+
const spinner = spinnerC()
|
|
129
134
|
spinner.start(`Checking bundle compatibility with channel ${channel}`)
|
|
130
135
|
const {
|
|
131
136
|
finalCompatibility: finalCompatibilityWithChannel,
|
|
@@ -137,18 +142,18 @@ async function verifyCompatibility(supabase: SupabaseType, pm: pmType, options:
|
|
|
137
142
|
|
|
138
143
|
if (finalCompatibility.find(x => x.localVersion !== x.remoteVersion)) {
|
|
139
144
|
spinner.stop(`Bundle NOT compatible with ${channel} channel`)
|
|
140
|
-
|
|
145
|
+
log.warn(`You can check compatibility with "${pm.runner} @capgo/cli bundle compatibility"`)
|
|
141
146
|
|
|
142
147
|
if (autoMinUpdateVersion) {
|
|
143
148
|
minUpdateVersion = bundle
|
|
144
|
-
|
|
149
|
+
log.info(`Auto set min-update-version to ${minUpdateVersion}`)
|
|
145
150
|
}
|
|
146
151
|
}
|
|
147
152
|
else if (autoMinUpdateVersion) {
|
|
148
153
|
try {
|
|
149
154
|
const { minUpdateVersion: lastMinUpdateVersion } = channelData.version as any
|
|
150
155
|
if (!lastMinUpdateVersion || !regexSemver.test(lastMinUpdateVersion)) {
|
|
151
|
-
|
|
156
|
+
log.error('Invalid remote min update version, skipping auto setting compatibility')
|
|
152
157
|
program.error('')
|
|
153
158
|
}
|
|
154
159
|
|
|
@@ -156,7 +161,7 @@ async function verifyCompatibility(supabase: SupabaseType, pm: pmType, options:
|
|
|
156
161
|
spinner.stop(`Auto set min-update-version to ${minUpdateVersion}`)
|
|
157
162
|
}
|
|
158
163
|
catch (error) {
|
|
159
|
-
|
|
164
|
+
log.error(`Cannot auto set compatibility, invalid data ${channelData}`)
|
|
160
165
|
program.error('')
|
|
161
166
|
}
|
|
162
167
|
}
|
|
@@ -165,23 +170,23 @@ async function verifyCompatibility(supabase: SupabaseType, pm: pmType, options:
|
|
|
165
170
|
}
|
|
166
171
|
}
|
|
167
172
|
else if (!ignoreMetadataCheck) {
|
|
168
|
-
|
|
173
|
+
log.warn(`Channel ${channel} is new or it's your first upload with compatibility check, it will be ignored this time`)
|
|
169
174
|
localDependencies = await getLocalDepenencies()
|
|
170
175
|
|
|
171
176
|
if (autoMinUpdateVersion) {
|
|
172
177
|
minUpdateVersion = bundle
|
|
173
|
-
|
|
178
|
+
log.info(`Auto set min-update-version to ${minUpdateVersion}`)
|
|
174
179
|
}
|
|
175
180
|
}
|
|
176
181
|
|
|
177
182
|
if (updateMetadataRequired && !minUpdateVersion && !ignoreMetadataCheck) {
|
|
178
|
-
|
|
183
|
+
log.error(`You need to provide a min-update-version to upload a bundle to this channel`)
|
|
179
184
|
program.error('')
|
|
180
185
|
}
|
|
181
186
|
|
|
182
187
|
if (minUpdateVersion) {
|
|
183
188
|
if (!regexSemver.test(minUpdateVersion)) {
|
|
184
|
-
|
|
189
|
+
log.error(`Your minimal version update ${minUpdateVersion}, is not valid it should follow semver convention : https://semver.org/`)
|
|
185
190
|
program.error('')
|
|
186
191
|
}
|
|
187
192
|
}
|
|
@@ -203,8 +208,8 @@ async function checkTrial(supabase: SupabaseType, orgId: string, localConfig: lo
|
|
|
203
208
|
.single()
|
|
204
209
|
if ((isTrial && isTrial > 0) || isTrialsError) {
|
|
205
210
|
// TODO: Come back to this to fix for orgs v3
|
|
206
|
-
|
|
207
|
-
|
|
211
|
+
log.warn(`WARNING !!\nTrial expires in ${isTrial} days`)
|
|
212
|
+
log.warn(`Upgrade here: ${localConfig.hostWeb}/dashboard/settings/plans?oid=${orgId}`)
|
|
208
213
|
}
|
|
209
214
|
}
|
|
210
215
|
|
|
@@ -217,7 +222,7 @@ async function checkVersionExists(supabase: SupabaseType, appid: string, bundle:
|
|
|
217
222
|
.single()
|
|
218
223
|
|
|
219
224
|
if (appVersion || appVersionError) {
|
|
220
|
-
|
|
225
|
+
log.error(`Version already exists ${formatError(appVersionError)}`)
|
|
221
226
|
program.error('')
|
|
222
227
|
}
|
|
223
228
|
}
|
|
@@ -229,28 +234,28 @@ async function prepareBundleFile(path: string, options: Options, localConfig: lo
|
|
|
229
234
|
const key = options.key
|
|
230
235
|
|
|
231
236
|
zipped = await zipFile(path)
|
|
232
|
-
const s =
|
|
237
|
+
const s = spinnerC()
|
|
233
238
|
s.start(`Calculating checksum`)
|
|
234
239
|
checksum = await getChecksum(zipped, 'crc32')
|
|
235
240
|
s.stop(`Checksum: ${checksum}`)
|
|
236
241
|
// key should be undefined or a string if false it should ingore encryption
|
|
237
242
|
if (!key) {
|
|
238
|
-
|
|
243
|
+
log.info(`Encryption ignored`)
|
|
239
244
|
}
|
|
240
245
|
else if (key || existsSync(baseKeyPub)) {
|
|
241
246
|
const publicKey = typeof key === 'string' ? key : baseKeyPub
|
|
242
247
|
let keyData = options.keyData || ''
|
|
243
248
|
// check if publicKey exist
|
|
244
249
|
if (!keyData && !existsSync(publicKey)) {
|
|
245
|
-
|
|
250
|
+
log.error(`Cannot find public key ${publicKey}`)
|
|
246
251
|
if (ciDetect.isCI) {
|
|
247
|
-
|
|
252
|
+
log.error('Cannot ask if user wants to use capgo public key on the cli')
|
|
248
253
|
program.error('')
|
|
249
254
|
}
|
|
250
255
|
|
|
251
|
-
const res = await
|
|
256
|
+
const res = await confirmC({ message: 'Do you want to use our public key ?' })
|
|
252
257
|
if (!res) {
|
|
253
|
-
|
|
258
|
+
log.error(`Error: Missing public key`)
|
|
254
259
|
program.error('')
|
|
255
260
|
}
|
|
256
261
|
keyData = localConfig.signKey || ''
|
|
@@ -271,11 +276,11 @@ async function prepareBundleFile(path: string, options: Options, localConfig: lo
|
|
|
271
276
|
keyData = keyFile.toString()
|
|
272
277
|
}
|
|
273
278
|
// encrypt
|
|
274
|
-
|
|
279
|
+
log.info(`Encrypting your bundle`)
|
|
275
280
|
const res = encryptSource(zipped, keyData)
|
|
276
281
|
sessionKey = res.ivSessionKey
|
|
277
282
|
if (options.displayIvSession) {
|
|
278
|
-
|
|
283
|
+
log.info(`Your Iv Session key is ${sessionKey},
|
|
279
284
|
keep it safe, you will need it to decrypt your bundle.
|
|
280
285
|
It will be also visible in your dashboard\n`)
|
|
281
286
|
}
|
|
@@ -283,8 +288,8 @@ It will be also visible in your dashboard\n`)
|
|
|
283
288
|
}
|
|
284
289
|
const mbSize = Math.floor((zipped?.byteLength ?? 0) / 1024 / 1024)
|
|
285
290
|
if (mbSize > alertMb) {
|
|
286
|
-
|
|
287
|
-
|
|
291
|
+
log.warn(`WARNING !!\nThe app size is ${mbSize} Mb, this may take a while to download for users\n`)
|
|
292
|
+
log.info(`Learn how to optimize your assets https://capgo.app/blog/optimise-your-images-for-updates/\n`)
|
|
288
293
|
await snag.track({
|
|
289
294
|
channel: 'app-error',
|
|
290
295
|
event: 'App Too Large',
|
|
@@ -301,19 +306,19 @@ It will be also visible in your dashboard\n`)
|
|
|
301
306
|
}
|
|
302
307
|
|
|
303
308
|
async function uploadBundleToCapgoCloud(supabase: SupabaseType, appid: string, bundle: string, orgId: string, zipped: Buffer, options: Options) {
|
|
304
|
-
const spinner =
|
|
309
|
+
const spinner = spinnerC()
|
|
305
310
|
spinner.start(`Uploading Bundle`)
|
|
306
311
|
const startTime = performance.now()
|
|
307
312
|
|
|
308
313
|
try {
|
|
309
314
|
if (options.multipart !== undefined && options.multipart) {
|
|
310
|
-
|
|
315
|
+
log.info(`Uploading bundle as multipart`)
|
|
311
316
|
await uploadMultipart(supabase, appid, bundle, zipped, orgId)
|
|
312
317
|
}
|
|
313
318
|
else {
|
|
314
319
|
const url = await uploadUrl(supabase, appid, bundle)
|
|
315
320
|
if (!url) {
|
|
316
|
-
|
|
321
|
+
log.error(`Cannot get upload url`)
|
|
317
322
|
program.error('')
|
|
318
323
|
}
|
|
319
324
|
await ky.put(url, {
|
|
@@ -327,10 +332,10 @@ async function uploadBundleToCapgoCloud(supabase: SupabaseType, appid: string, b
|
|
|
327
332
|
const endTime = performance.now()
|
|
328
333
|
const uploadTime = ((endTime - startTime) / 1000).toFixed(2)
|
|
329
334
|
spinner.stop(`Failed to upload bundle ( after ${uploadTime} seconds)`)
|
|
330
|
-
|
|
335
|
+
log.error(`Cannot upload bundle ( try again with --multipart option) ${formatError(errorUpload)}`)
|
|
331
336
|
if (errorUpload instanceof HTTPError) {
|
|
332
337
|
const body = await errorUpload.response.text()
|
|
333
|
-
|
|
338
|
+
log.error(`Response: ${formatError(body)}`)
|
|
334
339
|
}
|
|
335
340
|
// call delete version on path /delete_failed_version to delete the version
|
|
336
341
|
await deletedFailedVersion(supabase, appid, bundle)
|
|
@@ -367,68 +372,46 @@ async function setVersionInChannel(
|
|
|
367
372
|
owner_org: orgId,
|
|
368
373
|
})
|
|
369
374
|
if (dbError3) {
|
|
370
|
-
|
|
375
|
+
log.error(`Cannot set channel, the upload key is not allowed to do that, use the "all" for this. ${formatError(dbError3)}`)
|
|
371
376
|
program.error('')
|
|
372
377
|
}
|
|
373
378
|
const appidWeb = convertAppName(appid)
|
|
374
379
|
const bundleUrl = `${localConfig.hostWeb}/app/p/${appidWeb}/channel/${data.id}`
|
|
375
380
|
if (data?.public)
|
|
376
|
-
|
|
381
|
+
log.info('Your update is now available in your public channel 🎉')
|
|
377
382
|
else if (data?.id)
|
|
378
|
-
|
|
383
|
+
log.info(`Link device to this bundle to try it: ${bundleUrl}`)
|
|
379
384
|
|
|
380
385
|
if (displayBundleUrl) {
|
|
381
|
-
|
|
386
|
+
log.info(`Bundle url: ${bundleUrl}`)
|
|
382
387
|
}
|
|
383
388
|
else if (!versionId) {
|
|
384
|
-
|
|
389
|
+
log.warn('Cannot set bundle with upload key, use key with more rights for that')
|
|
385
390
|
program.error('')
|
|
386
391
|
}
|
|
387
392
|
else if (!hasOrganizationPerm(permissions, OrganizationPerm.write)) {
|
|
388
|
-
|
|
393
|
+
log.warn('Cannot set channel as a upload organization member')
|
|
389
394
|
}
|
|
390
395
|
}
|
|
391
396
|
}
|
|
392
397
|
|
|
393
398
|
export async function uploadBundle(preAppid: string, options: Options, shouldExit = true) {
|
|
394
|
-
|
|
399
|
+
intro(`Uploading`)
|
|
395
400
|
const pm = getPMAndCommand()
|
|
396
401
|
await checkLatest()
|
|
397
402
|
|
|
398
|
-
const { s3Region, s3Apikey, s3Apisecret, s3BucketName } = options
|
|
399
|
-
|
|
400
|
-
if (s3BucketName || s3Region || s3Apikey || s3Apisecret) {
|
|
401
|
-
if (!s3BucketName || !s3Region || !s3Apikey || !s3Apisecret) {
|
|
402
|
-
p.log.error('Missing argument, for S3 upload you need to provide a bucket name, region, API key, and API secret')
|
|
403
|
-
program.error('')
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
if (s3Region && s3Apikey && s3Apisecret && s3BucketName) {
|
|
408
|
-
p.log.info('Uploading to S3')
|
|
409
|
-
// const s3Client = new S3Client({
|
|
410
|
-
// region: s3Region,
|
|
411
|
-
// credentials: {
|
|
412
|
-
// accessKeyId: s3Apikey,
|
|
413
|
-
// secretAccessKey: s3Apisecret,
|
|
414
|
-
// },
|
|
415
|
-
// })
|
|
416
|
-
p.log.error('S3 upload is not available we have currenly an issue with it')
|
|
417
|
-
program.error('')
|
|
418
|
-
// todo: figure out s3 upload
|
|
419
|
-
return
|
|
420
|
-
}
|
|
403
|
+
const { s3Region, s3Apikey, s3Apisecret, s3BucketName, s3Endpoint, s3Port, s3SSL } = options
|
|
421
404
|
|
|
422
405
|
const apikey = getApikey(options)
|
|
423
|
-
const
|
|
424
|
-
const { appid, path } = getAppIdAndPath(preAppid, options, config)
|
|
425
|
-
const bundle = getBundle(config, options)
|
|
406
|
+
const extConfig = await getConfig()
|
|
407
|
+
const { appid, path } = getAppIdAndPath(preAppid, options, extConfig.config)
|
|
408
|
+
const bundle = await getBundle(extConfig.config, options)
|
|
426
409
|
const channel = options.channel || 'dev'
|
|
427
410
|
const snag = useLogSnag()
|
|
428
411
|
|
|
429
412
|
checkNotifyAppReady(options, path)
|
|
430
413
|
|
|
431
|
-
|
|
414
|
+
log.info(`Upload ${appid}@${bundle} started from path "${path}" to Capgo cloud`)
|
|
432
415
|
|
|
433
416
|
const localConfig = await getLocalConfig()
|
|
434
417
|
const supabase = await createSupabaseClient(apikey)
|
|
@@ -444,7 +427,7 @@ export async function uploadBundle(preAppid: string, options: Options, shouldExi
|
|
|
444
427
|
await checkVersionExists(supabase, appid, bundle)
|
|
445
428
|
|
|
446
429
|
if (options.external && !options.external.startsWith('https://')) {
|
|
447
|
-
|
|
430
|
+
log.error(`External link should should start with "https://" current is "${external}"`)
|
|
448
431
|
program.error('')
|
|
449
432
|
}
|
|
450
433
|
|
|
@@ -488,17 +471,41 @@ export async function uploadBundle(preAppid: string, options: Options, shouldExi
|
|
|
488
471
|
|
|
489
472
|
const { error: dbError } = await updateOrCreateVersion(supabase, versionData)
|
|
490
473
|
if (dbError) {
|
|
491
|
-
|
|
474
|
+
log.error(`Cannot add bundle ${formatError(dbError)}`)
|
|
492
475
|
program.error('')
|
|
493
476
|
}
|
|
494
477
|
|
|
495
|
-
if (zipped) {
|
|
478
|
+
if (zipped && (s3BucketName || s3Endpoint || s3Region || s3Apikey || s3Apisecret || s3Port || s3SSL)) {
|
|
479
|
+
if (!s3BucketName || !s3Endpoint || !s3Region || !s3Apikey || !s3Apisecret || !s3Port) {
|
|
480
|
+
log.error('Missing argument, for S3 upload you need to provide a bucket name, endpoint, region, port, API key, and API secret')
|
|
481
|
+
program.error('')
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
log.info('Uploading to S3')
|
|
485
|
+
const s3Client = new S3Client({
|
|
486
|
+
endPoint: s3Endpoint,
|
|
487
|
+
region: s3Region,
|
|
488
|
+
port: s3Port,
|
|
489
|
+
useSSL: s3SSL,
|
|
490
|
+
bucket: s3BucketName,
|
|
491
|
+
credentials: {
|
|
492
|
+
accessKeyId: s3Apikey,
|
|
493
|
+
secretAccessKey: s3Apisecret,
|
|
494
|
+
},
|
|
495
|
+
})
|
|
496
|
+
const fileName = `${appid}-${bundle}`
|
|
497
|
+
const encodeFileName = encodeURIComponent(fileName)
|
|
498
|
+
await s3Client.putObject(fileName, zipped)
|
|
499
|
+
versionData.external_url = `https://${s3Endpoint}/${encodeFileName}`
|
|
500
|
+
versionData.storage_provider = 'external'
|
|
501
|
+
}
|
|
502
|
+
else if (zipped) {
|
|
496
503
|
await uploadBundleToCapgoCloud(supabase, appid, bundle, orgId, zipped, options)
|
|
497
504
|
|
|
498
505
|
versionData.storage_provider = 'r2'
|
|
499
506
|
const { error: dbError2 } = await updateOrCreateVersion(supabase, versionData)
|
|
500
507
|
if (dbError2) {
|
|
501
|
-
|
|
508
|
+
log.error(`Cannot update bundle ${formatError(dbError2)}`)
|
|
502
509
|
program.error('')
|
|
503
510
|
}
|
|
504
511
|
}
|
|
@@ -516,8 +523,8 @@ export async function uploadBundle(preAppid: string, options: Options, shouldExi
|
|
|
516
523
|
notify: false,
|
|
517
524
|
}).catch()
|
|
518
525
|
if (shouldExit) {
|
|
519
|
-
|
|
520
|
-
|
|
526
|
+
outro('Time to share your update to the world 🌍')
|
|
527
|
+
exit()
|
|
521
528
|
}
|
|
522
529
|
return true
|
|
523
530
|
}
|
|
@@ -527,19 +534,19 @@ export async function uploadCommand(appid: string, options: Options) {
|
|
|
527
534
|
await uploadBundle(appid, options, true)
|
|
528
535
|
}
|
|
529
536
|
catch (error) {
|
|
530
|
-
|
|
537
|
+
log.error(formatError(error))
|
|
531
538
|
program.error('')
|
|
532
539
|
}
|
|
533
540
|
}
|
|
534
541
|
|
|
535
542
|
export async function uploadDeprecatedCommand(appid: string, options: Options) {
|
|
536
543
|
const pm = getPMAndCommand()
|
|
537
|
-
|
|
544
|
+
log.warn(`⚠️ This command is deprecated, use "${pm.runner} @capgo/cli bundle upload" instead ⚠️`)
|
|
538
545
|
try {
|
|
539
546
|
await uploadBundle(appid, options, true)
|
|
540
547
|
}
|
|
541
548
|
catch (error) {
|
|
542
|
-
|
|
549
|
+
log.error(formatError(error))
|
|
543
550
|
program.error('')
|
|
544
551
|
}
|
|
545
552
|
}
|
package/src/bundle/zip.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
import { writeFileSync } from 'node:fs'
|
|
3
|
-
import
|
|
3
|
+
import { exit } from 'node:process'
|
|
4
4
|
import { program } from 'commander'
|
|
5
|
-
import * as p from '@clack/prompts'
|
|
6
5
|
import { checksum as getChecksum } from '@tomasklaen/checksum'
|
|
6
|
+
import { intro, log, outro, spinner } from '@clack/prompts'
|
|
7
7
|
import { checkLatest } from '../api/update'
|
|
8
8
|
import type {
|
|
9
9
|
OptionsBase,
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import {
|
|
12
12
|
formatError,
|
|
13
13
|
getConfig,
|
|
14
|
+
readPackageJson,
|
|
14
15
|
regexSemver,
|
|
15
16
|
useLogSnag,
|
|
16
17
|
zipFile,
|
|
@@ -35,37 +36,38 @@ export async function zipBundle(appId: string, options: Options) {
|
|
|
35
36
|
if (!json)
|
|
36
37
|
await checkLatest()
|
|
37
38
|
|
|
38
|
-
const
|
|
39
|
-
appId = appId || config?.
|
|
39
|
+
const extConfig = await getConfig()
|
|
40
|
+
appId = appId || extConfig?.config?.appId
|
|
40
41
|
// create bundle name format : 1.0.0-beta.x where x is a uuid
|
|
41
42
|
const uuid = randomUUID().split('-')[0]
|
|
42
|
-
|
|
43
|
+
const pack = await readPackageJson()
|
|
44
|
+
bundle = bundle || pack?.version || `0.0.1-beta.${uuid}`
|
|
43
45
|
if (!json)
|
|
44
|
-
|
|
46
|
+
intro(`Zipping ${appId}@${bundle}`)
|
|
45
47
|
// check if bundle is valid
|
|
46
|
-
if (!regexSemver.test(bundle)) {
|
|
48
|
+
if (bundle && !regexSemver.test(bundle)) {
|
|
47
49
|
if (!json)
|
|
48
|
-
|
|
50
|
+
log.error(`Your bundle name ${bundle}, is not valid it should follow semver convention : https://semver.org/`)
|
|
49
51
|
else
|
|
50
52
|
console.error(formatError({ error: 'invalid_semver' }))
|
|
51
53
|
program.error('')
|
|
52
54
|
}
|
|
53
|
-
path = path || config?.
|
|
55
|
+
path = path || extConfig?.config?.webDir
|
|
54
56
|
if (!appId || !bundle || !path) {
|
|
55
57
|
if (!json)
|
|
56
|
-
|
|
58
|
+
log.error('Missing argument, you need to provide a appId and a bundle and a path, or be in a capacitor project')
|
|
57
59
|
else
|
|
58
60
|
console.error(formatError({ error: 'missing_argument' }))
|
|
59
61
|
program.error('')
|
|
60
62
|
}
|
|
61
63
|
if (!json)
|
|
62
|
-
|
|
64
|
+
log.info(`Started from path "${path}"`)
|
|
63
65
|
const checkNotifyAppReady = options.codeCheck
|
|
64
66
|
if (typeof checkNotifyAppReady === 'undefined' || checkNotifyAppReady) {
|
|
65
67
|
const isPluginConfigured = searchInDirectory(path, 'notifyAppReady')
|
|
66
68
|
if (!isPluginConfigured) {
|
|
67
69
|
if (!json)
|
|
68
|
-
|
|
70
|
+
log.error(`notifyAppReady() is missing in the source code. see: https://capgo.app/docs/plugin/api/#notifyappready`)
|
|
69
71
|
else
|
|
70
72
|
console.error(formatError({ error: 'notifyAppReady_not_in_source_code' }))
|
|
71
73
|
program.error('')
|
|
@@ -73,7 +75,7 @@ export async function zipBundle(appId: string, options: Options) {
|
|
|
73
75
|
const foundIndex = checkIndexPosition(path)
|
|
74
76
|
if (!foundIndex) {
|
|
75
77
|
if (!json)
|
|
76
|
-
|
|
78
|
+
log.error(`index.html is missing in the root folder or in the only folder in the root folder`)
|
|
77
79
|
else
|
|
78
80
|
console.error(formatError({ error: 'index_html_not_found' }))
|
|
79
81
|
program.error('')
|
|
@@ -81,8 +83,8 @@ export async function zipBundle(appId: string, options: Options) {
|
|
|
81
83
|
}
|
|
82
84
|
const zipped = await zipFile(path)
|
|
83
85
|
if (!json)
|
|
84
|
-
|
|
85
|
-
const s =
|
|
86
|
+
log.info(`Zipped ${zipped.byteLength} bytes`)
|
|
87
|
+
const s = spinner()
|
|
86
88
|
if (!json)
|
|
87
89
|
s.start(`Calculating checksum`)
|
|
88
90
|
const checksum = await getChecksum(zipped, 'crc32')
|
|
@@ -91,8 +93,8 @@ export async function zipBundle(appId: string, options: Options) {
|
|
|
91
93
|
const mbSize = Math.floor(zipped.byteLength / 1024 / 1024)
|
|
92
94
|
// We do not issue this warning for json
|
|
93
95
|
if (mbSize > alertMb && !json) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
log.warn(`WARNING !!\nThe app size is ${mbSize} Mb, this may take a while to download for users\n`)
|
|
97
|
+
log.warn(`Learn how to optimize your assets https://capgo.app/blog/optimise-your-images-for-updates/\n`)
|
|
96
98
|
await snag.track({
|
|
97
99
|
channel: 'app-error',
|
|
98
100
|
event: 'App Too Large',
|
|
@@ -103,7 +105,7 @@ export async function zipBundle(appId: string, options: Options) {
|
|
|
103
105
|
notify: false,
|
|
104
106
|
}).catch()
|
|
105
107
|
}
|
|
106
|
-
const s2 =
|
|
108
|
+
const s2 = spinner()
|
|
107
109
|
const name = options.name || `${appId}_${bundle}.zip`
|
|
108
110
|
if (!json)
|
|
109
111
|
s2.start(`Saving to ${name}`)
|
|
@@ -122,7 +124,7 @@ export async function zipBundle(appId: string, options: Options) {
|
|
|
122
124
|
}).catch()
|
|
123
125
|
|
|
124
126
|
if (!json)
|
|
125
|
-
|
|
127
|
+
outro(`Done ✅`)
|
|
126
128
|
|
|
127
129
|
if (json) {
|
|
128
130
|
const output = {
|
|
@@ -134,10 +136,10 @@ export async function zipBundle(appId: string, options: Options) {
|
|
|
134
136
|
// eslint-disable-next-line no-console
|
|
135
137
|
console.log(JSON.stringify(output, null, 2))
|
|
136
138
|
}
|
|
137
|
-
|
|
139
|
+
exit()
|
|
138
140
|
}
|
|
139
141
|
catch (error) {
|
|
140
|
-
|
|
142
|
+
log.error(formatError(error))
|
|
141
143
|
program.error('')
|
|
142
144
|
}
|
|
143
145
|
}
|
package/src/channel/add.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { exit } from 'node:process'
|
|
2
2
|
import { program } from 'commander'
|
|
3
|
-
import
|
|
3
|
+
import { intro, log, outro } from '@clack/prompts'
|
|
4
4
|
import { checkAppExistsAndHasPermissionOrgErr } from '../api/app'
|
|
5
5
|
import { createChannel, findUnknownVersion } from '../api/channels'
|
|
6
6
|
import type { OptionsBase } from '../utils'
|
|
@@ -11,18 +11,18 @@ interface Options extends OptionsBase {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export async function addChannel(channelId: string, appId: string, options: Options, shouldExit = true) {
|
|
14
|
-
|
|
14
|
+
intro(`Create channel`)
|
|
15
15
|
options.apikey = options.apikey || findSavedKey()
|
|
16
|
-
const
|
|
17
|
-
appId = appId || config?.
|
|
16
|
+
const extConfig = await getConfig()
|
|
17
|
+
appId = appId || extConfig?.config?.appId
|
|
18
18
|
const snag = useLogSnag()
|
|
19
19
|
|
|
20
20
|
if (!options.apikey) {
|
|
21
|
-
|
|
21
|
+
log.error('Missing API key, you need to provide a API key to upload your bundle')
|
|
22
22
|
program.error('')
|
|
23
23
|
}
|
|
24
24
|
if (!appId) {
|
|
25
|
-
|
|
25
|
+
log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
|
|
26
26
|
program.error('')
|
|
27
27
|
}
|
|
28
28
|
const supabase = await createSupabaseClient(options.apikey)
|
|
@@ -31,12 +31,12 @@ export async function addChannel(channelId: string, appId: string, options: Opti
|
|
|
31
31
|
// Check we have app access to this appId
|
|
32
32
|
await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.admin)
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
log.info(`Creating channel ${appId}#${channelId} to Capgo`)
|
|
35
35
|
try {
|
|
36
36
|
const data = await findUnknownVersion(supabase, appId)
|
|
37
37
|
const orgId = await getOrganizationId(supabase, appId)
|
|
38
38
|
if (!data) {
|
|
39
|
-
|
|
39
|
+
log.error(`Cannot find default version for channel creation, please contact Capgo support 🤨`)
|
|
40
40
|
program.error('')
|
|
41
41
|
}
|
|
42
42
|
const res = await createChannel(supabase, {
|
|
@@ -47,11 +47,11 @@ export async function addChannel(channelId: string, appId: string, options: Opti
|
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
if (res.error) {
|
|
50
|
-
|
|
50
|
+
log.error(`Cannot create Channel 🙀\n${formatError(res.error)}`)
|
|
51
51
|
program.error('')
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
log.success(`Channel created ✅`)
|
|
55
55
|
await snag.track({
|
|
56
56
|
channel: 'channel',
|
|
57
57
|
event: 'Create channel',
|
|
@@ -65,12 +65,12 @@ export async function addChannel(channelId: string, appId: string, options: Opti
|
|
|
65
65
|
}).catch()
|
|
66
66
|
}
|
|
67
67
|
catch (error) {
|
|
68
|
-
|
|
68
|
+
log.error(`Cannot create Channel 🙀`)
|
|
69
69
|
return false
|
|
70
70
|
}
|
|
71
71
|
if (shouldExit) {
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
outro(`Done ✅`)
|
|
73
|
+
exit()
|
|
74
74
|
}
|
|
75
75
|
return true
|
|
76
76
|
}
|