aboutsummaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
authorLibravatar vantezzen <hello@vantezzen.io>2020-04-15 19:41:54 +0200
committerLibravatar vantezzen <hello@vantezzen.io>2020-04-15 19:41:54 +0200
commit8a6f31efaab75d16797126fe9e9e03ba45877b91 (patch)
treef9faad1b667eefc7ed185064ad71e4dc99d49750 /scripts
parent#93 Update Zulip (diff)
downloadferdium-recipes-8a6f31efaab75d16797126fe9e9e03ba45877b91.tar.gz
ferdium-recipes-8a6f31efaab75d16797126fe9e9e03ba45877b91.tar.zst
ferdium-recipes-8a6f31efaab75d16797126fe9e9e03ba45877b91.zip
Improve recipe packaging script
This commit will improve the usability of the packaging script, especially for beginners. It will output better error messages if there are any problems with the recipe. It will also add more tests to improve detecting those problems. This way, more problems can be detected before even needing to create a PR.
Diffstat (limited to 'scripts')
-rw-r--r--scripts/add_github.js44
-rw-r--r--scripts/api/package.js204
-rw-r--r--scripts/package.js106
-rw-r--r--scripts/package.json1
-rw-r--r--scripts/yarn.lock14
5 files changed, 247 insertions, 122 deletions
diff --git a/scripts/add_github.js b/scripts/add_github.js
index 543e347..4a5cfce 100644
--- a/scripts/add_github.js
+++ b/scripts/add_github.js
@@ -6,6 +6,7 @@ const targz = require('targz');
6const fs = require('fs-extra'); 6const fs = require('fs-extra');
7const path = require('path'); 7const path = require('path');
8const GitUrlParse = require("git-url-parse"); 8const GitUrlParse = require("git-url-parse");
9const packageRecipe = require('./api/package');
9 10
10// Helper: Download file to filesystem 11// Helper: Download file to filesystem
11const downloadFile = (async (url, path) => { 12const downloadFile = (async (url, path) => {
@@ -30,7 +31,7 @@ const decompress = (src, dest) => {
30 dest 31 dest
31 }, function (err) { 32 }, function (err) {
32 if (err) { 33 if (err) {
33 console.log('Error while decompressing recipe:', err); 34 console.log('⚠️ Could not add your recipe: There was an error while decompressing your GitHub repository file: ', err);
34 } 35 }
35 resolve(); 36 resolve();
36 }); 37 });
@@ -40,7 +41,11 @@ const decompress = (src, dest) => {
40const repo = process.argv[2]; 41const repo = process.argv[2];
41 42
42if (!repo || !/https:\/\/github\.com\/[^\/]+\/[^\/]+\/?/gi.test(repo)) { 43if (!repo || !/https:\/\/github\.com\/[^\/]+\/[^\/]+\/?/gi.test(repo)) {
43 console.log("Please provide a valid repository URL"); 44 console.log(`⚠️ Could not add your recipe: The GitHub URL you provided doesn't seem to be valid.
45You should use this command like "yarn github https://github.com/user/repo".
46Please make sure you provide a URL in the format "https://github.com/user/repo"
47For more information about this script visit https://github.com/getferdi/recipes/blob/master/docs/integration.md#publishing
48If you want to package a local recipe, please use "yarn package" instead.`);
44 return; 49 return;
45} 50}
46 51
@@ -54,24 +59,24 @@ const compressed = path.join(__dirname, 'tmp.tar.gz');
54 59
55// Let us work in an async environment 60// Let us work in an async environment
56(async () => { 61(async () => {
57 console.log("Creating temporary directory"); 62 console.log("[Info] Creating temporary directory");
58 63
59 await fs.ensureDir(tempDir); 64 await fs.ensureDir(tempDir);
60 await fs.ensureDir(recipeSrc); 65 await fs.ensureDir(recipeSrc);
61 await fs.ensureDir(recipeSrcTmp); 66 await fs.ensureDir(recipeSrcTmp);
62 67
63 console.log("Downloading " + repo); 68 console.log("[Info] Downloading " + repo);
64 69
65 await downloadFile( 70 await downloadFile(
66 `https://github.com/${repoInfo.owner}/${repoInfo.name}/archive/master.tar.gz`, 71 `https://github.com/${repoInfo.owner}/${repoInfo.name}/archive/master.tar.gz`,
67 compressed 72 compressed
68 ); 73 );
69 74
70 console.log("Decompressing tarball"); 75 console.log("[Info] Decompressing repository");
71 76
72 await decompress(compressed, tempDir); 77 await decompress(compressed, tempDir);
73 78
74 console.log("Moving directories"); 79 console.log("[Info] Moving 'recipe_src' to 'recipe_src_tmp'");
75 80
76 await fs.move(recipeSrc, recipeSrcTmp, {overwrite: true}); 81 await fs.move(recipeSrc, recipeSrcTmp, {overwrite: true});
77 await fs.move( 82 await fs.move(
@@ -80,18 +85,21 @@ const compressed = path.join(__dirname, 'tmp.tar.gz');
80 {overwrite: true} 85 {overwrite: true}
81 ); 86 );
82 87
83 console.log("Adding to repository"); 88 console.log("[Info] Packaging your recipe");
84 require('./package.js'); 89 try {
85 console.log("Continuing in 1.5 seconds"); 90 await packageRecipe();
91 } catch(e) {
92 return;
93 }
86 94
87 setTimeout(async () => { 95 console.log("[Info] Deleting temporarydownloaded repository");
88 console.log("Deleting downloaded files");
89 96
90 await fs.remove(compressed); 97 await fs.remove(compressed);
91 await fs.remove(recipeSrc); 98 await fs.remove(recipeSrc);
92 99
93 console.log("Moving back recipe folder"); 100 console.log("[Info] Moving back 'recipe_src_tmp' to 'recipe_src'");
94 101
95 await fs.move(recipeSrcTmp, recipeSrc); 102 await fs.move(recipeSrcTmp, recipeSrc);
96 }, 1500); 103
104 console.log(`✅ Successfully packaged the recipe from your GitHub repository`);
97})(); \ No newline at end of file 105})(); \ No newline at end of file
diff --git a/scripts/api/package.js b/scripts/api/package.js
new file mode 100644
index 0000000..2d265f6
--- /dev/null
+++ b/scripts/api/package.js
@@ -0,0 +1,204 @@
1/**
2 * Package recipe into tar.gz file
3 */
4const targz = require('targz');
5const fs = require('fs-extra');
6const path = require('path');
7const sizeOf = require('image-size');
8const semver = require('semver');
9
10// Publicly availible link to this repository's uncompressed folder
11// Used for generating public icon URLs
12const repo = 'https://cdn.jsdelivr.net/gh/getferdi/recipes/uncompressed/';
13
14// Helper: Compress src folder into dest file
15const compress = (src, dest) => new Promise((resolve, reject) => {
16 targz.compress({
17 src,
18 dest,
19 tar: {
20 // Don't package .DS_Store files
21 ignore: function(name) {
22 return path.basename(name) === '.DS_Store'
23 }
24 },
25 }, (err) => {
26 if (err) {
27 reject(err);
28 } else {
29 resolve(dest);
30 }
31 });
32});
33// Let us work in an async environment
34module.exports = async () => {
35 // Create paths to important files
36 const recipeSrc = path.join(__dirname, '../recipe_src');
37 const packageJson = path.join(recipeSrc, 'package.json');
38 const svgIcon = path.join(recipeSrc, 'icon.svg');
39 const pngIcon = path.join(recipeSrc, 'icon.png');
40 const allJson = path.join(__dirname, '../../', 'all.json');
41 let all = await fs.readJson(allJson);
42
43 // Check that package.json exists
44 if (!await fs.pathExists(packageJson)) {
45 console.log(`⚠️ Could not add your recipe: Please add your recipe to ${recipeSrc} and make sure that folder contains a "package.json".
46For more information on how to add your recipe visit https://github.com/getferdi/recipes/blob/master/docs/integration.md`);
47 return;
48 }
49
50 // Check that icons exist
51 const hasSvg = await fs.pathExists(svgIcon);
52 const hasPng = await fs.pathExists(pngIcon);
53 if (!hasSvg && !hasPng) {
54 console.log(`⚠️ Could not add your recipe: Please make sure your recipe contains an icon.png and an icon.svg file.
55Those icons should be the logo of the recipe you are trying to add.
56Please also make sure that your icons are 1024x1024px in size.
57For more information about recipe icons visit https://github.com/getferdi/recipes/blob/master/docs/integration.md#icons`);
58 return;
59 } else if (!hasSvg) {
60 console.log(`⚠️ Could not add your recipe: Please make sure your recipe contains an icon.svg file.
61Your recipe already contains an "icon.png" but it also requires an "icon.svg" to display properly.
62Please also make sure that your icons are 1024x1024px in size.
63For more information about recipe icons visit https://github.com/getferdi/recipes/blob/master/docs/integration.md#icons`);
64 return;
65 } else if (!hasPng) {
66 console.log(`⚠️ Could not add your recipe: Please make sure your recipe contains an icon.png file.
67Your recipe already contains an "icon.svg" but it also requires an "icon.png" to display properly.
68Please also make sure that your icons are 1024x1024px in size.
69For more information about recipe icons visit https://github.com/getferdi/recipes/blob/master/docs/integration.md#icons`);
70 return;
71 }
72
73 // Check that icons have the right dimensions
74 const pngSize = sizeOf(pngIcon);
75 const pngHasRightSize = pngSize.width === 1024 && pngSize.height === 1024;
76 const svgSize = sizeOf(svgIcon);
77 const svgHasRightSize = svgSize.width === 1024 && svgSize.height === 1024;
78 if (!pngHasRightSize && !svgHasRightSize) {
79 console.log(`⚠️ Could not add your recipe: Icons require to be 1024x1024px in size.
80Both your "icon.png" and "icon.svg" don't seem to have the right size.
81You can use software like Photoshop, GIMP or Photopea (https://www.photopea.com/) to resize your icons.
82For more information about recipe icons visit https://github.com/getferdi/recipes/blob/master/docs/integration.md#icons`);
83 return;
84 } else if (!pngHasRightSize) {
85 console.log(`⚠️ Could not add your recipe: "icon.png" should be to be 1024x1024px in size.
86Please make sure that your "icon.png" has the right size of 1024x1024px in size.
87You can use software like Photoshop, GIMP or Photopea (https://www.photopea.com/) to resize your icons.
88For more information about recipe icons visit https://github.com/getferdi/recipes/blob/master/docs/integration.md#icons`);
89 return;
90 } else if (!svgHasRightSize) {
91 console.log(`⚠️ Could not add your recipe: "icon.svg" should be to be 1024x1024px in size.
92Please make sure that your "icon.svg" has the right size of 1024x1024px in size.
93You can use software like Photoshop, GIMP or Photopea (https://www.photopea.com/) to resize your icons.
94For more information about recipe icons visit https://github.com/getferdi/recipes/blob/master/docs/integration.md#icons`);
95 return;
96 }
97
98 // Read package.json
99 const config = await fs.readJson(packageJson)
100
101 // Make sure it contains all required fields
102 if (!config) {
103 console.log(`⚠️ Could not add your recipe: We could not read or parse your "package.json" configuration.
104Please make sure your "package.json" contains valid JSON.
105For more information about the package.json file visit https://github.com/getferdi/recipes/blob/master/docs/configuration.md`);
106 return;
107 }
108 let configErrors = [];
109 if (!config.id) {
110 configErrors.push("Your package.json contains no 'id' field. This field should contain a unique ID made of lowercase letters (a-z) and hyphens (-)");
111 } else if (!/^[a-z._\-]+$/.test(config.id)) {
112 configErrors.push("Your package.json defines an invalid recipe ID. Please make sure the 'id' field only contains lowercase letters (a-z) and hyphens (-)");
113 }
114 if (!config.name) {
115 configErrors.push("Your package.json contains no 'name' field. This field should contain the name of the service (e.g. 'Google Keep')");
116 }
117 if (!config.version) {
118 configErrors.push("Your package.json contains no 'version' field. This field should contain the a semver-compatible version number for your recipe (e.g. '1.0.0')");
119 }
120 if (!config.config || typeof config.config !== "object") {
121 configErrors.push("Your package.json contains no 'config' object. This field should contain a configuration for your service.");
122 } else if (!config.config.serviceURL) {
123 configErrors.push("Your package.json contains a 'config' object with no 'serviceURL' field. This field should contain the URL of your service.");
124 }
125
126 if (configErrors.length > 0) {
127 console.log(`⚠️ Could not add your recipe: There were errors in your package.json:
128${configErrors.reduce((str, err) => `${str}\n${err}`)}
129For more information about the package.json file visit https://github.com/getferdi/recipes/blob/master/docs/configuration.md`);
130 return;
131 }
132
133 // Index of the current recipe in all.json
134 const packageIndex = all.findIndex(e => e.id === config.id)
135
136 if (packageIndex !== -1) {
137 const currentVersion = config.version;
138 const repoVersion = all[packageIndex].version;
139
140 if (semver.gte(repoVersion, currentVersion)) {
141 console.log(`⚠️ Could not add your recipe: It looks like your recipe is using the same version number as the current recipe.
142Please make sure to increase the version number inside your "package.json" everytime you want to repackage (e.g. '1.0.0' to '1.0.1').
143If you don't increase your version number, Ferdi cannot detect that you have made changes to the recipe.
144For more information about versioning of recipes visit https://github.com/getferdi/recipes/blob/master/docs/configuration.md#config-flags`);
145 return;
146 }
147 }
148
149 if (!await fs.exists(path.join(recipeSrc, 'webview.js'))) {
150 console.log(`⚠️ Could not add your recipe: It looks like your recipe doesn't contain a "webview.js" file.
151Please make sure to create that file and add your features to it.
152For more information about the webview.js file visit https://github.com/getferdi/recipes/blob/master/docs/integration.md#webviewjs and https://github.com/getferdi/recipes/blob/master/docs/frontend_api.md`);
153 return;
154 }
155 if (!await fs.exists(path.join(recipeSrc, 'index.js'))) {
156 console.log(`⚠️ Could not add your recipe: It looks like your recipe doesn't contain a "index.js" file.
157Please make sure to create that file and add your features to it. For most recipes it is enough to simply add the basic template found at https://github.com/getferdi/recipes/blob/master/docs/integration.md#indexjs
158For more information about the webview.js file visit https://github.com/getferdi/recipes/blob/master/docs/integration.md#indexjs and https://github.com/getferdi/recipes/blob/master/docs/backend_api.md`);
159 return;
160 }
161
162 // Package to .tar.gz
163 console.log(`[Info] Packaging ${config.id}...`);
164 compress(recipeSrc, path.join(__dirname, '../../', 'archives', `${config.id}.tar.gz`));
165
166 // Copy recipe src folder to /uncompressed/:id folder
167 console.log('[Info] Copying to uncompressed recipes');
168 await fs.copy('recipe_src', path.join(__dirname, '../../', 'uncompressed', `${config.id}`));
169
170 // Add recipe to all.json
171 console.log('[Info] Adding to all.json');
172 const packageInfo = {
173 "author": config.author || '',
174 "featured": false,
175 "id": config.id,
176 "name": config.name,
177 "version": config.version || '1.0.0',
178 "icons": {
179 "png": `${repo}${config.id}/icon.png`,
180 "svg": `${repo}${config.id}/icon.svg`,
181 },
182 };
183 // Check if package ID already exists
184 if (packageIndex !== -1) {
185 console.log('[Info] Recipe with ID already exists - overwriting');
186 all[packageIndex] = packageInfo;
187 } else {
188 console.log('[Info] No recipe with ID found - creating new.');
189 all.push(packageInfo);
190 }
191
192 // Sort package list alphabetically
193 all = all.sort((a, b) => {
194 var textA = a.id.toLowerCase();
195 var textB = b.id.toLowerCase();
196 return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
197 });
198 await fs.writeJson(allJson, all, {
199 spaces: 2,
200 EOL: '\n',
201 });
202
203 console.log(`✅ Successfully packaged and added new recipe "${config.id}"`);
204}; \ No newline at end of file
diff --git a/scripts/package.js b/scripts/package.js
index a241edc..3726407 100644
--- a/scripts/package.js
+++ b/scripts/package.js
@@ -1,108 +1,6 @@
1/** 1/**
2 * Package recipe into tar.gz file 2 * Package recipe into tar.gz file
3 */ 3 */
4const targz = require('targz'); 4const packageRecipe = require('./api/package');
5const fs = require('fs-extra');
6const path = require('path');
7 5
8console.log('Ferdi Recipe Packager v1.0.0'); 6packageRecipe(); \ No newline at end of file
9
10// Publicly availible link to this repository's uncompressed folder
11// Used for generating public icon URLs
12const repo = 'https://cdn.jsdelivr.net/gh/getferdi/recipes/uncompressed/';
13
14// Helper: Compress src folder into dest file
15const compress = (src, dest) => new Promise((resolve, reject) => {
16 targz.compress({
17 src,
18 dest,
19 tar: {
20 // Don't package .DS_Store files
21 ignore: function(name) {
22 return path.basename(name) === '.DS_Store'
23 }
24 },
25 }, (err) => {
26 if (err) {
27 reject(err);
28 } else {
29 resolve(dest);
30 }
31 });
32});
33
34// Create paths to important files
35const recipeSrc = path.join(__dirname, 'recipe_src');
36const packageJson = path.join(recipeSrc, 'package.json');
37const svgIcon = path.join(recipeSrc, 'icon.svg');
38const pngIcon = path.join(recipeSrc, 'icon.png');
39const allJson = path.join('../', 'all.json');
40
41// Let us work in an async environment
42(async () => {
43 // Check that package.json exists
44 if (!await fs.pathExists(packageJson)) {
45 console.log(`Error: Please add your recipe to ${recipeSrc}. (package.json not found)`);
46 return;
47 }
48 // Check that icons exist
49 if (!await fs.pathExists(svgIcon) || !await fs.pathExists(pngIcon)) {
50 console.log(`Error: Please make sure your recipe contains an icon.png and an icon.svg file.`);
51 return;
52 }
53
54 // Read package.json
55 const config = await fs.readJson(packageJson)
56
57 // Make sure it contains all required fields
58 if (!config || !config.id || !config.name || !config.config) {
59 console.log(`Error: Your package.json does not contain all required fields.
60Please make sure it contains: id, name, config`);
61 return;
62 }
63
64 // Package to .tar.gz
65 console.log(`Packaging ${config.id}...`);
66 compress(recipeSrc, path.join('../', 'archives', `${config.id}.tar.gz`));
67
68 // Copy recipe src folder to /uncompressed/:id folder
69 console.log('Copying to uncompressed recipes');
70 await fs.copy('recipe_src', path.join('../', 'uncompressed', `${config.id}`));
71
72 // Add recipe to all.json
73 console.log('Adding to all.json');
74 const packageInfo = {
75 "author": config.author || '',
76 "featured": false,
77 "id": config.id,
78 "name": config.name,
79 "version": config.version || '1.0.0',
80 "icons": {
81 "png": `${repo}${config.id}/icon.png`,
82 "svg": `${repo}${config.id}/icon.svg`,
83 },
84 };
85 let all = await fs.readJson(allJson);
86 // Check if package ID already exists
87 const packageIndex = all.findIndex(e => e.id === config.id)
88 if (packageIndex !== -1) {
89 console.log('Package with ID already exists - overwriting');
90 all[packageIndex] = packageInfo;
91 } else {
92 console.log('No package with ID found - creating new.');
93 all.push(packageInfo);
94 }
95
96 // Sort package list alphabetically
97 all = all.sort((a, b) => {
98 var textA = a.id.toLowerCase();
99 var textB = b.id.toLowerCase();
100 return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
101 });
102 await fs.writeJson(allJson, all, {
103 spaces: 2,
104 EOL: '\n',
105 });
106
107 console.log(`Successfully packaged and added new package ${config.id}`);
108})(); \ No newline at end of file
diff --git a/scripts/package.json b/scripts/package.json
index b82f946..77d5f2f 100644
--- a/scripts/package.json
+++ b/scripts/package.json
@@ -10,6 +10,7 @@
10 "dir-compare": "^1.7.3", 10 "dir-compare": "^1.7.3",
11 "fs-extra": "^8.1.0", 11 "fs-extra": "^8.1.0",
12 "git-url-parse": "^11.1.2", 12 "git-url-parse": "^11.1.2",
13 "image-size": "^0.8.3",
13 "node-fetch": "^2.6.0", 14 "node-fetch": "^2.6.0",
14 "semver": "^6.3.0", 15 "semver": "^6.3.0",
15 "targz": "^1.0.1" 16 "targz": "^1.0.1"
diff --git a/scripts/yarn.lock b/scripts/yarn.lock
index 5f3a5c8..8007f08 100644
--- a/scripts/yarn.lock
+++ b/scripts/yarn.lock
@@ -141,6 +141,13 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0:
141 resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" 141 resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
142 integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= 142 integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
143 143
144image-size@^0.8.3:
145 version "0.8.3"
146 resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.8.3.tgz#f0b568857e034f29baffd37013587f2c0cad8b46"
147 integrity sha512-SMtq1AJ+aqHB45c3FsB4ERK0UCiA2d3H1uq8s+8T0Pf8A3W4teyBQyaFaktH6xvZqh+npwlKU7i4fJo0r7TYTg==
148 dependencies:
149 queue "6.0.1"
150
144inherits@~2.0.3: 151inherits@~2.0.3:
145 version "2.0.4" 152 version "2.0.4"
146 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 153 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
@@ -237,6 +244,13 @@ pump@^1.0.0:
237 end-of-stream "^1.1.0" 244 end-of-stream "^1.1.0"
238 once "^1.3.1" 245 once "^1.3.1"
239 246
247queue@6.0.1:
248 version "6.0.1"
249 resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.1.tgz#abd5a5b0376912f070a25729e0b6a7d565683791"
250 integrity sha512-AJBQabRCCNr9ANq8v77RJEv73DPbn55cdTb+Giq4X0AVnNVZvMHlYp7XlQiN+1npCZj1DuSmaA2hYVUUDgxFDg==
251 dependencies:
252 inherits "~2.0.3"
253
240readable-stream@^2.3.0, readable-stream@^2.3.5: 254readable-stream@^2.3.0, readable-stream@^2.3.5:
241 version "2.3.6" 255 version "2.3.6"
242 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" 256 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"