cloudron 5.11.4 → 5.11.6
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 +4 -11
- package/bin/cloudron-build +3 -4
- package/package.json +1 -1
- package/src/appstore-actions.js +84 -67
- package/src/build-actions.js +53 -44
- 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
|
@@ -22,16 +22,15 @@ program.command('info')
|
|
|
22
22
|
.option('--appstore-id <appid@version>', 'Appstore id and version')
|
|
23
23
|
.action(appstoreActions.info);
|
|
24
24
|
|
|
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
25
|
program.command('approve')
|
|
31
26
|
.description('Approve a submitted app version')
|
|
32
27
|
.option('--appstore-id <appid@version>', 'Appstore id and version')
|
|
33
28
|
.action(appstoreActions.approve);
|
|
34
29
|
|
|
30
|
+
program.command('publish <version>')
|
|
31
|
+
.description('Tag the repo and publish')
|
|
32
|
+
.action(appstoreActions.publish);
|
|
33
|
+
|
|
35
34
|
program.command('revoke')
|
|
36
35
|
.description('Revoke a published app version')
|
|
37
36
|
.option('--appstore-id <appid@version>', 'Appstore id and version')
|
|
@@ -41,12 +40,6 @@ program.command('submit')
|
|
|
41
40
|
.description('Submit app to the store for review')
|
|
42
41
|
.action(appstoreActions.submit);
|
|
43
42
|
|
|
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
43
|
program.command('upload')
|
|
51
44
|
.description('Upload app to the store for testing')
|
|
52
45
|
.option('-i, --image <image>', 'Docker image')
|
package/bin/cloudron-build
CHANGED
|
@@ -14,8 +14,8 @@ function collectArgs(value, collected) {
|
|
|
14
14
|
|
|
15
15
|
// global options
|
|
16
16
|
program.option('--server <server>', 'Cloudron domain')
|
|
17
|
-
.option('--build-service-token <token>', 'Build service token')
|
|
18
|
-
.option('--
|
|
17
|
+
.option('--token, --build-service-token <token>', 'Build service token')
|
|
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 })
|
|
21
21
|
.description('Build an app. This is the default subcommand')
|
|
@@ -31,8 +31,7 @@ program.command('build', { isDefault: true })
|
|
|
31
31
|
|
|
32
32
|
program.command('login')
|
|
33
33
|
.description('Login to the build service')
|
|
34
|
-
.option('-
|
|
35
|
-
.option('-p, --password <password>', 'Password (unsafe)')
|
|
34
|
+
.option('-t, --token <token>', 'Build service token')
|
|
36
35
|
.action(buildActions.login);
|
|
37
36
|
|
|
38
37
|
program.command('logs')
|
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
|
+
publish
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
const NO_MANIFEST_FOUND_ERROR_STRING = 'No CloudronManifest.json found';
|
|
@@ -267,22 +269,6 @@ async function updateVersion(manifest, baseDir) {
|
|
|
267
269
|
if (response.statusCode !== 204) throw new Error(`Failed to publish version: ${requestError(response)}`);
|
|
268
270
|
}
|
|
269
271
|
|
|
270
|
-
async function delVersion(manifest, force) {
|
|
271
|
-
assert.strictEqual(typeof manifest, 'object');
|
|
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
272
|
async function revokeVersion(appstoreId, version) {
|
|
287
273
|
assert.strictEqual(typeof appstoreId, 'string');
|
|
288
274
|
assert.strictEqual(typeof version, 'string');
|
|
@@ -313,22 +299,6 @@ async function approveVersion(appstoreId, version) {
|
|
|
313
299
|
console.log('');
|
|
314
300
|
}
|
|
315
301
|
|
|
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
302
|
async function submitAppForReview(manifest) {
|
|
333
303
|
assert.strictEqual(typeof manifest, 'object');
|
|
334
304
|
|
|
@@ -380,30 +350,17 @@ async function upload(options) {
|
|
|
380
350
|
|
|
381
351
|
function submit() {
|
|
382
352
|
// try to find the manifest of this project
|
|
383
|
-
|
|
353
|
+
const manifestFilePath = locateManifest();
|
|
384
354
|
if (!manifestFilePath) return exit(NO_MANIFEST_FOUND_ERROR_STRING);
|
|
385
355
|
|
|
386
|
-
|
|
356
|
+
const result = manifestFormat.parseFile(manifestFilePath);
|
|
387
357
|
if (result.error) return exit(result.error.message);
|
|
388
358
|
|
|
389
|
-
|
|
359
|
+
const manifest = result.manifest;
|
|
390
360
|
|
|
391
361
|
submitAppForReview(manifest, exit);
|
|
392
362
|
}
|
|
393
363
|
|
|
394
|
-
async function unpublish(options) {
|
|
395
|
-
const [id, version] = await getAppstoreId(options.appstoreId);
|
|
396
|
-
|
|
397
|
-
if (!version) {
|
|
398
|
-
console.log(`Unpublishing ${options.appstoreId}`);
|
|
399
|
-
await delApp(options.app, !!options.force);
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
console.log(`Unpublishing ${id}@${version}`);
|
|
404
|
-
await delVersion(id, !!options.force);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
364
|
async function revoke(options) {
|
|
408
365
|
const [id, version] = await getAppstoreId(options.appstoreId);
|
|
409
366
|
if (!version) return exit('--appstore-id must be of the format id@version');
|
|
@@ -422,24 +379,84 @@ async function approve(options) {
|
|
|
422
379
|
await approveVersion(id, version);
|
|
423
380
|
}
|
|
424
381
|
|
|
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.');
|
|
382
|
+
async function tagRepository(version) {
|
|
383
|
+
const basename = `${path.basename(process.cwd())}`;
|
|
384
|
+
if (!basename.endsWith('-app')) return exit('Does not look like a app repo. Has to end with -app');
|
|
430
385
|
|
|
431
|
-
|
|
386
|
+
if (!semver.valid(version)) return exit(`${version} is not a valid semver`);
|
|
432
387
|
|
|
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
|
-
});
|
|
388
|
+
const latestTag = safe.child_process.execSync('git describe --tags --abbrev=0', { encoding: 'utf8' });
|
|
389
|
+
if (safe.error) return exit(`Failed to get last release tag: ${safe.error.message}`);
|
|
442
390
|
|
|
443
|
-
|
|
444
|
-
|
|
391
|
+
const manifestFilePath = locateManifest();
|
|
392
|
+
if (!manifestFilePath) return exit('Could not locate CloudronManifest.json');
|
|
393
|
+
|
|
394
|
+
const result = manifestFormat.parseFile(manifestFilePath);
|
|
395
|
+
if (result.error) throw new Error(`Invalid CloudronManifest.json: ${result.error.message}`);
|
|
396
|
+
const { manifest } = result;
|
|
397
|
+
|
|
398
|
+
const latestVersion = latestTag.match(/v(.*)/)[1];
|
|
399
|
+
|
|
400
|
+
if (semver.lte(version, latestVersion)) return exit(`${version} is less than or equal to last repo tag ${latestVersion}`);
|
|
401
|
+
if (semver.inc(latestVersion, 'major') !== version
|
|
402
|
+
&& semver.inc(latestVersion, 'minor') !== version
|
|
403
|
+
&& semver.inc(latestVersion, 'patch') !== version) {
|
|
404
|
+
return exit(`${version} is not the next major/minor/patch of last published version ${latestVersion}`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const latestRenovateCommit = safe.child_process.execSync('git log -n 1 --committer=renovatebot@cloudron.io --pretty="format:%h,%aI,%s"', { encoding: 'utf8' });
|
|
408
|
+
if (!latestRenovateCommit) return exit('Could not find a commit from renovate bot');
|
|
409
|
+
|
|
410
|
+
const [ , , commitMessage ] = latestRenovateCommit.split(',');
|
|
411
|
+
const repoDir = path.dirname(manifestFilePath);
|
|
412
|
+
const upstreamVersion = commitMessage.match(/update dependency .* to (.*)/)[1];
|
|
413
|
+
|
|
414
|
+
console.log(`Enter the changelog for ${upstreamVersion}: (press ctrl+D to finish)`);
|
|
415
|
+
const rawChangelog = fs.readFileSync(0, 'utf-8');
|
|
416
|
+
const mdChangelog = rawChangelog.split('\n').map(line => {
|
|
417
|
+
line = line.trim();
|
|
418
|
+
line = line.replace(/[\u{0080}-\u{FFFF}]/gu, ''); // only ascii
|
|
419
|
+
line = line.replace(/^\* /, ''); // replace any "* " in the front
|
|
420
|
+
return line ? `* ${line}` : '';
|
|
421
|
+
}).join('\n');
|
|
422
|
+
const newChangelog = `\n[${version}]\n* Update ${manifest.title} to ${upstreamVersion}\n${mdChangelog}\n`;
|
|
423
|
+
const changelogFile = `${repoDir}/${manifest.changelog.replace('file://', '')}`; // sometimes CHANGELOG, sometimes CHANGELOG.md
|
|
424
|
+
fs.appendFileSync(changelogFile, newChangelog);
|
|
425
|
+
|
|
426
|
+
manifest.version = version;
|
|
427
|
+
manifest.upstreamVersion = upstreamVersion;
|
|
428
|
+
fs.writeFileSync('CloudronManifest.json', JSON.stringify(manifest, null, 2));
|
|
429
|
+
|
|
430
|
+
// git branch --show-current does not work in CI :/
|
|
431
|
+
const mainOrMaster = safe.child_process.execSync('git branch -r --list origin/master origin/main', { encoding: 'utf8' });
|
|
432
|
+
if (safe.error) return exit('Could not determine branch name');
|
|
433
|
+
const branch = mainOrMaster.includes('master') ? 'master' : 'main';
|
|
434
|
+
|
|
435
|
+
execSync(`git commit -a -m 'Version ${version}'`, { encoding: 'utf8' });
|
|
436
|
+
execSync(`git tag v${version} -a -m 'Version ${version}'`, { encoding: 'utf8' });
|
|
437
|
+
console.log(`git push --atomic origin ${branch} v${version}`);
|
|
438
|
+
execSync(`git push --atomic origin HEAD:${branch} v${version}`, { encoding: 'utf8' }); // push this tag only. in CI, we might have a git cache
|
|
439
|
+
if (safe.error) return exit(`Failed to push tag v${version} and branch ${branch}: ${safe.error.message}`, { encoding: 'utf8' });
|
|
440
|
+
|
|
441
|
+
console.log(`Created tag v${version} and pushed branch ${branch}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async function publish(version, options) {
|
|
445
|
+
await tagRepository(version);
|
|
446
|
+
|
|
447
|
+
const manifestFilePath = locateManifest();
|
|
448
|
+
if (!manifestFilePath) return exit('Could not locate CloudronManifest.json');
|
|
449
|
+
|
|
450
|
+
const latestRenovateCommit = safe.child_process.execSync('git log -n 1 --committer=renovatebot@cloudron.io --pretty="format:%h,%aI,%s"', { encoding: 'utf8' });
|
|
451
|
+
if (!latestRenovateCommit) return exit('Could not find a commit from renovate bot');
|
|
452
|
+
|
|
453
|
+
const [ abbrevHash, commitDate ] = latestRenovateCommit.split(',');
|
|
454
|
+
const cleanDate = commitDate.replace(/T.*/, '').replace(/[-]/g, '');
|
|
455
|
+
const repoDir = path.dirname(manifestFilePath);
|
|
456
|
+
const repoName = path.basename(repoDir).replace('-app', '');
|
|
457
|
+
const dockerImage = options.image || `cloudron/${repoName}:${cleanDate}-${abbrevHash}`;
|
|
458
|
+
|
|
459
|
+
await upload({ image: dockerImage, force: false });
|
|
460
|
+
await submit();
|
|
461
|
+
await approve();
|
|
445
462
|
}
|
package/src/build-actions.js
CHANGED
|
@@ -33,25 +33,50 @@ function requestError(response) {
|
|
|
33
33
|
return `${response.statusCode} message: ${response.body?.message || null}`;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// analyzes options and merges with any existing build service config
|
|
37
|
+
function getBuildServiceConfig(options) {
|
|
38
|
+
const buildService = config.getBuildServiceConfig();
|
|
39
|
+
if (!buildService.type) buildService.type = 'local'; // default
|
|
40
|
+
|
|
41
|
+
if (options.local) {
|
|
42
|
+
buildService.type = 'local';
|
|
43
|
+
} else if (options.setBuildService) { // stash for future use
|
|
44
|
+
buildService.token = null;
|
|
45
|
+
buildService.url = null;
|
|
46
|
+
buildService.type = 'remote';
|
|
47
|
+
|
|
48
|
+
let url;
|
|
49
|
+
if (typeof options.setBuildService === 'string') {
|
|
50
|
+
url = options.setBuildService;
|
|
51
|
+
} else {
|
|
52
|
+
url = readlineSync.question('Enter build service URL: ', { });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (url.indexOf('://') === -1) url = `https://${url}`;
|
|
56
|
+
buildService.url = url;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (options.buildServiceToken) buildService.token = options.buildServiceToken;
|
|
60
|
+
|
|
61
|
+
config.setBuildServiceConfig(buildService);
|
|
62
|
+
|
|
63
|
+
return buildService;
|
|
64
|
+
}
|
|
65
|
+
|
|
36
66
|
async function login(options) {
|
|
37
67
|
assert.strictEqual(typeof options, 'object');
|
|
38
68
|
|
|
39
|
-
const buildServiceConfig =
|
|
40
|
-
|
|
41
|
-
if (!options.hideBanner) console.log('Build Service login' + ` (${buildServiceConfig.url}):`);
|
|
69
|
+
const buildServiceConfig = getBuildServiceConfig(options);
|
|
42
70
|
|
|
43
|
-
|
|
44
|
-
const password = options.password || readlineSync.question('Password: ', { noEchoBack: true });
|
|
71
|
+
console.log('Build Service login' + ` (${buildServiceConfig.url}):`);
|
|
45
72
|
|
|
46
|
-
|
|
47
|
-
buildServiceConfig.token = null;
|
|
48
|
-
config.setBuildServiceConfig(buildServiceConfig);
|
|
73
|
+
const token = options.buildServiceToken || readlineSync.question('Token: ', {});
|
|
49
74
|
|
|
50
|
-
const response = await superagent.
|
|
51
|
-
if (response.statusCode === 401 || response.statusCode === 403) return exit(`Authentication error: ${requestError(response)}
|
|
52
|
-
if (response.statusCode !== 200
|
|
75
|
+
const response = await superagent.get(`${buildServiceConfig.url}/api/v1/profile`).query({ accessToken: token }).ok(() => true);
|
|
76
|
+
if (response.statusCode === 401 || response.statusCode === 403) return exit(`Authentication error: ${requestError(response)}`);
|
|
77
|
+
if (response.statusCode !== 200) return exit(`Unexpected response: ${requestError(response)}`);
|
|
53
78
|
|
|
54
|
-
buildServiceConfig.token =
|
|
79
|
+
buildServiceConfig.token = token;
|
|
55
80
|
config.setBuildServiceConfig(buildServiceConfig);
|
|
56
81
|
|
|
57
82
|
console.log('Login successful.');
|
|
@@ -276,7 +301,9 @@ async function buildRemote(manifest, sourceDir, appConfig, options) {
|
|
|
276
301
|
exit();
|
|
277
302
|
}
|
|
278
303
|
|
|
279
|
-
async function build(
|
|
304
|
+
async function build(localOptions, cmd) {
|
|
305
|
+
const options = cmd.optsWithGlobals();
|
|
306
|
+
|
|
280
307
|
// try to find the manifest of this project
|
|
281
308
|
const manifestFilePath = helper.locateManifest();
|
|
282
309
|
if (!manifestFilePath) return exit('No CloudronManifest.json found');
|
|
@@ -288,31 +315,7 @@ async function build(options) {
|
|
|
288
315
|
const sourceDir = path.dirname(manifestFilePath);
|
|
289
316
|
|
|
290
317
|
const appConfig = config.getAppConfig(sourceDir);
|
|
291
|
-
|
|
292
|
-
const buildService = config.getBuildServiceConfig();
|
|
293
|
-
if (!buildService.type) buildService.type = 'local'; // default
|
|
294
|
-
|
|
295
|
-
if (options.local) {
|
|
296
|
-
buildService.type = 'local';
|
|
297
|
-
} else if (options.setBuildService) {
|
|
298
|
-
buildService.token = null;
|
|
299
|
-
buildService.url = null;
|
|
300
|
-
buildService.type = 'remote';
|
|
301
|
-
|
|
302
|
-
let url;
|
|
303
|
-
if (typeof options.setBuildService === 'string') {
|
|
304
|
-
url = options.setBuildService;
|
|
305
|
-
} else {
|
|
306
|
-
url = readlineSync.question('Enter build service URL: ', { });
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (url.indexOf('://') === -1) url = `https://${url}`;
|
|
310
|
-
buildService.url = url;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (options.buildServiceToken) buildService.token = options.buildServiceToken;
|
|
314
|
-
|
|
315
|
-
config.setBuildServiceConfig(buildService);
|
|
318
|
+
const buildServiceConfig = getBuildServiceConfig(options);
|
|
316
319
|
|
|
317
320
|
let repository = appConfig.repository;
|
|
318
321
|
if (!repository || options.setRepository) {
|
|
@@ -328,23 +331,27 @@ async function build(options) {
|
|
|
328
331
|
config.setAppConfig(sourceDir, appConfig);
|
|
329
332
|
}
|
|
330
333
|
|
|
331
|
-
if (
|
|
334
|
+
if (buildServiceConfig.type === 'local') {
|
|
332
335
|
await buildLocal(manifest, sourceDir, appConfig, options);
|
|
333
|
-
} else if (
|
|
336
|
+
} else if (buildServiceConfig.type === 'remote' && buildServiceConfig.url) {
|
|
334
337
|
await buildRemote(manifest, sourceDir, appConfig, options);
|
|
335
338
|
} else {
|
|
336
339
|
exit('Unknown build service type or missing build service url. Rerun with --reset-build-service');
|
|
337
340
|
}
|
|
338
341
|
}
|
|
339
342
|
|
|
340
|
-
async function logs(
|
|
343
|
+
async function logs(localOptions, cmd) {
|
|
344
|
+
const options = cmd.optsWithGlobals();
|
|
345
|
+
|
|
341
346
|
if (!options.id) return exit('buildId is required');
|
|
342
347
|
|
|
343
348
|
const [logsError] = await safe(followBuildLog(options.id, !!options.raw));
|
|
344
349
|
if (logsError) console.log(`Failed to get logs: ${logsError.message}`);
|
|
345
350
|
}
|
|
346
351
|
|
|
347
|
-
async function status(
|
|
352
|
+
async function status(localOptions, cmd) {
|
|
353
|
+
const options = cmd.optsWithGlobals();
|
|
354
|
+
|
|
348
355
|
if (!options.id) return exit('buildId is required');
|
|
349
356
|
|
|
350
357
|
const [statusError, status] = await safe(getStatus(options.id));
|
|
@@ -352,12 +359,14 @@ async function status(options) {
|
|
|
352
359
|
console.log(status);
|
|
353
360
|
}
|
|
354
361
|
|
|
355
|
-
async function push(
|
|
362
|
+
async function push(localOptions, cmd) {
|
|
363
|
+
const options = cmd.optsWithGlobals();
|
|
364
|
+
|
|
356
365
|
if (!options.id) return exit('buildId is required');
|
|
357
366
|
if (!options.repository) return exit('repository is required');
|
|
358
367
|
if (!options.tag) return exit('tag is required');
|
|
359
368
|
|
|
360
|
-
const buildServiceConfig =
|
|
369
|
+
const buildServiceConfig = getBuildServiceConfig(options);
|
|
361
370
|
|
|
362
371
|
const response = await superagent.post(`${buildServiceConfig.url}/api/v1/builds/${options.id}/push`)
|
|
363
372
|
.query({ accessToken: buildServiceConfig.token })
|
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
|
-
}
|