@capgo/cli 4.13.8 → 4.13.9

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.
Files changed (57) hide show
  1. package/.github/FUNDING.yml +1 -0
  2. package/.github/workflows/build.yml +46 -0
  3. package/.github/workflows/bump_version.yml +56 -0
  4. package/.github/workflows/test.yml +30 -0
  5. package/.prettierignore +6 -0
  6. package/.vscode/launch.json +23 -0
  7. package/.vscode/settings.json +46 -0
  8. package/.vscode/tasks.json +42 -0
  9. package/CHANGELOG.md +2739 -0
  10. package/build.mjs +23 -0
  11. package/bun.lockb +0 -0
  12. package/capacitor.config.ts +33 -0
  13. package/crypto_explained.png +0 -0
  14. package/dist/index.js +114692 -205
  15. package/eslint.config.js +10 -0
  16. package/package.json +33 -32
  17. package/renovate.json +23 -0
  18. package/src/api/app.ts +55 -0
  19. package/src/api/channels.ts +140 -0
  20. package/src/api/crypto.ts +116 -0
  21. package/src/api/devices_override.ts +41 -0
  22. package/src/api/update.ts +13 -0
  23. package/src/api/versions.ts +101 -0
  24. package/src/app/add.ts +158 -0
  25. package/src/app/debug.ts +222 -0
  26. package/src/app/delete.ts +106 -0
  27. package/src/app/info.ts +90 -0
  28. package/src/app/list.ts +67 -0
  29. package/src/app/set.ts +94 -0
  30. package/src/bundle/check.ts +42 -0
  31. package/src/bundle/cleanup.ts +127 -0
  32. package/src/bundle/compatibility.ts +70 -0
  33. package/src/bundle/decrypt.ts +54 -0
  34. package/src/bundle/delete.ts +53 -0
  35. package/src/bundle/encrypt.ts +60 -0
  36. package/src/bundle/list.ts +43 -0
  37. package/src/bundle/unlink.ts +86 -0
  38. package/src/bundle/upload.ts +532 -0
  39. package/src/bundle/zip.ts +139 -0
  40. package/src/channel/add.ts +74 -0
  41. package/src/channel/currentBundle.ts +72 -0
  42. package/src/channel/delete.ts +52 -0
  43. package/src/channel/list.ts +49 -0
  44. package/src/channel/set.ts +178 -0
  45. package/src/index.ts +307 -0
  46. package/src/init.ts +342 -0
  47. package/src/key.ts +131 -0
  48. package/src/login.ts +70 -0
  49. package/src/types/capacitor__cli.d.ts +6 -0
  50. package/src/types/supabase.types.ts +2193 -0
  51. package/src/user/account.ts +11 -0
  52. package/src/utils.ts +956 -0
  53. package/test/chunk_convert.ts +28 -0
  54. package/test/data.ts +18769 -0
  55. package/test/test_headers_rls.ts +24 -0
  56. package/test/test_semver.ts +13 -0
  57. package/tsconfig.json +39 -0
package/src/index.ts ADDED
@@ -0,0 +1,307 @@
1
+ import { program } from 'commander'
2
+ import { getUserId } from 'user/account'
3
+ import pack from '../package.json'
4
+ import { zipBundle } from './bundle/zip'
5
+ import { initApp } from './init'
6
+ import { listBundle } from './bundle/list'
7
+ import { decryptZip } from './bundle/decrypt'
8
+ import { encryptZip } from './bundle/encrypt'
9
+ import { addCommand } from './app/add'
10
+ import { getInfo } from './app/info'
11
+ import { createKeyCommand, saveKeyCommand } from './key'
12
+ import { deleteBundle } from './bundle/delete'
13
+ import { setChannel } from './channel/set'
14
+ import { currentBundle } from './channel/currentBundle'
15
+ import { uploadCommand, uploadDeprecatedCommand } from './bundle/upload'
16
+ import { loginCommand } from './login'
17
+ import { listApp } from './app/list'
18
+ import { cleanupBundle } from './bundle/cleanup'
19
+ import { addChannelCommand } from './channel/add'
20
+ import { deleteChannel } from './channel/delete'
21
+ import { listChannels } from './channel/list'
22
+ import { setApp } from './app/set'
23
+ import { deleteApp } from './app/delete'
24
+
25
+ // import { watchApp } from './app/watch';
26
+ import { debugApp } from './app/debug'
27
+ import { checkCompatibilityCommand } from './bundle/compatibility'
28
+
29
+ program
30
+ .name(pack.name)
31
+ .description('Manage packages and bundle versions in Capgo Cloud')
32
+ .version(pack.version)
33
+
34
+ program
35
+ .command('login [apikey]')
36
+ .alias('l')
37
+ .description('Save apikey to your machine or folder')
38
+ .action(loginCommand)
39
+ .option('--local', 'Only save in local folder')
40
+
41
+ program
42
+ .command('doctor')
43
+ .description('Get info about your Capgo app install')
44
+ .action(getInfo)
45
+
46
+ program
47
+ .command('init [apikey] [appId]')
48
+ .description('Init a new app')
49
+ .action(initApp)
50
+ .option('-n, --name <name>', 'app name')
51
+ .option('-i, --icon <icon>', 'app icon path')
52
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
53
+
54
+ const app = program
55
+ .command('app')
56
+ .description('Manage app')
57
+
58
+ app
59
+ .command('add [appId]')
60
+ .alias('a')
61
+ .description('Add a new app in Capgo Cloud')
62
+ .action(addCommand)
63
+ .option('-n, --name <name>', 'app name')
64
+ .option('-i, --icon <icon>', 'app icon path')
65
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
66
+
67
+ app
68
+ .command('delete [appId]')
69
+ .description('Delete an app in Capgo Cloud')
70
+ .action(deleteApp)
71
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
72
+
73
+ app
74
+ .command('list')
75
+ .alias('l')
76
+ .description('list apps in Capgo Cloud')
77
+ .action(listApp)
78
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
79
+
80
+ app
81
+ .command('debug [appId]')
82
+ .description('Listen for live updates event in Capgo Cloud to debug your app')
83
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
84
+ .option('-d, --device <device>', 'the specific device to debug')
85
+ .action(debugApp)
86
+
87
+ // app
88
+ // .command('watch [port]')
89
+ // .alias('w')
90
+ // .description('watch for changes in your app and allow capgo app or your app to see changes in live')
91
+ // .action(watchApp);
92
+
93
+ app
94
+ .command('set [appId]')
95
+ .alias('s')
96
+ .description('Set an app in Capgo Cloud')
97
+ .action(setApp)
98
+ .option('-n, --name <name>', 'app name')
99
+ .option('-i, --icon <icon>', 'app icon path')
100
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
101
+ .option('-r, --retention <retention>', 'retention period of app bundle in days')
102
+
103
+ const bundle = program
104
+ .command('bundle')
105
+ .description('Manage bundle')
106
+
107
+ bundle
108
+ .command('upload [appId]')
109
+ .alias('u')
110
+ .description('Upload a new bundle in Capgo Cloud')
111
+ .action(uploadCommand)
112
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
113
+ .option('-p, --path <path>', 'path of the folder to upload')
114
+ .option('-c, --channel <channel>', 'channel to link to')
115
+ .option('-e, --external <url>', 'link to external url intead of upload to Capgo Cloud')
116
+ .option('--iv-session-key <key>', 'Set the iv and session key for bundle url external')
117
+ .option('--s3-region <region>', 'Region for your AWS S3 bucket')
118
+ .option('--s3-apikey <apikey>', 'apikey for your AWS S3 account')
119
+ .option('--s3-apisecret <apisecret>', 'api secret for your AWS S3 account')
120
+ .option('--s3-bucket-name <bucketName>', 'Name for your AWS S3 bucket')
121
+ .option('--key <key>', 'custom path for public signing key')
122
+ .option('--key-data <keyData>', 'base64 public signing key')
123
+ .option('--bundle-url', 'prints bundle url into stdout')
124
+ .option('--no-key', 'ignore signing key and send clear update')
125
+ .option('--no-code-check', 'Ignore checking if notifyAppReady() is called in soure code and index present in root folder')
126
+ .option('--display-iv-session', 'Show in the console the iv and session key used to encrypt the update')
127
+ .option('-b, --bundle <bundle>', 'bundle version number of the bundle to upload')
128
+ .option(
129
+ '--min-update-version <minUpdateVersion>',
130
+ 'Minimal version required to update to this version. Used only if the disable auto update is set to metadata in channel',
131
+ )
132
+ .option('--auto-min-update-version', 'Set the min update version based on native packages')
133
+ .option('--ignore-metadata-check', 'Ignores the metadata (node_modules) check when uploading')
134
+ .option('--timeout <timeout>', 'Timeout for the upload process in seconds')
135
+ .option('--multipart', 'Uses multipart protocol to upload data to S3')
136
+
137
+ bundle
138
+ .command('compatibility [appId]')
139
+ .action(checkCompatibilityCommand)
140
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
141
+ .option('-c, --channel <channel>', 'channel to check the compatibility with')
142
+ .option('--text', 'output text instead of emojis')
143
+
144
+ bundle
145
+ .command('delete [bundleId] [appId]')
146
+ .alias('d')
147
+ .description('Delete a bundle in Capgo Cloud')
148
+ .action(deleteBundle)
149
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
150
+
151
+ bundle
152
+ .command('list [appId]')
153
+ .alias('l')
154
+ .description('List bundle in Capgo Cloud')
155
+ .action(listBundle)
156
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
157
+
158
+ // TODO: Fix this command!
159
+ // bundle
160
+ // .command('unlink [appId]')
161
+ // .description('Unlink a bundle in Capgo Cloud')
162
+ // .action(listBundle)
163
+ // .option('-a, --apikey <apikey>', 'apikey to link to your account')
164
+ // .option('-b, --bundle <bundle>', 'bundle version number of the bundle to unlink')
165
+
166
+ bundle
167
+ .command('cleanup [appId]')
168
+ .alias('c')
169
+ .action(cleanupBundle)
170
+ .description('Cleanup bundle in Capgo Cloud')
171
+ .option('-b, --bundle <bundle>', 'bundle version number of the app to delete')
172
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
173
+ .option('-k, --keep <keep>', 'number of version to keep')
174
+ .option('-f, --force', 'force removal')
175
+
176
+ bundle
177
+ .command('decrypt [zipPath] [sessionKey]')
178
+ .description('Decrypt a signed zip bundle')
179
+ .action(decryptZip)
180
+ .option('--key <key>', 'custom path for private signing key')
181
+ .option('--key-data <keyData>', 'base64 private signing key')
182
+
183
+ bundle
184
+ .command('encrypt [zipPath]')
185
+ .description('Encrypt a zip bundle')
186
+ .action(encryptZip)
187
+ .option('--key <key>', 'custom path for private signing key')
188
+ .option('--key-data <keyData>', 'base64 private signing key')
189
+
190
+ bundle
191
+ .command('zip [appId]')
192
+ .description('Zip a bundle')
193
+ .action(zipBundle)
194
+ .option('-p, --path <path>', 'path of the folder to upload')
195
+ .option('-b, --bundle <bundle>', 'bundle version number to name the zip file')
196
+ .option('-n, --name <name>', 'name of the zip file')
197
+ .option('-j, --json', 'output in JSON')
198
+ .option('--no-code-check', 'Ignore checking if notifyAppReady() is called in soure code and index present in root folder')
199
+
200
+ const channel = program
201
+ .command('channel')
202
+ .description('Manage channel')
203
+
204
+ channel
205
+ .command('add [channelId] [appId]')
206
+ .alias('a')
207
+ .description('Create channel')
208
+ .action(addChannelCommand)
209
+ .option('-d, --default', 'set the channel as default')
210
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
211
+
212
+ channel
213
+ .command('delete [channelId] [appId]')
214
+ .alias('d')
215
+ .description('Delete channel')
216
+ .action(deleteChannel)
217
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
218
+
219
+ channel
220
+ .command('list [appId]')
221
+ .alias('l')
222
+ .description('List channel')
223
+ .action(listChannels)
224
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
225
+
226
+ channel
227
+ .command('currentBundle [channel] [appId]')
228
+ .description('Get current bundle for specific channel in Capgo Cloud')
229
+ .action(currentBundle)
230
+ .option('-c, --channel <channel>', 'channel to get the current bundle from')
231
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
232
+ .option('--quiet', 'only print the bundle version')
233
+
234
+ channel
235
+ .command('set [channelId] [appId]')
236
+ .alias('s')
237
+ .description('Set channel')
238
+ .action(setChannel)
239
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
240
+ .option('-b, --bundle <bundle>', 'bundle version number of the file to set')
241
+ .option('-s, --state <state>', 'set the state of the channel, default or normal')
242
+ .option('--latest', 'get the latest version key in the package.json to set it to the channel')
243
+ .option('--downgrade', 'Allow to downgrade to version under native one')
244
+ .option('--no-downgrade', 'Disable downgrade to version under native one')
245
+ .option('--upgrade', 'Allow to upgrade to version above native one')
246
+ .option('--no-upgrade', 'Disable upgrade to version above native one')
247
+ .option('--ios', 'Allow sending update to ios devices')
248
+ .option('--no-ios', 'Disable sending update to ios devices')
249
+ .option('--android', 'Allow sending update to android devices')
250
+ .option('--no-android', 'Disable sending update to android devices')
251
+ .option('--self-assign', 'Allow to device to self assign to this channel')
252
+ .option('--no-self-assign', 'Disable devices to self assign to this channel')
253
+ .option('--disable-auto-update <disableAutoUpdate>', 'Disable auto update strategy for this channel.The possible options are: major, minor, metadata, patch, none')
254
+ .option('--dev', 'Allow sending update to development devices')
255
+ .option('--no-dev', 'Disable sending update to development devices')
256
+ .option('--emulator', 'Allow sending update to emulator devices')
257
+ .option('--no-emulator', 'Disable sending update to emulator devices')
258
+
259
+ const key = program
260
+ .command('key')
261
+ .description('Manage key')
262
+
263
+ key
264
+ .command('save')
265
+ .description('Save base64 signing key in capacitor config, usefull for CI')
266
+ .action(saveKeyCommand)
267
+ .option('-f, --force', 'force generate a new one')
268
+ .option('--key', 'key path to save in capacitor config')
269
+ .option('--key-data', 'key data to save in capacitor config')
270
+
271
+ key
272
+ .command('create')
273
+ .description('Create a new signing key')
274
+ .action(createKeyCommand)
275
+ .option('-f, --force', 'force generate a new one')
276
+
277
+ program
278
+ .command('upload [appId]')
279
+ .alias('u')
280
+ .description('(Deprecated) Upload a new bundle to Capgo Cloud')
281
+ .action(uploadDeprecatedCommand)
282
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
283
+ .option('-p, --path <path>', 'path of the folder to upload')
284
+ .option('-c, --channel <channel>', 'channel to link to')
285
+ .option('-e, --external <url>', 'link to external url intead of upload to Capgo Cloud')
286
+ .option('--key <key>', 'custom path for public signing key')
287
+ .option('--key-data <keyData>', 'base64 public signing key')
288
+ .option('--bundle-url', 'prints bundle url into stdout')
289
+ .option('--no-key', 'ignore signing key and send clear update')
290
+ .option('--display-iv-session', 'Show in the console the iv and session key used to encrypt the update')
291
+ .option('-b, --bundle <bundle>', 'bundle version number of the file to upload')
292
+ .option(
293
+ '--min-update-version <minUpdateVersion>',
294
+ 'Minimal version required to update to this version. Used only if the disable auto update is set to metadata in channel',
295
+ )
296
+
297
+ const user = program
298
+ .command('user')
299
+ .description('Manage user')
300
+
301
+ user.command('account')
302
+ .alias('a')
303
+ .description('Get your account ID')
304
+ .action(getUserId)
305
+ .option('-a, --apikey <apikey>', 'apikey to link to your account')
306
+
307
+ program.parseAsync()
package/src/init.ts ADDED
@@ -0,0 +1,342 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs'
2
+ import type { ExecSyncOptions } from 'node:child_process'
3
+ import { execSync, spawnSync } from 'node:child_process'
4
+ import process from 'node:process'
5
+ import * as p from '@clack/prompts'
6
+ import type { SupabaseClient } from '@supabase/supabase-js'
7
+ import type LogSnag from 'logsnag'
8
+ import semver from 'semver'
9
+ import type { Database } from './types/supabase.types'
10
+ import { markSnag, waitLog } from './app/debug'
11
+ import { createKey } from './key'
12
+ import { addChannel } from './channel/add'
13
+ import { uploadBundle } from './bundle/upload'
14
+ import { doLoginExists, login } from './login'
15
+ import { addAppInternal } from './app/add'
16
+ import { checkLatest } from './api/update'
17
+ import type { Options } from './api/app'
18
+ import type { Organization } from './utils'
19
+ import { convertAppName, createSupabaseClient, findBuildCommandForProjectType, findMainFile, findMainFileForProjectType, findProjectType, findSavedKey, getConfig, getOrganization, getPMAndCommand, useLogSnag, verifyUser } from './utils'
20
+
21
+ interface SuperOptions extends Options {
22
+ local: boolean
23
+ }
24
+ const importInject = 'import { CapacitorUpdater } from \'@capgo/capacitor-updater\''
25
+ const codeInject = 'CapacitorUpdater.notifyAppReady()'
26
+ // create regex to find line who start by 'import ' and end by ' from '
27
+ const regexImport = /import.*from.*/g
28
+ const defaultChannel = 'production'
29
+ const execOption = { stdio: 'pipe' }
30
+
31
+ async function cancelCommand(command: boolean | symbol, orgId: string, snag: LogSnag) {
32
+ if (p.isCancel(command)) {
33
+ await markSnag('onboarding-v2', orgId, snag, 'canceled', '🤷')
34
+ process.exit()
35
+ }
36
+ }
37
+
38
+ async function markStep(orgId: string, snag: LogSnag, step: number | string) {
39
+ return markSnag('onboarding-v2', orgId, snag, `onboarding-step-${step}`)
40
+ }
41
+
42
+ async function step2(organization: Organization, snag: LogSnag, appId: string, options: SuperOptions) {
43
+ const pm = getPMAndCommand()
44
+ const doAdd = await p.confirm({ message: `Add ${appId} in Capgo?` })
45
+ await cancelCommand(doAdd, organization.gid, snag)
46
+ if (doAdd) {
47
+ const s = p.spinner()
48
+ s.start(`Running: ${pm.runner} @capgo/cli@latest app add ${appId}`)
49
+ const addRes = await addAppInternal(appId, options, organization, false)
50
+ if (!addRes)
51
+ s.stop(`App already add ✅`)
52
+ else
53
+ s.stop(`App add Done ✅`)
54
+ }
55
+ else {
56
+ p.log.info(`Run yourself "${pm.runner} @capgo/cli@latest app add ${appId}"`)
57
+ }
58
+ await markStep(organization.gid, snag, 2)
59
+ }
60
+
61
+ async function step3(orgId: string, snag: LogSnag, apikey: string, appId: string) {
62
+ const pm = getPMAndCommand()
63
+ const doChannel = await p.confirm({ message: `Create default channel ${defaultChannel} for ${appId} in Capgo?` })
64
+ await cancelCommand(doChannel, orgId, snag)
65
+ if (doChannel) {
66
+ const s = p.spinner()
67
+ // create production channel public
68
+ s.start(`Running: ${pm.runner} @capgo/cli@latest channel add ${defaultChannel} ${appId} --default`)
69
+ const addChannelRes = await addChannel(defaultChannel, appId, {
70
+ default: true,
71
+ apikey,
72
+ }, false)
73
+ if (!addChannelRes)
74
+ s.stop(`Channel already added ✅`)
75
+ else
76
+ s.stop(`Channel add Done ✅`)
77
+ }
78
+ else {
79
+ p.log.info(`Run yourself "${pm.runner} @capgo/cli@latest channel add ${defaultChannel} ${appId} --default"`)
80
+ }
81
+ await markStep(orgId, snag, 3)
82
+ }
83
+
84
+ const urlMigrateV6 = 'https://capacitorjs.com/docs/updating/6-0'
85
+ const urlMigrateV5 = 'https://capacitorjs.com/docs/updating/5-0'
86
+ async function step4(orgId: string, snag: LogSnag, apikey: string, appId: string) {
87
+ const pm = getPMAndCommand()
88
+ const doInstall = await p.confirm({ message: `Automatic Install "@capgo/capacitor-updater" dependency in ${appId}?` })
89
+ await cancelCommand(doInstall, orgId, snag)
90
+ if (doInstall) {
91
+ const s = p.spinner()
92
+ s.start(`Checking if @capgo/capacitor-updater is installed`)
93
+ let versionToInstall = 'latest'
94
+ const pack = JSON.parse(readFileSync('package.json').toString())
95
+ let coreVersion = pack.dependencies['@capacitor/core'] || pack.devDependencies['@capacitor/core']
96
+ coreVersion = coreVersion?.replace('^', '').replace('~', '')
97
+ if (!coreVersion) {
98
+ s.stop('Error')
99
+ p.log.warn(`Cannot find @capacitor/core in package.json, please run \`capgo init\` in a capacitor project`)
100
+ p.outro(`Bye 👋`)
101
+ process.exit()
102
+ }
103
+ else if (semver.lt(coreVersion, '5.0.0')) {
104
+ s.stop('Error')
105
+ p.log.warn(`@capacitor/core version is ${coreVersion}, please update to Capacitor v5 first: ${urlMigrateV5}`)
106
+ p.outro(`Bye 👋`)
107
+ process.exit()
108
+ }
109
+ else if (semver.lt(coreVersion, '6.0.0')) {
110
+ s.stop(`@capacitor/core version is ${coreVersion}, please update to Capacitor v6: ${urlMigrateV6} to access the best features of Capgo`)
111
+ versionToInstall = '^5.0.0'
112
+ }
113
+ if (pm.pm === 'unknown') {
114
+ s.stop('Error')
115
+ p.log.warn(`Cannot reconize package manager, please run \`capgo init\` in a capacitor project with npm, pnpm, bun or yarn`)
116
+ p.outro(`Bye 👋`)
117
+ process.exit()
118
+ }
119
+ // // use pm to install capgo
120
+ // // run command pm install @capgo/capacitor-updater@latest
121
+ // check if capgo is already installed in package.json
122
+ if (pack.dependencies['@capgo/capacitor-updater']) {
123
+ s.stop(`Capgo already installed ✅`)
124
+ }
125
+ else {
126
+ await execSync(`${pm.installCommand} @capgo/capacitor-updater@${versionToInstall}`, execOption as ExecSyncOptions)
127
+ s.stop(`Install Done ✅`)
128
+ }
129
+ }
130
+ else {
131
+ p.log.info(`Run yourself "${pm.installCommand} @capgo/capacitor-updater@latest"`)
132
+ }
133
+ await markStep(orgId, snag, 4)
134
+ }
135
+
136
+ async function step5(orgId: string, snag: LogSnag, apikey: string, appId: string) {
137
+ const doAddCode = await p.confirm({ message: `Automatic Add "${codeInject}" code and import in ${appId}?` })
138
+ await cancelCommand(doAddCode, orgId, snag)
139
+ if (doAddCode) {
140
+ const s = p.spinner()
141
+ s.start(`Adding @capacitor-updater to your main file`)
142
+ const projectType = await findProjectType()
143
+ let mainFilePath
144
+ if (projectType === 'unknown')
145
+ mainFilePath = await findMainFile()
146
+ else
147
+ mainFilePath = await findMainFileForProjectType(projectType)
148
+
149
+ if (!mainFilePath) {
150
+ s.stop('Error')
151
+ p.log.warn('Cannot find main file, You need to add @capgo/capacitor-updater manually')
152
+ p.outro(`Bye 👋`)
153
+ process.exit()
154
+ }
155
+ // open main file and inject codeInject
156
+ const mainFile = readFileSync(mainFilePath)
157
+ // find the last import line in the file and inject codeInject after it
158
+ const mainFileContent = mainFile.toString()
159
+ const matches = mainFileContent.match(regexImport)
160
+ const last = matches?.pop()
161
+ if (!last) {
162
+ s.stop('Error')
163
+ p.log.warn(`Cannot find import line in main file, use manual installation: https://capgo.app/docs/plugin/installation/`)
164
+ p.outro(`Bye 👋`)
165
+ process.exit()
166
+ }
167
+
168
+ if (mainFileContent.includes(codeInject)) {
169
+ s.stop(`Code already added to ${mainFilePath} ✅`)
170
+ }
171
+ else {
172
+ const newMainFileContent = mainFileContent.replace(last, `${last}\n${importInject};\n\n${codeInject};\n`)
173
+ writeFileSync(mainFilePath, newMainFileContent)
174
+ s.stop(`Code added to ${mainFilePath} ✅`)
175
+ }
176
+ await markStep(orgId, snag, 5)
177
+ }
178
+ else {
179
+ p.log.info(`Add to your main file the following code:\n\n${importInject};\n\n${codeInject};\n`)
180
+ }
181
+ }
182
+
183
+ async function step6(orgId: string, snag: LogSnag, apikey: string, appId: string) {
184
+ const pm = getPMAndCommand()
185
+ const doEncrypt = await p.confirm({ message: `Automatic configure end-to-end encryption in ${appId} updates?` })
186
+ await cancelCommand(doEncrypt, orgId, snag)
187
+ if (doEncrypt) {
188
+ const s = p.spinner()
189
+ s.start(`Running: ${pm.runner} @capgo/cli@latest key create`)
190
+ const keyRes = await createKey({ force: true }, false)
191
+ if (!keyRes) {
192
+ s.stop('Error')
193
+ p.log.warn(`Cannot create key ❌`)
194
+ p.outro(`Bye 👋`)
195
+ process.exit(1)
196
+ }
197
+ else {
198
+ s.stop(`key created 🔑`)
199
+ }
200
+ markSnag('onboarding-v2', orgId, snag, 'Use encryption')
201
+ }
202
+ await markStep(orgId, snag, 6)
203
+ }
204
+
205
+ async function step7(orgId: string, snag: LogSnag, apikey: string, appId: string) {
206
+ const pm = getPMAndCommand()
207
+ const doBuild = await p.confirm({ message: `Automatic build ${appId} with "${pm.pm} run build" ?` })
208
+ await cancelCommand(doBuild, orgId, snag)
209
+ if (doBuild) {
210
+ const s = p.spinner()
211
+ const projectType = await findProjectType()
212
+ const buildCommand = await findBuildCommandForProjectType(projectType)
213
+ s.start(`Running: ${pm.pm} run ${buildCommand} && ${pm.runner} cap sync`)
214
+ const pack = JSON.parse(readFileSync('package.json').toString())
215
+ // check in script build exist
216
+ if (!pack.scripts[buildCommand]) {
217
+ s.stop('Error')
218
+ p.log.warn(`Cannot find ${buildCommand} script in package.json, please add it and run \`capgo init\` again`)
219
+ p.outro(`Bye 👋`)
220
+ process.exit()
221
+ }
222
+ execSync(`${pm.pm} run ${buildCommand} && ${pm.runner} cap sync`, execOption as ExecSyncOptions)
223
+ s.stop(`Build & Sync Done ✅`)
224
+ }
225
+ else {
226
+ p.log.info(`Build yourself with command: ${pm.pm} run build && ${pm.runner} cap sync`)
227
+ }
228
+ await markStep(orgId, snag, 7)
229
+ }
230
+
231
+ async function step8(orgId: string, snag: LogSnag, apikey: string, appId: string) {
232
+ const pm = getPMAndCommand()
233
+ const doBundle = await p.confirm({ message: `Automatic upload ${appId} bundle to Capgo?` })
234
+ await cancelCommand(doBundle, orgId, snag)
235
+ if (doBundle) {
236
+ const s = p.spinner()
237
+ s.start(`Running: ${pm.runner} @capgo/cli@latest bundle upload`)
238
+ const uploadRes = await uploadBundle(appId, {
239
+ channel: defaultChannel,
240
+ apikey,
241
+ }, false)
242
+ if (!uploadRes) {
243
+ s.stop('Error')
244
+ p.log.warn(`Upload failed ❌`)
245
+ p.outro(`Bye 👋`)
246
+ process.exit()
247
+ }
248
+ else {
249
+ s.stop(`Upload Done ✅`)
250
+ }
251
+ }
252
+ else {
253
+ p.log.info(`Upload yourself with command: ${pm.runner} @capgo/cli@latest bundle upload`)
254
+ }
255
+ await markStep(orgId, snag, 8)
256
+ }
257
+
258
+ async function step9(orgId: string, snag: LogSnag) {
259
+ const pm = getPMAndCommand()
260
+ const doRun = await p.confirm({ message: `Run in device now ?` })
261
+ await cancelCommand(doRun, orgId, snag)
262
+ if (doRun) {
263
+ const plaformType = await p.select({
264
+ message: 'Pick a platform to run your app',
265
+ options: [
266
+ { value: 'ios', label: 'IOS' },
267
+ { value: 'android', label: 'Android' },
268
+ ],
269
+ })
270
+ if (p.isCancel(plaformType)) {
271
+ p.outro(`Bye 👋`)
272
+ process.exit()
273
+ }
274
+
275
+ const platform = plaformType as 'ios' | 'android'
276
+ const s = p.spinner()
277
+ s.start(`Running: ${pm.runner} cap run ${platform}`)
278
+ await spawnSync(pm.runner, ['cap', 'run', platform], { stdio: 'inherit' })
279
+ s.stop(`Started Done ✅`)
280
+ }
281
+ else {
282
+ p.log.info(`Run yourself with command: ${pm.runner} cap run <ios|android>`)
283
+ }
284
+ await markStep(orgId, snag, 9)
285
+ }
286
+
287
+ async function _step10(orgId: string, snag: LogSnag, supabase: SupabaseClient<Database>, appId: string) {
288
+ const doRun = await p.confirm({ message: `Automatic check if update working in device ?` })
289
+ await cancelCommand(doRun, orgId, snag)
290
+ if (doRun) {
291
+ 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)
294
+ }
295
+ else {
296
+ const appIdUrl = convertAppName(appId)
297
+ p.log.info(`Check logs in https://web.capgo.app/app/p/${appIdUrl}/logs to see if update works.`)
298
+ }
299
+ await markStep(orgId, snag, 10)
300
+ }
301
+
302
+ export async function initApp(apikeyCommand: string, appId: string, options: SuperOptions) {
303
+ const pm = getPMAndCommand()
304
+ p.intro(`Capgo onboarding 🛫`)
305
+ await checkLatest()
306
+ const snag = useLogSnag()
307
+ const config = await getConfig()
308
+ appId = appId || config?.app?.appId
309
+ const apikey = apikeyCommand || findSavedKey()
310
+
311
+ const log = p.spinner()
312
+ if (!doLoginExists() || apikeyCommand) {
313
+ log.start(`Running: ${pm.runner} @capgo/cli@latest login ***`)
314
+ await login(apikey, options, false)
315
+ log.stop('Login Done ✅')
316
+ }
317
+
318
+ const supabase = await createSupabaseClient(apikey)
319
+ await verifyUser(supabase, apikey, ['upload', 'all', 'read', 'write'])
320
+
321
+ const organization = await getOrganization(supabase, ['admin', 'super_admin'])
322
+ const orgId = organization.gid
323
+
324
+ await markStep(orgId, snag, 1)
325
+
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)
335
+
336
+ await markStep(orgId, snag, 0)
337
+ p.log.info(`Welcome onboard ✈️!`)
338
+ p.log.info(`Your Capgo update system is setup`)
339
+ p.log.info(`Next time use \`${pm.runner} @capgo/cli@latest bundle upload\` to only upload your bundle`)
340
+ p.outro(`Bye 👋`)
341
+ process.exit()
342
+ }