@applicaster/zapplicaster-cli 16.0.0-rc.25 → 16.0.0-rc.27
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 +5 -5
- package/src/commands/prepareWorkspace/configurator.js +7 -1
- package/src/commands/prepareWorkspace/parseFonts.js +6 -0
- package/src/commands/publishPlugin/generateManifest.js +1 -0
- package/src/download/__tests__/download.test.js +53 -0
- package/src/download/index.js +65 -1
- package/src/file/index.js +15 -9
- package/src/templates/__tests__/__snapshots__/templates.test.js.snap +1 -1
- package/src/templates/index.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapplicaster-cli",
|
|
3
|
-
"version": "16.0.0-rc.
|
|
3
|
+
"version": "16.0.0-rc.27",
|
|
4
4
|
"description": "CLI Tool for the zapp app and Quick Brick project",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"publishConfig": {
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@applicaster/zapp-react-dom-cli-template": "16.0.0-rc.
|
|
32
|
-
"@applicaster/zapp-react-native-android-tv-cli-template": "16.0.0-rc.
|
|
33
|
-
"@applicaster/zapp-react-native-cli-template": "16.0.0-rc.
|
|
34
|
-
"@applicaster/zapp-react-native-tvos-cli-template": "16.0.0-rc.
|
|
31
|
+
"@applicaster/zapp-react-dom-cli-template": "16.0.0-rc.27",
|
|
32
|
+
"@applicaster/zapp-react-native-android-tv-cli-template": "16.0.0-rc.27",
|
|
33
|
+
"@applicaster/zapp-react-native-cli-template": "16.0.0-rc.27",
|
|
34
|
+
"@applicaster/zapp-react-native-tvos-cli-template": "16.0.0-rc.27",
|
|
35
35
|
"axios": "^0.28.0",
|
|
36
36
|
"camelize": "^1.0.0",
|
|
37
37
|
"chalk": "^2.3.2",
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
const R = require("ramda");
|
|
2
2
|
const axios = require("axios");
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
platformIsTV,
|
|
6
|
+
} = require("@applicaster/zapp-react-native-utils/manifestUtils/platformIsTV");
|
|
7
|
+
|
|
3
8
|
const { getAppVersionParams } = require("../../zapp");
|
|
4
9
|
const { resolveAppTemplate } = require("../../templates");
|
|
5
10
|
const { isZappReactNativeRepo } = require("../../settings");
|
|
@@ -11,6 +16,7 @@ const store_map = {
|
|
|
11
16
|
samsung_app_store: "samsung_tv",
|
|
12
17
|
lg_content_store: "lg_tv",
|
|
13
18
|
vizio_app_store: "vizio",
|
|
19
|
+
vidaa_app_store: "vidaa",
|
|
14
20
|
};
|
|
15
21
|
|
|
16
22
|
/**
|
|
@@ -21,7 +27,7 @@ const store_map = {
|
|
|
21
27
|
*/
|
|
22
28
|
async function retrievesSpinnerColor(url, platform) {
|
|
23
29
|
try {
|
|
24
|
-
if (!
|
|
30
|
+
if (!platformIsTV(platform)) {
|
|
25
31
|
return;
|
|
26
32
|
}
|
|
27
33
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { saveRemoteJsonToFile, saveAssets, saveFontFiles } = require("../index");
|
|
2
2
|
const nock = require("nock");
|
|
3
3
|
const R = require("ramda");
|
|
4
|
+
const fs = require("fs");
|
|
4
5
|
const logger = require("../../logger");
|
|
5
6
|
|
|
6
7
|
const getFileName = R.compose(R.last, R.split("/"));
|
|
@@ -109,9 +110,37 @@ describe("saveAssets", () => {
|
|
|
109
110
|
|
|
110
111
|
afterEach(() => {
|
|
111
112
|
clearAllMocks();
|
|
113
|
+
|
|
114
|
+
["existsSync", "openSync", "closeSync", "readSync", "readFileSync"].forEach(
|
|
115
|
+
(method) => {
|
|
116
|
+
if (fs[method] && fs[method].mockRestore) {
|
|
117
|
+
fs[method].mockRestore();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
);
|
|
112
121
|
});
|
|
113
122
|
|
|
123
|
+
function mockDownloadedFile({ isZip, content } = {}) {
|
|
124
|
+
jest.spyOn(fs, "existsSync").mockReturnValue(true);
|
|
125
|
+
jest.spyOn(fs, "openSync").mockReturnValue(1);
|
|
126
|
+
jest.spyOn(fs, "closeSync").mockImplementation(() => ({}));
|
|
127
|
+
|
|
128
|
+
jest.spyOn(fs, "readSync").mockImplementation((fd, buffer) => {
|
|
129
|
+
const header = isZip
|
|
130
|
+
? Buffer.from([0x50, 0x4b, 0x03, 0x04])
|
|
131
|
+
: Buffer.from("<?xm");
|
|
132
|
+
|
|
133
|
+
header.copy(buffer);
|
|
134
|
+
|
|
135
|
+
return 4;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
jest.spyOn(fs, "readFileSync").mockReturnValue(content || "");
|
|
139
|
+
}
|
|
140
|
+
|
|
114
141
|
it("downloads the files with cURL, and unzips the assets", () => {
|
|
142
|
+
mockDownloadedFile({ isZip: true });
|
|
143
|
+
|
|
115
144
|
saveAssets({ destinationPath, platform }, assetsUrl);
|
|
116
145
|
|
|
117
146
|
expect(loggerSpy).toHaveBeenCalledWith(`saving assets from ${assetsUrl}`);
|
|
@@ -122,6 +151,30 @@ describe("saveAssets", () => {
|
|
|
122
151
|
filePath
|
|
123
152
|
);
|
|
124
153
|
});
|
|
154
|
+
|
|
155
|
+
it("throws a clear error and skips unzip when the bundle is not a zip", () => {
|
|
156
|
+
mockDownloadedFile({
|
|
157
|
+
isZip: false,
|
|
158
|
+
content:
|
|
159
|
+
'<?xml version="1.0" encoding="UTF-8"?><Error><Code>AccessDenied</Code></Error>',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(() => saveAssets({ destinationPath, platform }, assetsUrl)).toThrow(
|
|
163
|
+
/AccessDenied/
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
expect(shell.unzip).not.toHaveBeenCalled();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("throws a clear error when the bundle was not downloaded", () => {
|
|
170
|
+
jest.spyOn(fs, "existsSync").mockReturnValue(false);
|
|
171
|
+
|
|
172
|
+
expect(() => saveAssets({ destinationPath, platform }, assetsUrl)).toThrow(
|
|
173
|
+
/was not downloaded/
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
expect(shell.unzip).not.toHaveBeenCalled();
|
|
177
|
+
});
|
|
125
178
|
});
|
|
126
179
|
|
|
127
180
|
describe("saveFontFiles", () => {
|
package/src/download/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const R = require("ramda");
|
|
2
|
+
const fs = require("fs");
|
|
2
3
|
const axios = require("axios");
|
|
3
4
|
const { resolve } = require("path");
|
|
4
5
|
const { curlFile, curlMultipleFiles, unzip } = require("../shell");
|
|
@@ -12,6 +13,64 @@ const getFileName = R.ifElse(
|
|
|
12
13
|
R.prop("name")
|
|
13
14
|
);
|
|
14
15
|
|
|
16
|
+
// Local file signature for a zip archive: "PK\x03\x04"
|
|
17
|
+
const ZIP_SIGNATURE = "504b0304";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* checks whether a file on disk is a real zip archive by reading its magic bytes.
|
|
21
|
+
* @param {String} file path to the file to inspect
|
|
22
|
+
* @returns {Boolean} true if the file starts with the zip local file signature
|
|
23
|
+
*/
|
|
24
|
+
function isZipFile(file) {
|
|
25
|
+
const fd = fs.openSync(file, "r");
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const header = Buffer.alloc(4);
|
|
29
|
+
const bytesRead = fs.readSync(fd, header, 0, 4, 0);
|
|
30
|
+
|
|
31
|
+
return bytesRead === 4 && header.toString("hex") === ZIP_SIGNATURE;
|
|
32
|
+
} finally {
|
|
33
|
+
fs.closeSync(fd);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* verifies that the downloaded assets bundle is a real zip archive.
|
|
39
|
+
* The assets CDN responds with a small XML/HTML body (e.g. S3 "AccessDenied")
|
|
40
|
+
* when the bundle is missing or not yet published. Because the download uses
|
|
41
|
+
* `curl -O` without `-f`, that error body is saved as assets.zip and the process
|
|
42
|
+
* continues, only to crash later in webpack with a confusing parse error. This
|
|
43
|
+
* check fails fast in the prepare step with an actionable message instead.
|
|
44
|
+
* @param {String} file path to the downloaded assets bundle
|
|
45
|
+
* @param {String} assetsUrl original url, surfaced in the error message
|
|
46
|
+
*/
|
|
47
|
+
function assertValidAssetsBundle(file, assetsUrl) {
|
|
48
|
+
if (!fs.existsSync(file)) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Assets bundle was not downloaded from ${assetsUrl}. ` +
|
|
51
|
+
"Verify the app has a published build with generated assets for this platform."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (isZipFile(file)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const preview = fs
|
|
60
|
+
.readFileSync(file, "utf8")
|
|
61
|
+
.replace(/\s+/g, " ")
|
|
62
|
+
.trim()
|
|
63
|
+
.slice(0, 300);
|
|
64
|
+
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Invalid assets bundle downloaded from ${assetsUrl} - expected a zip archive ` +
|
|
67
|
+
`but received non-zip content:\n "${preview}"\n` +
|
|
68
|
+
"This usually means the assets bundle has not been generated/published for " +
|
|
69
|
+
'this platform, or the URL is not accessible (e.g. S3 "AccessDenied"). ' +
|
|
70
|
+
"Publish a build with assets for this platform before preparing the workspace."
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
15
74
|
/**
|
|
16
75
|
* saves a remote json file in the app's configuration folder.
|
|
17
76
|
* curried function of the shape saveRemoteJsonToFile(configuration)(fileUrl)
|
|
@@ -65,7 +124,12 @@ function saveAssets({ destinationPath, platform }, assetsUrl) {
|
|
|
65
124
|
});
|
|
66
125
|
|
|
67
126
|
curlFile(assetsUrl, filePath);
|
|
68
|
-
|
|
127
|
+
|
|
128
|
+
const downloadedFile = resolve(filePath, fileName);
|
|
129
|
+
|
|
130
|
+
assertValidAssetsBundle(downloadedFile, assetsUrl);
|
|
131
|
+
|
|
132
|
+
unzip(downloadedFile, filePath);
|
|
69
133
|
}
|
|
70
134
|
|
|
71
135
|
/**
|
package/src/file/index.js
CHANGED
|
@@ -61,8 +61,13 @@ function destinationFileNameMapper(destinationFileName) {
|
|
|
61
61
|
/**
|
|
62
62
|
* Copies an array of files from a folder to another
|
|
63
63
|
*
|
|
64
|
+
* Each entry can either be a string (same relative path for source and
|
|
65
|
+
* destination) or an object `{ source, destination }` when a file should be
|
|
66
|
+
* read from one relative path and written to another. The latter lets several
|
|
67
|
+
* platforms reuse a single set of bundled assets without duplicating them.
|
|
68
|
+
*
|
|
64
69
|
* @param {String} source folder of the files
|
|
65
|
-
* @param {String[]} files list of the files
|
|
70
|
+
* @param {(String|{source: String, destination: String})[]} files list of the files
|
|
66
71
|
* @param {String} destination path
|
|
67
72
|
* @returns {Promise[]} array of promises with the status of the copied files
|
|
68
73
|
*/
|
|
@@ -72,14 +77,15 @@ function copyFiles(source, filesOrFilesGetter, destination, config) {
|
|
|
72
77
|
? filesOrFilesGetter(config)
|
|
73
78
|
: filesOrFilesGetter;
|
|
74
79
|
|
|
75
|
-
return R.map(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
),
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
return R.map((file) => {
|
|
81
|
+
const sourceFile = typeof file === "string" ? file : file.source;
|
|
82
|
+
const destinationFile = typeof file === "string" ? file : file.destination;
|
|
83
|
+
|
|
84
|
+
return copyFileSync(
|
|
85
|
+
join(source, sourceFile),
|
|
86
|
+
destinationFileNameMapper(join(destination, destinationFile))
|
|
87
|
+
);
|
|
88
|
+
}, files);
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
module.exports = {
|
|
@@ -32,4 +32,4 @@ exports[`resolveAppTemplate retrieves the template from the provided path if pro
|
|
|
32
32
|
}
|
|
33
33
|
`;
|
|
34
34
|
|
|
35
|
-
exports[`resolveAppTemplate throws if resolving a template for an unknown platform 1`] = `"could not find template for foo - available templates: ios,android,samsung_tv,lg_tv,vizio,apple_tv,android_tv,amazon"`;
|
|
35
|
+
exports[`resolveAppTemplate throws if resolving a template for an unknown platform 1`] = `"could not find template for foo - available templates: ios,android,samsung_tv,lg_tv,vidaa,vizio,apple_tv,android_tv,amazon"`;
|
package/src/templates/index.js
CHANGED
|
@@ -27,6 +27,7 @@ const templatesMap = {
|
|
|
27
27
|
android: "@applicaster/zapp-react-native-cli-template",
|
|
28
28
|
samsung_tv: "@applicaster/zapp-react-dom-cli-template",
|
|
29
29
|
lg_tv: "@applicaster/zapp-react-dom-cli-template",
|
|
30
|
+
vidaa: "@applicaster/zapp-react-dom-cli-template",
|
|
30
31
|
vizio: "@applicaster/zapp-react-dom-cli-template",
|
|
31
32
|
apple_tv: "@applicaster/zapp-react-native-tvos-cli-template",
|
|
32
33
|
android_tv: "@applicaster/zapp-react-native-android-tv-cli-template",
|