cloudron 5.11.7 → 5.11.9
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 +4 -3
- package/bin/cloudron-build +1 -0
- package/package.json +4 -4
- package/src/appstore-actions.js +86 -52
- package/src/build-actions.js +11 -3
package/bin/cloudron-appstore
CHANGED
|
@@ -32,11 +32,12 @@ program.command('info')
|
|
|
32
32
|
program.command('approve')
|
|
33
33
|
.description('Approve a submitted app version')
|
|
34
34
|
.option('--appstore-id <appid@version>', 'Appstore id and version')
|
|
35
|
+
.option('--no-git-push', 'Do not attempt to push to git repo')
|
|
35
36
|
.action(appstoreActions.approve);
|
|
36
37
|
|
|
37
|
-
program.command('
|
|
38
|
-
.description('
|
|
39
|
-
.action(appstoreActions.
|
|
38
|
+
program.command('notify')
|
|
39
|
+
.description('Notify forum about successful app submission')
|
|
40
|
+
.action(appstoreActions.notify);
|
|
40
41
|
|
|
41
42
|
program.command('revoke')
|
|
42
43
|
.description('Revoke a published app version')
|
package/bin/cloudron-build
CHANGED
|
@@ -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.9",
|
|
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"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"author": "Cloudron Developers <support@cloudron.io>",
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"async": "^3.2.5",
|
|
21
|
-
"cloudron-manifestformat": "^5.
|
|
21
|
+
"cloudron-manifestformat": "^5.26.2",
|
|
22
22
|
"commander": "^12.1.0",
|
|
23
23
|
"debug": "^4.3.5",
|
|
24
24
|
"easy-table": "^1.2.0",
|
package/src/appstore-actions.js
CHANGED
|
@@ -25,7 +25,7 @@ exports = module.exports = {
|
|
|
25
25
|
revoke,
|
|
26
26
|
approve,
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
notify
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
const NO_MANIFEST_FOUND_ERROR_STRING = 'No CloudronManifest.json found';
|
|
@@ -280,8 +280,19 @@ async function upload(localOptions, cmd) {
|
|
|
280
280
|
const appConfig = config.getAppConfig(sourceDir);
|
|
281
281
|
|
|
282
282
|
// image can be passed in options for buildbot
|
|
283
|
-
|
|
284
|
-
|
|
283
|
+
if (options.image) {
|
|
284
|
+
manifest.dockerImage = options.image;
|
|
285
|
+
} else {
|
|
286
|
+
const gitCommit = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();
|
|
287
|
+
if (appConfig.gitCommit !== gitCommit) {
|
|
288
|
+
console.log(`This build ${appConfig.dockerImage} was made from git hash ${appConfig.gitCommit} but you are now at ${gitCommit}`);
|
|
289
|
+
if (!appConfig.gitCommit) return exit('The build is stale');
|
|
290
|
+
const output = execSync(`git diff ${appConfig.gitCommit}..HEAD --name-only`, { encoding: 'utf8'});
|
|
291
|
+
const changedFiles = output.trim().split('\n').filter(filepath => !filepath.match(/(^CHANGELOG|README|CloudronManifest|test\|.git|screenshots|renovate|LICENSE|POSTINSTALL|.docker|logo)/));
|
|
292
|
+
if (changedFiles.length) return exit(`The build is stale. Changed files: ${changedFiles.join(',')}`);
|
|
293
|
+
}
|
|
294
|
+
manifest.dockerImage = appConfig.dockerImage;
|
|
295
|
+
}
|
|
285
296
|
|
|
286
297
|
if (!manifest.dockerImage) exit('No docker image found, run `cloudron build` first');
|
|
287
298
|
|
|
@@ -291,6 +302,10 @@ async function upload(localOptions, cmd) {
|
|
|
291
302
|
const error = manifestFormat.checkAppstoreRequirements(manifest);
|
|
292
303
|
if (error) return exit(error);
|
|
293
304
|
|
|
305
|
+
const [repo, tag] = manifest.dockerImage.split(':');
|
|
306
|
+
const [tagError, tagResponse] = await safe(superagent.get(`https://hub.docker.com/v2/repositories/${repo}/tags/${tag}`).ok(() => true));
|
|
307
|
+
if (tagError || tagResponse.statusCode !== 200) return exit(`Failed to find docker image in dockerhub. check https://hub.docker.com/r/${repo}/tags : ${tagError || requestError(tagResponse)}`);
|
|
308
|
+
|
|
294
309
|
// ensure the app is known on the appstore side
|
|
295
310
|
const baseDir = path.dirname(manifestFilePath);
|
|
296
311
|
|
|
@@ -350,9 +365,32 @@ async function approve(localOptions, cmd) {
|
|
|
350
365
|
|
|
351
366
|
console.log(`Approving ${appstoreId}@${version}`);
|
|
352
367
|
|
|
368
|
+
let defaultBranch, latestTag;
|
|
369
|
+
if (options.gitPush) {
|
|
370
|
+
// Git repo pre-flight checks: checking if latest tag matches latest commit
|
|
371
|
+
defaultBranch = safe.child_process.execSync(`git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'`, { encoding: 'utf8' });
|
|
372
|
+
if (safe.error) return exit(`Failed to get default branch: ${safe.error.message}`);
|
|
373
|
+
|
|
374
|
+
const defaultBranchSha = safe.child_process.execSync(`git rev-parse ${defaultBranch}`, { encoding: 'utf8' });
|
|
375
|
+
if (safe.error) return exit(`Failed to get head of ${defaultBranch}: ${safe.error.message}`);
|
|
376
|
+
|
|
377
|
+
latestTag = safe.child_process.execSync('git describe --tags --abbrev=0', { encoding: 'utf8' });
|
|
378
|
+
if (safe.error) return exit(`Failed to get latest tag: ${safe.error.message}`);
|
|
379
|
+
|
|
380
|
+
const latestTagSha = safe.child_process.execSync(`git rev-list -n 1 ${latestTag}`, { encoding: 'utf8' });
|
|
381
|
+
if (safe.error) return exit(`Failed to get head of ${defaultBranch}: ${safe.error.message}`);
|
|
382
|
+
|
|
383
|
+
if (defaultBranchSha !== latestTagSha) return exit(`Latest tag ${latestTag} does not match HEAD of ${defaultBranch}`);
|
|
384
|
+
}
|
|
385
|
+
|
|
353
386
|
const response = await createRequest('POST', `/api/v1/developers/apps/${appstoreId}/versions/${version}/approve`, options);
|
|
354
387
|
if (response.statusCode !== 200) return exit(`Failed to approve version: ${requestError(response)}`);
|
|
355
388
|
|
|
389
|
+
if (options.gitPush) {
|
|
390
|
+
safe.child_process.execSync(`git push --atomic origin ${defaultBranch} ${latestTag}`, { encoding: 'utf8' });
|
|
391
|
+
if (safe.error) return exit(`Failed to get last release tag: ${safe.error.message}`);
|
|
392
|
+
}
|
|
393
|
+
|
|
356
394
|
console.log('Approved.');
|
|
357
395
|
console.log('');
|
|
358
396
|
|
|
@@ -366,14 +404,11 @@ async function approve(localOptions, cmd) {
|
|
|
366
404
|
console.log('');
|
|
367
405
|
}
|
|
368
406
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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}`);
|
|
407
|
+
// https://docs.nodebb.org/api/read/
|
|
408
|
+
// https://docs.nodebb.org/api/write/
|
|
409
|
+
async function notify() {
|
|
410
|
+
if (!process.env.NODEBB_API_TOKEN) return exit('NODEBB_API_TOKEN env var has to be set');
|
|
411
|
+
const apiToken = process.env.NODEBB_API_TOKEN;
|
|
377
412
|
|
|
378
413
|
const manifestFilePath = locateManifest();
|
|
379
414
|
if (!manifestFilePath) return exit('Could not locate CloudronManifest.json');
|
|
@@ -382,48 +417,47 @@ async function tag(version) {
|
|
|
382
417
|
if (result.error) return exit(new Error(`Invalid CloudronManifest.json: ${result.error.message}`));
|
|
383
418
|
const { manifest } = result;
|
|
384
419
|
|
|
385
|
-
|
|
420
|
+
let postContent = null;
|
|
421
|
+
if (manifest.changelog.slice(0, 7) === 'file://') {
|
|
422
|
+
const baseDir = path.dirname(manifestFilePath);
|
|
423
|
+
let changelogPath = manifest.changelog.slice(7);
|
|
424
|
+
changelogPath = path.isAbsolute(changelogPath) ? changelogPath : path.join(baseDir, changelogPath);
|
|
425
|
+
const changelog = parseChangelog(changelogPath, manifest.version);
|
|
426
|
+
if (!changelog) return exit('Bad changelog format or missing changelog for this version');
|
|
427
|
+
postContent = `[${manifest.version}]\n${changelog}\n`;
|
|
428
|
+
} else {
|
|
429
|
+
postContent = `[${manifest.version}]\n${manifest.changelog}\n`;
|
|
430
|
+
}
|
|
386
431
|
|
|
387
|
-
if (
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
432
|
+
if (!manifest.forumUrl) return exit(new Error('CloudronManifest.json does not have a forumUrl'));
|
|
433
|
+
const categoryMatch = manifest.forumUrl.match(/category\/(.*)\//);
|
|
434
|
+
if (!categoryMatch) return exit('Unable to detect category id');
|
|
435
|
+
const categoryId = categoryMatch[1];
|
|
436
|
+
|
|
437
|
+
const categoryResponse = await superagent.get(`https://forum.cloudron.io/api/v3/categories/${categoryId}/topics`).set('Authorization', `Bearer ${apiToken}`).ok(() => true);
|
|
438
|
+
if (categoryResponse.statusCode !== 200) return exit(`Unable to get topics of category: ${requestError(categoryResponse)}`);
|
|
439
|
+
const topic = categoryResponse.body.response.topics.find(t => t.title.includes('Package Updates'));
|
|
440
|
+
if (!topic) return exit('Could not find the Package Update topic');
|
|
441
|
+
const topicId = topic.tid;
|
|
442
|
+
|
|
443
|
+
const pageCountResponse = await superagent.get(`https://forum.cloudron.io/api/topic/pagination/${topicId}`).set('Authorization', `Bearer ${apiToken}`).ok(() => true);
|
|
444
|
+
if (pageCountResponse.statusCode !== 200) return exit(`Unable to get page count of topic: ${requestError(pageCountResponse)}`);
|
|
445
|
+
const pageCount = pageCountResponse.body.pagination.pageCount;
|
|
446
|
+
|
|
447
|
+
for (let page = 1; page <= pageCount; page++) {
|
|
448
|
+
const pageResponse = await superagent.get(`https://forum.cloudron.io/api/topic/${topicId}?page=${page}`).set('Authorization', `Bearer ${apiToken}`).ok(() => true);
|
|
449
|
+
if (pageResponse.statusCode !== 200) return exit(`Unable to get topics of category: ${requestError(pageResponse)}`);
|
|
450
|
+
for (const post of pageResponse.body.posts) { // post.content is html!
|
|
451
|
+
if (post.content.includes(`[${manifest.version}]`)) return exit(`Version ${manifest.version} is already on the forum.\n${post.content}`);
|
|
452
|
+
}
|
|
392
453
|
}
|
|
393
454
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
console.log(
|
|
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}`);
|
|
455
|
+
// https://docs.nodebb.org/api/write/#tag/topics/paths/~1topics~1%7Btid%7D/post
|
|
456
|
+
const postData = {
|
|
457
|
+
content: postContent,
|
|
458
|
+
toPid: 0 // which post is this post a reply to
|
|
459
|
+
};
|
|
460
|
+
const postResponse = await superagent.post(`https://forum.cloudron.io/api/v3/topics/${topicId}`).set('Authorization', `Bearer ${apiToken}`).send(postData).ok(() => true);
|
|
461
|
+
if (postResponse.statusCode !== 200) return exit(`Unable to create changelog post: ${requestError(postResponse)}`);
|
|
462
|
+
console.log('Posted to forum');
|
|
429
463
|
}
|
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
|
|