cloudron 5.11.5 → 5.11.7
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 +0 -1
- package/bin/cloudron-appstore +15 -14
- package/bin/cloudron-build +2 -2
- package/package.json +1 -1
- package/src/appstore-actions.js +130 -146
- package/bin/cloudron-repo +0 -18
- package/src/repo-actions.js +0 -102
package/bin/cloudron
CHANGED
|
@@ -32,7 +32,6 @@ program.option('--server <server>', 'Cloudron domain')
|
|
|
32
32
|
|
|
33
33
|
// these are separate binaries since global options are not applicable
|
|
34
34
|
program.command('appstore', 'Cloudron appstore commands');
|
|
35
|
-
program.command('repo', 'Cloudron repo commands');
|
|
36
35
|
program.command('build', 'Cloudron build commands');
|
|
37
36
|
|
|
38
37
|
const backupCommand = program.command('backup')
|
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')
|
|
@@ -22,16 +29,15 @@ program.command('info')
|
|
|
22
29
|
.option('--appstore-id <appid@version>', 'Appstore id and version')
|
|
23
30
|
.action(appstoreActions.info);
|
|
24
31
|
|
|
25
|
-
program.command('published')
|
|
26
|
-
.description('List published apps from this account')
|
|
27
|
-
.option('-i --image', 'Display docker image')
|
|
28
|
-
.action(appstoreActions.listPublishedApps);
|
|
29
|
-
|
|
30
32
|
program.command('approve')
|
|
31
33
|
.description('Approve a submitted app version')
|
|
32
34
|
.option('--appstore-id <appid@version>', 'Appstore id and version')
|
|
33
35
|
.action(appstoreActions.approve);
|
|
34
36
|
|
|
37
|
+
program.command('tag <version>')
|
|
38
|
+
.description('Tag the repo')
|
|
39
|
+
.action(appstoreActions.tag);
|
|
40
|
+
|
|
35
41
|
program.command('revoke')
|
|
36
42
|
.description('Revoke a published app version')
|
|
37
43
|
.option('--appstore-id <appid@version>', 'Appstore id and version')
|
|
@@ -41,12 +47,6 @@ program.command('submit')
|
|
|
41
47
|
.description('Submit app to the store for review')
|
|
42
48
|
.action(appstoreActions.submit);
|
|
43
49
|
|
|
44
|
-
program.command('unpublish')
|
|
45
|
-
.description('Delete app or app version from the store')
|
|
46
|
-
.option('--appstore-id <id@[version]>', 'Unpublish app')
|
|
47
|
-
.option('-f, --force', 'Do not ask anything')
|
|
48
|
-
.action(appstoreActions.unpublish);
|
|
49
|
-
|
|
50
50
|
program.command('upload')
|
|
51
51
|
.description('Upload app to the store for testing')
|
|
52
52
|
.option('-i, --image <image>', 'Docker image')
|
|
@@ -54,6 +54,7 @@ program.command('upload')
|
|
|
54
54
|
.action(appstoreActions.upload);
|
|
55
55
|
|
|
56
56
|
program.command('versions')
|
|
57
|
+
.alias('list')
|
|
57
58
|
.description('List published versions')
|
|
58
59
|
.option('--appstore-id <id>', 'Appstore id')
|
|
59
60
|
.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 })
|
package/package.json
CHANGED
package/src/appstore-actions.js
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
const assert = require('assert'),
|
|
6
6
|
config = require('./config.js'),
|
|
7
|
+
execSync = require('child_process').execSync,
|
|
7
8
|
fs = require('fs'),
|
|
8
9
|
{ exit, locateManifest } = require('./helper.js'),
|
|
9
10
|
manifestFormat = require('cloudron-manifestformat'),
|
|
10
11
|
path = require('path'),
|
|
11
12
|
readlineSync = require('readline-sync'),
|
|
12
13
|
safe = require('safetydance'),
|
|
14
|
+
semver = require('semver'),
|
|
13
15
|
superagent = require('superagent'),
|
|
14
16
|
Table = require('easy-table');
|
|
15
17
|
|
|
@@ -22,8 +24,8 @@ exports = module.exports = {
|
|
|
22
24
|
upload,
|
|
23
25
|
revoke,
|
|
24
26
|
approve,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
|
|
28
|
+
tag
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
const NO_MANIFEST_FOUND_ERROR_STRING = 'No CloudronManifest.json found';
|
|
@@ -34,10 +36,12 @@ function requestError(response) {
|
|
|
34
36
|
return `${response.statusCode} message: ${response.body.message || JSON.stringify(response.body)}`; // body is sometimes just a string like in 401
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
function createRequest(method, apiPath) {
|
|
39
|
+
function createRequest(method, apiPath, options) {
|
|
40
|
+
const token = options.appstoreToken || config.appStoreToken();
|
|
41
|
+
|
|
38
42
|
let url = `${config.appStoreOrigin()}${apiPath}`;
|
|
39
43
|
if (url.includes('?')) url += '&'; else url += '?';
|
|
40
|
-
url += `accessToken=${
|
|
44
|
+
url += `accessToken=${token}`;
|
|
41
45
|
const request = superagent(method, url);
|
|
42
46
|
request.retry(3);
|
|
43
47
|
request.ok(() => true);
|
|
@@ -61,7 +65,7 @@ async function getAppstoreId(appstoreId) {
|
|
|
61
65
|
return [manifest.id, manifest.version];
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
async function authenticate(options) {
|
|
68
|
+
async function authenticate(options) { // maybe we can use options.token to valid using a profile call?
|
|
65
69
|
if (!options.hideBanner) {
|
|
66
70
|
const webDomain = config.appStoreOrigin().replace('https://api.', '');
|
|
67
71
|
console.log(`${webDomain} login` + ` (If you do not have one, sign up at https://${webDomain}/console.html#/register)`);
|
|
@@ -99,7 +103,8 @@ async function authenticate(options) {
|
|
|
99
103
|
console.log('Login successful.');
|
|
100
104
|
}
|
|
101
105
|
|
|
102
|
-
async function login(
|
|
106
|
+
async function login(localOptions, cmd) {
|
|
107
|
+
const options = cmd.optsWithGlobals();
|
|
103
108
|
await authenticate(options);
|
|
104
109
|
}
|
|
105
110
|
|
|
@@ -108,11 +113,12 @@ function logout() {
|
|
|
108
113
|
console.log('Done.');
|
|
109
114
|
}
|
|
110
115
|
|
|
111
|
-
async function info(
|
|
116
|
+
async function info(localOptions, cmd) {
|
|
117
|
+
const options = cmd.optsWithGlobals();
|
|
112
118
|
const [id, version] = await getAppstoreId(options.appstoreId);
|
|
113
119
|
|
|
114
|
-
const response = await createRequest('GET', `/api/v1/developers/apps/${id}/versions/${version}
|
|
115
|
-
if (response.statusCode !== 200)
|
|
120
|
+
const response = await createRequest('GET', `/api/v1/developers/apps/${id}/versions/${version}`, options);
|
|
121
|
+
if (response.statusCode !== 200) return exit(new Error(`Failed to list versions: ${requestError(response)}`));
|
|
116
122
|
|
|
117
123
|
const manifest = response.body.manifest;
|
|
118
124
|
console.log('id: %s', manifest.id);
|
|
@@ -123,11 +129,12 @@ async function info(options) {
|
|
|
123
129
|
console.log('contactEmail: %s', manifest.contactEmail);
|
|
124
130
|
}
|
|
125
131
|
|
|
126
|
-
async function listVersions(
|
|
132
|
+
async function listVersions(localOptions, cmd) {
|
|
133
|
+
const options = cmd.optsWithGlobals();
|
|
127
134
|
const [id] = await getAppstoreId(options.appstoreId);
|
|
128
135
|
|
|
129
|
-
const response = await createRequest('GET', `/api/v1/developers/apps/${id}/versions
|
|
130
|
-
if (response.statusCode !== 200)
|
|
136
|
+
const response = await createRequest('GET', `/api/v1/developers/apps/${id}/versions`, options);
|
|
137
|
+
if (response.statusCode !== 200) return exit(new Error(`Failed to list versions: ${requestError(response)}`));
|
|
131
138
|
|
|
132
139
|
if (response.body.versions.length === 0) return console.log('No versions found.');
|
|
133
140
|
|
|
@@ -148,17 +155,6 @@ async function listVersions(options) {
|
|
|
148
155
|
console.log(t.toString());
|
|
149
156
|
}
|
|
150
157
|
|
|
151
|
-
async function addApp(manifest, baseDir) {
|
|
152
|
-
assert.strictEqual(typeof manifest, 'object');
|
|
153
|
-
assert.strictEqual(typeof baseDir, 'string');
|
|
154
|
-
|
|
155
|
-
const request = createRequest('POST', '/api/v1/developers/apps');
|
|
156
|
-
request.send({ id: manifest.id });
|
|
157
|
-
const response = await request;
|
|
158
|
-
if (response.statusCode === 409) return; // already exists
|
|
159
|
-
if (response.statusCode !== 201) return exit(`Failed to add app: ${requestError(response)}`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
158
|
function parseChangelog(file, version) {
|
|
163
159
|
let changelog = '';
|
|
164
160
|
const data = safe.fs.readFileSync(file, 'utf8');
|
|
@@ -182,9 +178,10 @@ function parseChangelog(file, version) {
|
|
|
182
178
|
return changelog;
|
|
183
179
|
}
|
|
184
180
|
|
|
185
|
-
async function addVersion(manifest, baseDir) {
|
|
181
|
+
async function addVersion(manifest, baseDir, options) {
|
|
186
182
|
assert.strictEqual(typeof manifest, 'object');
|
|
187
183
|
assert.strictEqual(typeof baseDir, 'string');
|
|
184
|
+
assert.strictEqual(typeof options, 'object');
|
|
188
185
|
|
|
189
186
|
let iconFilePath = null;
|
|
190
187
|
if (manifest.icon) {
|
|
@@ -218,7 +215,7 @@ async function addVersion(manifest, baseDir) {
|
|
|
218
215
|
if (!manifest.changelog) throw new Error('Bad changelog format or missing changelog for this version');
|
|
219
216
|
}
|
|
220
217
|
|
|
221
|
-
const request = createRequest('POST', `/api/v1/developers/apps/${manifest.id}/versions
|
|
218
|
+
const request = createRequest('POST', `/api/v1/developers/apps/${manifest.id}/versions`, options);
|
|
222
219
|
if (iconFilePath) request.attach('icon', iconFilePath);
|
|
223
220
|
request.attach('manifest', Buffer.from(JSON.stringify(manifest)), 'manifest');
|
|
224
221
|
const response = await request;
|
|
@@ -226,9 +223,10 @@ async function addVersion(manifest, baseDir) {
|
|
|
226
223
|
if (response.statusCode !== 204) throw new Error(`Failed to publish version: ${requestError(response)}`);
|
|
227
224
|
}
|
|
228
225
|
|
|
229
|
-
async function updateVersion(manifest, baseDir) {
|
|
226
|
+
async function updateVersion(manifest, baseDir, options) {
|
|
230
227
|
assert.strictEqual(typeof manifest, 'object');
|
|
231
228
|
assert.strictEqual(typeof baseDir, 'string');
|
|
229
|
+
assert.strictEqual(typeof options, 'object');
|
|
232
230
|
|
|
233
231
|
let iconFilePath = null;
|
|
234
232
|
if (manifest.icon) {
|
|
@@ -260,91 +258,15 @@ async function updateVersion(manifest, baseDir) {
|
|
|
260
258
|
if (!manifest.changelog) throw new Error('Could not read changelog or missing version changes');
|
|
261
259
|
}
|
|
262
260
|
|
|
263
|
-
const request = createRequest('PUT', `/api/v1/developers/apps/${manifest.id}/versions/${manifest.version}
|
|
261
|
+
const request = createRequest('PUT', `/api/v1/developers/apps/${manifest.id}/versions/${manifest.version}`, options);
|
|
264
262
|
if (iconFilePath) request.attach('icon', iconFilePath);
|
|
265
263
|
request.attach('manifest', Buffer.from(JSON.stringify(manifest)), 'manifest');
|
|
266
264
|
const response = await request;
|
|
267
265
|
if (response.statusCode !== 204) throw new Error(`Failed to publish version: ${requestError(response)}`);
|
|
268
266
|
}
|
|
269
267
|
|
|
270
|
-
async function
|
|
271
|
-
|
|
272
|
-
assert.strictEqual(typeof force, 'boolean');
|
|
273
|
-
|
|
274
|
-
if (!force) {
|
|
275
|
-
console.log(`This will delete the version ${manifest.version} of app ${manifest.id} from the appstore!`);
|
|
276
|
-
const reallyDelete = readlineSync.question('Really do this? [y/N]: ', {});
|
|
277
|
-
if (reallyDelete.toUpperCase() !== 'Y') exit();
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const response = await createRequest('DEL', `/api/v1/developers/apps/${manifest.id}/versions/${manifest.version}`);
|
|
281
|
-
if (response.statusCode !== 204) return exit(`Failed to unpublish version: ${requestError(response)}`);
|
|
282
|
-
|
|
283
|
-
console.log('version unpublished.');
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
async function revokeVersion(appstoreId, version) {
|
|
287
|
-
assert.strictEqual(typeof appstoreId, 'string');
|
|
288
|
-
assert.strictEqual(typeof version, 'string');
|
|
289
|
-
|
|
290
|
-
const response = await createRequest('POST', `/api/v1/developers/apps/${appstoreId}/versions/${version}/revoke`);
|
|
291
|
-
if (response.statusCode !== 200) return exit(`Failed to revoke version: ${requestError(response)}`);
|
|
292
|
-
|
|
293
|
-
console.log('version revoked.');
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async function approveVersion(appstoreId, version) {
|
|
297
|
-
assert.strictEqual(typeof appstoreId, 'string');
|
|
298
|
-
assert.strictEqual(typeof version, 'string');
|
|
299
|
-
|
|
300
|
-
const response = await createRequest('POST', `/api/v1/developers/apps/${appstoreId}/versions/${version}/approve`);
|
|
301
|
-
if (response.statusCode !== 200) return exit(`Failed to approve version: ${requestError(response)}`);
|
|
302
|
-
|
|
303
|
-
console.log('Approved.');
|
|
304
|
-
console.log('');
|
|
305
|
-
|
|
306
|
-
const response2 = await createRequest('GET', `/api/v1/developers/apps/${appstoreId}/versions/${version}`);
|
|
307
|
-
if (response2.statusCode !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
|
|
308
|
-
|
|
309
|
-
console.log('Changelog for forum update: ' + response2.body.manifest.forumUrl);
|
|
310
|
-
console.log('');
|
|
311
|
-
console.log('[' + version + ']');
|
|
312
|
-
console.log(response2.body.manifest.changelog);
|
|
313
|
-
console.log('');
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
async function delApp(appId, force) {
|
|
317
|
-
assert.strictEqual(typeof appId, 'string');
|
|
318
|
-
assert.strictEqual(typeof force, 'boolean');
|
|
319
|
-
|
|
320
|
-
if (!force) {
|
|
321
|
-
console.log('This will delete app %s from the appstore!', appId);
|
|
322
|
-
const reallyDelete = readlineSync.question('Really do this? [y/N]: ', {});
|
|
323
|
-
if (reallyDelete.toUpperCase() !== 'Y') return exit();
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const response = await createRequest('DEL', `/api/v1/developers/apps/${appId}`);
|
|
327
|
-
if (response.statusCode !== 204) exit(`Failed to unpublish app : ${requestError(response)}`);
|
|
328
|
-
|
|
329
|
-
console.log('App unpublished.');
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
async function submitAppForReview(manifest) {
|
|
333
|
-
assert.strictEqual(typeof manifest, 'object');
|
|
334
|
-
|
|
335
|
-
const response = await createRequest('POST', `/api/v1/developers/apps/${manifest.id}/versions/${manifest.version}/submit`);
|
|
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.');
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
async function upload(options) {
|
|
268
|
+
async function upload(localOptions, cmd) {
|
|
269
|
+
const options = cmd.optsWithGlobals();
|
|
348
270
|
// try to find the manifest of this project
|
|
349
271
|
const manifestFilePath = locateManifest();
|
|
350
272
|
if (!manifestFilePath) return exit(NO_MANIFEST_FOUND_ERROR_STRING);
|
|
@@ -371,75 +293,137 @@ async function upload(options) {
|
|
|
371
293
|
|
|
372
294
|
// ensure the app is known on the appstore side
|
|
373
295
|
const baseDir = path.dirname(manifestFilePath);
|
|
374
|
-
|
|
296
|
+
|
|
297
|
+
const request = createRequest('POST', '/api/v1/developers/apps', options);
|
|
298
|
+
request.send({ id: manifest.id });
|
|
299
|
+
const response = await request;
|
|
300
|
+
if (response.statusCode !== 409 && response.statusCode !== 201) return exit(`Failed to add app: ${requestError(response)}`); // 409 means already exists
|
|
375
301
|
console.log(`Uploading ${manifest.id}@${manifest.version} (dockerImage: ${manifest.dockerImage}) for testing`);
|
|
376
302
|
|
|
377
|
-
const [error2] = await safe(options.force ? updateVersion(manifest, baseDir) : addVersion(manifest, baseDir));
|
|
303
|
+
const [error2] = await safe(options.force ? updateVersion(manifest, baseDir, options) : addVersion(manifest, baseDir, options));
|
|
378
304
|
if (error2) return exit(error2);
|
|
379
305
|
}
|
|
380
306
|
|
|
381
|
-
function submit() {
|
|
307
|
+
async function submit(localOptions, cmd) {
|
|
308
|
+
const options = cmd.optsWithGlobals();
|
|
309
|
+
|
|
382
310
|
// try to find the manifest of this project
|
|
383
|
-
|
|
311
|
+
const manifestFilePath = locateManifest();
|
|
384
312
|
if (!manifestFilePath) return exit(NO_MANIFEST_FOUND_ERROR_STRING);
|
|
385
313
|
|
|
386
|
-
|
|
314
|
+
const result = manifestFormat.parseFile(manifestFilePath);
|
|
387
315
|
if (result.error) return exit(result.error.message);
|
|
388
316
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
submitAppForReview(manifest, exit);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
async function unpublish(options) {
|
|
395
|
-
const [id, version] = await getAppstoreId(options.appstoreId);
|
|
317
|
+
const manifest = result.manifest;
|
|
396
318
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
return;
|
|
319
|
+
const response = await createRequest('POST', `/api/v1/developers/apps/${manifest.id}/versions/${manifest.version}/submit`, options);
|
|
320
|
+
if (response.statusCode === 404) {
|
|
321
|
+
console.log(`No version ${manifest.version} found. Please use 'cloudron apsptore upload' first`);
|
|
322
|
+
return exit('Failed to submit app for review.');
|
|
401
323
|
}
|
|
402
324
|
|
|
403
|
-
|
|
404
|
-
|
|
325
|
+
if (response.statusCode !== 200) return exit(`Failed to submit app: ${requestError(response)}`);
|
|
326
|
+
|
|
327
|
+
console.log('App submitted for review.');
|
|
328
|
+
console.log('You will receive an email when approved.');
|
|
405
329
|
}
|
|
406
330
|
|
|
407
|
-
async function revoke(
|
|
331
|
+
async function revoke(localOptions, cmd) {
|
|
332
|
+
const options = cmd.optsWithGlobals();
|
|
408
333
|
const [id, version] = await getAppstoreId(options.appstoreId);
|
|
409
334
|
if (!version) return exit('--appstore-id must be of the format id@version');
|
|
410
335
|
|
|
411
336
|
console.log(`Revoking ${id}@${version}`);
|
|
412
|
-
|
|
337
|
+
|
|
338
|
+
const response = await createRequest('POST', `/api/v1/developers/apps/${id}/versions/${version}/revoke`, options);
|
|
339
|
+
if (response.statusCode !== 200) return exit(`Failed to revoke version: ${requestError(response)}`);
|
|
340
|
+
|
|
341
|
+
console.log('version revoked.');
|
|
413
342
|
}
|
|
414
343
|
|
|
415
|
-
async function approve(
|
|
416
|
-
const
|
|
344
|
+
async function approve(localOptions, cmd) {
|
|
345
|
+
const options = cmd.optsWithGlobals();
|
|
346
|
+
|
|
347
|
+
const [appstoreId, version] = await getAppstoreId(options.appstoreId);
|
|
417
348
|
|
|
418
349
|
if (!version) return exit('--appstore-id must be of the format id@version');
|
|
419
350
|
|
|
420
|
-
console.log(`Approving ${
|
|
351
|
+
console.log(`Approving ${appstoreId}@${version}`);
|
|
352
|
+
|
|
353
|
+
const response = await createRequest('POST', `/api/v1/developers/apps/${appstoreId}/versions/${version}/approve`, options);
|
|
354
|
+
if (response.statusCode !== 200) return exit(`Failed to approve version: ${requestError(response)}`);
|
|
355
|
+
|
|
356
|
+
console.log('Approved.');
|
|
357
|
+
console.log('');
|
|
421
358
|
|
|
422
|
-
await
|
|
359
|
+
const response2 = await createRequest('GET', `/api/v1/developers/apps/${appstoreId}/versions/${version}`, options);
|
|
360
|
+
if (response2.statusCode !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
|
|
361
|
+
|
|
362
|
+
console.log('Changelog for forum update: ' + response2.body.manifest.forumUrl);
|
|
363
|
+
console.log('');
|
|
364
|
+
console.log('[' + version + ']');
|
|
365
|
+
console.log(response2.body.manifest.changelog);
|
|
366
|
+
console.log('');
|
|
423
367
|
}
|
|
424
368
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
if (response.statusCode !== 200) return exit(`Failed to get list of published apps: ${requestError(response)}`);
|
|
429
|
-
if (response.body.apps.length === 0) return console.log('No apps published.');
|
|
369
|
+
async function tag(version) {
|
|
370
|
+
const basename = `${path.basename(process.cwd())}`;
|
|
371
|
+
if (!basename.endsWith('-app')) return exit('Does not look like a app repo. Has to end with -app');
|
|
430
372
|
|
|
431
|
-
|
|
373
|
+
if (!semver.valid(version)) return exit(`${version} is not a valid semver`);
|
|
432
374
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
t.cell('Title', app.manifest.title);
|
|
436
|
-
t.cell('Latest Version', app.manifest.version);
|
|
437
|
-
t.cell('Publish State', app.publishState);
|
|
438
|
-
t.cell('Creation Date', new Date(app.creationDate));
|
|
439
|
-
if (options.image) t.cell('Image', app.manifest.dockerImage);
|
|
440
|
-
t.newRow();
|
|
441
|
-
});
|
|
375
|
+
const latestTag = safe.child_process.execSync('git describe --tags --abbrev=0', { encoding: 'utf8' });
|
|
376
|
+
if (safe.error) return exit(`Failed to get last release tag: ${safe.error.message}`);
|
|
442
377
|
|
|
443
|
-
|
|
444
|
-
|
|
378
|
+
const manifestFilePath = locateManifest();
|
|
379
|
+
if (!manifestFilePath) return exit('Could not locate CloudronManifest.json');
|
|
380
|
+
|
|
381
|
+
const result = manifestFormat.parseFile(manifestFilePath);
|
|
382
|
+
if (result.error) return exit(new Error(`Invalid CloudronManifest.json: ${result.error.message}`));
|
|
383
|
+
const { manifest } = result;
|
|
384
|
+
|
|
385
|
+
const latestVersion = latestTag.match(/v(.*)/)[1];
|
|
386
|
+
|
|
387
|
+
if (semver.lte(version, latestVersion)) return exit(`${version} is less than or equal to last repo tag ${latestVersion}`);
|
|
388
|
+
if (semver.inc(latestVersion, 'major') !== version
|
|
389
|
+
&& semver.inc(latestVersion, 'minor') !== version
|
|
390
|
+
&& semver.inc(latestVersion, 'patch') !== version) {
|
|
391
|
+
return exit(`${version} is not the next major/minor/patch of last published version ${latestVersion}`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const latestRenovateCommit = safe.child_process.execSync('git log -n 1 --committer=renovatebot@cloudron.io --pretty="format:%h,%aI,%s"', { encoding: 'utf8' });
|
|
395
|
+
if (!latestRenovateCommit) return exit('Could not find a commit from renovate bot');
|
|
396
|
+
|
|
397
|
+
const [ , , commitMessage ] = latestRenovateCommit.split(',');
|
|
398
|
+
const repoDir = path.dirname(manifestFilePath);
|
|
399
|
+
const upstreamVersion = commitMessage.match(/update dependency .* to (.*)/)[1];
|
|
400
|
+
|
|
401
|
+
console.log(`Enter the changelog for ${upstreamVersion}: (press ctrl+D to finish)`);
|
|
402
|
+
const rawChangelog = fs.readFileSync(0, 'utf-8');
|
|
403
|
+
const mdChangelog = rawChangelog.split('\n').map(line => {
|
|
404
|
+
line = line.trim();
|
|
405
|
+
line = line.replace(/[\u{0080}-\u{FFFF}]/gu, ''); // only ascii
|
|
406
|
+
line = line.replace(/^\* /, ''); // replace any "* " in the front
|
|
407
|
+
return line ? `* ${line}` : '';
|
|
408
|
+
}).join('\n');
|
|
409
|
+
const newChangelog = `\n[${version}]\n* Update ${manifest.title} to ${upstreamVersion}\n${mdChangelog}\n`;
|
|
410
|
+
const changelogFile = `${repoDir}/${manifest.changelog.replace('file://', '')}`; // sometimes CHANGELOG, sometimes CHANGELOG.md
|
|
411
|
+
fs.appendFileSync(changelogFile, newChangelog);
|
|
412
|
+
|
|
413
|
+
manifest.version = version;
|
|
414
|
+
manifest.upstreamVersion = upstreamVersion;
|
|
415
|
+
fs.writeFileSync('CloudronManifest.json', JSON.stringify(manifest, null, 2));
|
|
416
|
+
|
|
417
|
+
// git branch --show-current does not work in CI :/
|
|
418
|
+
const mainOrMaster = safe.child_process.execSync('git branch -r --list origin/master origin/main', { encoding: 'utf8' });
|
|
419
|
+
if (safe.error) return exit('Could not determine branch name');
|
|
420
|
+
const branch = mainOrMaster.includes('master') ? 'master' : 'main';
|
|
421
|
+
|
|
422
|
+
execSync(`git commit -a -m 'Version ${version}'`, { encoding: 'utf8' });
|
|
423
|
+
execSync(`git tag v${version} -a -m 'Version ${version}'`, { encoding: 'utf8' });
|
|
424
|
+
console.log(`git push --atomic origin ${branch} v${version}`);
|
|
425
|
+
execSync(`git push --atomic origin HEAD:${branch} v${version}`, { encoding: 'utf8' }); // push this tag only. in CI, we might have a git cache
|
|
426
|
+
if (safe.error) return exit(`Failed to push tag v${version} and branch ${branch}: ${safe.error.message}`, { encoding: 'utf8' });
|
|
427
|
+
|
|
428
|
+
console.log(`Created tag v${version} and pushed branch ${branch}`);
|
|
445
429
|
}
|
package/bin/cloudron-repo
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
'use strict';
|
|
4
|
-
|
|
5
|
-
const { program } = require('commander'),
|
|
6
|
-
repoActions = require('../src/repo-actions.js');
|
|
7
|
-
|
|
8
|
-
program.version(require('../package.json').version);
|
|
9
|
-
|
|
10
|
-
program.command('tag <version>')
|
|
11
|
-
.description('Creates a new release with given tag')
|
|
12
|
-
.action(repoActions.tag);
|
|
13
|
-
|
|
14
|
-
program.command('publish')
|
|
15
|
-
.description('Publish to appstore')
|
|
16
|
-
.action(repoActions.publish);
|
|
17
|
-
|
|
18
|
-
program.parse(process.argv);
|
package/src/repo-actions.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/* jshint node:true */
|
|
2
|
-
|
|
3
|
-
'use strict';
|
|
4
|
-
|
|
5
|
-
const { exit, locateManifest } = require('./helper.js'),
|
|
6
|
-
execSync = require('child_process').execSync,
|
|
7
|
-
fs = require('fs'),
|
|
8
|
-
manifestFormat = require('cloudron-manifestformat'),
|
|
9
|
-
path = require('path'),
|
|
10
|
-
safe = require('safetydance'),
|
|
11
|
-
semver = require('semver');
|
|
12
|
-
|
|
13
|
-
exports = module.exports = {
|
|
14
|
-
tag,
|
|
15
|
-
publish
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
async function tag(version, options) {
|
|
19
|
-
const basename = `${path.basename(process.cwd())}`;
|
|
20
|
-
if (!basename.endsWith('-app')) return exit('Does not look like a app repo. Has to end with -app');
|
|
21
|
-
|
|
22
|
-
if (!semver.valid(version)) return exit(`${version} is not a valid semver`);
|
|
23
|
-
|
|
24
|
-
const latestTag = safe.child_process.execSync('git describe --tags --abbrev=0', { encoding: 'utf8' });
|
|
25
|
-
if (safe.error) return exit(`Failed to get last release tag: ${safe.error.message}`);
|
|
26
|
-
|
|
27
|
-
const manifestFilePath = locateManifest();
|
|
28
|
-
if (!manifestFilePath) return exit('Could not locate CloudronManifest.json');
|
|
29
|
-
|
|
30
|
-
const latestVersion = latestTag.match(/v(.*)/)[1];
|
|
31
|
-
|
|
32
|
-
if (semver.lte(version, latestVersion)) return exit(`${version} is less than or equal to last repo tag ${latestVersion}`);
|
|
33
|
-
if (semver.inc(latestVersion, 'major') !== version
|
|
34
|
-
&& semver.inc(latestVersion, 'minor') !== version
|
|
35
|
-
&& semver.inc(latestVersion, 'patch') !== version) {
|
|
36
|
-
return exit(`${version} is not the next major/minor/patch of last published version ${latestVersion}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const latestRenovateCommit = safe.child_process.execSync('git log -n 1 --committer=renovatebot@cloudron.io --pretty="format:%h,%aI,%s"', { encoding: 'utf8' });
|
|
40
|
-
if (!latestRenovateCommit) return exit('Could not find a commit from renovate bot');
|
|
41
|
-
|
|
42
|
-
const [ abbrevHash, commitDate, commitMessage ] = latestRenovateCommit.split(',');
|
|
43
|
-
const cleanDate = commitDate.replace(/T.*/, '').replace(/[-]/g, '');
|
|
44
|
-
const repoDir = path.dirname(manifestFilePath);
|
|
45
|
-
const repoName = path.basename(repoDir).replace('-app', '');
|
|
46
|
-
const dockerImage = options.image || `cloudron/${repoName}:${cleanDate}-${abbrevHash}`;
|
|
47
|
-
const upstreamVersion = commitMessage.match(/update dependency .* to (.*)/)[1];
|
|
48
|
-
|
|
49
|
-
const result = manifestFormat.parseFile(manifestFilePath);
|
|
50
|
-
if (result.error) throw new Error(`Invalid CloudronManifest.json: ${result.error.message}`);
|
|
51
|
-
const { manifest } = result;
|
|
52
|
-
|
|
53
|
-
console.log(`Enter the changelog for ${upstreamVersion}: (press ctrl+D to finish)`);
|
|
54
|
-
const rawChangelog = fs.readFileSync(0, 'utf-8');
|
|
55
|
-
const mdChangelog = rawChangelog.split('\n').map(line => {
|
|
56
|
-
line = line.trim();
|
|
57
|
-
line = line.replace(/[\u{0080}-\u{FFFF}]/gu, ''); // only ascii
|
|
58
|
-
line = line.replace(/^\* /, ''); // replace any "* " in the front
|
|
59
|
-
return line ? `* ${line}` : '';
|
|
60
|
-
}).join('\n');
|
|
61
|
-
const newChangelog = `\n[${version}]\n* Update ${manifest.title} to ${upstreamVersion}\n${mdChangelog}\n`;
|
|
62
|
-
const changelogFile = `${repoDir}/${manifest.changelog.replace('file://', '')}`; // sometimes CHANGELOG, sometimes CHANGELOG.md
|
|
63
|
-
fs.appendFileSync(changelogFile, newChangelog);
|
|
64
|
-
|
|
65
|
-
manifest.version = version;
|
|
66
|
-
manifest.upstreamVersion = upstreamVersion;
|
|
67
|
-
fs.writeFileSync('CloudronManifest.json', JSON.stringify(manifest, null, 2));
|
|
68
|
-
|
|
69
|
-
// git branch --show-current does not work in CI :/
|
|
70
|
-
const mainOrMaster = safe.child_process.execSync('git branch -r --list origin/master origin/main', { encoding: 'utf8' });
|
|
71
|
-
if (safe.error) return exit('Could not determine branch name');
|
|
72
|
-
const branch = mainOrMaster.includes('master') ? 'master' : 'main';
|
|
73
|
-
|
|
74
|
-
execSync(`git commit -a -m 'Version ${version}'`, { encoding: 'utf8' });
|
|
75
|
-
execSync(`git tag v${version} -a -m 'Version ${version}'`, { encoding: 'utf8' });
|
|
76
|
-
console.log(`git push --atomic origin ${branch} v${version}`);
|
|
77
|
-
execSync(`git push --atomic origin HEAD:${branch} v${version}`, { encoding: 'utf8' }); // push this tag only. in CI, we might have a git cache
|
|
78
|
-
if (safe.error) return exit(`Failed to push tag v${version} and branch ${branch}: ${safe.error.message}`, { encoding: 'utf8' });
|
|
79
|
-
|
|
80
|
-
console.log(`Created tag v${version} and pushed branch ${branch}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function publish(options) {
|
|
84
|
-
const manifestFilePath = locateManifest();
|
|
85
|
-
if (!manifestFilePath) return exit('Could not locate CloudronManifest.json');
|
|
86
|
-
|
|
87
|
-
const latestRenovateCommit = safe.child_process.execSync('git log -n 1 --committer=renovatebot@cloudron.io --pretty="format:%h,%aI,%s"', { encoding: 'utf8' });
|
|
88
|
-
if (!latestRenovateCommit) return exit('Could not find a commit from renovate bot');
|
|
89
|
-
|
|
90
|
-
const [ abbrevHash, commitDate ] = latestRenovateCommit.split(',');
|
|
91
|
-
const cleanDate = commitDate.replace(/T.*/, '').replace(/[-]/g, '');
|
|
92
|
-
const repoDir = path.dirname(manifestFilePath);
|
|
93
|
-
const repoName = path.basename(repoDir).replace('-app', '');
|
|
94
|
-
const dockerImage = options.image || `cloudron/${repoName}:${cleanDate}-${abbrevHash}`;
|
|
95
|
-
|
|
96
|
-
safe.child_process.execSync(`cloudron appstore upload --image ${dockerImage}`);
|
|
97
|
-
if (safe.error) return exit(`Failed to publish image to appstore: ${safe.error.message}`);
|
|
98
|
-
execSync(`cloudron appstore submit`);
|
|
99
|
-
execSync(`cloudron appstore approve`);
|
|
100
|
-
|
|
101
|
-
console.log('Published to appstore');
|
|
102
|
-
}
|