@capgo/cli 4.13.2 → 4.13.3

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 (69) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +1 -1
  3. package/.github/FUNDING.yml +0 -1
  4. package/.github/workflows/autofix.yml +0 -25
  5. package/.github/workflows/build.yml +0 -46
  6. package/.github/workflows/bump_version.yml +0 -56
  7. package/.github/workflows/check_posix_paths.yml +0 -229
  8. package/.github/workflows/test.yml +0 -30
  9. package/.prettierignore +0 -6
  10. package/.vscode/launch.json +0 -23
  11. package/.vscode/settings.json +0 -46
  12. package/.vscode/tasks.json +0 -42
  13. package/CHANGELOG.md +0 -3392
  14. package/build.mjs +0 -21
  15. package/bun.lockb +0 -0
  16. package/bunfig.toml +0 -2
  17. package/capacitor.config.ts +0 -33
  18. package/crypto_explained.png +0 -0
  19. package/eslint.config.js +0 -10
  20. package/renovate.json +0 -23
  21. package/src/api/app.ts +0 -55
  22. package/src/api/channels.ts +0 -163
  23. package/src/api/crypto.ts +0 -116
  24. package/src/api/devices_override.ts +0 -41
  25. package/src/api/update.ts +0 -13
  26. package/src/api/versions.ts +0 -101
  27. package/src/app/add.ts +0 -157
  28. package/src/app/debug.ts +0 -258
  29. package/src/app/delete.ts +0 -110
  30. package/src/app/info.ts +0 -99
  31. package/src/app/list.ts +0 -67
  32. package/src/app/set.ts +0 -96
  33. package/src/bundle/check.ts +0 -42
  34. package/src/bundle/cleanup.ts +0 -123
  35. package/src/bundle/compatibility.ts +0 -70
  36. package/src/bundle/decrypt.ts +0 -54
  37. package/src/bundle/delete.ts +0 -52
  38. package/src/bundle/encrypt.ts +0 -60
  39. package/src/bundle/list.ts +0 -42
  40. package/src/bundle/unlink.ts +0 -88
  41. package/src/bundle/upload.ts +0 -552
  42. package/src/bundle/zip.ts +0 -145
  43. package/src/channel/add.ts +0 -80
  44. package/src/channel/currentBundle.ts +0 -72
  45. package/src/channel/delete.ts +0 -57
  46. package/src/channel/list.ts +0 -49
  47. package/src/channel/set.ts +0 -179
  48. package/src/config/index.ts +0 -156
  49. package/src/index.ts +0 -310
  50. package/src/init.ts +0 -495
  51. package/src/key.ts +0 -135
  52. package/src/login.ts +0 -70
  53. package/src/types/capacitor__cli.d.ts +0 -6
  54. package/src/types/supabase.types.ts +0 -2123
  55. package/src/user/account.ts +0 -11
  56. package/src/utils.ts +0 -1076
  57. package/test/VerifyZip.java +0 -83
  58. package/test/check-posix-paths.js +0 -21
  59. package/test/chunk_convert.ts +0 -28
  60. package/test/data.ts +0 -18769
  61. package/test/test_headers_rls.ts +0 -24
  62. package/test/test_semver.ts +0 -13
  63. package/test/test_upload/app.js +0 -3
  64. package/test/test_upload/assets/check-posix-paths.js +0 -21
  65. package/test/test_upload/index.html +0 -0
  66. package/test/test_zip_swift/Package.resolved +0 -24
  67. package/test/test_zip_swift/Package.swift +0 -29
  68. package/test/test_zip_swift/Sources/main.swift +0 -80
  69. package/tsconfig.json +0 -39
@@ -1,80 +0,0 @@
1
- import { exit } from 'node:process'
2
- import { program } from 'commander'
3
- import { intro, log, outro } from '@clack/prompts'
4
- import { checkAppExistsAndHasPermissionOrgErr } from '../api/app'
5
- import { createChannel, findUnknownVersion } from '../api/channels'
6
- import type { OptionsBase } from '../utils'
7
- import { OrganizationPerm, createSupabaseClient, findSavedKey, formatError, getConfig, getOrganizationId, useLogSnag, verifyUser } from '../utils'
8
-
9
- interface Options extends OptionsBase {
10
- default?: boolean
11
- }
12
-
13
- export async function addChannel(channelId: string, appId: string, options: Options, shouldExit = true) {
14
- intro(`Create channel`)
15
- options.apikey = options.apikey || findSavedKey()
16
- const extConfig = await getConfig()
17
- appId = appId || extConfig?.config?.appId
18
- const snag = useLogSnag()
19
-
20
- if (!options.apikey) {
21
- log.error('Missing API key, you need to provide a API key to upload your bundle')
22
- program.error('')
23
- }
24
- if (!appId) {
25
- log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
26
- program.error('')
27
- }
28
- const supabase = await createSupabaseClient(options.apikey)
29
-
30
- await verifyUser(supabase, options.apikey, ['write', 'all'])
31
- // Check we have app access to this appId
32
- await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.admin)
33
-
34
- log.info(`Creating channel ${appId}#${channelId} to Capgo`)
35
- try {
36
- const data = await findUnknownVersion(supabase, appId)
37
- const orgId = await getOrganizationId(supabase, appId)
38
- if (!data) {
39
- log.error(`Cannot find default version for channel creation, please contact Capgo support 🤨`)
40
- program.error('')
41
- }
42
- const res = await createChannel(supabase, {
43
- name: channelId,
44
- app_id: appId,
45
- version: data.id,
46
- owner_org: orgId,
47
- })
48
-
49
- if (res.error) {
50
- log.error(`Cannot create Channel 🙀\n${formatError(res.error)}`)
51
- program.error('')
52
- }
53
-
54
- log.success(`Channel created ✅`)
55
- await snag.track({
56
- channel: 'channel',
57
- event: 'Create channel',
58
- icon: '✅',
59
- user_id: orgId,
60
- tags: {
61
- 'app-id': appId,
62
- 'channel': channelId,
63
- },
64
- notify: false,
65
- }).catch()
66
- }
67
- catch (error) {
68
- log.error(`Cannot create Channel 🙀`)
69
- return false
70
- }
71
- if (shouldExit) {
72
- outro(`Done ✅`)
73
- exit()
74
- }
75
- return true
76
- }
77
-
78
- export async function addChannelCommand(apikey: string, appId: string, options: Options) {
79
- addChannel(apikey, appId, options, true)
80
- }
@@ -1,72 +0,0 @@
1
- import { exit } from 'node:process'
2
- import { program } from 'commander'
3
- import { intro, log } from '@clack/prompts'
4
- import { checkAppExistsAndHasPermissionOrgErr } from '../api/app'
5
- import type { OptionsBase } from '../utils'
6
- import { OrganizationPerm, createSupabaseClient, findSavedKey, getConfig, verifyUser } from '../utils'
7
-
8
- interface Options extends OptionsBase {
9
- channel?: string
10
- quiet?: boolean
11
- }
12
-
13
- interface Channel {
14
- version: {
15
- name: string
16
- }
17
- }
18
-
19
- export async function currentBundle(channel: string, appId: string, options: Options) {
20
- const { quiet } = options
21
-
22
- if (!quiet)
23
- intro(`List current bundle`)
24
-
25
- options.apikey = options.apikey || findSavedKey(quiet)
26
- const extConfig = await getConfig()
27
- appId = appId || extConfig?.config?.appId
28
-
29
- if (!options.apikey) {
30
- log.error('Missing API key, you need to provide a API key to upload your bundle')
31
- program.error('')
32
- }
33
- if (!appId) {
34
- log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
35
- program.error('')
36
- }
37
- const supabase = await createSupabaseClient(options.apikey)
38
-
39
- const _userId = await verifyUser(supabase, options.apikey, ['write', 'all', 'read'])
40
- // Check we have app access to this appId
41
- await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.read)
42
-
43
- if (!channel) {
44
- log.error(`Please provide a channel to get the bundle from.`)
45
- program.error('')
46
- }
47
-
48
- const { data: supabaseChannel, error } = await supabase
49
- .from('channels')
50
- .select('version ( name )')
51
- .eq('name', channel)
52
- .eq('app_id', appId)
53
- .limit(1)
54
-
55
- if (error || supabaseChannel.length === 0) {
56
- log.error(`Error retrieving channel ${channel} for app ${appId}. Perhaps the channel does not exists?`)
57
- program.error('')
58
- }
59
-
60
- const { version } = supabaseChannel[0] as any as Channel
61
- if (!version) {
62
- log.error(`Error retrieving channel ${channel} for app ${appId}. Perhaps the channel does not exists?`)
63
- program.error('')
64
- }
65
-
66
- if (!quiet)
67
- log.info(`Current bundle for channel ${channel} is ${version.name}`)
68
- else
69
- log.info(version.name)
70
-
71
- exit()
72
- }
@@ -1,57 +0,0 @@
1
- import { exit } from 'node:process'
2
- import { program } from 'commander'
3
- import { intro, log, outro } from '@clack/prompts'
4
- import { checkAppExistsAndHasPermissionOrgErr } from '../api/app'
5
- import { delChannel } from '../api/channels'
6
- import type { OptionsBase } from '../utils'
7
- import { OrganizationPerm, createSupabaseClient, findSavedKey, formatError, getConfig, getOrganizationId, useLogSnag, verifyUser } from '../utils'
8
-
9
- export async function deleteChannel(channelId: string, appId: string, options: OptionsBase) {
10
- intro(`Delete channel`)
11
- options.apikey = options.apikey || findSavedKey()
12
- const extConfig = await getConfig()
13
- appId = appId || extConfig?.config?.appId
14
- const snag = useLogSnag()
15
-
16
- if (!options.apikey) {
17
- log.error('Missing API key, you need to provide a API key to upload your bundle')
18
- program.error('')
19
- }
20
- if (!appId) {
21
- log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
22
- program.error('')
23
- }
24
- const supabase = await createSupabaseClient(options.apikey)
25
-
26
- const userId = await verifyUser(supabase, options.apikey, ['write', 'all'])
27
- // Check we have app access to this appId
28
- await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.admin)
29
-
30
- log.info(`Deleting channel ${appId}#${channelId} from Capgo`)
31
- try {
32
- const deleteStatus = await delChannel(supabase, channelId, appId, userId)
33
- if (deleteStatus.error) {
34
- log.error(`Cannot delete Channel 🙀 ${formatError(deleteStatus.error)}`)
35
- program.error('')
36
- }
37
- const orgId = await getOrganizationId(supabase, appId)
38
- log.success(`Channel deleted`)
39
- await snag.track({
40
- channel: 'channel',
41
- event: 'Delete channel',
42
- icon: '✅',
43
- user_id: orgId,
44
- tags: {
45
- 'user-id': userId,
46
- 'app-id': appId,
47
- 'channel': channelId,
48
- },
49
- notify: false,
50
- }).catch()
51
- }
52
- catch (error) {
53
- log.error(`Cannot delete Channel 🙀`)
54
- }
55
- outro(`Done ✅`)
56
- exit()
57
- }
@@ -1,49 +0,0 @@
1
- import { exit } from 'node:process'
2
- import { program } from 'commander'
3
- import { intro, log, outro } from '@clack/prompts'
4
- import { checkAppExistsAndHasPermissionOrgErr } from '../api/app'
5
- import { displayChannels, getActiveChannels } from '../api/channels'
6
- import type { OptionsBase } from '../utils'
7
- import { OrganizationPerm, createSupabaseClient, findSavedKey, getConfig, useLogSnag, verifyUser } from '../utils'
8
-
9
- export async function listChannels(appId: string, options: OptionsBase) {
10
- intro(`List channels`)
11
- options.apikey = options.apikey || findSavedKey()
12
- const extConfig = await getConfig()
13
- appId = appId || extConfig?.config?.appId
14
- const snag = useLogSnag()
15
-
16
- if (!options.apikey)
17
- log.error('Missing API key, you need to provide a API key to upload your bundle')
18
-
19
- if (!appId) {
20
- log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
21
- program.error('')
22
- }
23
- const supabase = await createSupabaseClient(options.apikey)
24
-
25
- const userId = await verifyUser(supabase, options.apikey, ['write', 'all', 'read', 'upload'])
26
- // Check we have app access to this appId
27
- await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.read)
28
-
29
- log.info(`Querying available channels in Capgo`)
30
-
31
- // Get all active app versions we might possibly be able to cleanup
32
- const allVersions = await getActiveChannels(supabase, appId)
33
-
34
- log.info(`Active channels in Capgo: ${allVersions?.length}`)
35
-
36
- displayChannels(allVersions)
37
- await snag.track({
38
- channel: 'channel',
39
- event: 'List channel',
40
- icon: '✅',
41
- user_id: userId,
42
- tags: {
43
- 'app-id': appId,
44
- },
45
- notify: false,
46
- }).catch()
47
- outro(`Done ✅`)
48
- exit()
49
- }
@@ -1,179 +0,0 @@
1
- import { exit } from 'node:process'
2
- import { program } from 'commander'
3
- import { intro, log, outro } from '@clack/prompts'
4
- import type { Database } from '../types/supabase.types'
5
- import { checkAppExistsAndHasPermissionOrgErr } from '../api/app'
6
- import type {
7
- OptionsBase,
8
- } from '../utils'
9
- import {
10
- OrganizationPerm,
11
- checkPlanValid,
12
- createSupabaseClient,
13
- findSavedKey,
14
- formatError,
15
- getConfig,
16
- getOrganizationId,
17
- readPackageJson,
18
- updateOrCreateChannel,
19
- useLogSnag,
20
- verifyUser,
21
- } from '../utils'
22
-
23
- interface Options extends OptionsBase {
24
- bundle: string
25
- state?: string
26
- downgrade?: boolean
27
- latest?: boolean
28
- upgrade?: boolean
29
- ios?: boolean
30
- android?: boolean
31
- selfAssign?: boolean
32
- disableAutoUpdate: string
33
- dev?: boolean
34
- emulator?: boolean
35
- }
36
-
37
- const disableAutoUpdatesPossibleOptions = ['major', 'minor', 'metadata', 'patch', 'none']
38
-
39
- export async function setChannel(channel: string, appId: string, options: Options) {
40
- intro(`Set channel`)
41
- options.apikey = options.apikey || findSavedKey()
42
- const extConfig = await getConfig()
43
- appId = appId || extConfig?.config?.appId
44
- const snag = useLogSnag()
45
-
46
- if (!options.apikey) {
47
- log.error('Missing API key, you need to provide a API key to upload your bundle')
48
- program.error('')
49
- }
50
- if (!appId) {
51
- log.error('Missing argument, you need to provide a appId, or be in a capacitor project')
52
- program.error('')
53
- }
54
- const supabase = await createSupabaseClient(options.apikey)
55
-
56
- const userId = await verifyUser(supabase, options.apikey, ['write', 'all'])
57
- // Check we have app access to this appId
58
- await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.admin)
59
- const orgId = await getOrganizationId(supabase, appId)
60
-
61
- const { bundle, state, downgrade, latest, upgrade, ios, android, selfAssign, disableAutoUpdate, dev, emulator } = options
62
- if (!channel) {
63
- log.error('Missing argument, you need to provide a channel')
64
- program.error('')
65
- }
66
- if (latest && bundle) {
67
- log.error('Cannot set latest and bundle at the same time')
68
- program.error('')
69
- }
70
- if (bundle == null
71
- && state == null
72
- && latest == null
73
- && downgrade == null
74
- && upgrade == null
75
- && ios == null
76
- && android == null
77
- && selfAssign == null
78
- && dev == null
79
- && emulator == null
80
- && disableAutoUpdate == null) {
81
- log.error('Missing argument, you need to provide a option to set')
82
- program.error('')
83
- }
84
- try {
85
- await checkPlanValid(supabase, orgId, options.apikey, appId)
86
- const channelPayload: Database['public']['Tables']['channels']['Insert'] = {
87
- created_by: userId,
88
- app_id: appId,
89
- name: channel,
90
- owner_org: orgId,
91
- version: undefined as any,
92
- }
93
- const pack = await readPackageJson()
94
- const bundleVersion = latest ? pack?.version : bundle
95
- if (bundleVersion != null) {
96
- const { data, error: vError } = await supabase
97
- .from('app_versions')
98
- .select()
99
- .eq('app_id', appId)
100
- .eq('name', bundleVersion)
101
- .eq('user_id', userId)
102
- .eq('deleted', false)
103
- .single()
104
- if (vError || !data) {
105
- log.error(`Cannot find version ${bundleVersion}`)
106
- program.error('')
107
- }
108
- log.info(`Set ${appId} channel: ${channel} to @${bundleVersion}`)
109
- channelPayload.version = data.id
110
- }
111
- if (state != null) {
112
- if (state === 'public' || state === 'private')
113
- log.info(`Set ${appId} channel: ${channel} to public or private is deprecated, use default or normal instead`)
114
-
115
- log.info(`Set ${appId} channel: ${channel} to ${state === 'public' || state === 'default' ? 'default' : 'normal'}`)
116
- channelPayload.public = state === 'public' || state === 'default'
117
- }
118
- if (downgrade != null) {
119
- log.info(`Set ${appId} channel: ${channel} to ${downgrade ? 'allow' : 'disallow'} downgrade`)
120
- channelPayload.disableAutoUpdateUnderNative = !downgrade
121
- }
122
- if (ios != null) {
123
- log.info(`Set ${appId} channel: ${channel} to ${ios ? 'allow' : 'disallow'} ios update`)
124
- channelPayload.ios = !!ios
125
- }
126
- if (android != null) {
127
- log.info(`Set ${appId} channel: ${channel} to ${android ? 'allow' : 'disallow'} android update`)
128
- channelPayload.android = !!android
129
- }
130
- if (selfAssign != null) {
131
- log.info(`Set ${appId} channel: ${channel} to ${selfAssign ? 'allow' : 'disallow'} self assign to this channel`)
132
- channelPayload.allow_device_self_set = !!selfAssign
133
- }
134
- if (disableAutoUpdate != null) {
135
- let finalDisableAutoUpdate = disableAutoUpdate.toLocaleLowerCase()
136
-
137
- // The user passed an unimplemented strategy
138
- if (!disableAutoUpdatesPossibleOptions.includes(finalDisableAutoUpdate)) {
139
- log.error(`Channel strategy ${finalDisableAutoUpdate} is not known. The possible values are: ${disableAutoUpdatesPossibleOptions.join(', ')}.`)
140
- program.error('')
141
- }
142
-
143
- // This metadata is called differently in the database
144
- if (finalDisableAutoUpdate === 'metadata')
145
- finalDisableAutoUpdate = 'version_number'
146
-
147
- // This cast is safe, look above
148
- channelPayload.disableAutoUpdate = finalDisableAutoUpdate as any
149
- log.info(`Set ${appId} channel: ${channel} to ${finalDisableAutoUpdate} disable update strategy to this channel`)
150
- }
151
- try {
152
- const { error: dbError } = await updateOrCreateChannel(supabase, channelPayload)
153
- if (dbError) {
154
- log.error(`Cannot set channel the upload key is not allowed to do that, use the "all" for this.`)
155
- program.error('')
156
- }
157
- }
158
- catch (e) {
159
- log.error(`Cannot set channel the upload key is not allowed to do that, use the "all" for this.`)
160
- program.error('')
161
- }
162
- await snag.track({
163
- channel: 'channel',
164
- event: 'Set channel',
165
- icon: '✅',
166
- user_id: orgId,
167
- tags: {
168
- 'app-id': appId,
169
- },
170
- notify: false,
171
- }).catch()
172
- }
173
- catch (err) {
174
- log.error(`Unknow error ${formatError(err)}`)
175
- program.error('')
176
- }
177
- outro(`Done ✅`)
178
- exit()
179
- }
@@ -1,156 +0,0 @@
1
- import { resolve } from 'node:path'
2
- import { cwd } from 'node:process'
3
- import { accessSync, constants, readFileSync, writeFileSync } from 'node:fs'
4
-
5
- export const CONFIG_FILE_NAME_TS = 'capacitor.config.ts'
6
- export const CONFIG_FILE_NAME_JSON = 'capacitor.config.json'
7
-
8
- export interface CapacitorConfig {
9
- appId: string
10
- appName: string
11
- webDir: string
12
- plugins?: Record<string, any>
13
- android?: Record<string, any>
14
- [key: string]: any
15
- }
16
-
17
- interface ExtConfigPairs {
18
- config: CapacitorConfig
19
- path: string
20
- }
21
-
22
- function parseConfigObject(configString: string): CapacitorConfig {
23
- // Remove comments
24
- const noComments = configString.replace(/\/\/.*|\/\*[\s\S]*?\*\//g, '')
25
-
26
- // Parse the object
27
- // eslint-disable-next-line no-new-func
28
- const configObject = Function(`return ${noComments.trim()}`)()
29
-
30
- return configObject as CapacitorConfig
31
- }
32
-
33
- function loadConfigTs(content: string): CapacitorConfig {
34
- const configRegex = /const\s+config\s*:\s*CapacitorConfig\s*=\s*(\{[\s\S]*?\n\})/
35
- const match = content.match(configRegex)
36
-
37
- if (!match) {
38
- throw new Error('Unable to find config object in TypeScript file')
39
- }
40
-
41
- return parseConfigObject(match[1])
42
- }
43
-
44
- function loadConfigJson(content: string): CapacitorConfig {
45
- return JSON.parse(content) as CapacitorConfig
46
- }
47
-
48
- export async function loadConfig(): Promise<ExtConfigPairs | undefined> {
49
- const appRootDir = cwd()
50
- const extConfigFilePathTS = resolve(appRootDir, CONFIG_FILE_NAME_TS)
51
- const extConfigFilePathJSON = resolve(appRootDir, CONFIG_FILE_NAME_JSON)
52
-
53
- try {
54
- accessSync(extConfigFilePathTS, constants.R_OK)
55
- const configContentTS = readFileSync(extConfigFilePathTS, 'utf-8')
56
- return {
57
- config: loadConfigTs(configContentTS),
58
- path: extConfigFilePathTS,
59
- }
60
- }
61
- catch (err) {
62
- try {
63
- accessSync(extConfigFilePathJSON, constants.R_OK)
64
- const configContentJSON = readFileSync(extConfigFilePathJSON, 'utf-8')
65
- return {
66
- config: loadConfigJson(configContentJSON),
67
- path: extConfigFilePathJSON,
68
- }
69
- }
70
- catch (err) {
71
- console.error('Cannot find capacitor.config.ts or capacitor.config.json')
72
- return undefined
73
- }
74
- }
75
- }
76
-
77
- export async function writeConfig(config: ExtConfigPairs): Promise<void> {
78
- const { config: newConfig, path } = config
79
- const content = readFileSync(path, 'utf-8')
80
-
81
- const updatedContent = path.endsWith('.json')
82
- ? updateJsonContent(content, newConfig)
83
- : updateTsContent(content, newConfig)
84
-
85
- writeFileSync(path, updatedContent)
86
- }
87
-
88
- function updateJsonContent(content: string, newConfig: CapacitorConfig): string {
89
- const jsonObj = JSON.parse(content)
90
- if (!jsonObj.plugins)
91
- jsonObj.plugins = {}
92
- if (!jsonObj.plugins.CapacitorUpdater)
93
- jsonObj.plugins.CapacitorUpdater = {}
94
-
95
- Object.assign(jsonObj.plugins.CapacitorUpdater, newConfig.plugins?.CapacitorUpdater)
96
-
97
- return JSON.stringify(jsonObj, null, detectIndentation(content))
98
- }
99
-
100
- function updateTsContent(content: string, newConfig: CapacitorConfig): string {
101
- const capUpdaterRegex = /(\bCapacitorUpdater\s*:\s*\{[^}]*\})/g
102
-
103
- return content.replace(capUpdaterRegex, (match) => {
104
- const updatedSection = updateCapacitorUpdaterSection(match, newConfig.plugins?.CapacitorUpdater || {})
105
- return updatedSection
106
- })
107
- }
108
-
109
- function updateCapacitorUpdaterSection(section: string, newConfig: Record<string, any>): string {
110
- const lines = section.split('\n')
111
- const updatedLines = lines.map((line) => {
112
- // eslint-disable-next-line regexp/no-super-linear-backtracking
113
- const keyValueMatch = line.match(/^\s*(\w+)\s*:\s*(.+?),?\s*$/)
114
- if (keyValueMatch) {
115
- const [, key] = keyValueMatch
116
- if (key in newConfig) {
117
- const newValue = formatValue(newConfig[key])
118
- return line.replace(/:\s*.+/, `: ${newValue},`)
119
- }
120
- }
121
- return line
122
- })
123
-
124
- // Add new properties
125
- Object.entries(newConfig).forEach(([key, value]) => {
126
- if (!updatedLines.some(line => line.includes(`${key}:`))) {
127
- const indent = detectIndentation(section)
128
- updatedLines.splice(-1, 0, `${' '.repeat(indent)}${key}: ${formatValue(value)},`)
129
- }
130
- })
131
-
132
- return updatedLines.join('\n')
133
- }
134
-
135
- function formatValue(value: any): string {
136
- if (typeof value === 'string') {
137
- // Convert multiline strings (like RSA keys) to single line
138
- if (value.includes('\n')) {
139
- return `'${value.replace(/\n/g, '\\n')}'`
140
- }
141
- return `'${value}'`
142
- }
143
- if (typeof value === 'number' || typeof value === 'boolean')
144
- return String(value)
145
- if (Array.isArray(value))
146
- return `[${value.map(formatValue).join(', ')}]`
147
- if (typeof value === 'object' && value !== null) {
148
- return `{ ${Object.entries(value).map(([k, v]) => `${k}: ${formatValue(v)}`).join(', ')} }`
149
- }
150
- return 'null'
151
- }
152
-
153
- function detectIndentation(content: string): number {
154
- const match = content.match(/^( +)/m)
155
- return match ? match[1].length : 2
156
- }