@capgo/cli 4.10.3 → 4.10.6-beta.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/src/bundle/zip.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto'
2
2
  import { writeFileSync } from 'node:fs'
3
3
  import process from 'node:process'
4
- import AdmZip from 'adm-zip'
5
4
  import { program } from 'commander'
6
5
  import * as p from '@clack/prompts'
7
6
  import { checksum as getChecksum } from '@tomasklaen/checksum'
@@ -14,6 +13,7 @@ import {
14
13
  getConfig,
15
14
  regexSemver,
16
15
  useLogSnag,
16
+ zipFile,
17
17
  } from '../utils'
18
18
  import { checkIndexPosition, searchInDirectory } from './check'
19
19
 
@@ -28,112 +28,116 @@ interface Options extends OptionsBase {
28
28
  }
29
29
 
30
30
  export async function zipBundle(appId: string, options: Options) {
31
- let { bundle, path } = options
32
- const { json } = options
33
- const snag = useLogSnag()
34
- if (!json)
35
- await checkLatest()
36
-
37
- const config = await getConfig()
38
- appId = appId || config?.app?.appId
39
- // create bundle name format : 1.0.0-beta.x where x is a uuid
40
- const uuid = randomUUID().split('-')[0]
41
- bundle = bundle || config?.app?.package?.version || `0.0.1-beta.${uuid}`
42
- if (!json)
43
- p.intro(`Zipping ${appId}@${bundle}`)
44
- // check if bundle is valid
45
- if (!regexSemver.test(bundle)) {
31
+ try {
32
+ let { bundle, path } = options
33
+ const { json } = options
34
+ const snag = useLogSnag()
46
35
  if (!json)
47
- p.log.error(`Your bundle name ${bundle}, is not valid it should follow semver convention : https://semver.org/`)
48
- else
49
- console.error(formatError({ error: 'invalid_semver' }))
50
- program.error('')
51
- }
52
- path = path || config?.app?.webDir
53
- if (!appId || !bundle || !path) {
36
+ await checkLatest()
37
+
38
+ const config = await getConfig()
39
+ appId = appId || config?.app?.appId
40
+ // create bundle name format : 1.0.0-beta.x where x is a uuid
41
+ const uuid = randomUUID().split('-')[0]
42
+ bundle = bundle || config?.app?.package?.version || `0.0.1-beta.${uuid}`
54
43
  if (!json)
55
- p.log.error('Missing argument, you need to provide a appId and a bundle and a path, or be in a capacitor project')
56
- else
57
- console.error(formatError({ error: 'missing_argument' }))
58
- program.error('')
59
- }
60
- if (!json)
61
- p.log.info(`Started from path "${path}"`)
62
- const checkNotifyAppReady = options.codeCheck
63
- if (typeof checkNotifyAppReady === 'undefined' || checkNotifyAppReady) {
64
- const isPluginConfigured = searchInDirectory(path, 'notifyAppReady')
65
- if (!isPluginConfigured) {
44
+ p.intro(`Zipping ${appId}@${bundle}`)
45
+ // check if bundle is valid
46
+ if (!regexSemver.test(bundle)) {
66
47
  if (!json)
67
- p.log.error(`notifyAppReady() is missing in the source code. see: https://capgo.app/docs/plugin/api/#notifyappready`)
48
+ p.log.error(`Your bundle name ${bundle}, is not valid it should follow semver convention : https://semver.org/`)
68
49
  else
69
- console.error(formatError({ error: 'notifyAppReady_not_in_source_code' }))
50
+ console.error(formatError({ error: 'invalid_semver' }))
70
51
  program.error('')
71
52
  }
72
- const foundIndex = checkIndexPosition(path)
73
- if (!foundIndex) {
53
+ path = path || config?.app?.webDir
54
+ if (!appId || !bundle || !path) {
74
55
  if (!json)
75
- p.log.error(`index.html is missing in the root folder or in the only folder in the root folder`)
56
+ p.log.error('Missing argument, you need to provide a appId and a bundle and a path, or be in a capacitor project')
76
57
  else
77
- console.error(formatError({ error: 'index_html_not_found' }))
58
+ console.error(formatError({ error: 'missing_argument' }))
78
59
  program.error('')
79
60
  }
80
- }
81
- const zip = new AdmZip()
82
- zip.addLocalFolder(path)
83
- const zipped = zip.toBuffer()
84
- if (!json)
85
- p.log.info(`Zipped ${zipped.byteLength} bytes`)
86
- const s = p.spinner()
87
- if (!json)
88
- s.start(`Calculating checksum`)
89
- const checksum = await getChecksum(zipped, 'crc32')
90
- if (!json)
91
- s.stop(`Checksum: ${checksum}`)
92
- const mbSize = Math.floor(zipped.byteLength / 1024 / 1024)
93
- // We do not issue this warning for json
94
- if (mbSize > alertMb && !json) {
95
- p.log.warn(`WARNING !!\nThe app size is ${mbSize} Mb, this may take a while to download for users\n`)
96
- p.log.warn(`Learn how to optimize your assets https://capgo.app/blog/optimise-your-images-for-updates/\n`)
61
+ if (!json)
62
+ p.log.info(`Started from path "${path}"`)
63
+ const checkNotifyAppReady = options.codeCheck
64
+ if (typeof checkNotifyAppReady === 'undefined' || checkNotifyAppReady) {
65
+ const isPluginConfigured = searchInDirectory(path, 'notifyAppReady')
66
+ if (!isPluginConfigured) {
67
+ if (!json)
68
+ p.log.error(`notifyAppReady() is missing in the source code. see: https://capgo.app/docs/plugin/api/#notifyappready`)
69
+ else
70
+ console.error(formatError({ error: 'notifyAppReady_not_in_source_code' }))
71
+ program.error('')
72
+ }
73
+ const foundIndex = checkIndexPosition(path)
74
+ if (!foundIndex) {
75
+ if (!json)
76
+ p.log.error(`index.html is missing in the root folder or in the only folder in the root folder`)
77
+ else
78
+ console.error(formatError({ error: 'index_html_not_found' }))
79
+ program.error('')
80
+ }
81
+ }
82
+ const zipped = await zipFile(path)
83
+ if (!json)
84
+ p.log.info(`Zipped ${zipped.byteLength} bytes`)
85
+ const s = p.spinner()
86
+ if (!json)
87
+ s.start(`Calculating checksum`)
88
+ const checksum = await getChecksum(zipped, 'crc32')
89
+ if (!json)
90
+ s.stop(`Checksum: ${checksum}`)
91
+ const mbSize = Math.floor(zipped.byteLength / 1024 / 1024)
92
+ // We do not issue this warning for json
93
+ if (mbSize > alertMb && !json) {
94
+ p.log.warn(`WARNING !!\nThe app size is ${mbSize} Mb, this may take a while to download for users\n`)
95
+ p.log.warn(`Learn how to optimize your assets https://capgo.app/blog/optimise-your-images-for-updates/\n`)
96
+ await snag.track({
97
+ channel: 'app-error',
98
+ event: 'App Too Large',
99
+ icon: '🚛',
100
+ tags: {
101
+ 'app-id': appId,
102
+ },
103
+ notify: false,
104
+ }).catch()
105
+ }
106
+ const s2 = p.spinner()
107
+ const name = options.name || `${appId}_${bundle}.zip`
108
+ if (!json)
109
+ s2.start(`Saving to ${name}`)
110
+ writeFileSync(name, zipped)
111
+ if (!json)
112
+ s2.stop(`Saved to ${name}`)
113
+
97
114
  await snag.track({
98
- channel: 'app-error',
99
- event: 'App Too Large',
100
- icon: '🚛',
115
+ channel: 'app',
116
+ event: 'App zip',
117
+ icon: '',
101
118
  tags: {
102
119
  'app-id': appId,
103
120
  },
104
121
  notify: false,
105
122
  }).catch()
106
- }
107
- const s2 = p.spinner()
108
- const name = options.name || `${appId}_${bundle}.zip`
109
- if (!json)
110
- s2.start(`Saving to ${name}`)
111
- writeFileSync(name, zipped)
112
- if (!json)
113
- s2.stop(`Saved to ${name}`)
114
-
115
- await snag.track({
116
- channel: 'app',
117
- event: 'App zip',
118
- icon: '⏫',
119
- tags: {
120
- 'app-id': appId,
121
- },
122
- notify: false,
123
- }).catch()
124
123
 
125
- if (!json)
126
- p.outro(`Done ✅`)
124
+ if (!json)
125
+ p.outro(`Done ✅`)
127
126
 
128
- if (json) {
129
- const output = {
130
- bundle,
131
- filename: name,
132
- checksum,
127
+ if (json) {
128
+ const output = {
129
+ bundle,
130
+ filename: name,
131
+ checksum,
132
+ }
133
+ // Keep the console log and stringify for user who parse the output
134
+ // eslint-disable-next-line no-console
135
+ console.log(JSON.stringify(output, null, 2))
133
136
  }
134
- // Keep the console log and stringify for user who parse the output
135
- // eslint-disable-next-line no-console
136
- console.log(JSON.stringify(output, null, 2))
137
+ process.exit()
138
+ }
139
+ catch (error) {
140
+ p.log.error(formatError(error))
141
+ program.error('')
137
142
  }
138
- process.exit()
139
143
  }
@@ -4,7 +4,7 @@ import * as p 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'
7
- import { OrganizationPerm, createSupabaseClient, findSavedKey, getConfig, getOrganizationId, useLogSnag, verifyUser } from '../utils'
7
+ import { OrganizationPerm, createSupabaseClient, findSavedKey, formatError, getConfig, getOrganizationId, useLogSnag, verifyUser } from '../utils'
8
8
 
9
9
  interface Options extends OptionsBase {
10
10
  default?: boolean
@@ -27,7 +27,7 @@ export async function addChannel(channelId: string, appId: string, options: Opti
27
27
  }
28
28
  const supabase = await createSupabaseClient(options.apikey)
29
29
 
30
- const userId = await verifyUser(supabase, options.apikey, ['write', 'all'])
30
+ await verifyUser(supabase, options.apikey, ['write', 'all'])
31
31
  // Check we have app access to this appId
32
32
  await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.admin)
33
33
 
@@ -39,18 +39,24 @@ export async function addChannel(channelId: string, appId: string, options: Opti
39
39
  p.log.error(`Cannot find default version for channel creation, please contact Capgo support 🤨`)
40
40
  program.error('')
41
41
  }
42
- await createChannel(supabase, {
42
+ const res = await createChannel(supabase, {
43
43
  name: channelId,
44
44
  app_id: appId,
45
45
  version: data.id,
46
46
  owner_org: orgId,
47
47
  })
48
+
49
+ if (res.error) {
50
+ p.log.error(`Cannot create Channel 🙀\n${formatError(res.error)}`)
51
+ program.error('')
52
+ }
53
+
48
54
  p.log.success(`Channel created ✅`)
49
55
  await snag.track({
50
56
  channel: 'channel',
51
57
  event: 'Create channel',
52
58
  icon: '✅',
53
- user_id: userId,
59
+ user_id: orgId,
54
60
  tags: {
55
61
  'app-id': appId,
56
62
  'channel': channelId,
@@ -4,7 +4,7 @@ import * as p from '@clack/prompts'
4
4
  import { checkAppExistsAndHasPermissionOrgErr } from '../api/app'
5
5
  import { delChannel } from '../api/channels'
6
6
  import type { OptionsBase } from '../utils'
7
- import { OrganizationPerm, createSupabaseClient, findSavedKey, getConfig, useLogSnag, verifyUser } from '../utils'
7
+ import { OrganizationPerm, createSupabaseClient, findSavedKey, formatError, getConfig, getOrganizationId, useLogSnag, verifyUser } from '../utils'
8
8
 
9
9
  export async function deleteChannel(channelId: string, appId: string, options: OptionsBase) {
10
10
  p.intro(`Delete channel`)
@@ -29,13 +29,18 @@ export async function deleteChannel(channelId: string, appId: string, options: O
29
29
 
30
30
  p.log.info(`Deleting channel ${appId}#${channelId} from Capgo`)
31
31
  try {
32
- await delChannel(supabase, channelId, appId, userId)
32
+ const deleteStatus = await delChannel(supabase, channelId, appId, userId)
33
+ if (deleteStatus.error) {
34
+ p.log.error(`Cannot delete Channel 🙀 ${formatError(deleteStatus.error)}`)
35
+ program.error('')
36
+ }
37
+ const orgId = await getOrganizationId(supabase, appId)
33
38
  p.log.success(`Channel deleted`)
34
39
  await snag.track({
35
40
  channel: 'channel',
36
41
  event: 'Delete channel',
37
42
  icon: '✅',
38
- user_id: userId,
43
+ user_id: orgId,
39
44
  tags: {
40
45
  'user-id': userId,
41
46
  'app-id': appId,
@@ -161,9 +161,8 @@ export async function setChannel(channel: string, appId: string, options: Option
161
161
  channel: 'channel',
162
162
  event: 'Set channel',
163
163
  icon: '✅',
164
- user_id: userId,
164
+ user_id: orgId,
165
165
  tags: {
166
- 'user-id': userId,
167
166
  'app-id': appId,
168
167
  },
169
168
  notify: false,
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { program } from 'commander'
2
- import { getUserId } from 'user/account'
3
2
  import pack from '../package.json'
3
+ import { getUserId } from './user/account'
4
4
  import { zipBundle } from './bundle/zip'
5
5
  import { initApp } from './init'
6
6
  import { listBundle } from './bundle/list'
@@ -294,12 +294,11 @@ program
294
294
  'Minimal version required to update to this version. Used only if the disable auto update is set to metadata in channel',
295
295
  )
296
296
 
297
- const user = program
298
- .command('user')
299
- .description('Manage user')
297
+ const account = program
298
+ .command('account')
299
+ .description('Manage account')
300
300
 
301
- user.command('account')
302
- .alias('a')
301
+ account.command('id')
303
302
  .description('Get your account ID')
304
303
  .action(getUserId)
305
304
  .option('-a, --apikey <apikey>', 'apikey to link to your account')
package/src/init.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { readFileSync, writeFileSync } from 'node:fs'
1
+ import { readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
2
2
  import type { ExecSyncOptions } from 'node:child_process'
3
3
  import { execSync, spawnSync } from 'node:child_process'
4
4
  import process from 'node:process'
5
+ import { tmpdir } from 'node:os'
5
6
  import * as p from '@clack/prompts'
6
- import type { SupabaseClient } from '@supabase/supabase-js'
7
7
  import type LogSnag from 'logsnag'
8
8
  import semver from 'semver'
9
- import type { Database } from './types/supabase.types'
9
+ import tmp from 'tmp'
10
10
  import { markSnag, waitLog } from './app/debug'
11
11
  import { createKey } from './key'
12
12
  import { addChannel } from './channel/add'
@@ -28,6 +28,63 @@ const regexImport = /import.*from.*/g
28
28
  const defaultChannel = 'production'
29
29
  const execOption = { stdio: 'pipe' }
30
30
 
31
+ let tmpObject: tmp.FileResult['name'] | undefined
32
+
33
+ function readTmpObj() {
34
+ if (!tmpObject) {
35
+ tmpObject = readdirSync(tmp.tmpdir)
36
+ .map((name) => { return { name, full: `${tmp.tmpdir}/${name}` } })
37
+ .find(obj => obj.name.startsWith('capgocli'))?.full
38
+ ?? tmp.fileSync({ prefix: 'capgocli' }).name
39
+ }
40
+ }
41
+
42
+ function markStepDone(step: number) {
43
+ try {
44
+ readTmpObj()
45
+ writeFileSync(tmpObject!, JSON.stringify({ step_done: step }))
46
+ }
47
+ catch (err) {
48
+ p.log.error(`Cannot mark step as done in the CLI, error:\n${err}`)
49
+ p.log.warn('Onboarding will continue but please report it to the capgo team!')
50
+ }
51
+ }
52
+
53
+ async function readStepsDone(orgId: string, snag: LogSnag): Promise<number | undefined> {
54
+ try {
55
+ readTmpObj()
56
+ const rawData = readFileSync(tmpObject!, 'utf-8')
57
+ if (!rawData || rawData.length === 0)
58
+ return undefined
59
+
60
+ const { step_done } = JSON.parse(rawData)
61
+ p.log.info(`You have already got to the step ${step_done}/10 in the previous session`)
62
+ const skipSteps = await p.confirm({ message: 'Would you like to continue from where you left off?' })
63
+ await cancelCommand(skipSteps, orgId, snag)
64
+ if (skipSteps)
65
+ return step_done
66
+ return undefined
67
+ }
68
+ catch (err) {
69
+ p.log.error(`Cannot read which steps have been compleated, error:\n${err}`)
70
+ p.log.warn('Onboarding will continue but please report it to the capgo team!')
71
+ return undefined
72
+ }
73
+ }
74
+
75
+ function cleanupStepsDone() {
76
+ if (!tmpObject) {
77
+ return
78
+ }
79
+
80
+ try {
81
+ rmSync(tmpObject)
82
+ }
83
+ catch (err) {
84
+ p.log.error(`Cannot delete the tmp steps file.\nError: ${err}`)
85
+ }
86
+ }
87
+
31
88
  async function cancelCommand(command: boolean | symbol, orgId: string, snag: LogSnag) {
32
89
  if (p.isCancel(command)) {
33
90
  await markSnag('onboarding-v2', orgId, snag, 'canceled', '🤷')
@@ -53,7 +110,7 @@ async function step2(organization: Organization, snag: LogSnag, appId: string, o
53
110
  s.stop(`App add Done ✅`)
54
111
  }
55
112
  else {
56
- p.log.info(`Run yourself "${pm.runner} @capgo/cli@latest app add ${appId}"`)
113
+ p.log.info(`If you change your mind, run it for yourself with: "${pm.runner} @capgo/cli@latest app add ${appId}"`)
57
114
  }
58
115
  await markStep(organization.gid, snag, 2)
59
116
  }
@@ -76,7 +133,7 @@ async function step3(orgId: string, snag: LogSnag, apikey: string, appId: string
76
133
  s.stop(`Channel add Done ✅`)
77
134
  }
78
135
  else {
79
- p.log.info(`Run yourself "${pm.runner} @capgo/cli@latest channel add ${defaultChannel} ${appId} --default"`)
136
+ p.log.info(`If you change your mind, run it for yourself with: "${pm.runner} @capgo/cli@latest channel add ${defaultChannel} ${appId} --default"`)
80
137
  }
81
138
  await markStep(orgId, snag, 3)
82
139
  }
@@ -128,7 +185,7 @@ async function step4(orgId: string, snag: LogSnag, apikey: string, appId: string
128
185
  }
129
186
  }
130
187
  else {
131
- p.log.info(`Run yourself "${pm.installCommand} @capgo/capacitor-updater@latest"`)
188
+ p.log.info(`If you change your mind, run it for yourself with: "${pm.installCommand} @capgo/capacitor-updater@latest"`)
132
189
  }
133
190
  await markStep(orgId, snag, 4)
134
191
  }
@@ -279,18 +336,18 @@ async function step9(orgId: string, snag: LogSnag) {
279
336
  s.stop(`Started Done ✅`)
280
337
  }
281
338
  else {
282
- p.log.info(`Run yourself with command: ${pm.runner} cap run <ios|android>`)
339
+ p.log.info(`If you change your mind, run it for yourself with: ${pm.runner} cap run <ios|android>`)
283
340
  }
284
341
  await markStep(orgId, snag, 9)
285
342
  }
286
343
 
287
- async function _step10(orgId: string, snag: LogSnag, supabase: SupabaseClient<Database>, appId: string) {
344
+ async function step10(orgId: string, snag: LogSnag, apikey: string, appId: string) {
288
345
  const doRun = await p.confirm({ message: `Automatic check if update working in device ?` })
289
346
  await cancelCommand(doRun, orgId, snag)
290
347
  if (doRun) {
291
348
  p.log.info(`Wait logs sent to Capgo from ${appId} device, Put the app in background and open it again.`)
292
- p.log.info('Waiting...')
293
- await waitLog('onboarding-v2', supabase, appId, snag, orgId)
349
+ p.log.info('Waiting... (there is a usual delay of 15 seconds until the backend process the logs)')
350
+ await waitLog('onboarding-v2', apikey, appId, snag, orgId)
294
351
  }
295
352
  else {
296
353
  const appIdUrl = convertAppName(appId)
@@ -321,19 +378,62 @@ export async function initApp(apikeyCommand: string, appId: string, options: Sup
321
378
  const organization = await getOrganization(supabase, ['admin', 'super_admin'])
322
379
  const orgId = organization.gid
323
380
 
324
- await markStep(orgId, snag, 1)
381
+ const stepToSkip = await readStepsDone(orgId, snag) ?? 0
325
382
 
326
- await step2(organization, snag, appId, options)
327
- await step3(orgId, snag, apikey, appId)
328
- await step4(orgId, snag, apikey, appId)
329
- await step5(orgId, snag, apikey, appId)
330
- await step6(orgId, snag, apikey, appId)
331
- await step7(orgId, snag, apikey, appId)
332
- await step8(orgId, snag, apikey, appId)
333
- await step9(orgId, snag)
334
- // await step10(orgId, snag, supabase, appId)
383
+ try {
384
+ if (stepToSkip < 1)
385
+ await markStep(orgId, snag, 1)
386
+
387
+ if (stepToSkip < 2) {
388
+ await step2(organization, snag, appId, options)
389
+ markStepDone(2)
390
+ }
391
+
392
+ if (stepToSkip < 3) {
393
+ await step3(orgId, snag, apikey, appId)
394
+ markStepDone(3)
395
+ }
396
+
397
+ if (stepToSkip < 4) {
398
+ await step4(orgId, snag, apikey, appId)
399
+ markStepDone(4)
400
+ }
401
+
402
+ if (stepToSkip < 5) {
403
+ await step5(orgId, snag, apikey, appId)
404
+ markStepDone(5)
405
+ }
406
+
407
+ if (stepToSkip < 6) {
408
+ await step6(orgId, snag, apikey, appId)
409
+ markStepDone(6)
410
+ }
411
+
412
+ if (stepToSkip < 7) {
413
+ await step7(orgId, snag, apikey, appId)
414
+ markStepDone(7)
415
+ }
416
+
417
+ if (stepToSkip < 8) {
418
+ await step8(orgId, snag, apikey, appId)
419
+ markStepDone(8)
420
+ }
421
+
422
+ if (stepToSkip < 9) {
423
+ await step9(orgId, snag)
424
+ markStepDone(9)
425
+ }
426
+
427
+ await step10(orgId, snag, apikey, appId)
428
+ await markStep(orgId, snag, 0)
429
+ cleanupStepsDone()
430
+ }
431
+ catch (e) {
432
+ console.error(e)
433
+ p.log.error(`Error during onboarding, please try again later`)
434
+ process.exit(1)
435
+ }
335
436
 
336
- await markStep(orgId, snag, 0)
337
437
  p.log.info(`Welcome onboard ✈️!`)
338
438
  p.log.info(`Your Capgo update system is setup`)
339
439
  p.log.info(`Next time use \`${pm.runner} @capgo/cli@latest bundle upload\` to only upload your bundle`)
package/src/key.ts CHANGED
@@ -76,8 +76,8 @@ export async function createKey(options: Options, log = true) {
76
76
 
77
77
  // check if baseName already exist
78
78
  if (existsSync(baseKeyPub) && !options.force) {
79
+ p.log.error('Public Key already exists, use --force to overwrite')
79
80
  if (log) {
80
- p.log.error('Public Key already exists, use --force to overwrite')
81
81
  program.error('')
82
82
  }
83
83
  else {
@@ -86,8 +86,8 @@ export async function createKey(options: Options, log = true) {
86
86
  }
87
87
  writeFileSync(baseKeyPub, publicKey)
88
88
  if (existsSync(baseKey) && !options.force) {
89
+ p.log.error('Private Key already exists, use --force to overwrite')
89
90
  if (log) {
90
- p.log.error('Private Key already exists, use --force to overwrite')
91
91
  program.error('')
92
92
  }
93
93
  else {