@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/.github/workflows/check_posix_paths.yml +49 -28
- package/CHANGELOG.md +165 -0
- package/bun.lockb +0 -0
- package/dist/index.js +12709 -6680
- package/package.json +2 -1
- package/src/bundle/upload.ts +2 -2
- package/src/bundle/zip.ts +92 -86
- package/src/utils.ts +42 -8
- package/test/test_zip_swift/Package.resolved +24 -0
- package/test/test_zip_swift/Package.swift +29 -0
- package/test/test_zip_swift/Sources/main.swift +80 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/cli",
|
|
3
|
-
"version": "4.
|
|
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",
|
package/src/bundle/upload.ts
CHANGED
|
@@ -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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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.
|
|
56
|
-
|
|
57
|
-
|
|
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(`
|
|
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: '
|
|
50
|
+
console.error(formatError({ error: 'invalid_semver' }))
|
|
70
51
|
program.error('')
|
|
71
52
|
}
|
|
72
|
-
|
|
73
|
-
if (!
|
|
53
|
+
path = path || config?.app?.webDir
|
|
54
|
+
if (!appId || !bundle || !path) {
|
|
74
55
|
if (!json)
|
|
75
|
-
p.log.error(
|
|
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: '
|
|
58
|
+
console.error(formatError({ error: 'missing_argument' }))
|
|
78
59
|
program.error('')
|
|
79
60
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
97
|
-
event: 'App
|
|
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
|
-
|
|
124
|
-
|
|
124
|
+
if (!json)
|
|
125
|
+
p.outro(`Done ✅`)
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
+
}
|