@applicaster/zapplicaster-cli 16.0.0-alpha.9803580571 → 16.0.0-alpha.9953202182

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": "@applicaster/zapplicaster-cli",
3
- "version": "16.0.0-alpha.9803580571",
3
+ "version": "16.0.0-alpha.9953202182",
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-alpha.9803580571",
32
- "@applicaster/zapp-react-native-android-tv-cli-template": "16.0.0-alpha.9803580571",
33
- "@applicaster/zapp-react-native-cli-template": "16.0.0-alpha.9803580571",
34
- "@applicaster/zapp-react-native-tvos-cli-template": "16.0.0-alpha.9803580571",
31
+ "@applicaster/zapp-react-dom-cli-template": "16.0.0-alpha.9953202182",
32
+ "@applicaster/zapp-react-native-android-tv-cli-template": "16.0.0-alpha.9953202182",
33
+ "@applicaster/zapp-react-native-cli-template": "16.0.0-alpha.9953202182",
34
+ "@applicaster/zapp-react-native-tvos-cli-template": "16.0.0-alpha.9953202182",
35
35
  "axios": "^0.28.0",
36
36
  "camelize": "^1.0.0",
37
37
  "chalk": "^2.3.2",
@@ -1,7 +1,8 @@
1
1
  const R = require("ramda");
2
2
  const fs = require("fs");
3
3
  const semver = require("semver");
4
- const { resolve } = require("path");
4
+ const { resolve, join } = require("path");
5
+ const { existsSync } = require("fs");
5
6
  const packageJSON = require("../../../package.json");
6
7
 
7
8
  const {
@@ -30,6 +31,36 @@ const {
30
31
 
31
32
  const { buildMobileApp } = require("./buildMobile");
32
33
 
34
+ async function patchPackages() {
35
+ if (existsSync(join(process.cwd(), "quick_brick", "patch_rn.js"))) {
36
+ exec(`node ${process.cwd()}/quick_brick/patch_rn.js`);
37
+ } else if (
38
+ existsSync(join(process.cwd(), "development-app", "patch_rn.js"))
39
+ ) {
40
+ exec(`node ${process.cwd()}/development-app/patch_rn.js`);
41
+ } else if (
42
+ existsSync(
43
+ join(
44
+ process.cwd(),
45
+ "packages",
46
+ "zapp-react-dom-development-app",
47
+ "patch_rn.js"
48
+ )
49
+ )
50
+ ) {
51
+ const pathToFile = join(
52
+ process.cwd(),
53
+ "packages",
54
+ "zapp-react-dom-development-app",
55
+ "patch_rn.js"
56
+ );
57
+
58
+ exec(`node ${pathToFile}`);
59
+ } else {
60
+ logger.log("Cannot patch packages, can't find the patch_rn.js file");
61
+ }
62
+ }
63
+
33
64
  /**
34
65
  * fallback to en for missing dayjslocalization
35
66
  * converts the locale code to a dayjs compatible locale code
@@ -185,6 +216,8 @@ async function appBootstrapper(configuration) {
185
216
  const quickBrickVersion = packageJSON.version;
186
217
  checkAllPluginsSatisfiesQuickBrickVersion({ quickBrickVersion, plugins });
187
218
 
219
+ await patchPackages();
220
+
188
221
  if (buildMobile) {
189
222
  await buildMobileApp(configuration);
190
223
  }
@@ -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 (!R.contains(platform, ["samsung_tv", "lg_tv", "vizio"])) {
30
+ if (!platformIsTV(platform)) {
25
31
  return;
26
32
  }
27
33
 
@@ -133,6 +133,12 @@ const defaultFonts = {
133
133
  "Miso-Bold.ttf",
134
134
  ...commonFonts,
135
135
  ],
136
+ vidaa: [
137
+ "Miso-Light.ttf",
138
+ "Miso-Regular.ttf",
139
+ "Miso-Bold.ttf",
140
+ ...commonFonts,
141
+ ],
136
142
  vizio: [
137
143
  "Miso-Light.ttf",
138
144
  "Miso-Regular.ttf",
@@ -49,6 +49,7 @@ function renderManifest({
49
49
 
50
50
  const validPlatforms = [
51
51
  "lg_tv",
52
+ "vidaa",
52
53
  "samsung_tv",
53
54
  "vizio",
54
55
  "android_tv_for_quickbrick",
@@ -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", () => {
@@ -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
- unzip(resolve(filePath, fileName), filePath);
127
+
128
+ const downloadedFile = resolve(filePath, fileName);
129
+
130
+ assertValidAssetsBundle(downloadedFile, assetsUrl);
131
+
132
+ unzip(downloadedFile, filePath);
69
133
  }
70
134
 
71
135
  /**
@@ -13,19 +13,6 @@ exports[`copyFiles when destination path has no package.json doesn't skips the c
13
13
  ]
14
14
  `;
15
15
 
16
- exports[`copyFiles when there is no package.json to copy copies the dir 1`] = `
17
- [
18
- [
19
- "some/source/path/files/file1.txt",
20
- "some/destination/Path/files/file1.txt",
21
- ],
22
- [
23
- "some/source/path/files/file2.txt",
24
- "some/destination/Path/files/file2.txt",
25
- ],
26
- ]
27
- `;
28
-
29
16
  exports[`copyFiles when there is no package.json to copy copies the files 1`] = `
30
17
  [
31
18
  [
@@ -39,7 +39,6 @@ describe("copyFiles", () => {
39
39
  fs.writeFile("bar.json", { bar: "baz" }, jest.fn());
40
40
  fs.writeFile("qux.json", { qux: 42 }, jest.fn());
41
41
  fs.copyFileSync.mockClear();
42
- fs.readdir.mockClear();
43
42
  });
44
43
 
45
44
  describe("when there is no package.json to copy", () => {
@@ -48,21 +47,6 @@ describe("copyFiles", () => {
48
47
  expect(fs.copyFileSync).toHaveBeenCalledTimes(2);
49
48
  expect(fs.copyFileSync.mock.calls).toMatchSnapshot();
50
49
  });
51
-
52
- it("copies the dir", async () => {
53
- // Mock fs.readdir to return some files when the directory is read
54
- fs.readdir.mockImplementationOnce((path, callback) => {
55
- callback(null, ["file1.txt", "file2.txt"]);
56
- });
57
-
58
- const results = copyFiles(sourcePath, ["files/"], destinationPath);
59
-
60
- // Wait for the promise to resolve (the directory copy operation)
61
- await Promise.all(results);
62
-
63
- expect(fs.copyFileSync).toHaveBeenCalledTimes(2);
64
- expect(fs.copyFileSync.mock.calls).toMatchSnapshot();
65
- });
66
50
  });
67
51
 
68
52
  describe("when destination path has package.json", () => {
package/src/file/index.js CHANGED
@@ -5,17 +5,7 @@ const R = require("ramda");
5
5
  * @module @applicaster/zapplicaster-cli/util/file
6
6
  */
7
7
 
8
- const {
9
- writeFile,
10
- readFile,
11
- readdir,
12
- copyFile,
13
- copyFileSync,
14
- lstatSync,
15
- mkdirSync,
16
- existsSync,
17
- } = require("fs");
18
-
8
+ const { writeFile, readFile, readdir, copyFile, copyFileSync } = require("fs");
19
9
  const { promisify } = require("util");
20
10
  const { join } = require("path");
21
11
 
@@ -71,8 +61,13 @@ function destinationFileNameMapper(destinationFileName) {
71
61
  /**
72
62
  * Copies an array of files from a folder to another
73
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
+ *
74
69
  * @param {String} source folder of the files
75
- * @param {String[]} files list of the files
70
+ * @param {(String|{source: String, destination: String})[]} files list of the files
76
71
  * @param {String} destination path
77
72
  * @returns {Promise[]} array of promises with the status of the copied files
78
73
  */
@@ -83,25 +78,13 @@ function copyFiles(source, filesOrFilesGetter, destination, config) {
83
78
  : filesOrFilesGetter;
84
79
 
85
80
  return R.map((file) => {
86
- let sourcePath, destinationPath;
87
-
88
- if (Array.isArray(file)) {
89
- sourcePath = join(source, file[0]);
90
- destinationPath = destinationFileNameMapper(join(destination, file[1]));
91
- } else {
92
- sourcePath = join(source, file);
93
- destinationPath = destinationFileNameMapper(join(destination, file));
94
- }
95
-
96
- if (lstatSync(sourcePath).isDirectory()) {
97
- if (!existsSync(destinationPath)) {
98
- mkdirSync(destinationPath, { recursive: true });
99
- }
100
-
101
- return copyFolder(sourcePath, destinationPath);
102
- }
81
+ const sourceFile = typeof file === "string" ? file : file.source;
82
+ const destinationFile = typeof file === "string" ? file : file.destination;
103
83
 
104
- return copyFileSync(sourcePath, destinationPath);
84
+ return copyFileSync(
85
+ join(source, sourceFile),
86
+ destinationFileNameMapper(join(destination, destinationFile))
87
+ );
105
88
  }, files);
106
89
  }
107
90
 
@@ -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"`;
@@ -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",
@@ -2,18 +2,10 @@ const fs = jest.genMockFromModule("fs");
2
2
  const _fs = jest.requireActual("fs");
3
3
  const R = require("ramda");
4
4
 
5
- function mkdirSync() {
6
- return {};
7
- }
8
-
9
5
  function readdirSync(folder) {
10
6
  return _fs.readdirSync(folder);
11
7
  }
12
8
 
13
- async function readdirAsync(folder) {
14
- return Promise.resolve(_fs.readdirSync(folder));
15
- }
16
-
17
9
  function readdir(folder, callback) {
18
10
  const files = R.map(R.prop("fileName"), fs.__writenFiles);
19
11
 
@@ -85,17 +77,10 @@ function __resetFiles() {
85
77
  fs.__writenFiles = [];
86
78
  }
87
79
 
88
- fs.lstatSync = (_path) => ({
89
- isDirectory: () => _path.endsWith("/"),
90
- isFile: () => _path.endsWith("/") === false,
91
- });
92
-
93
80
  fs.__writenFiles = [];
94
81
  fs.__resetFiles = __resetFiles;
95
82
  fs.__addMockedFiles = __addMockedFiles;
96
83
  fs.readdirSync = readdirSync;
97
- fs.readdirAsync = readdirAsync;
98
- fs.mkdirSync = mkdirSync;
99
84
  fs.readdir = jest.fn(readdir);
100
85
  fs.writeFile = jest.fn(writeFile);
101
86
  fs.readFile = jest.fn(readFile);