cloudron 7.0.2 → 7.0.4
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 +1 -0
- package/bin/cloudron-build +3 -3
- package/package.json +1 -1
- package/src/actions.js +70 -64
- package/src/appstore-actions.js +2 -2
- package/src/build-actions.js +27 -11
- package/src/config.js +6 -6
- package/src/versions-actions.js +95 -2
package/bin/cloudron
CHANGED
|
@@ -165,6 +165,7 @@ program.command('install')
|
|
|
165
165
|
.option('-a, --alias-domains [domain,...]', 'Alias domains')
|
|
166
166
|
.option('-m, --memory-limit [domain,...]', 'Memory Limit (e.g 1.5G, 512M)')
|
|
167
167
|
.option('--appstore-id <appid[@version]>', 'Use app from the store')
|
|
168
|
+
.option('--versions-url <url>', 'Install community app from CloudronVersions.json URL')
|
|
168
169
|
.option('--no-sso', 'Disable Cloudron SSO [false]')
|
|
169
170
|
.option('--debug [cmd...]', 'Enable debug mode', false)
|
|
170
171
|
.option('--readonly', 'Mount filesystem readonly. Default is read/write in debug mode.')
|
package/bin/cloudron-build
CHANGED
|
@@ -33,9 +33,9 @@ program.command('build', { isDefault: true })
|
|
|
33
33
|
.option('--tag <docker image tag>', 'Docker image tag. Note that this does not include the repository name')
|
|
34
34
|
.action(buildActions.build);
|
|
35
35
|
|
|
36
|
-
program.command('
|
|
37
|
-
.description('
|
|
38
|
-
.action(buildActions.
|
|
36
|
+
program.command('reset')
|
|
37
|
+
.description('Reset build configuration for this directory')
|
|
38
|
+
.action(buildActions.reset);
|
|
39
39
|
|
|
40
40
|
program.command('info')
|
|
41
41
|
.description('Print build information')
|
package/package.json
CHANGED
package/src/actions.js
CHANGED
|
@@ -103,35 +103,13 @@ async function stopActiveTask(app, options) {
|
|
|
103
103
|
if (response.status !== 204) throw `Failed to stop active task: ${requestError(response)}`;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
assert.strictEqual(typeof options, 'object');
|
|
109
|
-
|
|
110
|
-
const response = await createRequest('GET', '/api/v1/apps', options);
|
|
111
|
-
if (response.status !== 200) throw new Error(`Failed to install app: ${requestError(response)}`);
|
|
112
|
-
|
|
113
|
-
const matchingApps = response.body.apps.filter(function (app) {
|
|
114
|
-
return !app.appStoreId && app.manifest.dockerImage.startsWith(repository); // never select apps from the store
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
if (matchingApps.length === 0) return [ ];
|
|
118
|
-
if (matchingApps.length === 1) return matchingApps[0];
|
|
119
|
-
|
|
120
|
-
console.log();
|
|
121
|
-
console.log('Available apps using same repository %s:', repository);
|
|
122
|
-
matchingApps.sort(function (a, b) { return a.fqdn < b.fqdn ? -1 : 1; });
|
|
123
|
-
matchingApps.forEach(function (app, index) {
|
|
124
|
-
console.log('[%s]\t%s', index, app.fqdn);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
let index;
|
|
128
|
-
while (true) {
|
|
129
|
-
index = parseInt(await readline.question('Choose app [0-' + (matchingApps.length-1) + ']: ', {}), 10);
|
|
130
|
-
if (isNaN(index) || index < 0 || index > matchingApps.length-1) console.log('Invalid selection');
|
|
131
|
-
else break;
|
|
132
|
-
}
|
|
106
|
+
function saveCwdAppId(appId, manifestFilePath) {
|
|
107
|
+
if (!manifestFilePath) return;
|
|
133
108
|
|
|
134
|
-
|
|
109
|
+
const sourceDir = path.dirname(manifestFilePath);
|
|
110
|
+
const cwdConfig = config.getCwdConfig(sourceDir);
|
|
111
|
+
cwdConfig.appId = appId;
|
|
112
|
+
config.setCwdConfig(sourceDir, cwdConfig);
|
|
135
113
|
}
|
|
136
114
|
|
|
137
115
|
// appId may be the appId or the location
|
|
@@ -140,20 +118,19 @@ async function getApp(options) {
|
|
|
140
118
|
|
|
141
119
|
const app = options.app || null;
|
|
142
120
|
|
|
143
|
-
if (!app) {
|
|
121
|
+
if (!app) {
|
|
144
122
|
const manifestFilePath = locateManifest();
|
|
145
|
-
|
|
146
123
|
if (!manifestFilePath) throw new Error(NO_APP_FOUND_ERROR_STRING);
|
|
147
124
|
|
|
148
125
|
const sourceDir = path.dirname(manifestFilePath);
|
|
149
|
-
const
|
|
126
|
+
const cwdConfig = config.getCwdConfig(sourceDir);
|
|
150
127
|
|
|
151
|
-
if (!
|
|
128
|
+
if (!cwdConfig.appId) throw new Error(NO_APP_FOUND_ERROR_STRING);
|
|
152
129
|
|
|
153
|
-
const
|
|
154
|
-
if (
|
|
130
|
+
const response = await createRequest('GET', `/api/v1/apps/${cwdConfig.appId}`, options);
|
|
131
|
+
if (response.status !== 200) throw new Error(NO_APP_FOUND_ERROR_STRING);
|
|
155
132
|
|
|
156
|
-
return
|
|
133
|
+
return response.body;
|
|
157
134
|
} else if (app.match(/.{8}-.{4}-.{4}-.{4}-.{8}/)) { // it is an id
|
|
158
135
|
const response = await createRequest('GET', `/api/v1/apps/${app}`, options);
|
|
159
136
|
if (response.status !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
|
|
@@ -554,34 +531,46 @@ async function install(localOptions, cmd) {
|
|
|
554
531
|
const options = cmd.optsWithGlobals();
|
|
555
532
|
|
|
556
533
|
try {
|
|
557
|
-
|
|
558
|
-
const { manifest, manifestFilePath } = result;
|
|
559
|
-
|
|
534
|
+
let manifest = null, manifestFilePath = null, versionsUrl = null;
|
|
560
535
|
let sourceArchiveFilePath = null; // will be set if we need to send a tarball
|
|
561
536
|
|
|
562
|
-
if (
|
|
563
|
-
const
|
|
564
|
-
const
|
|
537
|
+
if (options.versionsUrl) {
|
|
538
|
+
const [url, version] = options.versionsUrl.split('@');
|
|
539
|
+
const request = createRequest('GET', `/api/v1/community/app?url=${encodeURIComponent(url)}&version=${encodeURIComponent(version || 'latest')}`, options);
|
|
540
|
+
const response = await request;
|
|
541
|
+
if (response.status !== 200) return exit(`Failed to get community app: ${requestError(response)}`);
|
|
565
542
|
|
|
566
|
-
|
|
567
|
-
|
|
543
|
+
manifest = response.body.manifest;
|
|
544
|
+
versionsUrl = response.body.versionsUrl;
|
|
545
|
+
} else {
|
|
546
|
+
const result = await getManifest(options.appstoreId || '');
|
|
547
|
+
manifest = result.manifest;
|
|
548
|
+
manifestFilePath = result.manifestFilePath;
|
|
568
549
|
|
|
569
|
-
|
|
570
|
-
const
|
|
550
|
+
if (!manifest.dockerImage) { // not a manifest from appstore
|
|
551
|
+
const sourceDir = path.dirname(manifestFilePath);
|
|
552
|
+
const image = options.image || config.getCwdConfig(sourceDir).dockerImage;
|
|
571
553
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
return ignoreMatcher(name.slice(sourceDir.length + 1)); // make name as relative path
|
|
575
|
-
}
|
|
576
|
-
});
|
|
554
|
+
if (!image) {
|
|
555
|
+
console.log('No build detected. This package will be built on the server.');
|
|
577
556
|
|
|
578
|
-
|
|
579
|
-
|
|
557
|
+
const dockerignoreFilePath = path.join(sourceDir, '.dockerignore');
|
|
558
|
+
const ignoreMatcher = dockerignoreMatcher(dockerignoreFilePath);
|
|
580
559
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
560
|
+
const tarStream = tar.pack(sourceDir, {
|
|
561
|
+
ignore: function (name) {
|
|
562
|
+
return ignoreMatcher(name.slice(sourceDir.length + 1)); // make name as relative path
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
sourceArchiveFilePath = path.join(os.tmpdir(), `cloudron-source-${Date.now()}.tar`);
|
|
567
|
+
const sourceArchiveStream = fs.createWriteStream(sourceArchiveFilePath);
|
|
568
|
+
|
|
569
|
+
const [tarError] = await safe(stream.pipeline(tarStream, sourceArchiveStream));
|
|
570
|
+
if (tarError) return exit(`Could not create source archive: ${tarError.message}`);
|
|
571
|
+
} else {
|
|
572
|
+
manifest.dockerImage = image;
|
|
573
|
+
}
|
|
585
574
|
}
|
|
586
575
|
}
|
|
587
576
|
|
|
@@ -600,10 +589,10 @@ async function install(localOptions, cmd) {
|
|
|
600
589
|
const tmp = kv.split('=');
|
|
601
590
|
secondaryDomains[tmp[0]] = await selectDomain(tmp[1], options);
|
|
602
591
|
}
|
|
603
|
-
} else {
|
|
592
|
+
} else if (manifest) {
|
|
604
593
|
secondaryDomains = await querySecondaryDomains(null /* existing app */, manifest, options);
|
|
605
594
|
}
|
|
606
|
-
} else if (manifest.httpPorts) { // just put in defaults
|
|
595
|
+
} else if (manifest && manifest.httpPorts) { // just put in defaults
|
|
607
596
|
for (const env in manifest.httpPorts) {
|
|
608
597
|
secondaryDomains[env] = await selectDomain(manifest.httpPorts[env].defaultValue, options);
|
|
609
598
|
}
|
|
@@ -629,10 +618,10 @@ async function install(localOptions, cmd) {
|
|
|
629
618
|
if (isNaN(parseInt(tmp[1], 10))) return; // disable the port
|
|
630
619
|
ports[tmp[0]] = parseInt(tmp[1], 10);
|
|
631
620
|
});
|
|
632
|
-
} else {
|
|
621
|
+
} else if (manifest) {
|
|
633
622
|
ports = await queryPortBindings(null /* existing app */, manifest);
|
|
634
623
|
}
|
|
635
|
-
} else { // just put in defaults
|
|
624
|
+
} else if (manifest) { // just put in defaults
|
|
636
625
|
const allPorts = Object.assign({}, manifest.tcpPorts, manifest.udpPorts);
|
|
637
626
|
for (const portName in allPorts) {
|
|
638
627
|
ports[portName] = allPorts[portName].defaultValue;
|
|
@@ -655,7 +644,6 @@ async function install(localOptions, cmd) {
|
|
|
655
644
|
|
|
656
645
|
const data = {
|
|
657
646
|
appStoreId: options.appstoreId || '', // note case change
|
|
658
|
-
manifest: options.appstoreId ? null : manifest, // cloudron ignores manifest anyway if appStoreId is set
|
|
659
647
|
location: domainObject.subdomain, // LEGACY
|
|
660
648
|
subdomain: domainObject.subdomain,
|
|
661
649
|
domain: domainObject.domain,
|
|
@@ -667,8 +655,14 @@ async function install(localOptions, cmd) {
|
|
|
667
655
|
env
|
|
668
656
|
};
|
|
669
657
|
|
|
658
|
+
if (versionsUrl) {
|
|
659
|
+
data.versionsUrl = versionsUrl;
|
|
660
|
+
} else {
|
|
661
|
+
data.manifest = options.appstoreId ? null : manifest; // cloudron ignores manifest anyway if appStoreId is set
|
|
662
|
+
}
|
|
663
|
+
|
|
670
664
|
// the sso only applies for apps which allow optional sso
|
|
671
|
-
if (manifest.optionalSso) data.sso = options.sso;
|
|
665
|
+
if (manifest && manifest.optionalSso) data.sso = options.sso;
|
|
672
666
|
|
|
673
667
|
if (options.debug) { // 'true' when no args. otherwise, array
|
|
674
668
|
const debugCmd = options.debug === true ? [ '/bin/bash', '-c', 'echo "Repair mode. Use the webterminal or cloudron exec to repair. Sleeping" && sleep infinity' ] : options.debug;
|
|
@@ -680,7 +674,7 @@ async function install(localOptions, cmd) {
|
|
|
680
674
|
options.wait = false; // in debug mode, health check never succeeds
|
|
681
675
|
}
|
|
682
676
|
|
|
683
|
-
if (!options.appstoreId && manifest.icon) {
|
|
677
|
+
if (!options.appstoreId && !options.versionsUrl && manifest && manifest.icon) {
|
|
684
678
|
let iconFilename = manifest.icon.slice(0, 7) === 'file://' ? manifest.icon.slice(7) : manifest.icon;
|
|
685
679
|
iconFilename = path.resolve(path.dirname(manifestFilePath), iconFilename); // resolve filename wrt manifest
|
|
686
680
|
data.icon = safe.fs.readFileSync(iconFilename, { encoding: 'base64' });
|
|
@@ -720,6 +714,8 @@ async function install(localOptions, cmd) {
|
|
|
720
714
|
|
|
721
715
|
const appId = response.body.id;
|
|
722
716
|
|
|
717
|
+
saveCwdAppId(appId, manifestFilePath);
|
|
718
|
+
|
|
723
719
|
console.log('App is being installed.');
|
|
724
720
|
|
|
725
721
|
if (!options.wait) return;
|
|
@@ -841,7 +837,7 @@ async function update(localOptions, cmd) {
|
|
|
841
837
|
|
|
842
838
|
if (!manifest.dockerImage) { // not a manifest from appstore
|
|
843
839
|
const sourceDir = path.dirname(manifestFilePath);
|
|
844
|
-
const image = options.image || config.
|
|
840
|
+
const image = options.image || config.getCwdConfig(sourceDir).dockerImage;
|
|
845
841
|
|
|
846
842
|
if (!image) {
|
|
847
843
|
console.log('No docker image detected. Creating source archive from this folder.');
|
|
@@ -900,6 +896,8 @@ async function update(localOptions, cmd) {
|
|
|
900
896
|
}
|
|
901
897
|
if (response.status !== 202) return exit(`Failed to update app: ${requestError(response)}`);
|
|
902
898
|
|
|
899
|
+
saveCwdAppId(app.id, manifestFilePath);
|
|
900
|
+
|
|
903
901
|
process.stdout.write('\n => ' + 'Waiting for app to be updated ');
|
|
904
902
|
|
|
905
903
|
await waitForFinishInstallation(app.id, response.body.taskId, options);
|
|
@@ -1007,6 +1005,14 @@ async function uninstall(localOptions, cmd) {
|
|
|
1007
1005
|
await waitForTask(response.body.taskId, options);
|
|
1008
1006
|
const response2 = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1009
1007
|
if (response2.status === 404) {
|
|
1008
|
+
const manifestFilePath = locateManifest();
|
|
1009
|
+
if (manifestFilePath) {
|
|
1010
|
+
const sourceDir = path.dirname(manifestFilePath);
|
|
1011
|
+
const cwdConfig = config.getCwdConfig(sourceDir);
|
|
1012
|
+
delete cwdConfig.appId;
|
|
1013
|
+
config.setCwdConfig(sourceDir, cwdConfig);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1010
1016
|
console.log('\n\nApp %s successfully uninstalled.', app.fqdn);
|
|
1011
1017
|
} else if (response2.body.installationState === 'error') {
|
|
1012
1018
|
console.log('\n\nApp uninstallation failed.\n');
|
package/src/appstore-actions.js
CHANGED
|
@@ -235,7 +235,7 @@ async function verifyManifest(localOptions, cmd) {
|
|
|
235
235
|
const manifest = result.manifest;
|
|
236
236
|
|
|
237
237
|
const sourceDir = path.dirname(manifestFilePath);
|
|
238
|
-
const appConfig = config.
|
|
238
|
+
const appConfig = config.getCwdConfig(sourceDir);
|
|
239
239
|
|
|
240
240
|
// image can be passed in options for buildbot
|
|
241
241
|
if (options.image) {
|
|
@@ -282,7 +282,7 @@ async function upload(localOptions, cmd) {
|
|
|
282
282
|
const manifest = result.manifest;
|
|
283
283
|
|
|
284
284
|
const sourceDir = path.dirname(manifestFilePath);
|
|
285
|
-
const appConfig = config.
|
|
285
|
+
const appConfig = config.getCwdConfig(sourceDir);
|
|
286
286
|
|
|
287
287
|
// image can be passed in options for buildbot
|
|
288
288
|
if (options.image) {
|
package/src/build-actions.js
CHANGED
|
@@ -202,7 +202,7 @@ async function buildLocal(manifest, sourceDir, appConfig, options) {
|
|
|
202
202
|
|
|
203
203
|
appConfig.dockerImage = dockerImage;
|
|
204
204
|
appConfig.dockerImageSha256 = match[1]; // stash this separately for now
|
|
205
|
-
config.
|
|
205
|
+
config.setCwdConfig(sourceDir, appConfig);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
async function buildRemote(manifest, sourceDir, appConfig, options, buildServiceConfig) {
|
|
@@ -272,7 +272,7 @@ async function buildRemote(manifest, sourceDir, appConfig, options, buildService
|
|
|
272
272
|
|
|
273
273
|
appConfig.dockerImage = dockerImage;
|
|
274
274
|
// appConfig.dockerImageSha256 = match[1]; // stash this separately for now
|
|
275
|
-
config.
|
|
275
|
+
config.setCwdConfig(sourceDir, appConfig);
|
|
276
276
|
|
|
277
277
|
console.log(`Docker image: ${dockerImage}`);
|
|
278
278
|
console.log('\nBuild successful');
|
|
@@ -293,8 +293,13 @@ async function build(localOptions, cmd) {
|
|
|
293
293
|
const manifest = result.manifest;
|
|
294
294
|
const sourceDir = path.dirname(manifestFilePath);
|
|
295
295
|
|
|
296
|
-
const appConfig = config.
|
|
296
|
+
const appConfig = config.getCwdConfig(sourceDir);
|
|
297
297
|
const buildServiceConfig = getEffectiveBuildServiceConfig(options);
|
|
298
|
+
if (buildServiceConfig.type === 'remote' && buildServiceConfig.url) {
|
|
299
|
+
console.log('Building using remote build service at %s', buildServiceConfig.url);
|
|
300
|
+
} else {
|
|
301
|
+
console.log('Building locally with Docker.');
|
|
302
|
+
}
|
|
298
303
|
|
|
299
304
|
let repository = appConfig.repository;
|
|
300
305
|
if (!repository || options.repository) {
|
|
@@ -310,13 +315,15 @@ async function build(localOptions, cmd) {
|
|
|
310
315
|
if (parts.length > 1) exit(`repository should not be a URL. Try again without ${parts[0]}://`);
|
|
311
316
|
|
|
312
317
|
appConfig.repository = repository;
|
|
313
|
-
config.
|
|
318
|
+
config.setCwdConfig(sourceDir, appConfig);
|
|
314
319
|
}
|
|
315
320
|
|
|
316
321
|
appConfig.gitCommit = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); // when the build gets saved, save the gitCommit also
|
|
317
322
|
if (buildServiceConfig.type === 'remote' && buildServiceConfig.url) {
|
|
323
|
+
console.log('Building using remote build service at %s', buildServiceConfig.url);
|
|
318
324
|
await buildRemote(manifest, sourceDir, appConfig, options, buildServiceConfig);
|
|
319
325
|
} else {
|
|
326
|
+
console.log('Building locally with Docker.');
|
|
320
327
|
await buildLocal(manifest, sourceDir, appConfig, options);
|
|
321
328
|
}
|
|
322
329
|
}
|
|
@@ -377,16 +384,25 @@ async function push(localOptions, cmd) {
|
|
|
377
384
|
if (buildStatus !== 'success') return exit('Failed to push app. See log output above.');
|
|
378
385
|
}
|
|
379
386
|
|
|
380
|
-
async function
|
|
381
|
-
// const options = cmd.optsWithGlobals();
|
|
382
|
-
|
|
383
|
-
// try to find the manifest of this project
|
|
387
|
+
async function reset(/* localOptions, cmd */) {
|
|
384
388
|
const manifestFilePath = helper.locateManifest();
|
|
385
389
|
if (!manifestFilePath) return exit('No CloudronManifest.json found');
|
|
386
390
|
|
|
387
391
|
const sourceDir = path.dirname(manifestFilePath);
|
|
392
|
+
const cwdConfig = config.getCwdConfig(sourceDir);
|
|
393
|
+
|
|
394
|
+
if (!cwdConfig.repository && !cwdConfig.dockerImage) return console.log('Nothing to reset.');
|
|
395
|
+
|
|
396
|
+
const answer = await readline.question('Clear saved repository, image, and build info for this directory? [y/N] ', {});
|
|
397
|
+
if (answer.toLowerCase() !== 'y') return;
|
|
398
|
+
|
|
399
|
+
delete cwdConfig.repository;
|
|
400
|
+
delete cwdConfig.dockerImage;
|
|
401
|
+
delete cwdConfig.dockerImageSha256;
|
|
402
|
+
delete cwdConfig.gitCommit;
|
|
388
403
|
|
|
389
|
-
config.
|
|
404
|
+
config.setCwdConfig(sourceDir, cwdConfig);
|
|
405
|
+
console.log('Build configuration reset.');
|
|
390
406
|
}
|
|
391
407
|
|
|
392
408
|
async function info(localOptions, cmd) {
|
|
@@ -401,7 +417,7 @@ async function info(localOptions, cmd) {
|
|
|
401
417
|
if (!manifestFilePath) return exit();
|
|
402
418
|
|
|
403
419
|
const sourceDir = path.dirname(manifestFilePath);
|
|
404
|
-
const appConfig = config.
|
|
420
|
+
const appConfig = config.getCwdConfig(sourceDir);
|
|
405
421
|
|
|
406
422
|
console.log('Build info');
|
|
407
423
|
if (appConfig?.dockerImage) {
|
|
@@ -420,7 +436,7 @@ export default {
|
|
|
420
436
|
logs,
|
|
421
437
|
status,
|
|
422
438
|
push,
|
|
423
|
-
|
|
439
|
+
reset,
|
|
424
440
|
info,
|
|
425
441
|
dockerignoreMatcher
|
|
426
442
|
};
|
package/src/config.js
CHANGED
|
@@ -60,9 +60,9 @@ function setAppStoreToken(value) {
|
|
|
60
60
|
set(['appStore', appStoreOrigin().replace('https://', ''), 'token'], value);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const
|
|
63
|
+
const getCwdConfig = (p) => get(['apps', p]) || {};
|
|
64
|
+
const setCwdConfig = (p, c) => set(['apps', p], c);
|
|
65
|
+
const unsetCwdConfig = (p) => unset(['apps', p]);
|
|
66
66
|
|
|
67
67
|
const getBuildServiceConfig = () => get('buildService') || {};
|
|
68
68
|
const setBuildServiceConfig = (c) => set('buildService', c);
|
|
@@ -94,9 +94,9 @@ export {
|
|
|
94
94
|
appStoreOrigin,
|
|
95
95
|
|
|
96
96
|
// per app
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
getCwdConfig,
|
|
98
|
+
setCwdConfig,
|
|
99
|
+
unsetCwdConfig,
|
|
100
100
|
|
|
101
101
|
// build service
|
|
102
102
|
getBuildServiceConfig,
|
package/src/versions-actions.js
CHANGED
|
@@ -41,7 +41,14 @@ async function init(/*localOptions, cmd*/) {
|
|
|
41
41
|
if (fs.existsSync(versionsFilePath)) return exit(`${path.relative(process.cwd(), versionsFilePath)} already exists.`);
|
|
42
42
|
|
|
43
43
|
await writeVersions(versionsFilePath, { stable: true, versions: {} });
|
|
44
|
-
console.log(`Created ${path.relative(process.cwd(), versionsFilePath)}
|
|
44
|
+
console.log(`Created ${path.relative(process.cwd(), versionsFilePath)}.`);
|
|
45
|
+
|
|
46
|
+
const result = manifestFormat.parseFile(manifestFilePath);
|
|
47
|
+
if (!result.error) {
|
|
48
|
+
ensurePublishFields(result.manifest, manifestFilePath);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log('\nUse "cloudron versions add" to add a version.');
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
async function resolveManifest(manifest, baseDir) {
|
|
@@ -69,6 +76,92 @@ async function resolveManifest(manifest, baseDir) {
|
|
|
69
76
|
}
|
|
70
77
|
}
|
|
71
78
|
|
|
79
|
+
function createStubFile(filePath, content) {
|
|
80
|
+
if (fs.existsSync(filePath)) return false;
|
|
81
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function ensurePublishFields(manifest, manifestFilePath) {
|
|
86
|
+
const baseDir = path.dirname(manifestFilePath);
|
|
87
|
+
const rawManifest = JSON.parse(fs.readFileSync(manifestFilePath, 'utf8'));
|
|
88
|
+
const added = [];
|
|
89
|
+
const created = [];
|
|
90
|
+
|
|
91
|
+
const defaultFields = {
|
|
92
|
+
id: 'com.example.app',
|
|
93
|
+
title: '',
|
|
94
|
+
author: '',
|
|
95
|
+
tagline: '',
|
|
96
|
+
website: '',
|
|
97
|
+
contactEmail: '',
|
|
98
|
+
iconUrl: '',
|
|
99
|
+
packagerName: '',
|
|
100
|
+
packagerUrl: '',
|
|
101
|
+
minBoxVersion: '9.1.0',
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
for (const [key, defaultValue] of Object.entries(defaultFields)) {
|
|
105
|
+
if (rawManifest[key]) continue;
|
|
106
|
+
|
|
107
|
+
rawManifest[key] = defaultValue;
|
|
108
|
+
manifest[key] = defaultValue;
|
|
109
|
+
added.push(key);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!rawManifest.description) {
|
|
113
|
+
rawManifest.description = 'file://DESCRIPTION.md';
|
|
114
|
+
manifest.description = 'file://DESCRIPTION.md';
|
|
115
|
+
added.push('description');
|
|
116
|
+
if (createStubFile(path.join(baseDir, 'DESCRIPTION.md'), 'Describe your app here.\n')) {
|
|
117
|
+
created.push('DESCRIPTION.md');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!rawManifest.changelog) {
|
|
122
|
+
rawManifest.changelog = 'file://CHANGELOG';
|
|
123
|
+
manifest.changelog = 'file://CHANGELOG';
|
|
124
|
+
added.push('changelog');
|
|
125
|
+
const version = rawManifest.version || '0.1.0';
|
|
126
|
+
if (createStubFile(path.join(baseDir, 'CHANGELOG'), `[${version}]\n* Initial release\n`)) {
|
|
127
|
+
created.push('CHANGELOG');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!rawManifest.postInstallMessage) {
|
|
132
|
+
rawManifest.postInstallMessage = 'file://POSTINSTALL.md';
|
|
133
|
+
manifest.postInstallMessage = 'file://POSTINSTALL.md';
|
|
134
|
+
added.push('postInstallMessage');
|
|
135
|
+
if (createStubFile(path.join(baseDir, 'POSTINSTALL.md'), 'Post-installation instructions.\n')) {
|
|
136
|
+
created.push('POSTINSTALL.md');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!rawManifest.tags || rawManifest.tags.length === 0) {
|
|
141
|
+
rawManifest.tags = ['uncategorized'];
|
|
142
|
+
manifest.tags = ['uncategorized'];
|
|
143
|
+
added.push('tags');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!rawManifest.mediaLinks || rawManifest.mediaLinks.length === 0) {
|
|
147
|
+
rawManifest.mediaLinks = ['https://example.com/screenshot.png'];
|
|
148
|
+
manifest.mediaLinks = ['https://example.com/screenshot.png'];
|
|
149
|
+
added.push('mediaLinks');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (added.length === 0) return;
|
|
153
|
+
|
|
154
|
+
fs.writeFileSync(manifestFilePath, JSON.stringify(rawManifest, null, 2) + '\n', 'utf8');
|
|
155
|
+
|
|
156
|
+
console.log(`\nAdded missing fields to ${path.relative(process.cwd(), manifestFilePath)}: ${added.join(', ')}`);
|
|
157
|
+
if (created.length > 0) {
|
|
158
|
+
console.log(`Created stub files: ${created.join(', ')}`);
|
|
159
|
+
}
|
|
160
|
+
console.log('\nEdit the following fields in CloudronManifest.json before publishing:');
|
|
161
|
+
console.log(' id, title, author, tagline, website, contactEmail, iconUrl, packagerName, packagerUrl');
|
|
162
|
+
console.log(' tags, mediaLinks, DESCRIPTION.md, CHANGELOG, POSTINSTALL.md');
|
|
163
|
+
}
|
|
164
|
+
|
|
72
165
|
async function addOrUpdate(localOptions, cmd) {
|
|
73
166
|
const isUpdate = cmd.parent.args[0] === 'update';
|
|
74
167
|
const versionsFilePath = await locateVersions();
|
|
@@ -85,7 +178,7 @@ async function addOrUpdate(localOptions, cmd) {
|
|
|
85
178
|
const manifest = result.manifest;
|
|
86
179
|
|
|
87
180
|
const sourceDir = path.dirname(manifestFilePath);
|
|
88
|
-
const appConfig = config.
|
|
181
|
+
const appConfig = config.getCwdConfig(sourceDir);
|
|
89
182
|
|
|
90
183
|
if (options.image) {
|
|
91
184
|
manifest.dockerImage = options.image;
|