cloudron 5.8.2 → 5.10.0
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 +11 -10
- package/package.json +1 -1
- package/src/actions.js +5 -12
- package/src/appstore-actions.js +218 -315
- package/src/build-actions.js +1 -1
package/bin/cloudron
CHANGED
|
@@ -109,16 +109,6 @@ program.command('clone')
|
|
|
109
109
|
.option('--location <domain>', 'Subdomain or full domain')
|
|
110
110
|
.action(actions.clone);
|
|
111
111
|
|
|
112
|
-
program.command('configure')
|
|
113
|
-
.description('Change location of an app')
|
|
114
|
-
.option('--app <id/location>', 'App id or location')
|
|
115
|
-
.option('--no-wait', 'Wait for healthcheck to succeed [false]')
|
|
116
|
-
.option('-p, --port-bindings [PORT=port,...]', 'Query port bindings')
|
|
117
|
-
.option('-l, --location <location>', 'Location')
|
|
118
|
-
.option('-s, --secondary-domains [DOMAIN=domain,...]', 'Query/Set secondary domains')
|
|
119
|
-
.option('-a, --alias-domains [domain,...]', 'Alias domains')
|
|
120
|
-
.action(actions.configure);
|
|
121
|
-
|
|
122
112
|
program.command('debug [cmd...]')
|
|
123
113
|
.description('Put app in debug mode and run [cmd] as entrypoint. If cmd is "default" the main app entrypoint is run.')
|
|
124
114
|
.option('--app <id/location>', 'App id or location')
|
|
@@ -275,6 +265,17 @@ program.command('restart')
|
|
|
275
265
|
.option('--no-wait', 'Wait for healthcheck to succeed [false]')
|
|
276
266
|
.action(actions.restart);
|
|
277
267
|
|
|
268
|
+
program.command('set-location')
|
|
269
|
+
.description('Set the location of an app')
|
|
270
|
+
.option('--app <id/location>', 'App id or location')
|
|
271
|
+
.option('--no-wait', 'Wait for healthcheck to succeed [false]')
|
|
272
|
+
.option('-p, --port-bindings [PORT=port,...]', 'Query port bindings')
|
|
273
|
+
.option('-l, --location <location>', 'Location')
|
|
274
|
+
.option('-s, --secondary-domains [DOMAIN=domain,...]', 'Query/Set secondary domains')
|
|
275
|
+
.option('-a, --alias-domains [domain,...]', 'Alias domains')
|
|
276
|
+
.alias('configure') // deprecated
|
|
277
|
+
.action(actions.setLocation);
|
|
278
|
+
|
|
278
279
|
program.command('start')
|
|
279
280
|
.description('Start an installed application')
|
|
280
281
|
.option('--app <id/location>', 'App id or location')
|
package/package.json
CHANGED
package/src/actions.js
CHANGED
|
@@ -31,7 +31,7 @@ exports = module.exports = {
|
|
|
31
31
|
logout,
|
|
32
32
|
open,
|
|
33
33
|
install,
|
|
34
|
-
|
|
34
|
+
setLocation,
|
|
35
35
|
debug,
|
|
36
36
|
update,
|
|
37
37
|
uninstall,
|
|
@@ -677,7 +677,7 @@ async function install(localOptions, cmd) {
|
|
|
677
677
|
}
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
-
async function
|
|
680
|
+
async function setLocation(localOptions, cmd) {
|
|
681
681
|
const options = cmd.optsWithGlobals();
|
|
682
682
|
|
|
683
683
|
try {
|
|
@@ -873,18 +873,11 @@ async function repair(localOptions, cmd) {
|
|
|
873
873
|
const app = await getApp(options);
|
|
874
874
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
875
875
|
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
let dockerImage = options.image;
|
|
879
|
-
if (!dockerImage) {
|
|
880
|
-
const sourceDir = path.dirname(manifestFilePath);
|
|
881
|
-
dockerImage = config.getAppConfig(sourceDir).dockerImage;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
if (!dockerImage) return exit('No image found, please run `cloudron build` first or use --image');
|
|
876
|
+
const data = {};
|
|
877
|
+
if (options.image) data.dockerImage = options.image;
|
|
885
878
|
|
|
886
879
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/repair`, options);
|
|
887
|
-
const response = await request.send(
|
|
880
|
+
const response = await request.send(data);
|
|
888
881
|
if (response.statusCode !== 202) return exit(`Failed to set repair mode: ${requestError(response)}`);
|
|
889
882
|
|
|
890
883
|
process.stdout.write('\n => ' + 'Waiting for app to be repaired ');
|
package/src/appstore-actions.js
CHANGED
|
@@ -11,8 +11,7 @@ const assert = require('assert'),
|
|
|
11
11
|
readlineSync = require('readline-sync'),
|
|
12
12
|
safe = require('safetydance'),
|
|
13
13
|
superagent = require('superagent'),
|
|
14
|
-
Table = require('easy-table')
|
|
15
|
-
util = require('util');
|
|
14
|
+
Table = require('easy-table');
|
|
16
15
|
|
|
17
16
|
exports = module.exports = {
|
|
18
17
|
login,
|
|
@@ -29,83 +28,79 @@ exports = module.exports = {
|
|
|
29
28
|
|
|
30
29
|
const NO_MANIFEST_FOUND_ERROR_STRING = 'No CloudronManifest.json found';
|
|
31
30
|
|
|
31
|
+
function requestError(response) {
|
|
32
|
+
if (response.statusCode === 401) return 'Invalid token. Use cloudron appstore login again.';
|
|
33
|
+
|
|
34
|
+
return `${response.statusCode} message: ${response.body.message || JSON.stringify(response.body)}`; // body is sometimes just a string like in 401
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createRequest(method, apiPath) {
|
|
38
|
+
let url = `${config.appStoreOrigin()}${apiPath}`;
|
|
39
|
+
if (url.includes('?')) url += '&'; else url += '?';
|
|
40
|
+
url += `accessToken=${config.appStoreToken()}`;
|
|
41
|
+
const request = superagent(method, url);
|
|
42
|
+
request.retry(3);
|
|
43
|
+
request.ok(() => true);
|
|
44
|
+
return request;
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
function createUrl(api) {
|
|
33
48
|
return config.appStoreOrigin() + api;
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
// the app argument allows us in the future to get by name or id
|
|
37
|
-
function getAppstoreId(appstoreId
|
|
38
|
-
if (appstoreId)
|
|
39
|
-
var parts = appstoreId.split('@');
|
|
40
|
-
|
|
41
|
-
return callback(null, parts[0], parts[1]);
|
|
42
|
-
}
|
|
52
|
+
async function getAppstoreId(appstoreId) {
|
|
53
|
+
if (appstoreId) return appstoreId.split('@');
|
|
43
54
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (!manifestFilePath) return callback('No CloudronManifest.json found');
|
|
55
|
+
const manifestFilePath = locateManifest();
|
|
56
|
+
if (!manifestFilePath) throw new Error('No CloudronManifest.json found');
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
if (!manifest)
|
|
58
|
+
const manifest = safe.JSON.parse(safe.fs.readFileSync(manifestFilePath));
|
|
59
|
+
if (!manifest) throw new Error(`Unable to read manifest ${manifestFilePath}. Error: ${safe.error.message}`);
|
|
50
60
|
|
|
51
|
-
return
|
|
61
|
+
return [manifest.id, manifest.version];
|
|
52
62
|
}
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
function superagentEnd(requestFactory, callback) {
|
|
56
|
-
requestFactory().end(function (error, result) {
|
|
57
|
-
if (error && !error.response) return callback(error);
|
|
58
|
-
if (result.statusCode === 401) return authenticate({ error: true }, superagentEnd.bind(null, requestFactory, callback));
|
|
59
|
-
if (result.statusCode === 403) return callback(new Error(result.type === 'application/javascript' ? JSON.stringify(result.body) : result.text));
|
|
60
|
-
callback(error, result);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function authenticate(options, callback) {
|
|
64
|
+
async function authenticate(options) {
|
|
65
65
|
if (!options.hideBanner) {
|
|
66
66
|
const webDomain = config.appStoreOrigin().replace('https://api.', '');
|
|
67
67
|
console.log(`${webDomain} login` + ` (If you do not have one, sign up at https://${webDomain}/console.html#/register)`);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
const email = options.email || readlineSync.question('Email: ', {});
|
|
71
|
+
const password = options.password || readlineSync.question('Password: ', { noEchoBack: true });
|
|
72
72
|
|
|
73
73
|
config.setAppStoreToken(null);
|
|
74
74
|
|
|
75
|
-
superagent.post(createUrl('/api/v1/login')).auth(email, password).send({ totpToken: options.totpToken }).
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (result.statusCode === 401 && result.body.message.indexOf('TOTP') !== -1) {
|
|
79
|
-
if (result.body.message === 'TOTP token missing') console.log('A 2FA TOTP Token is required for this account.');
|
|
80
|
-
|
|
81
|
-
options.totpToken = readlineSync.question('2FA token: ', {});
|
|
82
|
-
options.email = email;
|
|
83
|
-
options.password = password;
|
|
84
|
-
options.hideBanner = true;
|
|
75
|
+
const response = await superagent.post(createUrl('/api/v1/login')).auth(email, password).send({ totpToken: options.totpToken }).ok(() => true);
|
|
76
|
+
if (response.statusCode === 401 && response.body.message.indexOf('TOTP') !== -1) {
|
|
77
|
+
if (response.body.message === 'TOTP token missing') console.log('A 2FA TOTP Token is required for this account.');
|
|
85
78
|
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
options.totpToken = readlineSync.question('2FA token: ', {});
|
|
80
|
+
options.email = email;
|
|
81
|
+
options.password = password;
|
|
82
|
+
options.hideBanner = true;
|
|
88
83
|
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
return await authenticate(options); // try again with top set
|
|
85
|
+
}
|
|
91
86
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
options.password = '';
|
|
87
|
+
if (response.statusCode !== 200) {
|
|
88
|
+
console.log('Login failed.');
|
|
95
89
|
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
options.hideBanner = true;
|
|
91
|
+
options.email = '';
|
|
92
|
+
options.password = '';
|
|
98
93
|
|
|
99
|
-
|
|
94
|
+
return await authenticate(options);
|
|
95
|
+
}
|
|
100
96
|
|
|
101
|
-
|
|
97
|
+
config.setAppStoreToken(response.body.accessToken);
|
|
102
98
|
|
|
103
|
-
|
|
104
|
-
});
|
|
99
|
+
console.log('Login successful.');
|
|
105
100
|
}
|
|
106
101
|
|
|
107
|
-
function login(options) {
|
|
108
|
-
authenticate(options);
|
|
102
|
+
async function login(options) {
|
|
103
|
+
await authenticate(options);
|
|
109
104
|
}
|
|
110
105
|
|
|
111
106
|
function logout() {
|
|
@@ -113,93 +108,67 @@ function logout() {
|
|
|
113
108
|
console.log('Done.');
|
|
114
109
|
}
|
|
115
110
|
|
|
116
|
-
function info(options) {
|
|
117
|
-
getAppstoreId(options.appstoreId
|
|
118
|
-
if (error) exit(error);
|
|
119
|
-
|
|
120
|
-
superagentEnd(function () {
|
|
121
|
-
return superagent.get(createUrl('/api/v1/developers/apps/' + id + '/versions/' + version)).query({ accessToken: config.appStoreToken() });
|
|
122
|
-
}, function (error, result) {
|
|
123
|
-
if (error && !error.response) exit(util.format('Failed to list apps: %s', error.message));
|
|
124
|
-
if (result.statusCode !== 200) exit(util.format('Failed to list apps: %s message: %s', result.statusCode, result.text));
|
|
125
|
-
|
|
126
|
-
var manifest = result.body.manifest;
|
|
127
|
-
console.log('id: %s', manifest.id);
|
|
128
|
-
console.log('title: %s', manifest.title);
|
|
129
|
-
console.log('tagline: %s', manifest.tagline);
|
|
130
|
-
console.log('description: %s', manifest.description);
|
|
131
|
-
console.log('website: %s', manifest.website);
|
|
132
|
-
console.log('contactEmail: %s', manifest.contactEmail);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
}
|
|
111
|
+
async function info(options) {
|
|
112
|
+
const [id, version] = await getAppstoreId(options.appstoreId);
|
|
136
113
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (error) exit(error);
|
|
114
|
+
const response = await createRequest('GET', `/api/v1/developers/apps/${id}/versions/${version}`);
|
|
115
|
+
if (response.statusCode !== 200) throw new Error(`Failed to list domains: ${requestError(response)}`);
|
|
140
116
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
117
|
+
const manifest = response.body.manifest;
|
|
118
|
+
console.log('id: %s', manifest.id);
|
|
119
|
+
console.log('title: %s', manifest.title);
|
|
120
|
+
console.log('tagline: %s', manifest.tagline);
|
|
121
|
+
console.log('description: %s', manifest.description);
|
|
122
|
+
console.log('website: %s', manifest.website);
|
|
123
|
+
console.log('contactEmail: %s', manifest.contactEmail);
|
|
124
|
+
}
|
|
147
125
|
|
|
148
|
-
|
|
126
|
+
async function listVersions(options) {
|
|
127
|
+
const [id] = await getAppstoreId(options.appstoreId);
|
|
149
128
|
|
|
150
|
-
|
|
129
|
+
const response = await createRequest('GET', `/api/v1/developers/apps/${id}/versions`);
|
|
130
|
+
if (response.statusCode !== 200) throw new Error(`Failed to list domains: ${requestError(response)}`);
|
|
151
131
|
|
|
152
|
-
|
|
132
|
+
if (response.body.versions.length === 0) return console.log('No versions found.');
|
|
153
133
|
|
|
154
|
-
|
|
155
|
-
var t = new Table();
|
|
134
|
+
if (options.raw) return console.log(JSON.stringify(response.body.versions, null, 2));
|
|
156
135
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
t.cell('Creation Date', version.creationDate);
|
|
160
|
-
t.cell('Image', version.manifest.dockerImage);
|
|
161
|
-
t.cell('Publish state', version.publishState);
|
|
162
|
-
t.newRow();
|
|
163
|
-
});
|
|
136
|
+
const versions = response.body.versions.reverse();
|
|
137
|
+
const t = new Table();
|
|
164
138
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
139
|
+
versions.forEach(function (version) {
|
|
140
|
+
t.cell('Version', version.manifest.version);
|
|
141
|
+
t.cell('Creation Date', version.creationDate);
|
|
142
|
+
t.cell('Image', version.manifest.dockerImage);
|
|
143
|
+
t.cell('Publish state', version.publishState);
|
|
144
|
+
t.newRow();
|
|
168
145
|
});
|
|
146
|
+
|
|
147
|
+
console.log();
|
|
148
|
+
console.log(t.toString());
|
|
169
149
|
}
|
|
170
150
|
|
|
171
|
-
function addApp(manifest, baseDir
|
|
172
|
-
assert(typeof manifest
|
|
173
|
-
assert(typeof baseDir
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}, function (error, result) {
|
|
181
|
-
if (error && !error.response) return exit(util.format('Failed to create app: %s', error.message));
|
|
182
|
-
if (result.statusCode !== 201 && result.statusCode !== 409) {
|
|
183
|
-
return exit(util.format('Failed to create app: %s message: %s', result.statusCode, result.text));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (result.statusCode === 201) {
|
|
187
|
-
console.log('New application added to the appstore with id %s.', manifest.id);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
callback();
|
|
191
|
-
});
|
|
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)}`);
|
|
192
160
|
}
|
|
193
161
|
|
|
194
162
|
function parseChangelog(file, version) {
|
|
195
|
-
|
|
196
|
-
|
|
163
|
+
let changelog = '';
|
|
164
|
+
const data = safe.fs.readFileSync(file, 'utf8');
|
|
197
165
|
if (!data) return null;
|
|
198
|
-
|
|
166
|
+
const lines = data.split('\n');
|
|
199
167
|
|
|
200
168
|
version = version.replace(/-.*/, ''); // remove any prerelease
|
|
201
169
|
|
|
202
|
-
|
|
170
|
+
let i;
|
|
171
|
+
for (i = 0; i < lines.length; i++) {
|
|
203
172
|
if (lines[i] === '[' + version + ']') break;
|
|
204
173
|
}
|
|
205
174
|
|
|
@@ -213,226 +182,177 @@ function parseChangelog(file, version) {
|
|
|
213
182
|
return changelog;
|
|
214
183
|
}
|
|
215
184
|
|
|
216
|
-
function addVersion(manifest, baseDir
|
|
185
|
+
async function addVersion(manifest, baseDir) {
|
|
217
186
|
assert.strictEqual(typeof manifest, 'object');
|
|
218
187
|
assert.strictEqual(typeof baseDir, 'string');
|
|
219
188
|
|
|
220
|
-
|
|
189
|
+
let iconFilePath = null;
|
|
221
190
|
if (manifest.icon) {
|
|
222
|
-
|
|
191
|
+
let iconFile = manifest.icon; // backward compat
|
|
223
192
|
if (iconFile.slice(0, 7) === 'file://') iconFile = iconFile.slice(7);
|
|
224
193
|
|
|
225
194
|
iconFilePath = path.isAbsolute(iconFile) ? iconFile : path.join(baseDir, iconFile);
|
|
226
|
-
if (!fs.existsSync(iconFilePath))
|
|
195
|
+
if (!fs.existsSync(iconFilePath)) throw new Error('icon not found at ' + iconFilePath);
|
|
227
196
|
}
|
|
228
197
|
|
|
229
198
|
if (manifest.description.slice(0, 7) === 'file://') {
|
|
230
|
-
|
|
199
|
+
let descriptionFilePath = manifest.description.slice(7);
|
|
231
200
|
descriptionFilePath = path.isAbsolute(descriptionFilePath) ? descriptionFilePath : path.join(baseDir, descriptionFilePath);
|
|
232
201
|
manifest.description = safe.fs.readFileSync(descriptionFilePath, 'utf8');
|
|
233
|
-
if (!manifest.description && safe.error)
|
|
234
|
-
if (!manifest.description)
|
|
202
|
+
if (!manifest.description && safe.error) throw(new Error('Could not read/parse description ' + safe.error.message));
|
|
203
|
+
if (!manifest.description) throw new Error('Description cannot be empty');
|
|
235
204
|
}
|
|
236
205
|
|
|
237
206
|
if (manifest.postInstallMessage && manifest.postInstallMessage.slice(0, 7) === 'file://') {
|
|
238
|
-
|
|
207
|
+
let postInstallFilePath = manifest.postInstallMessage.slice(7);
|
|
239
208
|
postInstallFilePath = path.isAbsolute(postInstallFilePath) ? postInstallFilePath : path.join(baseDir, postInstallFilePath);
|
|
240
209
|
manifest.postInstallMessage = safe.fs.readFileSync(postInstallFilePath, 'utf8');
|
|
241
|
-
if (!manifest.postInstallMessage && safe.error)
|
|
242
|
-
if (!manifest.postInstallMessage)
|
|
210
|
+
if (!manifest.postInstallMessage && safe.error) throw(new Error('Could not read/parse postInstall ' + safe.error.message));
|
|
211
|
+
if (!manifest.postInstallMessage) throw new Error('PostInstall file specified but it is empty');
|
|
243
212
|
}
|
|
244
213
|
|
|
245
214
|
if (manifest.changelog.slice(0, 7) === 'file://') {
|
|
246
|
-
|
|
215
|
+
let changelogPath = manifest.changelog.slice(7);
|
|
247
216
|
changelogPath = path.isAbsolute(changelogPath) ? changelogPath : path.join(baseDir, changelogPath);
|
|
248
217
|
manifest.changelog = parseChangelog(changelogPath, manifest.version);
|
|
249
|
-
if (!manifest.changelog)
|
|
218
|
+
if (!manifest.changelog) throw new Error('Bad changelog format or missing changelog for this version');
|
|
250
219
|
}
|
|
251
220
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}, function (error, result) {
|
|
259
|
-
if (error && !error.response) return callback(new Error(util.format('Failed to publish version: %s', error.message)));
|
|
260
|
-
if (result.statusCode === 409) return callback('This version already exists. Use --force to overwrite.');
|
|
261
|
-
if (result.statusCode !== 204) return callback(new Error(util.format('Failed to publish version (statusCode %s): \n%s', result.statusCode, result.body && result.body.message ? result.body.message : result.text)));
|
|
262
|
-
|
|
263
|
-
callback();
|
|
264
|
-
});
|
|
221
|
+
const request = createRequest('POST', `/api/v1/developers/apps/${manifest.id}/versions`);
|
|
222
|
+
if (iconFilePath) request.attach('icon', iconFilePath);
|
|
223
|
+
request.attach('manifest', Buffer.from(JSON.stringify(manifest)), 'manifest');
|
|
224
|
+
const response = await request;
|
|
225
|
+
if (response.statusCode === 409) throw new Error('This version already exists. Use --force to overwrite.');
|
|
226
|
+
if (response.statusCode !== 204) throw new Error(`Failed to publish version: ${requestError(response)}`);
|
|
265
227
|
}
|
|
266
228
|
|
|
267
|
-
function updateVersion(manifest, baseDir
|
|
229
|
+
async function updateVersion(manifest, baseDir) {
|
|
268
230
|
assert.strictEqual(typeof manifest, 'object');
|
|
269
231
|
assert.strictEqual(typeof baseDir, 'string');
|
|
270
232
|
|
|
271
|
-
|
|
233
|
+
let iconFilePath = null;
|
|
272
234
|
if (manifest.icon) {
|
|
273
|
-
|
|
235
|
+
let iconFile = manifest.icon; // backward compat
|
|
274
236
|
if (iconFile.slice(0, 7) === 'file://') iconFile = iconFile.slice(7);
|
|
275
237
|
|
|
276
238
|
iconFilePath = path.isAbsolute(iconFile) ? iconFile : path.join(baseDir, iconFile);
|
|
277
|
-
if (!fs.existsSync(iconFilePath))
|
|
239
|
+
if (!fs.existsSync(iconFilePath)) throw new Error('icon not found at ' + iconFilePath);
|
|
278
240
|
}
|
|
279
241
|
|
|
280
242
|
if (manifest.description.slice(0, 7) === 'file://') {
|
|
281
|
-
|
|
243
|
+
let descriptionFilePath = manifest.description.slice(7);
|
|
282
244
|
descriptionFilePath = path.isAbsolute(descriptionFilePath) ? descriptionFilePath : path.join(baseDir, descriptionFilePath);
|
|
283
245
|
manifest.description = safe.fs.readFileSync(descriptionFilePath, 'utf8');
|
|
284
|
-
if (!manifest.description)
|
|
246
|
+
if (!manifest.description) throw new Error('Could not read description ' + safe.error.message);
|
|
285
247
|
}
|
|
286
248
|
|
|
287
249
|
if (manifest.postInstallMessage && manifest.postInstallMessage.slice(0, 7) === 'file://') {
|
|
288
|
-
|
|
250
|
+
let postInstallFilePath = manifest.postInstallMessage.slice(7);
|
|
289
251
|
postInstallFilePath = path.isAbsolute(postInstallFilePath) ? postInstallFilePath : path.join(baseDir, postInstallFilePath);
|
|
290
252
|
manifest.postInstallMessage = safe.fs.readFileSync(postInstallFilePath, 'utf8');
|
|
291
|
-
if (!manifest.postInstallMessage)
|
|
253
|
+
if (!manifest.postInstallMessage) throw new Error('Could not read/parse postInstall ' + safe.error.message);
|
|
292
254
|
}
|
|
293
255
|
|
|
294
256
|
if (manifest.changelog.slice(0, 7) === 'file://') {
|
|
295
|
-
|
|
257
|
+
let changelogPath = manifest.changelog.slice(7);
|
|
296
258
|
changelogPath = path.isAbsolute(changelogPath) ? changelogPath : path.join(baseDir, changelogPath);
|
|
297
259
|
manifest.changelog = parseChangelog(changelogPath, manifest.version);
|
|
298
|
-
if (!manifest.changelog)
|
|
260
|
+
if (!manifest.changelog) throw new Error('Could not read changelog or missing version changes');
|
|
299
261
|
}
|
|
300
262
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return req;
|
|
307
|
-
}, function (error, result) {
|
|
308
|
-
if (error && !error.response) return callback(new Error(util.format('Failed to publish version: %s', error.message)));
|
|
309
|
-
if (result.statusCode !== 204) {
|
|
310
|
-
return callback(new Error(util.format('Failed to publish version (statusCode %s): \n%s', result.statusCode, result.body && result.body.message ? result.body.message : result.text)));
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
callback();
|
|
314
|
-
});
|
|
263
|
+
const request = createRequest('PUT', `/api/v1/developers/apps/${manifest.id}/versions/${manifest.version}`);
|
|
264
|
+
if (iconFilePath) request.attach('icon', iconFilePath);
|
|
265
|
+
request.attach('manifest', Buffer.from(JSON.stringify(manifest)), 'manifest');
|
|
266
|
+
const response = await request;
|
|
267
|
+
if (response.statusCode !== 204) throw new Error(`Failed to publish version: ${requestError(response)}`);
|
|
315
268
|
}
|
|
316
269
|
|
|
317
|
-
function delVersion(manifest, force) {
|
|
318
|
-
assert(typeof manifest
|
|
319
|
-
assert(typeof force
|
|
270
|
+
async function delVersion(manifest, force) {
|
|
271
|
+
assert.strictEqual(typeof manifest, 'object');
|
|
272
|
+
assert.strictEqual(typeof force, 'boolean');
|
|
320
273
|
|
|
321
274
|
if (!force) {
|
|
322
|
-
console.log(
|
|
323
|
-
|
|
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]: ', {});
|
|
324
277
|
if (reallyDelete.toUpperCase() !== 'Y') exit();
|
|
325
278
|
}
|
|
326
279
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}, function (error, result) {
|
|
330
|
-
if (error && !error.response) return exit(util.format('Failed to unpublish version: %s', error.message));
|
|
331
|
-
if (result.statusCode !== 204) exit(util.format('Failed to unpublish version (statusCode %s): \n%s', result.statusCode, result.body && result.body.message ? result.body.message : result.text));
|
|
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)}`);
|
|
332
282
|
|
|
333
|
-
|
|
334
|
-
});
|
|
283
|
+
console.log('version unpublished.');
|
|
335
284
|
}
|
|
336
285
|
|
|
337
|
-
function revokeVersion(appstoreId, version) {
|
|
286
|
+
async function revokeVersion(appstoreId, version) {
|
|
338
287
|
assert.strictEqual(typeof appstoreId, 'string');
|
|
339
288
|
assert.strictEqual(typeof version, 'string');
|
|
340
289
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
.query({ accessToken: config.appStoreToken() })
|
|
344
|
-
.send({ });
|
|
345
|
-
}, function (error, result) {
|
|
346
|
-
if (error && !error.response) return exit(util.format('Failed to revoke version: %s', error.message));
|
|
347
|
-
if (result.statusCode !== 200) exit(util.format('Failed to revoke version (statusCode %s): \n%s', result.statusCode, result.body && result.body.message ? result.body.message : result.text));
|
|
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)}`);
|
|
348
292
|
|
|
349
|
-
|
|
350
|
-
});
|
|
293
|
+
console.log('version revoked.');
|
|
351
294
|
}
|
|
352
295
|
|
|
353
|
-
function approveVersion(appstoreId, version) {
|
|
296
|
+
async function approveVersion(appstoreId, version) {
|
|
354
297
|
assert.strictEqual(typeof appstoreId, 'string');
|
|
355
298
|
assert.strictEqual(typeof version, 'string');
|
|
356
299
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (error && !error.response) return exit(util.format('Failed to approve version: %s', error.message));
|
|
363
|
-
if (result.statusCode !== 200) exit(util.format('Failed to approve version (statusCode %s): \n%s', result.statusCode, result.body && result.body.message ? result.body.message : result.text));
|
|
364
|
-
|
|
365
|
-
console.log('Approved.');
|
|
366
|
-
console.log('');
|
|
367
|
-
|
|
368
|
-
superagentEnd(function () {
|
|
369
|
-
return superagent.get(createUrl('/api/v1/developers/apps/' + appstoreId + '/versions/' + version)).query({ accessToken: config.appStoreToken() });
|
|
370
|
-
}, function (error, result) {
|
|
371
|
-
if (error && !error.response) exit(util.format('Failed to list apps: %s', error.message));
|
|
372
|
-
if (result.statusCode !== 200) exit(util.format('Failed to list apps: %s message: %s', result.statusCode, result.text));
|
|
373
|
-
|
|
374
|
-
console.log('Changelog for forum update: ' + result.body.manifest.forumUrl);
|
|
375
|
-
console.log('');
|
|
376
|
-
console.log('[' + version + ']');
|
|
377
|
-
console.log(result.body.manifest.changelog);
|
|
378
|
-
console.log('');
|
|
379
|
-
});
|
|
380
|
-
});
|
|
381
|
-
}
|
|
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('');
|
|
382
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)}`);
|
|
383
308
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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');
|
|
387
319
|
|
|
388
320
|
if (!force) {
|
|
389
321
|
console.log('This will delete app %s from the appstore!', appId);
|
|
390
|
-
|
|
391
|
-
if (reallyDelete.toUpperCase() !== 'Y') exit();
|
|
322
|
+
const reallyDelete = readlineSync.question('Really do this? [y/N]: ', {});
|
|
323
|
+
if (reallyDelete.toUpperCase() !== 'Y') return exit();
|
|
392
324
|
}
|
|
393
325
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}, function (error, result) {
|
|
397
|
-
if (error && !error.response) return exit(util.format('Failed to unpublish app: %s', error.message));
|
|
398
|
-
if (result.statusCode !== 204) exit(util.format('Failed to unpublish app (statusCode %s): \n%s', result.statusCode, result.body && result.body.message ? result.body.message : result.text));
|
|
326
|
+
const response = await createRequest('DEL', `/api/v1/developers/apps/${appId}`);
|
|
327
|
+
if (response.statusCode !== 204) exit(`Failed to unpublish app : ${requestError(response)}`);
|
|
399
328
|
|
|
400
|
-
|
|
401
|
-
});
|
|
329
|
+
console.log('App unpublished.');
|
|
402
330
|
}
|
|
403
331
|
|
|
404
|
-
function submitAppForReview(manifest
|
|
405
|
-
assert(typeof manifest
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
if (result.statusCode !== 200) return exit(util.format('Failed to submit app (statusCode %s): \n%s', result.statusCode, result.body && result.body.message ? result.body.message : result.text));
|
|
419
|
-
|
|
420
|
-
console.log('App submitted for review.');
|
|
421
|
-
console.log('You will receive an email when approved.');
|
|
422
|
-
|
|
423
|
-
callback();
|
|
424
|
-
});
|
|
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.');
|
|
425
345
|
}
|
|
426
346
|
|
|
427
|
-
function upload(options) {
|
|
347
|
+
async function upload(options) {
|
|
428
348
|
// try to find the manifest of this project
|
|
429
|
-
|
|
349
|
+
const manifestFilePath = locateManifest();
|
|
430
350
|
if (!manifestFilePath) return exit(NO_MANIFEST_FOUND_ERROR_STRING);
|
|
431
351
|
|
|
432
|
-
|
|
352
|
+
const result = manifestFormat.parseFile(manifestFilePath);
|
|
433
353
|
if (result.error) return exit(result.error.message);
|
|
434
354
|
|
|
435
|
-
|
|
355
|
+
const manifest = result.manifest;
|
|
436
356
|
|
|
437
357
|
const sourceDir = path.dirname(manifestFilePath);
|
|
438
358
|
const appConfig = config.getAppConfig(sourceDir);
|
|
@@ -446,19 +366,16 @@ function upload(options) {
|
|
|
446
366
|
// ensure we remove the docker hub handle
|
|
447
367
|
if (manifest.dockerImage.indexOf('docker.io/') === 0) manifest.dockerImage = manifest.dockerImage.slice('docker.io/'.length);
|
|
448
368
|
|
|
449
|
-
|
|
369
|
+
const error = manifestFormat.checkAppstoreRequirements(manifest);
|
|
450
370
|
if (error) return exit(error);
|
|
451
371
|
|
|
452
372
|
// ensure the app is known on the appstore side
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
var func = options.force ? updateVersion : addVersion;
|
|
373
|
+
const baseDir = path.dirname(manifestFilePath);
|
|
374
|
+
await addApp(manifest, baseDir);
|
|
375
|
+
console.log(`Uploading ${manifest.id}@${manifest.version} (dockerImage: ${manifest.dockerImage}) for testing`);
|
|
457
376
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
});
|
|
461
|
-
});
|
|
377
|
+
const [error2] = await safe(options.force ? updateVersion(manifest, baseDir) : addVersion(manifest, baseDir));
|
|
378
|
+
if (error2) return exit(error2);
|
|
462
379
|
}
|
|
463
380
|
|
|
464
381
|
function submit() {
|
|
@@ -474,69 +391,55 @@ function submit() {
|
|
|
474
391
|
submitAppForReview(manifest, exit);
|
|
475
392
|
}
|
|
476
393
|
|
|
477
|
-
function unpublish(options) {
|
|
478
|
-
getAppstoreId(options.appstoreId
|
|
479
|
-
if (error) exit(error);
|
|
394
|
+
async function unpublish(options) {
|
|
395
|
+
const [id, version] = await getAppstoreId(options.appstoreId);
|
|
480
396
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
397
|
+
if (!version) {
|
|
398
|
+
console.log(`Unpublishing ${options.appstoreId}`);
|
|
399
|
+
await delApp(options.app, !!options.force);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
486
402
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
});
|
|
403
|
+
console.log(`Unpublishing ${id}@${version}`);
|
|
404
|
+
await delVersion(id, !!options.force);
|
|
490
405
|
}
|
|
491
406
|
|
|
492
|
-
function revoke(options) {
|
|
493
|
-
getAppstoreId(options.appstoreId
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (!version) return exit('--appstore-id must be of the format id@version');
|
|
407
|
+
async function revoke(options) {
|
|
408
|
+
const [id, version] = await getAppstoreId(options.appstoreId);
|
|
409
|
+
if (!version) return exit('--appstore-id must be of the format id@version');
|
|
497
410
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
});
|
|
411
|
+
console.log(`Revoking ${id}@${version}`);
|
|
412
|
+
await revokeVersion(id, version);
|
|
501
413
|
}
|
|
502
414
|
|
|
503
|
-
function approve(options) {
|
|
504
|
-
getAppstoreId(options.appstoreId
|
|
505
|
-
if (error) return exit(error);
|
|
415
|
+
async function approve(options) {
|
|
416
|
+
const [id, version] = await getAppstoreId(options.appstoreId);
|
|
506
417
|
|
|
507
|
-
|
|
418
|
+
if (!version) return exit('--appstore-id must be of the format id@version');
|
|
508
419
|
|
|
509
|
-
|
|
420
|
+
console.log(`Approving ${id}@${version}`);
|
|
510
421
|
|
|
511
|
-
|
|
512
|
-
});
|
|
422
|
+
await approveVersion(id, version);
|
|
513
423
|
}
|
|
514
424
|
|
|
515
425
|
// TODO currently no pagination, only needed once we have users with more than 100 apps
|
|
516
|
-
function listPublishedApps(options) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
t.cell('Title', app.manifest.title);
|
|
532
|
-
t.cell('Latest Version', app.manifest.version);
|
|
533
|
-
t.cell('Publish State', app.publishState);
|
|
534
|
-
t.cell('Creation Date', new Date(app.creationDate));
|
|
535
|
-
if (options.image) t.cell('Image', app.manifest.dockerImage);
|
|
536
|
-
t.newRow();
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
console.log();
|
|
540
|
-
console.log(t.toString());
|
|
426
|
+
async function listPublishedApps(options) {
|
|
427
|
+
const response = await createRequest('GET', '/api/v1/developers/apps?per_page=100');
|
|
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.');
|
|
430
|
+
|
|
431
|
+
const t = new Table();
|
|
432
|
+
|
|
433
|
+
response.body.apps.forEach(function (app) {
|
|
434
|
+
t.cell('Id', app.id);
|
|
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();
|
|
541
441
|
});
|
|
442
|
+
|
|
443
|
+
console.log();
|
|
444
|
+
console.log(t.toString());
|
|
542
445
|
}
|
package/src/build-actions.js
CHANGED
|
@@ -323,7 +323,7 @@ function build(options) {
|
|
|
323
323
|
url = readlineSync.question('Enter build service URL: ', { });
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
if (
|
|
326
|
+
if (url.indexOf('://') === -1) url = `https://${url}`;
|
|
327
327
|
buildService.url = url;
|
|
328
328
|
}
|
|
329
329
|
|