@capgo/cli 4.10.53 → 4.11.0

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.53",
3
+ "version": "4.11.0",
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/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, release as osRelease } 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'
@@ -17,6 +17,7 @@ import type { InstallCommand, PackageManagerRunner, PackageManagerType } from '@
17
17
  import { findInstallCommand, findPackageManagerRunner, findPackageManagerType } from '@capgo/find-package-manager'
18
18
  import AdmZip from 'adm-zip'
19
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,50 @@ 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
+ if (osRelease().toLowerCase().includes('microsoft') && !isWsl) {
524
+ return zipFileWindows(filePath)
525
+ }
526
+ else {
527
+ return zipFileUnix(filePath)
526
528
  }
529
+ }
530
+
531
+ export function zipFileUnix(filePath: string) {
527
532
  const zip = new AdmZip()
528
533
  zip.addLocalFolder(filePath)
529
534
  return zip.toBuffer()
530
535
  }
531
536
 
537
+ export async function zipFileWindows(filePath: string): Promise<Buffer> {
538
+ const zip = new JSZip()
539
+
540
+ // Helper function to recursively add files and folders to the ZIP archive
541
+ const addToZip = async (folderPath: string, zipPath: string) => {
542
+ const items = readdirSync(folderPath)
543
+
544
+ for (const item of items) {
545
+ const itemPath = join(folderPath, item)
546
+ const stats = statSync(itemPath)
547
+
548
+ if (stats.isFile()) {
549
+ const fileContent = await readFileSync(itemPath)
550
+ zip.file(join(zipPath, item).split(sep).join('/'), fileContent)
551
+ }
552
+ else if (stats.isDirectory()) {
553
+ await addToZip(itemPath, join(zipPath, item))
554
+ }
555
+ }
556
+ }
557
+
558
+ // Start adding files and folders to the ZIP archive
559
+ await addToZip(filePath, '')
560
+
561
+ // Generate the ZIP file as a Buffer
562
+ const zipBuffer = await zip.generateAsync({ type: 'nodebuffer', platform: 'UNIX', compression: 'DEFLATE' })
563
+ return zipBuffer
564
+ }
565
+
532
566
  async function finishMultipartDownload(key: string, uploadId: string, url: string, parts: any[]) {
533
567
  const metadata = {
534
568
  action: 'mpu-complete',
@@ -0,0 +1,24 @@
1
+ {
2
+ "originHash" : "be28ad70f15d01b567aeb05f85c074fc7d437a0ade8a8ceab7a6149b8f5b3593",
3
+ "pins" : [
4
+ {
5
+ "identity" : "swift-argument-parser",
6
+ "kind" : "remoteSourceControl",
7
+ "location" : "https://github.com/apple/swift-argument-parser",
8
+ "state" : {
9
+ "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b",
10
+ "version" : "1.4.0"
11
+ }
12
+ },
13
+ {
14
+ "identity" : "ziparchive",
15
+ "kind" : "remoteSourceControl",
16
+ "location" : "https://github.com/ZipArchive/ZipArchive.git",
17
+ "state" : {
18
+ "revision" : "79d4dc9729096c6ad83dd3cee2b9f354d1b4ab7b",
19
+ "version" : "2.5.5"
20
+ }
21
+ }
22
+ ],
23
+ "version" : 3
24
+ }
@@ -0,0 +1,29 @@
1
+ // swift-tools-version: 5.10
2
+ // The swift-tools-version declares the minimum version of Swift required to build this package.
3
+
4
+ import PackageDescription
5
+
6
+ let package = Package(
7
+ name: "MyCLI",
8
+ platforms: [
9
+ .macOS(.v11)
10
+ ], dependencies: [
11
+ .package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMajor(from: "1.4.0")),
12
+ .package(url: "https://github.com/ZipArchive/ZipArchive.git", .upToNextMajor(from: "2.5.5"))
13
+ ],
14
+ targets: [
15
+ // Targets are the basic building blocks of a package, defining a module or a test suite.
16
+ // Targets can depend on other targets in this package and products from dependencies.
17
+ .executableTarget(
18
+ name: "MyCLI",
19
+ dependencies: [
20
+ .product(name: "ZipArchive", package: "ZipArchive"),
21
+ .product(name: "ArgumentParser", package: "swift-argument-parser"),
22
+ ],
23
+ path: "Sources",
24
+ swiftSettings: [
25
+ .unsafeFlags(["-parse-as-library"])
26
+ ]
27
+ ),
28
+ ]
29
+ )
@@ -0,0 +1,80 @@
1
+ // The Swift Programming Language
2
+ // https://docs.swift.org/swift-book
3
+
4
+ import ArgumentParser
5
+ import Foundation
6
+ import ZipArchive
7
+ import Darwin
8
+
9
+ extension URL {
10
+ var isDirectory: Bool {
11
+ (try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true
12
+ }
13
+ var exist: Bool {
14
+ return FileManager().fileExists(atPath: self.path)
15
+ }
16
+ }
17
+
18
+
19
+ func verifyZipFile(zipFilePath: String) {
20
+ let destUnZip = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("extracted")
21
+
22
+ var unzipError: NSError?
23
+ let success = SSZipArchive.unzipFile(atPath: zipFilePath,
24
+ toDestination: destUnZip.path,
25
+ preserveAttributes: true,
26
+ overwrite: true,
27
+ nestedZipLevel: 1,
28
+ password: nil,
29
+ error: &unzipError,
30
+ delegate: nil,
31
+ progressHandler: { (entry, _, _, _) in
32
+ if entry.contains("\\") {
33
+ print("Windows path is not supported: \(entry)")
34
+ exit(1)
35
+ }
36
+
37
+ let fileURL = destUnZip.appendingPathComponent(entry)
38
+ let canonicalPath = fileURL.path
39
+ let canonicalDir = destUnZip.path
40
+
41
+ if !canonicalPath.hasPrefix(canonicalDir) {
42
+ print("SecurityException, Failed to ensure directory is the start path: \(canonicalDir) of \(canonicalPath)")
43
+ exit(1)
44
+ }
45
+ },
46
+ completionHandler: nil)
47
+
48
+ if !success || unzipError != nil {
49
+ print("Failed to unzip file: \(zipFilePath)")
50
+ print("Error: \(unzipError?.localizedDescription ?? "")")
51
+ exit(1)
52
+ }
53
+
54
+ print("ZIP file is valid: \(zipFilePath)")
55
+ }
56
+
57
+ @main
58
+ struct CapgoCliTest: ParsableCommand {
59
+ @Option(parsing: .upToNextOption, help: "Specify the files to test")
60
+ public var zipFiles: [String]
61
+
62
+ public func run() throws {
63
+ print("Hello capgo test", zipFiles)
64
+
65
+ for file in zipFiles {
66
+ guard let fileUrl = URL(string: file) else {
67
+ print("Cannot convert \"\(file)\" into a file")
68
+ Darwin.exit(1)
69
+ }
70
+
71
+ if (!fileUrl.exist) {
72
+ print("File \"\(fileUrl)\" does not exist")
73
+ Darwin.exit(1)
74
+ }
75
+
76
+ print("Testing file \(file)")
77
+ verifyZipFile(zipFilePath: file)
78
+ }
79
+ }
80
+ }