aboutsummaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/add_github.js107
-rw-r--r--scripts/api/package.js207
-rw-r--r--scripts/api/require-depts.js20
-rw-r--r--scripts/create.js2
-rw-r--r--scripts/gh_load.js85
-rw-r--r--scripts/load.js39
-rw-r--r--scripts/package-lock.json402
-rw-r--r--scripts/package.js177
-rw-r--r--scripts/package.json29
-rw-r--r--scripts/update.js125
-rw-r--r--scripts/verify-all.js97
-rw-r--r--scripts/verify.js70
12 files changed, 174 insertions, 1186 deletions
diff --git a/scripts/add_github.js b/scripts/add_github.js
deleted file mode 100644
index c3a7ca4..0000000
--- a/scripts/add_github.js
+++ /dev/null
@@ -1,107 +0,0 @@
1/**
2 * Add GitHub repository as recipe
3 */
4require('./api/require-depts')();
5
6const fetch = require('node-fetch');
7const targz = require('targz');
8const fs = require('fs-extra');
9const path = require('path');
10const GitUrlParse = require("git-url-parse");
11const packageRecipe = require('./api/package');
12
13// Helper: Download file to filesystem
14const downloadFile = (async (url, path) => {
15 const res = await fetch(url);
16 const fileStream = fs.createWriteStream(path);
17 await new Promise((resolve, reject) => {
18 res.body.pipe(fileStream);
19 res.body.on("error", (err) => {
20 reject(err);
21 });
22 fileStream.on("finish", function () {
23 resolve();
24 });
25 });
26});
27
28// Helper: Decompress .tar.gz file
29const decompress = (src, dest) => {
30 return new Promise(resolve => {
31 targz.decompress({
32 src,
33 dest
34 }, function (err) {
35 if (err) {
36 console.log('⚠️ Could not add your recipe: There was an error while decompressing your GitHub repository file: ', err);
37 }
38 resolve();
39 });
40 })
41}
42
43const repo = process.argv[2];
44
45if (!repo || !/https:\/\/github\.com\/[^\/]+\/[^\/]+\/?/gi.test(repo)) {
46 console.log(`⚠️ Could not add your recipe: The GitHub URL you provided doesn't seem to be valid.
47You should use this command like "yarn github https://github.com/user/repo".
48Please make sure you provide a URL in the format "https://github.com/user/repo"
49For more information about this script visit https://github.com/getferdi/recipes/blob/master/docs/integration.md#publishing
50If you want to package a local recipe, please use "yarn package" instead.`);
51 return;
52}
53
54const repoInfo = GitUrlParse(repo);
55const tempDir = path.join(__dirname, 'tmp');
56
57const recipeSrc = path.join(__dirname, 'recipe_src');
58const recipeSrcTmp = path.join(__dirname, 'recipe_src_tmp');
59
60const compressed = path.join(__dirname, 'tmp.tar.gz');
61
62// Let us work in an async environment
63(async () => {
64 console.log("[Info] Creating temporary directory");
65
66 await fs.ensureDir(tempDir);
67 await fs.ensureDir(recipeSrc);
68 await fs.ensureDir(recipeSrcTmp);
69
70 console.log("[Info] Downloading " + repo);
71
72 await downloadFile(
73 `https://github.com/${repoInfo.owner}/${repoInfo.name}/archive/master.tar.gz`,
74 compressed
75 );
76
77 console.log("[Info] Decompressing repository");
78
79 await decompress(compressed, tempDir);
80
81 console.log("[Info] Moving 'recipe_src' to 'recipe_src_tmp'");
82
83 await fs.move(recipeSrc, recipeSrcTmp, {overwrite: true});
84 await fs.move(
85 path.join(tempDir, `${repoInfo.name}-master`),
86 recipeSrc,
87 {overwrite: true}
88 );
89
90 console.log("[Info] Packaging your recipe");
91 try {
92 await packageRecipe();
93 } catch(e) {
94 return;
95 }
96
97 console.log("[Info] Deleting temporarydownloaded repository");
98
99 await fs.remove(compressed);
100 await fs.remove(recipeSrc);
101
102 console.log("[Info] Moving back 'recipe_src_tmp' to 'recipe_src'");
103
104 await fs.move(recipeSrcTmp, recipeSrc);
105
106 console.log(`✅ Successfully packaged the recipe from your GitHub repository`);
107})(); \ No newline at end of file
diff --git a/scripts/api/package.js b/scripts/api/package.js
deleted file mode 100644
index c64132f..0000000
--- a/scripts/api/package.js
+++ /dev/null
@@ -1,207 +0,0 @@
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 let errorMessages = []
44
45 // Check that package.json exists
46 if (!await fs.pathExists(packageJson)) {
47 errorMessages.push(`⚠️ It looks like your recipe is missing the "package.json" file.
48 ↪ Please add your recipe to ${recipeSrc} and make sure that folder contains a "package.json".
49 ℹ For more information on how to add your recipe visit: https://github.com/getferdi/recipes/blob/master/docs/integration.md`);
50 }
51
52 // Check that icons exist
53 const hasSvg = await fs.pathExists(svgIcon);
54 if (!hasSvg) {
55 errorMessages.push(`⚠️ It looks like your recipe is missing the "icon.svg" file.
56 ↪ Please make sure your recipe contains an icon.svg file.
57 ℹ For more information about recipe icons visit: https://github.com/getferdi/recipes/blob/master/docs/integration.md#icons`);
58 }
59
60 const hasPng = await fs.pathExists(pngIcon);
61 if (!hasPng) {
62 errorMessages.push(`⚠️ It looks like your recipe is missing the "icon.png" file.
63 ↪ Please make sure your recipe contains an icon.png file.
64 ↪ Please also make sure that your PNG icon is 1024x1024px in size.
65 ℹ For more information about recipe icons visit: https://github.com/getferdi/recipes/blob/master/docs/integration.md#icons`);
66 }
67
68 // Check that icons have the right dimensions
69 if (hasSvg) {
70 const svgSize = sizeOf(svgIcon);
71 const svgHasRightSize = svgSize.width === svgSize.height;
72 if (!svgHasRightSize) {
73 errorMessages.push(`⚠️ It looks like your "icon.svg" is not a square.
74 ↪ Please make sure that your "icon.svg" has the right dimensions to make a square- width and height should be the same.
75 ℹ You can use software like Photoshop, GIMP or Photopea (https://www.photopea.com/) to resize your icons.
76 ℹ For more information about recipe icons visit: https://github.com/getferdi/recipes/blob/master/docs/integration.md#icons`);
77 }
78 }
79
80 if (hasPng) {
81 const pngSize = sizeOf(pngIcon);
82 const pngHasRightSize = pngSize.width === 1024 && pngSize.height === 1024;
83 if (hasPng && !pngHasRightSize) {
84 errorMessages.push(`⚠️ it looks like your "icon.png" is not 1024x1024 in size.
85 ↪ Please make sure that your "icon.png" has the right dimeensions of 1024x1024px.
86 ℹ You can use software like Photoshop, GIMP or Photopea (https://www.photopea.com/) to resize your icons.
87 ℹ For more information about recipe icons visit: https://github.com/getferdi/recipes/blob/master/docs/integration.md#icons`);
88 }
89 }
90
91 // Read package.json
92 const config = await fs.readJson(packageJson)
93
94 // Make sure it contains all required fields
95 if (!config) {
96 errorMessages.push(`⚠️ It looks like your "package.json" file could not read or parsed.
97 ↪ Please make sure your "package.json" contains valid JSON.
98 ℹ You can use a JSON Validator like JSONLint: https://jsonlint.com/
99 ℹ For more information about the package.json file visit: https://github.com/getferdi/recipes/blob/master/docs/configuration.md`);
100 }
101
102 if (!config.id) {
103 errorMessages.push(`⚠️ It looks like your "package.json" does not contain an "id" field.
104 ↪ Please make sure the "id" field contains a unique ID made of lowercase letters (a-z), numbers (0-9), hyphens (-), periods (.), and underscores (_)
105 ℹ For more information about the package.json file visit: https://github.com/getferdi/recipes/blob/master/docs/configuration.md`);
106 } else if (!/^[a-z._\-]+$/.test(config.id)) {
107 errorMessages.push(`⚠️ It looks like your "package.json" defines an invalid recipe ID.
108 ↪ Please make sure the "id" field only contains lowercase letters (a-z), numbers (0-9), hyphens (-), periods (.), and underscores (_)
109 ℹ For more information about the package.json file visit: https://github.com/getferdi/recipes/blob/master/docs/configuration.md`);
110 }
111 if (!config.name) {
112 errorMessages.push(`⚠️ It looks like your "package.json" does not contain a "name" field.
113 ↪ Please make sure the "name" field contains the name of the service (e.g. "Google Keep")
114 ℹ For more information about the package.json file visit: https://github.com/getferdi/recipes/blob/master/docs/configuration.md`);
115 }
116 if (!config.version) {
117 errorMessages.push(`⚠️ It looks like your "package.json" does not contain a "version" field.
118 ↪ Please make sure the "version" field contains a semver-compatible version number for your recipe (e.g. "1.0.0")
119 ℹ For more information about the package.json file visit: https://github.com/getferdi/recipes/blob/master/docs/configuration.md`);
120 }
121 if (!config.config || typeof config.config !== "object") {
122 errorMessages.push(`⚠️ It looks like your "package.json" does not contain a "config" object.
123 ↪ Please make sure the "config" object contains a configuration for your service.
124 ℹ For more information about the package.json file visit: https://github.com/getferdi/recipes/blob/master/docs/configuration.md`);
125 } else if (!config.config.serviceURL) {
126 errorMessages.push(`⚠️ It looks like your "package.json" does not contain a "config" object without a "serviceURL" field.
127 ↪ Please make sure the "serviceURL" contains the URL of your service.
128 ℹ For more information about the package.json file visit: https://github.com/getferdi/recipes/blob/master/docs/configuration.md`);
129 }
130
131 // Index of the current recipe in all.json
132 const packageIndex = all.findIndex(e => e.id === config.id)
133
134 if (packageIndex !== -1) {
135 const currentVersion = config.version;
136 const repoVersion = all[packageIndex].version;
137
138 if (semver.gte(repoVersion, currentVersion)) {
139 errorMessages.push(`⚠️ It looks like your recipe is using the same version number as the current recipe.
140 ↪ Please 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').
141 ↪ If you don't increase your version number, Ferdi cannot detect that you have made changes to the recipe.
142 ℹ For more information about versioning of recipes visit: https://github.com/getferdi/recipes/blob/master/docs/configuration.md#config-flags`);
143 }
144 }
145
146 if (!await fs.exists(path.join(recipeSrc, 'webview.js'))) {
147 errorMessages.push(`⚠️ It looks like your recipe doesn't contain a "webview.js" file.
148 ↪ Please make sure to create that file and add your features to it.
149 ℹ For 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`);
150 }
151 if (!await fs.exists(path.join(recipeSrc, 'index.js'))) {
152 errorMessages.push(`⚠️ It looks like your recipe doesn't contain a "index.js" file.
153 ↪ Please 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
154 ℹ For 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`);
155 }
156
157 if (errorMessages.length > 0) {
158 console.log(`❌ Could not add your recipe, the following ${errorMessages.length} error(s) were found:
159${errorMessages.reduce((str, err) => `${str}\n${err}`)}
160ℹ For more information, visit: https://github.com/getferdi/recipes/tree/master/docs`);
161 return;
162 }
163
164 // Package to .tar.gz
165 console.log(`[Info] Packaging ${config.id}...`);
166 compress(recipeSrc, path.join(__dirname, '../../', 'archives', `${config.id}.tar.gz`));
167
168 // Copy recipe src folder to /uncompressed/:id folder
169 console.log('[Info] Copying to uncompressed recipes');
170 await fs.copy('recipe_src', path.join(__dirname, '../../', 'uncompressed', `${config.id}`));
171
172 // Add recipe to all.json
173 console.log('[Info] Adding to all.json');
174 const isFeatured = packageIndex !== -1 ? all[packageIndex].featured : false;
175 const packageInfo = {
176 "author": config.author || '',
177 "featured": isFeatured,
178 "id": config.id,
179 "name": config.name,
180 "version": config.version || '1.0.0',
181 "icons": {
182 "png": `${repo}${config.id}/icon.png`,
183 "svg": `${repo}${config.id}/icon.svg`,
184 },
185 };
186 // Check if package ID already exists
187 if (packageIndex !== -1) {
188 console.log('[Info] Recipe with ID already exists - overwriting');
189 all[packageIndex] = packageInfo;
190 } else {
191 console.log('[Info] No recipe with ID found - creating new.');
192 all.push(packageInfo);
193 }
194
195 // Sort package list alphabetically
196 all = all.sort((a, b) => {
197 var textA = a.id.toLowerCase();
198 var textB = b.id.toLowerCase();
199 return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
200 });
201 await fs.writeJson(allJson, all, {
202 spaces: 2,
203 EOL: '\n',
204 });
205
206 console.log(`✅ Successfully packaged and added new recipe "${config.id}"`);
207};
diff --git a/scripts/api/require-depts.js b/scripts/api/require-depts.js
deleted file mode 100644
index c9c8814..0000000
--- a/scripts/api/require-depts.js
+++ /dev/null
@@ -1,20 +0,0 @@
1/**
2 * Require the dependencies to be installed or fail otherwise
3 */
4module.exports = () => {
5 try {
6 // Try to include some package to see if it throws an error
7 require('targz');
8 require('fs-extra');
9 require('git-url-parse');
10 require('image-size');
11 require('semver');
12 } catch (e) {
13 console.log(`⚠️ Could not add your recipe: Please make sure to install the dependencies first!
14It looks like you havn't run "npm install" yet. Please run that command in order to install all the dependencies required to run the scripts.
15If you get an error similar to "command not found: npm" while installing the dependencies, make sure to install npm first using the guide at https://www.npmjs.com/get-npm.
16If you've already installed the dependencies before, please re-run "npm install" again as the dependencies might have changed.
17For more information about installing this script visit https://github.com/getferdi/recipes/blob/master/docs/integration.md#publishing`);
18 process.exit(0);
19 }
20} \ No newline at end of file
diff --git a/scripts/create.js b/scripts/create.js
index 724eeb6..12b0c15 100644
--- a/scripts/create.js
+++ b/scripts/create.js
@@ -66,5 +66,5 @@ const filesThatNeedTextReplace = ['package.json', 'index.js', 'README.md'];
66What's next? 66What's next?
67- Make sure you restart Ferdi in order for the recipe to show up 67- Make sure you restart Ferdi in order for the recipe to show up
68- Customise "webview.js", "package.json", "icon.svg" and "icon.png (see https://github.com/getferdi/recipes/blob/master/docs/integration.md#recipe-structure) 68- Customise "webview.js", "package.json", "icon.svg" and "icon.png (see https://github.com/getferdi/recipes/blob/master/docs/integration.md#recipe-structure)
69- Publish and package your recipe (see https://github.com/getferdi/recipes/blob/master/docs/integration.md#publishing)`); 69- Publish your recipe (see https://github.com/getferdi/recipes/blob/master/docs/integration.md#publishing)`);
70})(); 70})();
diff --git a/scripts/gh_load.js b/scripts/gh_load.js
deleted file mode 100644
index e72cf23..0000000
--- a/scripts/gh_load.js
+++ /dev/null
@@ -1,85 +0,0 @@
1/**
2 * Add GitHub repository as recipe
3 */
4const fetch = require('node-fetch');
5const targz = require('targz');
6const fs = require('fs-extra');
7const path = require('path');
8const GitUrlParse = require("git-url-parse");
9
10// Helper: Download file to filesystem
11const downloadFile = (async (url, path) => {
12 const res = await fetch(url);
13 const fileStream = fs.createWriteStream(path);
14 await new Promise((resolve, reject) => {
15 res.body.pipe(fileStream);
16 res.body.on("error", (err) => {
17 reject(err);
18 });
19 fileStream.on("finish", function () {
20 resolve();
21 });
22 });
23});
24
25// Helper: Decompress .tar.gz file
26const decompress = (src, dest) => {
27 return new Promise(resolve => {
28 targz.decompress({
29 src,
30 dest
31 }, function (err) {
32 if (err) {
33 console.log('Error while decompressing recipe:', err);
34 }
35 resolve();
36 });
37 })
38}
39
40const repo = process.argv[2];
41
42if (!repo || !/https:\/\/github\.com\/[^\/]+\/[^\/]+\/?/gi.test(repo)) {
43 console.log("Please provide a valid repository URL");
44 return;
45}
46
47const repoInfo = GitUrlParse(repo);
48const tempDir = path.join(__dirname, 'tmp');
49
50const recipeSrc = path.join(__dirname, 'recipe_src');
51const recipeSrcTmp = path.join(__dirname, 'recipe_src_tmp');
52
53const compressed = path.join(__dirname, 'tmp.tar.gz');
54
55// Let us work in an async environment
56(async () => {
57 console.log("Creating temporary directory");
58
59 await fs.ensureDir(tempDir);
60 await fs.ensureDir(recipeSrc);
61 await fs.ensureDir(recipeSrcTmp);
62
63 console.log("Downloading " + repo);
64
65 await downloadFile(
66 `https://github.com/${repoInfo.owner}/${repoInfo.name}/archive/master.tar.gz`,
67 compressed
68 );
69
70 console.log("Decompressing tarball");
71
72 await decompress(compressed, tempDir);
73
74 console.log("Moving directories");
75
76 await fs.move(recipeSrc, recipeSrcTmp, {overwrite: true});
77 await fs.move(
78 path.join(tempDir, `${repoInfo.name}-master`),
79 recipeSrc,
80 {overwrite: true}
81 );
82
83 await fs.remove(compressed);
84 await fs.remove(recipeSrcTmp);
85})(); \ No newline at end of file
diff --git a/scripts/load.js b/scripts/load.js
deleted file mode 100644
index 3c11240..0000000
--- a/scripts/load.js
+++ /dev/null
@@ -1,39 +0,0 @@
1/**
2 * Load recipe into development folder
3 */
4const fs = require('fs-extra');
5const path = require('path');
6
7console.log('load: Load recipe into development folder');
8console.log('This command will empty the recipe_src folder. Please make sure that there are no important files in that directory.');
9
10const recipe = process.argv[2];
11if (!recipe) {
12 console.log('Usage: yarn load [recipe]');
13 return;
14}
15
16console.log(`Loading ${recipe}`);
17
18// Create paths to important files
19const recipeSrc = path.join(__dirname, 'recipe_src');
20const recipePkg = path.join(__dirname, '../', 'uncompressed', recipe);
21
22// Let us work in an async environment
23(async () => {
24 // Check that recipe folder exists
25 if (!await fs.pathExists(recipePkg)) {
26 console.log(`Error: Recipe ${recipe} does not exist.`);
27 return;
28 }
29
30 console.log('Emptying directory...');
31
32 await fs.emptyDir(recipeSrc);
33
34 console.log('Copying data...');
35
36 await fs.copy(recipePkg, recipeSrc);
37
38 console.log('Done');
39})(); \ No newline at end of file
diff --git a/scripts/package-lock.json b/scripts/package-lock.json
deleted file mode 100644
index 4eafb50..0000000
--- a/scripts/package-lock.json
+++ /dev/null
@@ -1,402 +0,0 @@
1{
2 "name": "ferdi-recipes-scripts",
3 "version": "1.0.0",
4 "lockfileVersion": 1,
5 "requires": true,
6 "dependencies": {
7 "@types/node": {
8 "version": "10.17.19",
9 "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.19.tgz",
10 "integrity": "sha512-46/xThm3zvvc9t9/7M3AaLEqtOpqlYYYcCZbpYVAQHG20+oMZBkae/VMrn4BTi6AJ8cpack0mEXhGiKmDNbLrQ=="
11 },
12 "balanced-match": {
13 "version": "1.0.0",
14 "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
15 "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
16 },
17 "bl": {
18 "version": "1.2.2",
19 "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
20 "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
21 "requires": {
22 "readable-stream": "^2.3.5",
23 "safe-buffer": "^5.1.1"
24 }
25 },
26 "bluebird": {
27 "version": "3.4.1",
28 "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.1.tgz",
29 "integrity": "sha1-tzHd9I4t077awudeEhWhG8uR+gc="
30 },
31 "brace-expansion": {
32 "version": "1.1.11",
33 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
34 "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
35 "requires": {
36 "balanced-match": "^1.0.0",
37 "concat-map": "0.0.1"
38 }
39 },
40 "buffer-alloc": {
41 "version": "1.2.0",
42 "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
43 "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
44 "requires": {
45 "buffer-alloc-unsafe": "^1.1.0",
46 "buffer-fill": "^1.0.0"
47 }
48 },
49 "buffer-alloc-unsafe": {
50 "version": "1.1.0",
51 "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
52 "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
53 },
54 "buffer-equal": {
55 "version": "1.0.0",
56 "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz",
57 "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74="
58 },
59 "buffer-fill": {
60 "version": "1.0.0",
61 "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
62 "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw="
63 },
64 "chownr": {
65 "version": "1.1.4",
66 "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
67 "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
68 },
69 "colors": {
70 "version": "1.0.3",
71 "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
72 "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs="
73 },
74 "commander": {
75 "version": "2.9.0",
76 "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
77 "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
78 "requires": {
79 "graceful-readlink": ">= 1.0.0"
80 }
81 },
82 "concat-map": {
83 "version": "0.0.1",
84 "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
85 "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
86 },
87 "core-util-is": {
88 "version": "1.0.2",
89 "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
90 "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
91 },
92 "dir-compare": {
93 "version": "1.7.3",
94 "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-1.7.3.tgz",
95 "integrity": "sha512-YsKGvVzy5938B9Rokol3N9jq4QcFhlgostWr+TX4omexpR0UztlbYu5UZRxZ0WTeLMFt+7TCIm7nn27byQKZHw==",
96 "requires": {
97 "@types/node": "^10.12.18",
98 "bluebird": "3.4.1",
99 "buffer-equal": "1.0.0",
100 "colors": "1.0.3",
101 "commander": "2.9.0",
102 "minimatch": "3.0.2"
103 }
104 },
105 "end-of-stream": {
106 "version": "1.4.4",
107 "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
108 "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
109 "requires": {
110 "once": "^1.4.0"
111 }
112 },
113 "fs-constants": {
114 "version": "1.0.0",
115 "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
116 "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
117 },
118 "fs-extra": {
119 "version": "8.1.0",
120 "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
121 "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
122 "requires": {
123 "graceful-fs": "^4.2.0",
124 "jsonfile": "^4.0.0",
125 "universalify": "^0.1.0"
126 }
127 },
128 "git-up": {
129 "version": "4.0.1",
130 "resolved": "https://registry.npmjs.org/git-up/-/git-up-4.0.1.tgz",
131 "integrity": "sha512-LFTZZrBlrCrGCG07/dm1aCjjpL1z9L3+5aEeI9SBhAqSc+kiA9Or1bgZhQFNppJX6h/f5McrvJt1mQXTFm6Qrw==",
132 "requires": {
133 "is-ssh": "^1.3.0",
134 "parse-url": "^5.0.0"
135 }
136 },
137 "git-url-parse": {
138 "version": "11.1.2",
139 "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.1.2.tgz",
140 "integrity": "sha512-gZeLVGY8QVKMIkckncX+iCq2/L8PlwncvDFKiWkBn9EtCfYDbliRTTp6qzyQ1VMdITUfq7293zDzfpjdiGASSQ==",
141 "requires": {
142 "git-up": "^4.0.0"
143 }
144 },
145 "graceful-fs": {
146 "version": "4.2.3",
147 "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
148 "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
149 },
150 "graceful-readlink": {
151 "version": "1.0.1",
152 "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
153 "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
154 },
155 "image-size": {
156 "version": "0.8.3",
157 "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.8.3.tgz",
158 "integrity": "sha512-SMtq1AJ+aqHB45c3FsB4ERK0UCiA2d3H1uq8s+8T0Pf8A3W4teyBQyaFaktH6xvZqh+npwlKU7i4fJo0r7TYTg==",
159 "requires": {
160 "queue": "6.0.1"
161 }
162 },
163 "inherits": {
164 "version": "2.0.4",
165 "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
166 "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
167 },
168 "is-docker": {
169 "version": "2.0.0",
170 "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz",
171 "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ=="
172 },
173 "is-ssh": {
174 "version": "1.3.1",
175 "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz",
176 "integrity": "sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg==",
177 "requires": {
178 "protocols": "^1.1.0"
179 }
180 },
181 "is-wsl": {
182 "version": "2.2.0",
183 "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
184 "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
185 "requires": {
186 "is-docker": "^2.0.0"
187 }
188 },
189 "isarray": {
190 "version": "1.0.0",
191 "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
192 "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
193 },
194 "jsonfile": {
195 "version": "4.0.0",
196 "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
197 "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
198 "requires": {
199 "graceful-fs": "^4.1.6"
200 }
201 },
202 "minimatch": {
203 "version": "3.0.2",
204 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.2.tgz",
205 "integrity": "sha1-DzmKcwDqRB6cNIyD2Yq4ydv5xAo=",
206 "requires": {
207 "brace-expansion": "^1.0.0"
208 }
209 },
210 "minimist": {
211 "version": "1.2.5",
212 "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
213 "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
214 },
215 "mkdirp": {
216 "version": "0.5.5",
217 "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
218 "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
219 "requires": {
220 "minimist": "^1.2.5"
221 }
222 },
223 "node-fetch": {
224 "version": "2.6.0",
225 "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
226 "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
227 },
228 "normalize-url": {
229 "version": "3.3.0",
230 "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz",
231 "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg=="
232 },
233 "once": {
234 "version": "1.4.0",
235 "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
236 "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
237 "requires": {
238 "wrappy": "1"
239 }
240 },
241 "open": {
242 "version": "7.0.3",
243 "resolved": "https://registry.npmjs.org/open/-/open-7.0.3.tgz",
244 "integrity": "sha512-sP2ru2v0P290WFfv49Ap8MF6PkzGNnGlAwHweB4WR4mr5d2d0woiCluUeJ218w7/+PmoBy9JmYgD5A4mLcWOFA==",
245 "requires": {
246 "is-docker": "^2.0.0",
247 "is-wsl": "^2.1.1"
248 }
249 },
250 "parse-path": {
251 "version": "4.0.1",
252 "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.1.tgz",
253 "integrity": "sha512-d7yhga0Oc+PwNXDvQ0Jv1BuWkLVPXcAoQ/WREgd6vNNoKYaW52KI+RdOFjI63wjkmps9yUE8VS4veP+AgpQ/hA==",
254 "requires": {
255 "is-ssh": "^1.3.0",
256 "protocols": "^1.4.0"
257 }
258 },
259 "parse-url": {
260 "version": "5.0.1",
261 "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-5.0.1.tgz",
262 "integrity": "sha512-flNUPP27r3vJpROi0/R3/2efgKkyXqnXwyP1KQ2U0SfFRgdizOdWfvrrvJg1LuOoxs7GQhmxJlq23IpQ/BkByg==",
263 "requires": {
264 "is-ssh": "^1.3.0",
265 "normalize-url": "^3.3.0",
266 "parse-path": "^4.0.0",
267 "protocols": "^1.4.0"
268 }
269 },
270 "process-nextick-args": {
271 "version": "2.0.1",
272 "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
273 "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
274 },
275 "protocols": {
276 "version": "1.4.7",
277 "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.7.tgz",
278 "integrity": "sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg=="
279 },
280 "pump": {
281 "version": "1.0.3",
282 "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz",
283 "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==",
284 "requires": {
285 "end-of-stream": "^1.1.0",
286 "once": "^1.3.1"
287 }
288 },
289 "queue": {
290 "version": "6.0.1",
291 "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.1.tgz",
292 "integrity": "sha512-AJBQabRCCNr9ANq8v77RJEv73DPbn55cdTb+Giq4X0AVnNVZvMHlYp7XlQiN+1npCZj1DuSmaA2hYVUUDgxFDg==",
293 "requires": {
294 "inherits": "~2.0.3"
295 }
296 },
297 "readable-stream": {
298 "version": "2.3.7",
299 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
300 "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
301 "requires": {
302 "core-util-is": "~1.0.0",
303 "inherits": "~2.0.3",
304 "isarray": "~1.0.0",
305 "process-nextick-args": "~2.0.0",
306 "safe-buffer": "~5.1.1",
307 "string_decoder": "~1.1.1",
308 "util-deprecate": "~1.0.1"
309 },
310 "dependencies": {
311 "safe-buffer": {
312 "version": "5.1.2",
313 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
314 "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
315 }
316 }
317 },
318 "safe-buffer": {
319 "version": "5.2.0",
320 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
321 "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
322 },
323 "semver": {
324 "version": "6.3.0",
325 "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
326 "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
327 },
328 "string_decoder": {
329 "version": "1.1.1",
330 "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
331 "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
332 "requires": {
333 "safe-buffer": "~5.1.0"
334 },
335 "dependencies": {
336 "safe-buffer": {
337 "version": "5.1.2",
338 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
339 "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
340 }
341 }
342 },
343 "tar-fs": {
344 "version": "1.16.3",
345 "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz",
346 "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==",
347 "requires": {
348 "chownr": "^1.0.1",
349 "mkdirp": "^0.5.1",
350 "pump": "^1.0.0",
351 "tar-stream": "^1.1.2"
352 }
353 },
354 "tar-stream": {
355 "version": "1.6.2",
356 "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz",
357 "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==",
358 "requires": {
359 "bl": "^1.0.0",
360 "buffer-alloc": "^1.2.0",
361 "end-of-stream": "^1.0.0",
362 "fs-constants": "^1.0.0",
363 "readable-stream": "^2.3.0",
364 "to-buffer": "^1.1.1",
365 "xtend": "^4.0.0"
366 }
367 },
368 "targz": {
369 "version": "1.0.1",
370 "resolved": "https://registry.npmjs.org/targz/-/targz-1.0.1.tgz",
371 "integrity": "sha1-j3alI2lM3t+7XWCkB2/27uzFOY8=",
372 "requires": {
373 "tar-fs": "^1.8.1"
374 }
375 },
376 "to-buffer": {
377 "version": "1.1.1",
378 "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz",
379 "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg=="
380 },
381 "universalify": {
382 "version": "0.1.2",
383 "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
384 "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
385 },
386 "util-deprecate": {
387 "version": "1.0.2",
388 "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
389 "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
390 },
391 "wrappy": {
392 "version": "1.0.2",
393 "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
394 "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
395 },
396 "xtend": {
397 "version": "4.0.2",
398 "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
399 "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
400 }
401 }
402}
diff --git a/scripts/package.js b/scripts/package.js
index d650121..82dfc81 100644
--- a/scripts/package.js
+++ b/scripts/package.js
@@ -1,8 +1,177 @@
1/** 1/**
2 * Package recipe into tar.gz file 2 * Package all recipes
3 */ 3 */
4require('./api/require-depts')(); 4const targz = require('targz');
5const fs = require('fs-extra');
6const path = require('path');
7const sizeOf = require('image-size');
5 8
6const packageRecipe = require('./api/package'); 9// Publicly availible link to this repository's recipe folder
10// Used for generating public icon URLs
11const repo = 'https://cdn.jsdelivr.net/gh/getferdi/recipes/recipes/';
7 12
8packageRecipe(); \ No newline at end of file 13// Helper: Compress src folder into dest file
14const compress = (src, dest) => new Promise((resolve, reject) => {
15 targz.compress({
16 src,
17 dest,
18 tar: {
19 // Don't package .DS_Store files
20 ignore: function(name) {
21 return path.basename(name) === '.DS_Store'
22 }
23 },
24 }, (err) => {
25 if (err) {
26 reject(err);
27 } else {
28 resolve(dest);
29 }
30 });
31});
32
33// Let us work in an async environment
34(async () => {
35 // Create paths to important files
36 const recipesFolder = path.join(__dirname, '../recipes');
37 const outputFolder = path.join(__dirname, '../archives');
38 const allJson = path.join(__dirname, '../all.json');
39 const featuredFile = path.join(__dirname, '../featured.json');
40 const featuredRecipes = await fs.readJSON(featuredFile);
41 let recipeList = [];
42 let unsuccessful = 0;
43
44 await fs.ensureDir(outputFolder);
45 await fs.emptyDir(outputFolder);
46 await fs.remove(allJson);
47
48 const availableRecipes = fs.readdirSync(recipesFolder, { withFileTypes: true })
49 .filter(dir => dir.isDirectory())
50 .map(dir => dir.name);
51
52 for(let recipe of availableRecipes) {
53 const recipeSrc = path.join(recipesFolder, recipe);
54 const packageJson = path.join(recipeSrc, 'package.json');
55 const svgIcon = path.join(recipeSrc, 'icon.svg');
56 const pngIcon = path.join(recipeSrc, 'icon.png');
57
58 // Check that package.json exists
59 if (!await fs.pathExists(packageJson)) {
60 console.log(`⚠️ Couldn't package "${recipe}": Folder doesn't contain a "package.json".`);
61 unsuccessful++;
62 continue;
63 }
64
65 // Check that icons exist
66 const hasSvg = await fs.pathExists(svgIcon);
67 const hasPng = await fs.pathExists(pngIcon);
68 if (!hasSvg && !hasPng) {
69 console.log(`⚠️ Couldn't package "${recipe}": Recipe doesn't contain an icon SVG and PNG`);
70 unsuccessful++;
71 } else if (!hasSvg) {
72 console.log(`⚠️ Couldn't package "${recipe}": Recipe doesn't contain an icon SVG`);
73 unsuccessful++;
74 continue;
75 } else if (!hasPng) {
76 console.log(`⚠️ Couldn't package "${recipe}": Recipe doesn't contain an icon PNG`);
77 unsuccessful++;
78 continue;
79 }
80
81 // Check icons sizes
82 const svgSize = sizeOf(svgIcon);
83 const svgHasRightSize = svgSize.width === svgSize.height;
84 if (!svgHasRightSize) {
85 console.log(`⚠️ Couldn't package "${recipe}": Recipe SVG icon isn't a square`);
86 unsuccessful++;
87 continue;
88 }
89
90 const pngSize = sizeOf(pngIcon);
91 const pngHasRightSize = pngSize.width === 1024 && pngSize.height === 1024;
92 if (!pngHasRightSize) {
93 console.log(`⚠️ Couldn't package "${recipe}": Recipe PNG icon dimensions should be 1024x1024`);
94 unsuccessful++;
95 continue;
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(`⚠️ Couldn't package "${recipe}": Could not read or parse "package.json"`);
104 unsuccessful++;
105 continue;
106 }
107 let configErrors = [];
108 if (!config.id) {
109 configErrors.push("The recipe's package.json contains no 'id' field. This field should contain a unique ID made of lowercase letters (a-z), numbers (0-9), hyphens (-), periods (.), and underscores (_)");
110 } else if (!/^[a-zA-Z0-9._\-]+$/.test(config.id)) {
111 configErrors.push("The recipe's package.json defines an invalid recipe ID. Please make sure the 'id' field only contains lowercase letters (a-z), numbers (0-9), hyphens (-), periods (.), and underscores (_)");
112 }
113 if (!config.name) {
114 configErrors.push("The recipe's package.json contains no 'name' field. This field should contain the name of the service (e.g. 'Google Keep')");
115 }
116 if (!config.version) {
117 configErrors.push("The recipe's package.json contains no 'version' field. This field should contain the a semver-compatible version number for your recipe (e.g. '1.0.0')");
118 }
119 if (!config.config || typeof config.config !== "object") {
120 configErrors.push("The recipe's package.json contains no 'config' object. This field should contain a configuration for your service.");
121 }
122
123 if (configErrors.length > 0) {
124 console.log(`⚠️ Couldn't package "${recipe}": There were errors in the recipe's package.json:
125 ${configErrors.reduce((str, err) => `${str}\n${err}`)}`);
126 unsuccessful++;
127 continue;
128 }
129
130 if (!await fs.exists(path.join(recipeSrc, 'webview.js'))) {
131 console.log(`⚠️ Couldn't package "${recipe}": The recipe doesn't contain a "webview.js"`);
132 unsuccessful++;
133 continue;
134 }
135 if (!await fs.exists(path.join(recipeSrc, 'index.js'))) {
136 console.log(`⚠️ Couldn't package "${recipe}": The recipe doesn't contain a "index.js"`);
137 unsuccessful++;
138 continue;
139 }
140
141 // Package to .tar.gz
142 compress(recipeSrc, path.join(outputFolder, `${config.id}.tar.gz`));
143
144 // Add recipe to all.json
145 const isFeatured = featuredRecipes.includes(config.id);
146 const packageInfo = {
147 "author": config.author || '',
148 "featured": isFeatured,
149 "id": config.id,
150 "name": config.name,
151 "version": config.version || '1.0.0',
152 "icons": {
153 "png": `${repo}${config.id}/icon.png`,
154 "svg": `${repo}${config.id}/icon.svg`,
155 },
156 };
157 recipeList.push(packageInfo);
158 }
159
160
161 // Sort package list alphabetically
162 recipeList = recipeList.sort((a, b) => {
163 var textA = a.id.toLowerCase();
164 var textB = b.id.toLowerCase();
165 return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
166 });
167 await fs.writeJson(allJson, recipeList, {
168 spaces: 2,
169 EOL: '\n',
170 });
171
172 console.log(`✅ Successfully packaged and added ${recipeList.length} recipes (${unsuccessful} unsuccessful recipes)`);
173
174 if (unsuccessful > 0) {
175 process.exit(1);
176 }
177})();
diff --git a/scripts/package.json b/scripts/package.json
deleted file mode 100644
index 1402e52..0000000
--- a/scripts/package.json
+++ /dev/null
@@ -1,29 +0,0 @@
1{
2 "name": "ferdi-recipes-scripts",
3 "version": "1.0.0",
4 "description": "Scripts for managing the ferdi-recipes repository",
5 "main": "index.js",
6 "repository": "https://github.com/getferdi/recipes",
7 "author": "The Ferdi Team",
8 "license": "MIT",
9 "dependencies": {
10 "dir-compare": "^1.7.3",
11 "fs-extra": "^8.1.0",
12 "git-url-parse": "^11.1.2",
13 "image-size": "^0.8.3",
14 "node-fetch": "^2.6.0",
15 "open": "^7.0.3",
16 "semver": "^6.3.0",
17 "targz": "^1.0.1"
18 },
19 "scripts": {
20 "package": "node package.js",
21 "verify": "node verify.js",
22 "verify-all": "node verify-all.js",
23 "update": "node update.js",
24 "github": "node add_github.js",
25 "gh-load": "node gh_load.js",
26 "load": "node load.js",
27 "create": "node create.js"
28 }
29}
diff --git a/scripts/update.js b/scripts/update.js
deleted file mode 100644
index 7538019..0000000
--- a/scripts/update.js
+++ /dev/null
@@ -1,125 +0,0 @@
1/**
2 * Update recipes from a Ferdi-compatible server
3 */
4const fetch = require('node-fetch');
5const targz = require('targz');
6const fs = require('fs-extra');
7const path = require('path');
8const semver = require('semver');
9
10console.log("Ferdi Recipe Repository Updater v1.0.0");
11
12// Server to update from
13const server = "http://api.franzinfra.com/v1";
14
15// Create paths to important files
16const allJson = path.join('../', 'all.json');
17
18// Helper: Download file to filesystem
19const downloadFile = (async (url, path) => {
20 const res = await fetch(url);
21 const fileStream = fs.createWriteStream(path);
22 await new Promise((resolve, reject) => {
23 res.body.pipe(fileStream);
24 res.body.on("error", (err) => {
25 reject(err);
26 });
27 fileStream.on("finish", function () {
28 resolve();
29 });
30 });
31});
32
33// Helper: Decompress .tar.gz file
34const decompress = (src, dest) => {
35 return new Promise(resolve => {
36 targz.decompress({
37 src,
38 dest
39 }, function (err) {
40 if (err) {
41 console.log('Error while decompressing recipe:', err);
42 }
43 resolve();
44 });
45 })
46}
47
48// Let us work in an async environment
49(async () => {
50 // Get current recipes from server
51 const serverRecipes = await (await fetch(server + '/recipes')).json();
52
53 // Get current local recipes
54 const localRecipes = await fs.readJson(allJson);
55
56 for (const recipe of serverRecipes) {
57 // Find local recipe info
58 const localRecipe = localRecipes.find(e => e.id === recipe.id);
59
60 if (!localRecipe || semver.gt(recipe.version, localRecipe.version)) {
61 // Update is availible
62 console.log(`Updating ${recipe.id} from ${localRecipe ? localRecipe.version : '-1'} to ${recipe.version}`);
63
64 const compressed = path.join('../archives', `${recipe.id}.tar.gz`);
65 const uncompressed = path.join('../uncompressed', recipe.id);
66
67 // Download recipe to filesystem
68 try {
69 console.log("Downloading " + server + '/recipes/download/' + recipe.id);
70 await downloadFile(
71 server + '/recipes/download/' + recipe.id,
72 compressed
73 );
74 } catch(e) {
75 console.log(`Could not download ${recipe.id}`);
76 return;
77 }
78
79 // Extract recipe
80 await decompress(compressed, uncompressed)
81
82 // Make sure we have all icons
83 const iconPng = path.join(uncompressed, '/icon.png');
84 const iconSvg = path.join(uncompressed, '/icon.svg');
85 if (!await fs.exists(iconPng)) {
86 downloadFile(recipe.icons.png, iconPng);
87 }
88 if (!await fs.exists(iconSvg)) {
89 downloadFile(recipe.icons.svg, iconSvg);
90 }
91
92 // Update entry in all.json
93 // Check if package ID already exists
94 const recipeIndex = localRecipes.findIndex(e => e.id === recipe.id);
95
96 const recipeInfo = recipe;
97 recipeInfo.icons.png = recipeInfo.icons.png.replace(
98 'https://cdn.franzinfra.com/recipes/dist/',
99 'https://cdn.jsdelivr.net/gh/getferdi/recipes/uncompressed/'
100 ).replace(
101 'src/icon.png',
102 'icon.png'
103 );
104 recipeInfo.icons.svg = recipeInfo.icons.svg.replace(
105 'https://cdn.franzinfra.com/recipes/dist/',
106 'https://cdn.jsdelivr.net/gh/getferdi/recipes/uncompressed/'
107 ).replace(
108 'src/icon.svg',
109 'icon.svg'
110 );
111
112 if (recipeIndex !== -1) {
113 localRecipes[recipeIndex] = recipeInfo;
114 } else {
115 localRecipes.push(recipeInfo);
116 }
117 }
118 }
119
120 // Write updated package info to all.json
121 await fs.writeJson(allJson, localRecipes, {
122 spaces: 2,
123 EOL: '\n',
124 });
125})(); \ No newline at end of file
diff --git a/scripts/verify-all.js b/scripts/verify-all.js
deleted file mode 100644
index f8702b3..0000000
--- a/scripts/verify-all.js
+++ /dev/null
@@ -1,97 +0,0 @@
1/**
2 * Verify all archived recipes to match uncompressed recipes
3 */
4const targz = require('targz');
5const fs = require('fs-extra');
6const dircompare = require('dir-compare');
7const path = require('path');
8
9// Helper: Compress src folder into dest file
10const decompress = (src, dest) => new Promise((resolve, reject) => {
11 targz.decompress({
12 src,
13 dest,
14 tar: {
15 // Don't unpackage .DS_Store files
16 ignore: function(name) {
17 return path.basename(name) === '.DS_Store'
18 }
19 },
20 }, (err) => {
21 if (err) {
22 reject(err);
23 } else {
24 resolve(dest);
25 }
26 });
27});
28
29// Let Promise errors crash the script
30process.on('unhandledRejection', (error) => {
31 console.log('Promise rejection:', error);
32 process.exit(1);
33});
34
35// Let us work in an async environment
36(async () => {
37 // Read list of all recipes
38 const allJsonPath = path.join(__dirname, '../', 'all.json');
39 const all = await fs.readJSON(allJsonPath);
40
41 const tempUncompressed = path.join(__dirname, `uncompressed/`);
42
43 for (const recipeInfo of all) {
44 try {
45 // Get recipe infos
46 const recipe = recipeInfo.id;
47 const recipeNum = all.findIndex(e => e === recipeInfo);
48 const compressedRecipe = path.join(__dirname, '../', 'archives', `${recipe}.tar.gz`);
49 const uncompressedRecipe = path.join(__dirname, '../', 'uncompressed', recipe);
50
51 // Check that recipe exists
52 if (!await fs.pathExists(compressedRecipe) || !await fs.pathExists(uncompressedRecipe)) {
53 console.log(`Error: Recipe "${recipe}" exists in all.json but not found.`);
54 process.exit(1);
55 }
56
57 // Clear temporary extraction folder
58 if (await fs.pathExists(tempUncompressed)) {
59 await fs.remove(tempUncompressed);
60 }
61 await fs.mkdir(tempUncompressed);
62
63 // Package to uncompressed recipe to .tar.gz
64 await decompress(compressedRecipe, tempUncompressed);
65
66 // Compare directories
67 const compare = await dircompare.compare(uncompressedRecipe, tempUncompressed, {
68 compareContent: true,
69 // Don't fail because of DS_Store files
70 excludeFilter: '.DS_Store',
71 });
72
73 if (compare.same) {
74 console.log(`✓ ${recipe} is valid (${recipeNum + 1} of ${all.length})`);
75 } else {
76 console.log(`❌ Compressed and uncompressed files for "${recipe}" are NOT equal:`);
77
78 // Output information about differences
79 for (const file of compare.diffSet) {
80 if (file.state !== 'equal') {
81 console.log(`- "${file.name1 || file.name2}" is not equal (${file.type1} in uncompressed, ${file.type2} in archive)`);
82 }
83 }
84
85 process.exit(1);
86 }
87
88 // Remove temporary compressed file
89 await fs.remove(tempUncompressed);
90 } catch(e) {
91 console.log('Error while verifying recipe:', e);
92 process.exit(1);
93 }
94 }
95
96 console.log('All recipes are valid.');
97})();
diff --git a/scripts/verify.js b/scripts/verify.js
deleted file mode 100644
index e2124e6..0000000
--- a/scripts/verify.js
+++ /dev/null
@@ -1,70 +0,0 @@
1/**
2 * Verify packaged recipe to match uncompressed recipe
3 */
4const targz = require('targz');
5const fs = require('fs-extra');
6const dircompare = require('dir-compare');
7const path = require('path');
8
9// Helper: Compress src folder into dest file
10const decompress = (src, dest) => new Promise((resolve, reject) => {
11 targz.decompress({
12 src,
13 dest,
14 tar: {
15 // Don't unpackage .DS_Store files
16 ignore: function(name) {
17 return path.basename(name) === '.DS_Store'
18 }
19 },
20 }, (err) => {
21 if (err) {
22 reject(err);
23 } else {
24 resolve(dest);
25 }
26 });
27});
28
29if (!process.argv[2]) {
30 console.log('Usage: yarn verify <recipe>');
31 return;
32}
33
34// Let us work in an async environment
35(async () => {
36 const recipe = process.argv[2];
37
38 const compressedRecipe = path.join(__dirname, '../', 'archives', `${recipe}.tar.gz`);
39 const uncompressedRecipe = path.join(__dirname, '../', 'uncompressed', recipe);
40 const tempUncompressed = path.join(__dirname, `uncompressed/`);
41
42 // Check that recipe exists
43 if (!await fs.pathExists(compressedRecipe) || !await fs.pathExists(uncompressedRecipe)) {
44 console.log(`Error: Recipe does not exist.`);
45 return;
46 }
47
48 if (await fs.pathExists(tempUncompressed)) {
49 await fs.remove(tempUncompressed);
50 }
51 await fs.mkdir(tempUncompressed);
52
53 // Package to uncompressed recipe to .tar.gz
54 console.log(`Decompressing...`);
55 await decompress(compressedRecipe, tempUncompressed);
56
57 // Compare directories
58 const compare = dircompare.compareSync(uncompressedRecipe, tempUncompressed, {
59 compareContent: true,
60 });
61
62 if (compare.same) {
63 console.log('✓ Compressed and uncompressed files are equal');
64 } else {
65 console.log('❌ Compressed and uncompressed files are NOT equal');
66 }
67
68 // Remove temporary compressed file
69 await fs.remove(tempUncompressed);
70})(); \ No newline at end of file