cloudron 5.11.6 → 5.11.8
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/bin/cloudron-appstore +18 -6
- package/bin/cloudron-build +3 -2
- package/package.json +3 -3
- package/src/appstore-actions.js +139 -98
- package/src/build-actions.js +11 -3
package/bin/cloudron-appstore
CHANGED
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
5
|
+
const appstoreActions = require('../src/appstore-actions.js'),
|
|
6
|
+
Command = require('commander').Command;
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const version = require('../package.json').version;
|
|
9
|
+
|
|
10
|
+
const program = new Command();
|
|
11
|
+
program.version(version);
|
|
12
|
+
|
|
13
|
+
// global options. IMPORTANT: These cannot conflict with global options!
|
|
14
|
+
program
|
|
15
|
+
.option('--appstore-token <token>', 'AppStore token');
|
|
9
16
|
|
|
10
17
|
program.command('login')
|
|
11
18
|
.description('Login to the appstore')
|
|
@@ -27,9 +34,13 @@ program.command('approve')
|
|
|
27
34
|
.option('--appstore-id <appid@version>', 'Appstore id and version')
|
|
28
35
|
.action(appstoreActions.approve);
|
|
29
36
|
|
|
30
|
-
program.command('
|
|
31
|
-
.description('
|
|
32
|
-
.action(appstoreActions.
|
|
37
|
+
program.command('notify')
|
|
38
|
+
.description('Notify forum about successful app submission')
|
|
39
|
+
.action(appstoreActions.notify);
|
|
40
|
+
|
|
41
|
+
program.command('tag <version>')
|
|
42
|
+
.description('Tag the repo')
|
|
43
|
+
.action(appstoreActions.tag);
|
|
33
44
|
|
|
34
45
|
program.command('revoke')
|
|
35
46
|
.description('Revoke a published app version')
|
|
@@ -47,6 +58,7 @@ program.command('upload')
|
|
|
47
58
|
.action(appstoreActions.upload);
|
|
48
59
|
|
|
49
60
|
program.command('versions')
|
|
61
|
+
.alias('list')
|
|
50
62
|
.description('List published versions')
|
|
51
63
|
.option('--appstore-id <id>', 'Appstore id')
|
|
52
64
|
.option('--raw', 'Dump versions as json')
|
package/bin/cloudron-build
CHANGED
|
@@ -12,9 +12,9 @@ function collectArgs(value, collected) {
|
|
|
12
12
|
return collected;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
// global options
|
|
15
|
+
// global options. IMPORTANT: These cannot conflict with global options!
|
|
16
16
|
program.option('--server <server>', 'Cloudron domain')
|
|
17
|
-
.option('--token, --build-service-token <token>', 'Build service token')
|
|
17
|
+
.option('--build-token, --build-service-token <token>', 'Build service token')
|
|
18
18
|
.option('--url, --set-build-service [buildservice url]', 'Set build service URL. This build service is automatically used for future calls from this project');
|
|
19
19
|
|
|
20
20
|
program.command('build', { isDefault: true })
|
|
@@ -45,6 +45,7 @@ program.command('push')
|
|
|
45
45
|
.option('--id <buildid>', 'Build ID')
|
|
46
46
|
.option('--repository [repository url]', 'Set repository to push to. e.g registry/username/projectname')
|
|
47
47
|
.option('--tag <docker image tag>', 'Docker image tag. Note that this does not include the repository name')
|
|
48
|
+
.option('--image <docker image>', 'Docker image of the form registry/repo:tag')
|
|
48
49
|
.action(buildActions.push);
|
|
49
50
|
|
|
50
51
|
program.command('status')
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudron",
|
|
3
|
-
"version": "5.11.
|
|
3
|
+
"version": "5.11.8",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Cloudron Commandline Tool",
|
|
6
6
|
"main": "main.js",
|
|
7
|
-
"homepage": "https://git.cloudron.io/
|
|
7
|
+
"homepage": "https://git.cloudron.io/platform/cloudron-cli",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://git.cloudron.io/
|
|
10
|
+
"url": "https://git.cloudron.io/platform/cloudron-cli.git"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"test": "mocha test/test.js"
|
package/src/appstore-actions.js
CHANGED
|
@@ -25,7 +25,8 @@ exports = module.exports = {
|
|
|
25
25
|
revoke,
|
|
26
26
|
approve,
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
tag,
|
|
29
|
+
notify
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
const NO_MANIFEST_FOUND_ERROR_STRING = 'No CloudronManifest.json found';
|
|
@@ -36,10 +37,12 @@ function requestError(response) {
|
|
|
36
37
|
return `${response.statusCode} message: ${response.body.message || JSON.stringify(response.body)}`; // body is sometimes just a string like in 401
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
function createRequest(method, apiPath) {
|
|
40
|
+
function createRequest(method, apiPath, options) {
|
|
41
|
+
const token = options.appstoreToken || config.appStoreToken();
|
|
42
|
+
|
|
40
43
|
let url = `${config.appStoreOrigin()}${apiPath}`;
|
|
41
44
|
if (url.includes('?')) url += '&'; else url += '?';
|
|
42
|
-
url += `accessToken=${
|
|
45
|
+
url += `accessToken=${token}`;
|
|
43
46
|
const request = superagent(method, url);
|
|
44
47
|
request.retry(3);
|
|
45
48
|
request.ok(() => true);
|
|
@@ -63,7 +66,7 @@ async function getAppstoreId(appstoreId) {
|
|
|
63
66
|
return [manifest.id, manifest.version];
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
async function authenticate(options) {
|
|
69
|
+
async function authenticate(options) { // maybe we can use options.token to valid using a profile call?
|
|
67
70
|
if (!options.hideBanner) {
|
|
68
71
|
const webDomain = config.appStoreOrigin().replace('https://api.', '');
|
|
69
72
|
console.log(`${webDomain} login` + ` (If you do not have one, sign up at https://${webDomain}/console.html#/register)`);
|
|
@@ -101,7 +104,8 @@ async function authenticate(options) {
|
|
|
101
104
|
console.log('Login successful.');
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
async function login(
|
|
107
|
+
async function login(localOptions, cmd) {
|
|
108
|
+
const options = cmd.optsWithGlobals();
|
|
105
109
|
await authenticate(options);
|
|
106
110
|
}
|
|
107
111
|
|
|
@@ -110,11 +114,12 @@ function logout() {
|
|
|
110
114
|
console.log('Done.');
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
async function info(
|
|
117
|
+
async function info(localOptions, cmd) {
|
|
118
|
+
const options = cmd.optsWithGlobals();
|
|
114
119
|
const [id, version] = await getAppstoreId(options.appstoreId);
|
|
115
120
|
|
|
116
|
-
const response = await createRequest('GET', `/api/v1/developers/apps/${id}/versions/${version}
|
|
117
|
-
if (response.statusCode !== 200)
|
|
121
|
+
const response = await createRequest('GET', `/api/v1/developers/apps/${id}/versions/${version}`, options);
|
|
122
|
+
if (response.statusCode !== 200) return exit(new Error(`Failed to list versions: ${requestError(response)}`));
|
|
118
123
|
|
|
119
124
|
const manifest = response.body.manifest;
|
|
120
125
|
console.log('id: %s', manifest.id);
|
|
@@ -125,11 +130,12 @@ async function info(options) {
|
|
|
125
130
|
console.log('contactEmail: %s', manifest.contactEmail);
|
|
126
131
|
}
|
|
127
132
|
|
|
128
|
-
async function listVersions(
|
|
133
|
+
async function listVersions(localOptions, cmd) {
|
|
134
|
+
const options = cmd.optsWithGlobals();
|
|
129
135
|
const [id] = await getAppstoreId(options.appstoreId);
|
|
130
136
|
|
|
131
|
-
const response = await createRequest('GET', `/api/v1/developers/apps/${id}/versions
|
|
132
|
-
if (response.statusCode !== 200)
|
|
137
|
+
const response = await createRequest('GET', `/api/v1/developers/apps/${id}/versions`, options);
|
|
138
|
+
if (response.statusCode !== 200) return exit(new Error(`Failed to list versions: ${requestError(response)}`));
|
|
133
139
|
|
|
134
140
|
if (response.body.versions.length === 0) return console.log('No versions found.');
|
|
135
141
|
|
|
@@ -150,17 +156,6 @@ async function listVersions(options) {
|
|
|
150
156
|
console.log(t.toString());
|
|
151
157
|
}
|
|
152
158
|
|
|
153
|
-
async function addApp(manifest, baseDir) {
|
|
154
|
-
assert.strictEqual(typeof manifest, 'object');
|
|
155
|
-
assert.strictEqual(typeof baseDir, 'string');
|
|
156
|
-
|
|
157
|
-
const request = createRequest('POST', '/api/v1/developers/apps');
|
|
158
|
-
request.send({ id: manifest.id });
|
|
159
|
-
const response = await request;
|
|
160
|
-
if (response.statusCode === 409) return; // already exists
|
|
161
|
-
if (response.statusCode !== 201) return exit(`Failed to add app: ${requestError(response)}`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
159
|
function parseChangelog(file, version) {
|
|
165
160
|
let changelog = '';
|
|
166
161
|
const data = safe.fs.readFileSync(file, 'utf8');
|
|
@@ -184,9 +179,10 @@ function parseChangelog(file, version) {
|
|
|
184
179
|
return changelog;
|
|
185
180
|
}
|
|
186
181
|
|
|
187
|
-
async function addVersion(manifest, baseDir) {
|
|
182
|
+
async function addVersion(manifest, baseDir, options) {
|
|
188
183
|
assert.strictEqual(typeof manifest, 'object');
|
|
189
184
|
assert.strictEqual(typeof baseDir, 'string');
|
|
185
|
+
assert.strictEqual(typeof options, 'object');
|
|
190
186
|
|
|
191
187
|
let iconFilePath = null;
|
|
192
188
|
if (manifest.icon) {
|
|
@@ -220,7 +216,7 @@ async function addVersion(manifest, baseDir) {
|
|
|
220
216
|
if (!manifest.changelog) throw new Error('Bad changelog format or missing changelog for this version');
|
|
221
217
|
}
|
|
222
218
|
|
|
223
|
-
const request = createRequest('POST', `/api/v1/developers/apps/${manifest.id}/versions
|
|
219
|
+
const request = createRequest('POST', `/api/v1/developers/apps/${manifest.id}/versions`, options);
|
|
224
220
|
if (iconFilePath) request.attach('icon', iconFilePath);
|
|
225
221
|
request.attach('manifest', Buffer.from(JSON.stringify(manifest)), 'manifest');
|
|
226
222
|
const response = await request;
|
|
@@ -228,9 +224,10 @@ async function addVersion(manifest, baseDir) {
|
|
|
228
224
|
if (response.statusCode !== 204) throw new Error(`Failed to publish version: ${requestError(response)}`);
|
|
229
225
|
}
|
|
230
226
|
|
|
231
|
-
async function updateVersion(manifest, baseDir) {
|
|
227
|
+
async function updateVersion(manifest, baseDir, options) {
|
|
232
228
|
assert.strictEqual(typeof manifest, 'object');
|
|
233
229
|
assert.strictEqual(typeof baseDir, 'string');
|
|
230
|
+
assert.strictEqual(typeof options, 'object');
|
|
234
231
|
|
|
235
232
|
let iconFilePath = null;
|
|
236
233
|
if (manifest.icon) {
|
|
@@ -262,59 +259,15 @@ async function updateVersion(manifest, baseDir) {
|
|
|
262
259
|
if (!manifest.changelog) throw new Error('Could not read changelog or missing version changes');
|
|
263
260
|
}
|
|
264
261
|
|
|
265
|
-
const request = createRequest('PUT', `/api/v1/developers/apps/${manifest.id}/versions/${manifest.version}
|
|
262
|
+
const request = createRequest('PUT', `/api/v1/developers/apps/${manifest.id}/versions/${manifest.version}`, options);
|
|
266
263
|
if (iconFilePath) request.attach('icon', iconFilePath);
|
|
267
264
|
request.attach('manifest', Buffer.from(JSON.stringify(manifest)), 'manifest');
|
|
268
265
|
const response = await request;
|
|
269
266
|
if (response.statusCode !== 204) throw new Error(`Failed to publish version: ${requestError(response)}`);
|
|
270
267
|
}
|
|
271
268
|
|
|
272
|
-
async function
|
|
273
|
-
|
|
274
|
-
assert.strictEqual(typeof version, 'string');
|
|
275
|
-
|
|
276
|
-
const response = await createRequest('POST', `/api/v1/developers/apps/${appstoreId}/versions/${version}/revoke`);
|
|
277
|
-
if (response.statusCode !== 200) return exit(`Failed to revoke version: ${requestError(response)}`);
|
|
278
|
-
|
|
279
|
-
console.log('version revoked.');
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
async function approveVersion(appstoreId, version) {
|
|
283
|
-
assert.strictEqual(typeof appstoreId, 'string');
|
|
284
|
-
assert.strictEqual(typeof version, 'string');
|
|
285
|
-
|
|
286
|
-
const response = await createRequest('POST', `/api/v1/developers/apps/${appstoreId}/versions/${version}/approve`);
|
|
287
|
-
if (response.statusCode !== 200) return exit(`Failed to approve version: ${requestError(response)}`);
|
|
288
|
-
|
|
289
|
-
console.log('Approved.');
|
|
290
|
-
console.log('');
|
|
291
|
-
|
|
292
|
-
const response2 = await createRequest('GET', `/api/v1/developers/apps/${appstoreId}/versions/${version}`);
|
|
293
|
-
if (response2.statusCode !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
|
|
294
|
-
|
|
295
|
-
console.log('Changelog for forum update: ' + response2.body.manifest.forumUrl);
|
|
296
|
-
console.log('');
|
|
297
|
-
console.log('[' + version + ']');
|
|
298
|
-
console.log(response2.body.manifest.changelog);
|
|
299
|
-
console.log('');
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
async function submitAppForReview(manifest) {
|
|
303
|
-
assert.strictEqual(typeof manifest, 'object');
|
|
304
|
-
|
|
305
|
-
const response = await createRequest('POST', `/api/v1/developers/apps/${manifest.id}/versions/${manifest.version}/submit`);
|
|
306
|
-
if (response.statusCode === 404) {
|
|
307
|
-
console.log(`No version ${manifest.version} found. Please use 'cloudron apsptore upload' first`);
|
|
308
|
-
return exit('Failed to submit app for review.');
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (response.statusCode !== 200) return exit(`Failed to submit app: ${requestError(response)}`);
|
|
312
|
-
|
|
313
|
-
console.log('App submitted for review.');
|
|
314
|
-
console.log('You will receive an email when approved.');
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
async function upload(options) {
|
|
269
|
+
async function upload(localOptions, cmd) {
|
|
270
|
+
const options = cmd.optsWithGlobals();
|
|
318
271
|
// try to find the manifest of this project
|
|
319
272
|
const manifestFilePath = locateManifest();
|
|
320
273
|
if (!manifestFilePath) return exit(NO_MANIFEST_FOUND_ERROR_STRING);
|
|
@@ -328,8 +281,19 @@ async function upload(options) {
|
|
|
328
281
|
const appConfig = config.getAppConfig(sourceDir);
|
|
329
282
|
|
|
330
283
|
// image can be passed in options for buildbot
|
|
331
|
-
|
|
332
|
-
|
|
284
|
+
if (options.image) {
|
|
285
|
+
manifest.dockerImage = options.image;
|
|
286
|
+
} else {
|
|
287
|
+
const gitCommit = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();
|
|
288
|
+
if (appConfig.gitCommit !== gitCommit) {
|
|
289
|
+
console.log(`This build ${appConfig.dockerImage} was made from git hash ${appConfig.gitCommit} but you are now at ${gitCommit}`);
|
|
290
|
+
if (!appConfig.gitCommit) return exit('The build is stale');
|
|
291
|
+
const output = execSync(`git diff ${appConfig.gitCommit}..HEAD --name-only`, { encoding: 'utf8'});
|
|
292
|
+
const changedFiles = output.trim().split('\n').filter(filepath => !filepath.match(/(^CHANGELOG|README|CloudronManifest|test\|.git|screenshots|renovate|LICENSE|POSTINSTALL|.docker|logo)/));
|
|
293
|
+
if (changedFiles.length) return exit(`The build is stale. Changed files: ${changedFiles.join(',')}`);
|
|
294
|
+
}
|
|
295
|
+
manifest.dockerImage = appConfig.dockerImage;
|
|
296
|
+
}
|
|
333
297
|
|
|
334
298
|
if (!manifest.dockerImage) exit('No docker image found, run `cloudron build` first');
|
|
335
299
|
|
|
@@ -339,16 +303,26 @@ async function upload(options) {
|
|
|
339
303
|
const error = manifestFormat.checkAppstoreRequirements(manifest);
|
|
340
304
|
if (error) return exit(error);
|
|
341
305
|
|
|
306
|
+
const [repo, tag] = manifest.dockerImage.split(':');
|
|
307
|
+
const [tagError, tagResponse] = await safe(superagent.get(`https://hub.docker.com/v2/repositories/${repo}/tags/${tag}`).ok(() => true));
|
|
308
|
+
if (tagError || tagResponse.statusCode !== 200) return exit(`Failed to find docker image in dockerhub. check https://hub.docker.com/r/${repo}/tags : ${requestError(tagResponse)}`);
|
|
309
|
+
|
|
342
310
|
// ensure the app is known on the appstore side
|
|
343
311
|
const baseDir = path.dirname(manifestFilePath);
|
|
344
|
-
|
|
312
|
+
|
|
313
|
+
const request = createRequest('POST', '/api/v1/developers/apps', options);
|
|
314
|
+
request.send({ id: manifest.id });
|
|
315
|
+
const response = await request;
|
|
316
|
+
if (response.statusCode !== 409 && response.statusCode !== 201) return exit(`Failed to add app: ${requestError(response)}`); // 409 means already exists
|
|
345
317
|
console.log(`Uploading ${manifest.id}@${manifest.version} (dockerImage: ${manifest.dockerImage}) for testing`);
|
|
346
318
|
|
|
347
|
-
const [error2] = await safe(options.force ? updateVersion(manifest, baseDir) : addVersion(manifest, baseDir));
|
|
319
|
+
const [error2] = await safe(options.force ? updateVersion(manifest, baseDir, options) : addVersion(manifest, baseDir, options));
|
|
348
320
|
if (error2) return exit(error2);
|
|
349
321
|
}
|
|
350
322
|
|
|
351
|
-
function submit() {
|
|
323
|
+
async function submit(localOptions, cmd) {
|
|
324
|
+
const options = cmd.optsWithGlobals();
|
|
325
|
+
|
|
352
326
|
// try to find the manifest of this project
|
|
353
327
|
const manifestFilePath = locateManifest();
|
|
354
328
|
if (!manifestFilePath) return exit(NO_MANIFEST_FOUND_ERROR_STRING);
|
|
@@ -358,28 +332,57 @@ function submit() {
|
|
|
358
332
|
|
|
359
333
|
const manifest = result.manifest;
|
|
360
334
|
|
|
361
|
-
|
|
335
|
+
const response = await createRequest('POST', `/api/v1/developers/apps/${manifest.id}/versions/${manifest.version}/submit`, options);
|
|
336
|
+
if (response.statusCode === 404) {
|
|
337
|
+
console.log(`No version ${manifest.version} found. Please use 'cloudron apsptore upload' first`);
|
|
338
|
+
return exit('Failed to submit app for review.');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (response.statusCode !== 200) return exit(`Failed to submit app: ${requestError(response)}`);
|
|
342
|
+
|
|
343
|
+
console.log('App submitted for review.');
|
|
344
|
+
console.log('You will receive an email when approved.');
|
|
362
345
|
}
|
|
363
346
|
|
|
364
|
-
async function revoke(
|
|
347
|
+
async function revoke(localOptions, cmd) {
|
|
348
|
+
const options = cmd.optsWithGlobals();
|
|
365
349
|
const [id, version] = await getAppstoreId(options.appstoreId);
|
|
366
350
|
if (!version) return exit('--appstore-id must be of the format id@version');
|
|
367
351
|
|
|
368
352
|
console.log(`Revoking ${id}@${version}`);
|
|
369
|
-
|
|
353
|
+
|
|
354
|
+
const response = await createRequest('POST', `/api/v1/developers/apps/${id}/versions/${version}/revoke`, options);
|
|
355
|
+
if (response.statusCode !== 200) return exit(`Failed to revoke version: ${requestError(response)}`);
|
|
356
|
+
|
|
357
|
+
console.log('version revoked.');
|
|
370
358
|
}
|
|
371
359
|
|
|
372
|
-
async function approve(
|
|
373
|
-
const
|
|
360
|
+
async function approve(localOptions, cmd) {
|
|
361
|
+
const options = cmd.optsWithGlobals();
|
|
362
|
+
|
|
363
|
+
const [appstoreId, version] = await getAppstoreId(options.appstoreId);
|
|
374
364
|
|
|
375
365
|
if (!version) return exit('--appstore-id must be of the format id@version');
|
|
376
366
|
|
|
377
|
-
console.log(`Approving ${
|
|
367
|
+
console.log(`Approving ${appstoreId}@${version}`);
|
|
368
|
+
|
|
369
|
+
const response = await createRequest('POST', `/api/v1/developers/apps/${appstoreId}/versions/${version}/approve`, options);
|
|
370
|
+
if (response.statusCode !== 200) return exit(`Failed to approve version: ${requestError(response)}`);
|
|
378
371
|
|
|
379
|
-
|
|
372
|
+
console.log('Approved.');
|
|
373
|
+
console.log('');
|
|
374
|
+
|
|
375
|
+
const response2 = await createRequest('GET', `/api/v1/developers/apps/${appstoreId}/versions/${version}`, options);
|
|
376
|
+
if (response2.statusCode !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
|
|
377
|
+
|
|
378
|
+
console.log('Changelog for forum update: ' + response2.body.manifest.forumUrl);
|
|
379
|
+
console.log('');
|
|
380
|
+
console.log('[' + version + ']');
|
|
381
|
+
console.log(response2.body.manifest.changelog);
|
|
382
|
+
console.log('');
|
|
380
383
|
}
|
|
381
384
|
|
|
382
|
-
async function
|
|
385
|
+
async function tag(version) {
|
|
383
386
|
const basename = `${path.basename(process.cwd())}`;
|
|
384
387
|
if (!basename.endsWith('-app')) return exit('Does not look like a app repo. Has to end with -app');
|
|
385
388
|
|
|
@@ -392,7 +395,7 @@ async function tagRepository(version) {
|
|
|
392
395
|
if (!manifestFilePath) return exit('Could not locate CloudronManifest.json');
|
|
393
396
|
|
|
394
397
|
const result = manifestFormat.parseFile(manifestFilePath);
|
|
395
|
-
if (result.error)
|
|
398
|
+
if (result.error) return exit(new Error(`Invalid CloudronManifest.json: ${result.error.message}`));
|
|
396
399
|
const { manifest } = result;
|
|
397
400
|
|
|
398
401
|
const latestVersion = latestTag.match(/v(.*)/)[1];
|
|
@@ -441,22 +444,60 @@ async function tagRepository(version) {
|
|
|
441
444
|
console.log(`Created tag v${version} and pushed branch ${branch}`);
|
|
442
445
|
}
|
|
443
446
|
|
|
444
|
-
|
|
445
|
-
|
|
447
|
+
// https://docs.nodebb.org/api/read/
|
|
448
|
+
// https://docs.nodebb.org/api/write/
|
|
449
|
+
async function notify() {
|
|
450
|
+
if (!process.env.NODEBB_API_TOKEN) return exit('NODEBB_API_TOKEN env var has to be set');
|
|
451
|
+
const apiToken = process.env.NODEBB_API_TOKEN;
|
|
446
452
|
|
|
447
453
|
const manifestFilePath = locateManifest();
|
|
448
454
|
if (!manifestFilePath) return exit('Could not locate CloudronManifest.json');
|
|
449
455
|
|
|
450
|
-
const
|
|
451
|
-
if (
|
|
456
|
+
const result = manifestFormat.parseFile(manifestFilePath);
|
|
457
|
+
if (result.error) return exit(new Error(`Invalid CloudronManifest.json: ${result.error.message}`));
|
|
458
|
+
const { manifest } = result;
|
|
452
459
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
460
|
+
let postContent = null;
|
|
461
|
+
if (manifest.changelog.slice(0, 7) === 'file://') {
|
|
462
|
+
const baseDir = path.dirname(manifestFilePath);
|
|
463
|
+
let changelogPath = manifest.changelog.slice(7);
|
|
464
|
+
changelogPath = path.isAbsolute(changelogPath) ? changelogPath : path.join(baseDir, changelogPath);
|
|
465
|
+
const changelog = parseChangelog(changelogPath, manifest.version);
|
|
466
|
+
if (!changelog) return exit('Bad changelog format or missing changelog for this version');
|
|
467
|
+
postContent = `[${manifest.version}]\n${changelog}\n`;
|
|
468
|
+
} else {
|
|
469
|
+
postContent = `[${manifest.version}]\n${manifest.changelog}\n`;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (!manifest.forumUrl) return exit(new Error('CloudronManifest.json does not have a forumUrl'));
|
|
473
|
+
const categoryMatch = manifest.forumUrl.match(/category\/(.*)\//);
|
|
474
|
+
if (!categoryMatch) return exit('Unable to detect category id');
|
|
475
|
+
const categoryId = categoryMatch[1];
|
|
476
|
+
|
|
477
|
+
const categoryResponse = await superagent.get(`https://forum.cloudron.io/api/v3/categories/${categoryId}/topics`).set('Authorization', `Bearer ${apiToken}`).ok(() => true);
|
|
478
|
+
if (categoryResponse.statusCode !== 200) return exit(`Unable to get topics of category: ${requestError(categoryResponse)}`);
|
|
479
|
+
const topic = categoryResponse.body.response.topics.find(t => t.title.includes('Package Updates'));
|
|
480
|
+
if (!topic) return exit('Could not find the Package Update topic');
|
|
481
|
+
const topicId = topic.tid;
|
|
482
|
+
|
|
483
|
+
const pageCountResponse = await superagent.get(`https://forum.cloudron.io/api/topic/pagination/${topicId}`).set('Authorization', `Bearer ${apiToken}`).ok(() => true);
|
|
484
|
+
if (pageCountResponse.statusCode !== 200) return exit(`Unable to get page count of topic: ${requestError(pageCountResponse)}`);
|
|
485
|
+
const pageCount = pageCountResponse.body.pagination.pageCount;
|
|
486
|
+
|
|
487
|
+
for (let page = 1; page <= pageCount; page++) {
|
|
488
|
+
const pageResponse = await superagent.get(`https://forum.cloudron.io/api/topic/${topicId}?page=${page}`).set('Authorization', `Bearer ${apiToken}`).ok(() => true);
|
|
489
|
+
if (pageResponse.statusCode !== 200) return exit(`Unable to get topics of category: ${requestError(pageResponse)}`);
|
|
490
|
+
for (const post of pageResponse.body.posts) { // post.content is html!
|
|
491
|
+
if (post.content.includes(`[${manifest.version}]`)) return exit(`Version ${manifest.version} is already on the forum.\n${post.content}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
458
494
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
495
|
+
// https://docs.nodebb.org/api/write/#tag/topics/paths/~1topics~1%7Btid%7D/post
|
|
496
|
+
const postData = {
|
|
497
|
+
content: postContent,
|
|
498
|
+
toPid: 0 // which post is this post a reply to
|
|
499
|
+
};
|
|
500
|
+
const postResponse = await superagent.post(`https://forum.cloudron.io/api/v3/topics/${topicId}`).set('Authorization', `Bearer ${apiToken}`).send(postData).ok(() => true);
|
|
501
|
+
if (postResponse.statusCode !== 200) return exit(`Unable to create changelog post: ${requestError(postResponse)}`);
|
|
502
|
+
console.log('Posted to forum');
|
|
462
503
|
}
|
package/src/build-actions.js
CHANGED
|
@@ -331,6 +331,7 @@ async function build(localOptions, cmd) {
|
|
|
331
331
|
config.setAppConfig(sourceDir, appConfig);
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
appConfig.gitCommit = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); // when the build gets saved, save the gitCommit also
|
|
334
335
|
if (buildServiceConfig.type === 'local') {
|
|
335
336
|
await buildLocal(manifest, sourceDir, appConfig, options);
|
|
336
337
|
} else if (buildServiceConfig.type === 'remote' && buildServiceConfig.url) {
|
|
@@ -363,14 +364,21 @@ async function push(localOptions, cmd) {
|
|
|
363
364
|
const options = cmd.optsWithGlobals();
|
|
364
365
|
|
|
365
366
|
if (!options.id) return exit('buildId is required');
|
|
366
|
-
|
|
367
|
-
if (
|
|
367
|
+
let repository, tag;
|
|
368
|
+
if (options.image) {
|
|
369
|
+
[repository, tag] = options.image.split(':');
|
|
370
|
+
} else {
|
|
371
|
+
if (!options.repository) return exit('repository is required');
|
|
372
|
+
if (!options.tag) return exit('tag is required');
|
|
373
|
+
repository = options.repository;
|
|
374
|
+
tag = options.tag;
|
|
375
|
+
}
|
|
368
376
|
|
|
369
377
|
const buildServiceConfig = getBuildServiceConfig(options);
|
|
370
378
|
|
|
371
379
|
const response = await superagent.post(`${buildServiceConfig.url}/api/v1/builds/${options.id}/push`)
|
|
372
380
|
.query({ accessToken: buildServiceConfig.token })
|
|
373
|
-
.send({ dockerImageRepo:
|
|
381
|
+
.send({ dockerImageRepo: repository, dockerImageTag: tag })
|
|
374
382
|
.ok(() => true);
|
|
375
383
|
if (response.statusCode !== 201) return exit(`Failed to push: ${requestError(response)}`);
|
|
376
384
|
|