@capgo/cli 4.10.52 → 4.10.65-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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/cli",
3
- "version": "4.10.52",
3
+ "version": "4.10.65-beta.1",
4
4
  "description": "A CLI to upload to capgo servers",
5
5
  "author": "github.com/riderx",
6
6
  "license": "Apache 2.0",
@@ -58,6 +58,7 @@
58
58
  "console-table-printer": "^2.12.1",
59
59
  "get-latest-version": "^5.1.0",
60
60
  "is-wsl": "^3.1.0",
61
+ "jszip": "^3.10.1",
61
62
  "ky": "^1.3.0",
62
63
  "logsnag": "1.0.0",
63
64
  "mime": "^4.0.3",
@@ -256,7 +256,7 @@ export async function uploadBundle(appid: string, options: Options, shouldExit =
256
256
  let checksum = ''
257
257
  let zipped: Buffer | null = null
258
258
  if (!external && useS3 === false) {
259
- zipped = zipFile(path)
259
+ zipped = await zipFile(path)
260
260
  const s = p.spinner()
261
261
  s.start(`Calculating checksum`)
262
262
  checksum = await getChecksum(zipped, 'crc32')
@@ -329,7 +329,7 @@ It will be also visible in your dashboard\n`)
329
329
  }
330
330
  else {
331
331
  if (useS3) {
332
- zipped = zipFile(path)
332
+ zipped = await zipFile(path)
333
333
  const s = p.spinner()
334
334
  s.start(`Calculating checksum`)
335
335
  checksum = await getChecksum(zipped, 'crc32')
package/src/bundle/zip.ts CHANGED
@@ -28,110 +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 zipped = zipFile(path)
82
- if (!json)
83
- p.log.info(`Zipped ${zipped.byteLength} bytes`)
84
- const s = p.spinner()
85
- if (!json)
86
- s.start(`Calculating checksum`)
87
- const checksum = await getChecksum(zipped, 'crc32')
88
- if (!json)
89
- s.stop(`Checksum: ${checksum}`)
90
- const mbSize = Math.floor(zipped.byteLength / 1024 / 1024)
91
- // We do not issue this warning for json
92
- if (mbSize > alertMb && !json) {
93
- p.log.warn(`WARNING !!\nThe app size is ${mbSize} Mb, this may take a while to download for users\n`)
94
- 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
+
95
114
  await snag.track({
96
- channel: 'app-error',
97
- event: 'App Too Large',
98
- icon: '🚛',
115
+ channel: 'app',
116
+ event: 'App zip',
117
+ icon: '',
99
118
  tags: {
100
119
  'app-id': appId,
101
120
  },
102
121
  notify: false,
103
122
  }).catch()
104
- }
105
- const s2 = p.spinner()
106
- const name = options.name || `${appId}_${bundle}.zip`
107
- if (!json)
108
- s2.start(`Saving to ${name}`)
109
- writeFileSync(name, zipped)
110
- if (!json)
111
- s2.stop(`Saved to ${name}`)
112
-
113
- await snag.track({
114
- channel: 'app',
115
- event: 'App zip',
116
- icon: '⏫',
117
- tags: {
118
- 'app-id': appId,
119
- },
120
- notify: false,
121
- }).catch()
122
123
 
123
- if (!json)
124
- p.outro(`Done ✅`)
124
+ if (!json)
125
+ p.outro(`Done ✅`)
125
126
 
126
- if (json) {
127
- const output = {
128
- bundle,
129
- filename: name,
130
- 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))
131
136
  }
132
- // Keep the console log and stringify for user who parse the output
133
- // eslint-disable-next-line no-console
134
- console.log(JSON.stringify(output, null, 2))
137
+ process.exit()
138
+ }
139
+ catch (error) {
140
+ p.log.error(formatError(error))
141
+ program.error('')
135
142
  }
136
- process.exit()
137
143
  }
package/src/init.ts CHANGED
@@ -2,6 +2,7 @@ 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
7
  import type LogSnag from 'logsnag'
7
8
  import semver from 'semver'
package/src/utils.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { existsSync, readFileSync, readdirSync } from 'node:fs'
2
- import os, { homedir } from 'node:os'
3
- import { resolve } from 'node:path'
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
2
+ import { homedir } from 'node:os'
3
+ import { join, resolve, sep } from 'node:path'
4
4
  import process from 'node:process'
5
5
  import type { Buffer } from 'node:buffer'
6
6
  import { loadConfig } from '@capacitor/cli/dist/config'
@@ -15,8 +15,9 @@ import { promiseFiles } from 'node-dir'
15
15
  import { findRootSync } from '@manypkg/find-root'
16
16
  import type { InstallCommand, PackageManagerRunner, PackageManagerType } from '@capgo/find-package-manager'
17
17
  import { findInstallCommand, findPackageManagerRunner, findPackageManagerType } from '@capgo/find-package-manager'
18
- import AdmZip from 'adm-zip'
19
- import isWsl from 'is-wsl'
18
+ // import AdmZip from 'adm-zip'
19
+ // import isWsl from 'is-wsl'
20
+ import JSZip from 'jszip'
20
21
  import type { Database } from './types/supabase.types'
21
22
 
22
23
  export const baseKey = '.capgo_key'
@@ -518,17 +519,46 @@ async function prepareMultipart(supabase: SupabaseClient<Database>, appId: strin
518
519
  }
519
520
  }
520
521
 
521
- export function zipFile(filePath: string) {
522
- // if windows and not wsl then do error
523
- if (os.release().toLowerCase().includes('microsoft') && !isWsl) {
524
- p.log.error(`Windows powershell is not supported, please use WSL or a Linux distribution`)
525
- program.error('')
522
+ export async function zipFile(filePath: string): Promise<Buffer> {
523
+ const zip = new JSZip()
524
+
525
+ // Helper function to recursively add files and folders to the ZIP archive
526
+ const addToZip = async (folderPath: string, zipPath: string) => {
527
+ const items = readdirSync(folderPath)
528
+
529
+ for (const item of items) {
530
+ const itemPath = join(folderPath, item)
531
+ const stats = statSync(itemPath)
532
+
533
+ if (stats.isFile()) {
534
+ const fileContent = await readFileSync(itemPath)
535
+ zip.file(join(zipPath, item).split(sep).join('/'), fileContent)
536
+ }
537
+ else if (stats.isDirectory()) {
538
+ await addToZip(itemPath, join(zipPath, item))
539
+ }
540
+ }
526
541
  }
527
- const zip = new AdmZip()
528
- zip.addLocalFolder(filePath)
529
- return zip.toBuffer()
542
+
543
+ // Start adding files and folders to the ZIP archive
544
+ await addToZip(filePath, '')
545
+
546
+ // Generate the ZIP file as a Buffer
547
+ const zipBuffer = await zip.generateAsync({ type: 'nodebuffer', platform: 'UNIX' })
548
+ return zipBuffer
530
549
  }
531
550
 
551
+ // export function zipFile(filePath: string) {
552
+ // // if windows and not wsl then do error
553
+ // if (os.release().toLowerCase().includes('microsoft') && !isWsl) {
554
+ // p.log.error(`Windows powershell is not supported, please use WSL or a Linux distribution`)
555
+ // program.error('')
556
+ // }
557
+ // const zip = new AdmZip()
558
+ // zip.addLocalFolder(filePath)
559
+ // return zip.toBuffer()
560
+ // }
561
+
532
562
  async function finishMultipartDownload(key: string, uploadId: string, url: string, parts: any[]) {
533
563
  const metadata = {
534
564
  action: 'mpu-complete',
@@ -1,6 +1,9 @@
1
1
  import java.io.File;
2
2
  import java.io.FileInputStream;
3
3
  import java.io.IOException;
4
+ import java.io.BufferedInputStream;
5
+ import java.io.FileNotFoundException;
6
+ import java.io.FileOutputStream;
4
7
  import java.util.zip.ZipEntry;
5
8
  import java.util.zip.ZipInputStream;
6
9
 
@@ -12,18 +15,63 @@ public class VerifyZip {
12
15
  }
13
16
 
14
17
  String zipFilePath = args[0];
15
- File file = new File(zipFilePath);
18
+ File zipFile = new File(zipFilePath);
19
+ File targetDirectory = new File("extracted");
16
20
 
17
- if (!file.exists()) {
21
+ if (!zipFile.exists()) {
18
22
  System.out.println("File not found: " + zipFilePath);
19
23
  System.exit(1);
20
24
  }
21
25
 
22
- try (ZipInputStream zis = new ZipInputStream(new FileInputStream(file))) {
26
+ try (
27
+ BufferedInputStream bis = new BufferedInputStream(new FileInputStream(zipFile));
28
+ ZipInputStream zis = new ZipInputStream(bis)
29
+ ) {
30
+ int count;
31
+ int bufferSize = 8192;
32
+ byte[] buffer = new byte[bufferSize];
33
+ long lengthTotal = zipFile.length();
34
+ long lengthRead = bufferSize;
35
+ int percent = 0;
36
+
23
37
  ZipEntry entry;
24
38
  while ((entry = zis.getNextEntry()) != null) {
25
- System.out.println("Extracting: " + entry.getName());
26
- zis.closeEntry();
39
+ if (entry.getName().contains("\\")) {
40
+ System.out.println("Windows path is not supported: " + entry.getName());
41
+ System.exit(1);
42
+ }
43
+ File file = new File(targetDirectory, entry.getName());
44
+ String canonicalPath = file.getCanonicalPath();
45
+ String canonicalDir = targetDirectory.getCanonicalPath();
46
+ File dir = entry.isDirectory() ? file : file.getParentFile();
47
+
48
+ if (!canonicalPath.startsWith(canonicalDir)) {
49
+ System.out.println("SecurityException, Failed to ensure directory is the start path: " +
50
+ canonicalDir + " of " + canonicalPath);
51
+ System.exit(1);
52
+ }
53
+
54
+ if (!dir.isDirectory() && !dir.mkdirs()) {
55
+ System.out.println("Failed to ensure directory: " + dir.getAbsolutePath());
56
+ System.exit(1);
57
+ }
58
+
59
+ if (entry.isDirectory()) {
60
+ continue;
61
+ }
62
+
63
+ try (FileOutputStream outputStream = new FileOutputStream(file)) {
64
+ while ((count = zis.read(buffer)) != -1) {
65
+ outputStream.write(buffer, 0, count);
66
+ }
67
+ }
68
+
69
+ int newPercent = (int) ((lengthRead / (float) lengthTotal) * 100);
70
+ if (lengthTotal > 1 && newPercent != percent) {
71
+ percent = newPercent;
72
+ }
73
+
74
+ lengthRead += entry.getCompressedSize();
27
75
  }
28
76
  System.out.println("ZIP file is valid: " + zipFilePath);
29
77
  } catch (IOException e) {
@@ -0,0 +1,54 @@
1
+ import Foundation
2
+ import SSZipArchive
3
+
4
+ func verifyZipFile(zipFilePath: String) -> Bool {
5
+ let destUnZip = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("extracted")
6
+
7
+ var unzipError: NSError?
8
+ let success = SSZipArchive.unzipFile(atPath: zipFilePath,
9
+ toDestination: destUnZip.path,
10
+ preserveAttributes: true,
11
+ overwrite: true,
12
+ nestedZipLevel: 1,
13
+ password: nil,
14
+ error: &unzipError,
15
+ delegate: nil,
16
+ progressHandler: { (entry, _, _, _) in
17
+ if entry.contains("\\") {
18
+ print("Windows path is not supported: \(entry)")
19
+ exit(1)
20
+ }
21
+
22
+ let fileURL = destUnZip.appendingPathComponent(entry)
23
+ let canonicalPath = fileURL.path
24
+ let canonicalDir = destUnZip.path
25
+
26
+ if !canonicalPath.hasPrefix(canonicalDir) {
27
+ print("SecurityException, Failed to ensure directory is the start path: \(canonicalDir) of \(canonicalPath)")
28
+ exit(1)
29
+ }
30
+ },
31
+ completionHandler: nil)
32
+
33
+ if !success || unzipError != nil {
34
+ print("Failed to unzip file: \(zipFilePath)")
35
+ print("Error: \(unzipError?.localizedDescription ?? "")")
36
+ return false
37
+ }
38
+
39
+ print("ZIP file is valid: \(zipFilePath)")
40
+ return true
41
+ }
42
+
43
+ let zipFilePaths = CommandLine.arguments.dropFirst()
44
+
45
+ if zipFilePaths.isEmpty {
46
+ print("Usage: swift run VerifyZip <zip-file1> <zip-file2> ...")
47
+ exit(1)
48
+ }
49
+
50
+ for zipFilePath in zipFilePaths {
51
+ if !verifyZipFile(zipFilePath: zipFilePath) {
52
+ exit(1)
53
+ }
54
+ }