diff options
Diffstat (limited to 'scripts/api')
-rw-r--r-- | scripts/api/package.js | 204 |
1 files changed, 204 insertions, 0 deletions
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 | */ | ||
4 | const targz = require('targz'); | ||
5 | const fs = require('fs-extra'); | ||
6 | const path = require('path'); | ||
7 | const sizeOf = require('image-size'); | ||
8 | const semver = require('semver'); | ||
9 | |||
10 | // Publicly availible link to this repository's uncompressed folder | ||
11 | // Used for generating public icon URLs | ||
12 | const repo = 'https://cdn.jsdelivr.net/gh/getferdi/recipes/uncompressed/'; | ||
13 | |||
14 | // Helper: Compress src folder into dest file | ||
15 | const 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 | ||
34 | module.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". | ||
46 | For 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. | ||
55 | Those icons should be the logo of the recipe you are trying to add. | ||
56 | Please also make sure that your icons are 1024x1024px in size. | ||
57 | For 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. | ||
61 | Your recipe already contains an "icon.png" but it also requires an "icon.svg" to display properly. | ||
62 | Please also make sure that your icons are 1024x1024px in size. | ||
63 | For 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. | ||
67 | Your recipe already contains an "icon.svg" but it also requires an "icon.png" to display properly. | ||
68 | Please also make sure that your icons are 1024x1024px in size. | ||
69 | For 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. | ||
80 | Both your "icon.png" and "icon.svg" don't seem to have the right size. | ||
81 | You can use software like Photoshop, GIMP or Photopea (https://www.photopea.com/) to resize your icons. | ||
82 | For 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. | ||
86 | Please make sure that your "icon.png" has the right size of 1024x1024px in size. | ||
87 | You can use software like Photoshop, GIMP or Photopea (https://www.photopea.com/) to resize your icons. | ||
88 | For 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. | ||
92 | Please make sure that your "icon.svg" has the right size of 1024x1024px in size. | ||
93 | You can use software like Photoshop, GIMP or Photopea (https://www.photopea.com/) to resize your icons. | ||
94 | For 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. | ||
104 | Please make sure your "package.json" contains valid JSON. | ||
105 | For 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}`)} | ||
129 | For 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. | ||
142 | 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'). | ||
143 | If you don't increase your version number, Ferdi cannot detect that you have made changes to the recipe. | ||
144 | For 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. | ||
151 | Please make sure to create that file and add your features to it. | ||
152 | 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`); | ||
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. | ||
157 | 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 | ||
158 | 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`); | ||
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 | ||