@capgo/cli 4.12.14 → 4.13.2
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/CHANGELOG.md +36 -0
- package/build.mjs +3 -5
- package/bun.lockb +0 -0
- package/dist/index.js +183 -92196
- package/package.json +20 -22
- 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 +12 -12
- package/src/bundle/list.ts +11 -12
- package/src/bundle/unlink.ts +15 -13
- package/src/bundle/upload.ts +65 -64
- 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/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/src/bundle/upload.ts
CHANGED
|
@@ -1,18 +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'
|
|
10
9
|
import { S3Client } from '@bradenmacdonald/s3-lite-client'
|
|
11
10
|
import ky, { HTTPError } from 'ky'
|
|
11
|
+
import { confirm as confirmC, intro, log, outro, spinner as spinnerC } from '@clack/prompts'
|
|
12
12
|
import { encryptSource } from '../api/crypto'
|
|
13
|
-
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'
|
|
14
14
|
import { checkAppExistsAndHasPermissionOrgErr } from '../api/app'
|
|
15
15
|
import { checkLatest } from '../api/update'
|
|
16
|
+
import type { CapacitorConfig } from '../config'
|
|
16
17
|
import { checkIndexPosition, searchInDirectory } from './check'
|
|
17
18
|
|
|
18
19
|
interface Options extends OptionsBase {
|
|
@@ -44,20 +45,20 @@ interface Options extends OptionsBase {
|
|
|
44
45
|
const alertMb = 20
|
|
45
46
|
const UPLOAD_TIMEOUT = 120000
|
|
46
47
|
|
|
47
|
-
type ConfigType = Awaited<ReturnType<typeof getConfig>>
|
|
48
48
|
type SupabaseType = Awaited<ReturnType<typeof createSupabaseClient>>
|
|
49
49
|
type pmType = ReturnType<typeof getPMAndCommand>
|
|
50
50
|
type localConfigType = Awaited<ReturnType<typeof getLocalConfig>>
|
|
51
51
|
|
|
52
|
-
function getBundle(config:
|
|
52
|
+
async function getBundle(config: CapacitorConfig, options: Options) {
|
|
53
|
+
const pkg = await readPackageJson()
|
|
53
54
|
// create bundle name format : 1.0.0-beta.x where x is a uuid
|
|
54
55
|
const bundle = options.bundle
|
|
55
|
-
|| config?.
|
|
56
|
-
||
|
|
56
|
+
|| config?.plugins?.CapacitorUpdater?.version
|
|
57
|
+
|| pkg?.version
|
|
57
58
|
|| `0.0.1-beta.${randomUUID().split('-')[0]}`
|
|
58
59
|
|
|
59
60
|
if (!regexSemver.test(bundle)) {
|
|
60
|
-
|
|
61
|
+
log.error(`Your bundle name ${bundle}, is not valid it should follow semver convention : https://semver.org/`)
|
|
61
62
|
program.error('')
|
|
62
63
|
}
|
|
63
64
|
|
|
@@ -67,24 +68,24 @@ function getBundle(config: ConfigType, options: Options) {
|
|
|
67
68
|
function getApikey(options: Options) {
|
|
68
69
|
const apikey = options.apikey || findSavedKey()
|
|
69
70
|
if (!apikey) {
|
|
70
|
-
|
|
71
|
+
log.error(`Missing API key, you need to provide a API key to upload your bundle`)
|
|
71
72
|
program.error('')
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
return apikey
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
function getAppIdAndPath(appId: string | undefined, options: Options, config:
|
|
78
|
-
const finalAppId = appId || config?.
|
|
79
|
-
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
|
|
80
81
|
|
|
81
82
|
if (!finalAppId || !path) {
|
|
82
|
-
|
|
83
|
+
log.error('Missing argument, you need to provide a appid and a path (--path), or be in a capacitor project')
|
|
83
84
|
program.error('')
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
if (!existsSync(path)) {
|
|
87
|
-
|
|
88
|
+
log.error(`Path ${path} does not exist, build your app first, or provide a valid path`)
|
|
88
89
|
program.error('')
|
|
89
90
|
}
|
|
90
91
|
|
|
@@ -97,12 +98,12 @@ function checkNotifyAppReady(options: Options, path: string) {
|
|
|
97
98
|
if (typeof checkNotifyAppReady === 'undefined' || checkNotifyAppReady) {
|
|
98
99
|
const isPluginConfigured = searchInDirectory(path, 'notifyAppReady')
|
|
99
100
|
if (!isPluginConfigured) {
|
|
100
|
-
|
|
101
|
+
log.error(`notifyAppReady() is missing in the source code. see: https://capgo.app/docs/plugin/api/#notifyappready`)
|
|
101
102
|
program.error('')
|
|
102
103
|
}
|
|
103
104
|
const foundIndex = checkIndexPosition(path)
|
|
104
105
|
if (!foundIndex) {
|
|
105
|
-
|
|
106
|
+
log.error(`index.html is missing in the root folder or in the only folder in the root folder`)
|
|
106
107
|
program.error('')
|
|
107
108
|
}
|
|
108
109
|
}
|
|
@@ -129,7 +130,7 @@ async function verifyCompatibility(supabase: SupabaseType, pm: pmType, options:
|
|
|
129
130
|
|
|
130
131
|
// We only check compatibility IF the channel exists
|
|
131
132
|
if (!channelError && channelData && channelData.version && (channelData.version as any).native_packages && !ignoreMetadataCheck) {
|
|
132
|
-
const spinner =
|
|
133
|
+
const spinner = spinnerC()
|
|
133
134
|
spinner.start(`Checking bundle compatibility with channel ${channel}`)
|
|
134
135
|
const {
|
|
135
136
|
finalCompatibility: finalCompatibilityWithChannel,
|
|
@@ -141,18 +142,18 @@ async function verifyCompatibility(supabase: SupabaseType, pm: pmType, options:
|
|
|
141
142
|
|
|
142
143
|
if (finalCompatibility.find(x => x.localVersion !== x.remoteVersion)) {
|
|
143
144
|
spinner.stop(`Bundle NOT compatible with ${channel} channel`)
|
|
144
|
-
|
|
145
|
+
log.warn(`You can check compatibility with "${pm.runner} @capgo/cli bundle compatibility"`)
|
|
145
146
|
|
|
146
147
|
if (autoMinUpdateVersion) {
|
|
147
148
|
minUpdateVersion = bundle
|
|
148
|
-
|
|
149
|
+
log.info(`Auto set min-update-version to ${minUpdateVersion}`)
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
152
|
else if (autoMinUpdateVersion) {
|
|
152
153
|
try {
|
|
153
154
|
const { minUpdateVersion: lastMinUpdateVersion } = channelData.version as any
|
|
154
155
|
if (!lastMinUpdateVersion || !regexSemver.test(lastMinUpdateVersion)) {
|
|
155
|
-
|
|
156
|
+
log.error('Invalid remote min update version, skipping auto setting compatibility')
|
|
156
157
|
program.error('')
|
|
157
158
|
}
|
|
158
159
|
|
|
@@ -160,7 +161,7 @@ async function verifyCompatibility(supabase: SupabaseType, pm: pmType, options:
|
|
|
160
161
|
spinner.stop(`Auto set min-update-version to ${minUpdateVersion}`)
|
|
161
162
|
}
|
|
162
163
|
catch (error) {
|
|
163
|
-
|
|
164
|
+
log.error(`Cannot auto set compatibility, invalid data ${channelData}`)
|
|
164
165
|
program.error('')
|
|
165
166
|
}
|
|
166
167
|
}
|
|
@@ -169,23 +170,23 @@ async function verifyCompatibility(supabase: SupabaseType, pm: pmType, options:
|
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
else if (!ignoreMetadataCheck) {
|
|
172
|
-
|
|
173
|
+
log.warn(`Channel ${channel} is new or it's your first upload with compatibility check, it will be ignored this time`)
|
|
173
174
|
localDependencies = await getLocalDepenencies()
|
|
174
175
|
|
|
175
176
|
if (autoMinUpdateVersion) {
|
|
176
177
|
minUpdateVersion = bundle
|
|
177
|
-
|
|
178
|
+
log.info(`Auto set min-update-version to ${minUpdateVersion}`)
|
|
178
179
|
}
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
if (updateMetadataRequired && !minUpdateVersion && !ignoreMetadataCheck) {
|
|
182
|
-
|
|
183
|
+
log.error(`You need to provide a min-update-version to upload a bundle to this channel`)
|
|
183
184
|
program.error('')
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
if (minUpdateVersion) {
|
|
187
188
|
if (!regexSemver.test(minUpdateVersion)) {
|
|
188
|
-
|
|
189
|
+
log.error(`Your minimal version update ${minUpdateVersion}, is not valid it should follow semver convention : https://semver.org/`)
|
|
189
190
|
program.error('')
|
|
190
191
|
}
|
|
191
192
|
}
|
|
@@ -207,8 +208,8 @@ async function checkTrial(supabase: SupabaseType, orgId: string, localConfig: lo
|
|
|
207
208
|
.single()
|
|
208
209
|
if ((isTrial && isTrial > 0) || isTrialsError) {
|
|
209
210
|
// TODO: Come back to this to fix for orgs v3
|
|
210
|
-
|
|
211
|
-
|
|
211
|
+
log.warn(`WARNING !!\nTrial expires in ${isTrial} days`)
|
|
212
|
+
log.warn(`Upgrade here: ${localConfig.hostWeb}/dashboard/settings/plans?oid=${orgId}`)
|
|
212
213
|
}
|
|
213
214
|
}
|
|
214
215
|
|
|
@@ -221,7 +222,7 @@ async function checkVersionExists(supabase: SupabaseType, appid: string, bundle:
|
|
|
221
222
|
.single()
|
|
222
223
|
|
|
223
224
|
if (appVersion || appVersionError) {
|
|
224
|
-
|
|
225
|
+
log.error(`Version already exists ${formatError(appVersionError)}`)
|
|
225
226
|
program.error('')
|
|
226
227
|
}
|
|
227
228
|
}
|
|
@@ -233,28 +234,28 @@ async function prepareBundleFile(path: string, options: Options, localConfig: lo
|
|
|
233
234
|
const key = options.key
|
|
234
235
|
|
|
235
236
|
zipped = await zipFile(path)
|
|
236
|
-
const s =
|
|
237
|
+
const s = spinnerC()
|
|
237
238
|
s.start(`Calculating checksum`)
|
|
238
239
|
checksum = await getChecksum(zipped, 'crc32')
|
|
239
240
|
s.stop(`Checksum: ${checksum}`)
|
|
240
241
|
// key should be undefined or a string if false it should ingore encryption
|
|
241
242
|
if (!key) {
|
|
242
|
-
|
|
243
|
+
log.info(`Encryption ignored`)
|
|
243
244
|
}
|
|
244
245
|
else if (key || existsSync(baseKeyPub)) {
|
|
245
246
|
const publicKey = typeof key === 'string' ? key : baseKeyPub
|
|
246
247
|
let keyData = options.keyData || ''
|
|
247
248
|
// check if publicKey exist
|
|
248
249
|
if (!keyData && !existsSync(publicKey)) {
|
|
249
|
-
|
|
250
|
+
log.error(`Cannot find public key ${publicKey}`)
|
|
250
251
|
if (ciDetect.isCI) {
|
|
251
|
-
|
|
252
|
+
log.error('Cannot ask if user wants to use capgo public key on the cli')
|
|
252
253
|
program.error('')
|
|
253
254
|
}
|
|
254
255
|
|
|
255
|
-
const res = await
|
|
256
|
+
const res = await confirmC({ message: 'Do you want to use our public key ?' })
|
|
256
257
|
if (!res) {
|
|
257
|
-
|
|
258
|
+
log.error(`Error: Missing public key`)
|
|
258
259
|
program.error('')
|
|
259
260
|
}
|
|
260
261
|
keyData = localConfig.signKey || ''
|
|
@@ -275,11 +276,11 @@ async function prepareBundleFile(path: string, options: Options, localConfig: lo
|
|
|
275
276
|
keyData = keyFile.toString()
|
|
276
277
|
}
|
|
277
278
|
// encrypt
|
|
278
|
-
|
|
279
|
+
log.info(`Encrypting your bundle`)
|
|
279
280
|
const res = encryptSource(zipped, keyData)
|
|
280
281
|
sessionKey = res.ivSessionKey
|
|
281
282
|
if (options.displayIvSession) {
|
|
282
|
-
|
|
283
|
+
log.info(`Your Iv Session key is ${sessionKey},
|
|
283
284
|
keep it safe, you will need it to decrypt your bundle.
|
|
284
285
|
It will be also visible in your dashboard\n`)
|
|
285
286
|
}
|
|
@@ -287,8 +288,8 @@ It will be also visible in your dashboard\n`)
|
|
|
287
288
|
}
|
|
288
289
|
const mbSize = Math.floor((zipped?.byteLength ?? 0) / 1024 / 1024)
|
|
289
290
|
if (mbSize > alertMb) {
|
|
290
|
-
|
|
291
|
-
|
|
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`)
|
|
292
293
|
await snag.track({
|
|
293
294
|
channel: 'app-error',
|
|
294
295
|
event: 'App Too Large',
|
|
@@ -305,19 +306,19 @@ It will be also visible in your dashboard\n`)
|
|
|
305
306
|
}
|
|
306
307
|
|
|
307
308
|
async function uploadBundleToCapgoCloud(supabase: SupabaseType, appid: string, bundle: string, orgId: string, zipped: Buffer, options: Options) {
|
|
308
|
-
const spinner =
|
|
309
|
+
const spinner = spinnerC()
|
|
309
310
|
spinner.start(`Uploading Bundle`)
|
|
310
311
|
const startTime = performance.now()
|
|
311
312
|
|
|
312
313
|
try {
|
|
313
314
|
if (options.multipart !== undefined && options.multipart) {
|
|
314
|
-
|
|
315
|
+
log.info(`Uploading bundle as multipart`)
|
|
315
316
|
await uploadMultipart(supabase, appid, bundle, zipped, orgId)
|
|
316
317
|
}
|
|
317
318
|
else {
|
|
318
319
|
const url = await uploadUrl(supabase, appid, bundle)
|
|
319
320
|
if (!url) {
|
|
320
|
-
|
|
321
|
+
log.error(`Cannot get upload url`)
|
|
321
322
|
program.error('')
|
|
322
323
|
}
|
|
323
324
|
await ky.put(url, {
|
|
@@ -331,10 +332,10 @@ async function uploadBundleToCapgoCloud(supabase: SupabaseType, appid: string, b
|
|
|
331
332
|
const endTime = performance.now()
|
|
332
333
|
const uploadTime = ((endTime - startTime) / 1000).toFixed(2)
|
|
333
334
|
spinner.stop(`Failed to upload bundle ( after ${uploadTime} seconds)`)
|
|
334
|
-
|
|
335
|
+
log.error(`Cannot upload bundle ( try again with --multipart option) ${formatError(errorUpload)}`)
|
|
335
336
|
if (errorUpload instanceof HTTPError) {
|
|
336
337
|
const body = await errorUpload.response.text()
|
|
337
|
-
|
|
338
|
+
log.error(`Response: ${formatError(body)}`)
|
|
338
339
|
}
|
|
339
340
|
// call delete version on path /delete_failed_version to delete the version
|
|
340
341
|
await deletedFailedVersion(supabase, appid, bundle)
|
|
@@ -371,46 +372,46 @@ async function setVersionInChannel(
|
|
|
371
372
|
owner_org: orgId,
|
|
372
373
|
})
|
|
373
374
|
if (dbError3) {
|
|
374
|
-
|
|
375
|
+
log.error(`Cannot set channel, the upload key is not allowed to do that, use the "all" for this. ${formatError(dbError3)}`)
|
|
375
376
|
program.error('')
|
|
376
377
|
}
|
|
377
378
|
const appidWeb = convertAppName(appid)
|
|
378
379
|
const bundleUrl = `${localConfig.hostWeb}/app/p/${appidWeb}/channel/${data.id}`
|
|
379
380
|
if (data?.public)
|
|
380
|
-
|
|
381
|
+
log.info('Your update is now available in your public channel 🎉')
|
|
381
382
|
else if (data?.id)
|
|
382
|
-
|
|
383
|
+
log.info(`Link device to this bundle to try it: ${bundleUrl}`)
|
|
383
384
|
|
|
384
385
|
if (displayBundleUrl) {
|
|
385
|
-
|
|
386
|
+
log.info(`Bundle url: ${bundleUrl}`)
|
|
386
387
|
}
|
|
387
388
|
else if (!versionId) {
|
|
388
|
-
|
|
389
|
+
log.warn('Cannot set bundle with upload key, use key with more rights for that')
|
|
389
390
|
program.error('')
|
|
390
391
|
}
|
|
391
392
|
else if (!hasOrganizationPerm(permissions, OrganizationPerm.write)) {
|
|
392
|
-
|
|
393
|
+
log.warn('Cannot set channel as a upload organization member')
|
|
393
394
|
}
|
|
394
395
|
}
|
|
395
396
|
}
|
|
396
397
|
|
|
397
398
|
export async function uploadBundle(preAppid: string, options: Options, shouldExit = true) {
|
|
398
|
-
|
|
399
|
+
intro(`Uploading`)
|
|
399
400
|
const pm = getPMAndCommand()
|
|
400
401
|
await checkLatest()
|
|
401
402
|
|
|
402
403
|
const { s3Region, s3Apikey, s3Apisecret, s3BucketName, s3Endpoint, s3Port, s3SSL } = options
|
|
403
404
|
|
|
404
405
|
const apikey = getApikey(options)
|
|
405
|
-
const
|
|
406
|
-
const { appid, path } = getAppIdAndPath(preAppid, options, config)
|
|
407
|
-
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)
|
|
408
409
|
const channel = options.channel || 'dev'
|
|
409
410
|
const snag = useLogSnag()
|
|
410
411
|
|
|
411
412
|
checkNotifyAppReady(options, path)
|
|
412
413
|
|
|
413
|
-
|
|
414
|
+
log.info(`Upload ${appid}@${bundle} started from path "${path}" to Capgo cloud`)
|
|
414
415
|
|
|
415
416
|
const localConfig = await getLocalConfig()
|
|
416
417
|
const supabase = await createSupabaseClient(apikey)
|
|
@@ -426,7 +427,7 @@ export async function uploadBundle(preAppid: string, options: Options, shouldExi
|
|
|
426
427
|
await checkVersionExists(supabase, appid, bundle)
|
|
427
428
|
|
|
428
429
|
if (options.external && !options.external.startsWith('https://')) {
|
|
429
|
-
|
|
430
|
+
log.error(`External link should should start with "https://" current is "${external}"`)
|
|
430
431
|
program.error('')
|
|
431
432
|
}
|
|
432
433
|
|
|
@@ -470,17 +471,17 @@ export async function uploadBundle(preAppid: string, options: Options, shouldExi
|
|
|
470
471
|
|
|
471
472
|
const { error: dbError } = await updateOrCreateVersion(supabase, versionData)
|
|
472
473
|
if (dbError) {
|
|
473
|
-
|
|
474
|
+
log.error(`Cannot add bundle ${formatError(dbError)}`)
|
|
474
475
|
program.error('')
|
|
475
476
|
}
|
|
476
477
|
|
|
477
478
|
if (zipped && (s3BucketName || s3Endpoint || s3Region || s3Apikey || s3Apisecret || s3Port || s3SSL)) {
|
|
478
479
|
if (!s3BucketName || !s3Endpoint || !s3Region || !s3Apikey || !s3Apisecret || !s3Port) {
|
|
479
|
-
|
|
480
|
+
log.error('Missing argument, for S3 upload you need to provide a bucket name, endpoint, region, port, API key, and API secret')
|
|
480
481
|
program.error('')
|
|
481
482
|
}
|
|
482
483
|
|
|
483
|
-
|
|
484
|
+
log.info('Uploading to S3')
|
|
484
485
|
const s3Client = new S3Client({
|
|
485
486
|
endPoint: s3Endpoint,
|
|
486
487
|
region: s3Region,
|
|
@@ -504,7 +505,7 @@ export async function uploadBundle(preAppid: string, options: Options, shouldExi
|
|
|
504
505
|
versionData.storage_provider = 'r2'
|
|
505
506
|
const { error: dbError2 } = await updateOrCreateVersion(supabase, versionData)
|
|
506
507
|
if (dbError2) {
|
|
507
|
-
|
|
508
|
+
log.error(`Cannot update bundle ${formatError(dbError2)}`)
|
|
508
509
|
program.error('')
|
|
509
510
|
}
|
|
510
511
|
}
|
|
@@ -522,8 +523,8 @@ export async function uploadBundle(preAppid: string, options: Options, shouldExi
|
|
|
522
523
|
notify: false,
|
|
523
524
|
}).catch()
|
|
524
525
|
if (shouldExit) {
|
|
525
|
-
|
|
526
|
-
|
|
526
|
+
outro('Time to share your update to the world 🌍')
|
|
527
|
+
exit()
|
|
527
528
|
}
|
|
528
529
|
return true
|
|
529
530
|
}
|
|
@@ -533,19 +534,19 @@ export async function uploadCommand(appid: string, options: Options) {
|
|
|
533
534
|
await uploadBundle(appid, options, true)
|
|
534
535
|
}
|
|
535
536
|
catch (error) {
|
|
536
|
-
|
|
537
|
+
log.error(formatError(error))
|
|
537
538
|
program.error('')
|
|
538
539
|
}
|
|
539
540
|
}
|
|
540
541
|
|
|
541
542
|
export async function uploadDeprecatedCommand(appid: string, options: Options) {
|
|
542
543
|
const pm = getPMAndCommand()
|
|
543
|
-
|
|
544
|
+
log.warn(`⚠️ This command is deprecated, use "${pm.runner} @capgo/cli bundle upload" instead ⚠️`)
|
|
544
545
|
try {
|
|
545
546
|
await uploadBundle(appid, options, true)
|
|
546
547
|
}
|
|
547
548
|
catch (error) {
|
|
548
|
-
|
|
549
|
+
log.error(formatError(error))
|
|
549
550
|
program.error('')
|
|
550
551
|
}
|
|
551
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
|
}
|