@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/.github/workflows/build.yml +11 -11
- package/.github/workflows/check_posix_paths.yml +42 -28
- package/CHANGELOG.md +84 -0
- package/bun.lockb +0 -0
- package/dist/index.js +28312 -18716
- package/package.json +2 -1
- package/src/bundle/upload.ts +2 -2
- package/src/bundle/zip.ts +92 -86
- package/src/init.ts +1 -0
- package/src/utils.ts +43 -13
- package/test/VerifyZip.java +53 -5
- package/test/VerifyZip.swift +54 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/cli",
|
|
3
|
-
"version": "4.10.
|
|
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",
|
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/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
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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',
|
package/test/VerifyZip.java
CHANGED
|
@@ -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
|
|
18
|
+
File zipFile = new File(zipFilePath);
|
|
19
|
+
File targetDirectory = new File("extracted");
|
|
16
20
|
|
|
17
|
-
if (!
|
|
21
|
+
if (!zipFile.exists()) {
|
|
18
22
|
System.out.println("File not found: " + zipFilePath);
|
|
19
23
|
System.exit(1);
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
try (
|
|
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
|
-
|
|
26
|
-
|
|
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
|
+
}
|