@appsemble/cli 0.27.8 → 0.27.10
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/README.md +25 -7
- package/commands/app/index.js +2 -0
- package/commands/app/patch.d.ts +22 -0
- package/commands/app/patch.js +64 -0
- package/commands/app/publish.js +3 -0
- package/commands/app/update.js +3 -0
- package/commands/serve.js +1 -1
- package/lib/app.d.ts +67 -5
- package/lib/app.js +179 -77
- package/lib/app.test.d.ts +1 -0
- package/lib/app.test.js +164 -0
- package/package.json +8 -5
- package/server/options/getAppReadmes.d.ts +2 -0
- package/server/options/getAppReadmes.js +11 -0
- package/server/options/index.d.ts +1 -0
- package/server/options/index.js +1 -0
- package/server/options/options.js +2 -1
- package/templates/apps/empty/app-definition.yaml +2 -2
- package/templates/blocks/mini-jsx/package.json +1 -1
- package/templates/blocks/preact/package.json +2 -2
- package/templates/blocks/vanilla/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
#  Appsemble CLI
|
|
2
2
|
|
|
3
3
|
> Manage apps and blocks from the command line.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@appsemble/cli)
|
|
6
|
-
[](https://gitlab.com/appsemble/appsemble/-/releases/0.27.10)
|
|
7
7
|
[](https://prettier.io)
|
|
8
8
|
|
|
9
9
|
## Table of Contents
|
|
@@ -157,6 +157,24 @@ Similarly, to update an app using an app directory, run the following command:
|
|
|
157
157
|
appsemble app update --id <app-id> <path/to/updated app>.
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
+
If you want to update settings for an app, run the following command:
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
appsemble app patch --id <app-id>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Use `--help` flag with the above command to view the supported arguments.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
**🛈NOTE**
|
|
171
|
+
|
|
172
|
+
> The `app patch` command supports updating the `locked` property of an app, locked property is
|
|
173
|
+
> updated before other properties. If you set the locked property to `fullLock`, any other changes
|
|
174
|
+
> won’t be applied unless you are using `--force` flag.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
160
178
|
To delete an app using the CLI use the following command:
|
|
161
179
|
|
|
162
180
|
```sh
|
|
@@ -169,10 +187,10 @@ To export an app as a zip file, run the following:
|
|
|
169
187
|
appsemble app export --id <app-id>
|
|
170
188
|
```
|
|
171
189
|
|
|
172
|
-
Supported arguments for this
|
|
173
|
-
whether to include the resources in the exported file, the same can be applied to assets and
|
|
174
|
-
the path of the folder where you want to put your downloaded file. The default path is
|
|
175
|
-
resources and assets are by default `false`.
|
|
190
|
+
Supported arguments for this command are `resources`, `assets` and `path`. Resources is used to
|
|
191
|
+
specify whether to include the resources in the exported file, the same can be applied to assets and
|
|
192
|
+
path is the path of the folder where you want to put your downloaded file. The default path is
|
|
193
|
+
`./apps`, and resources and assets are by default `false`.
|
|
176
194
|
|
|
177
195
|
To import an app from a zip file, use the following command:
|
|
178
196
|
|
|
@@ -305,5 +323,5 @@ appsemble run-cronjobs --interval 30
|
|
|
305
323
|
|
|
306
324
|
## License
|
|
307
325
|
|
|
308
|
-
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.27.
|
|
326
|
+
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.27.10/LICENSE.md) ©
|
|
309
327
|
[Appsemble](https://appsemble.com)
|
package/commands/app/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as remove from './delete.js';
|
|
|
3
3
|
import * as exportApp from './export.js';
|
|
4
4
|
import * as extractMessages from './extract-messages.js';
|
|
5
5
|
import * as importApp from './import.js';
|
|
6
|
+
import * as patch from './patch.js';
|
|
6
7
|
import * as publish from './publish.js';
|
|
7
8
|
import * as update from './update.js';
|
|
8
9
|
export { noop as handler } from '@appsemble/utils';
|
|
@@ -16,6 +17,7 @@ export function builder(yargs) {
|
|
|
16
17
|
.command(exportApp)
|
|
17
18
|
.command(extractMessages)
|
|
18
19
|
.command(importApp)
|
|
20
|
+
.command(patch)
|
|
19
21
|
.command(update)
|
|
20
22
|
.demandCommand(1);
|
|
21
23
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type AppLock, type AppVisibility } from '@appsemble/types';
|
|
2
|
+
import { type Argv } from 'yargs';
|
|
3
|
+
import { type BaseArguments } from '../../types.js';
|
|
4
|
+
interface PatchAppArguments extends BaseArguments {
|
|
5
|
+
id: number;
|
|
6
|
+
iconBackground?: string;
|
|
7
|
+
force?: boolean;
|
|
8
|
+
path?: string;
|
|
9
|
+
visibility?: AppVisibility;
|
|
10
|
+
showAppDefinition?: boolean;
|
|
11
|
+
template?: boolean;
|
|
12
|
+
demoMode?: boolean;
|
|
13
|
+
locked?: AppLock;
|
|
14
|
+
showAppsembleOAuth2Login?: boolean;
|
|
15
|
+
showAppsembleLogin?: boolean;
|
|
16
|
+
enableSelfRegistration?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare const command = "patch";
|
|
19
|
+
export declare const description = "Change the settings of an app without having the entire source.";
|
|
20
|
+
export declare function builder(yargs: Argv): Argv<any>;
|
|
21
|
+
export declare function handler({ clientCredentials, id, remote, ...args }: PatchAppArguments): Promise<void>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { authenticate, logger } from '@appsemble/node-utils';
|
|
2
|
+
import { patchApp } from '../../lib/app.js';
|
|
3
|
+
export const command = 'patch';
|
|
4
|
+
export const description = 'Change the settings of an app without having the entire source.';
|
|
5
|
+
export function builder(yargs) {
|
|
6
|
+
return yargs
|
|
7
|
+
.option('id', {
|
|
8
|
+
description: 'Id of the app to patch.',
|
|
9
|
+
demandOption: true,
|
|
10
|
+
})
|
|
11
|
+
.option('iconBackground', {
|
|
12
|
+
description: 'The background color to use for the icon in opaque contexts.',
|
|
13
|
+
})
|
|
14
|
+
.option('path', {
|
|
15
|
+
description: 'Path to be used to determine the URL path of the app',
|
|
16
|
+
})
|
|
17
|
+
.option('visibility', {
|
|
18
|
+
description: 'Visibility of the app in the public app store.',
|
|
19
|
+
choices: ['public', 'unlisted', 'private'],
|
|
20
|
+
})
|
|
21
|
+
.option('showAppDefinition', {
|
|
22
|
+
description: 'Whether to expose the app definition in Appsemble studio.',
|
|
23
|
+
type: 'boolean',
|
|
24
|
+
})
|
|
25
|
+
.option('template', {
|
|
26
|
+
description: 'Whether to mark the app as template',
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
})
|
|
29
|
+
.option('demoMode', {
|
|
30
|
+
description: 'Whether the app should be used in demo mode',
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
})
|
|
33
|
+
.option('locked', {
|
|
34
|
+
description: 'Change the value of AppLock for your app.',
|
|
35
|
+
type: 'string',
|
|
36
|
+
choices: ['fullLock', 'studioLock', 'unlocked'],
|
|
37
|
+
})
|
|
38
|
+
.option('showAppsembleOAuth2Login', {
|
|
39
|
+
description: 'Whether the Appsemble OAuth2 login method should be shown',
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
})
|
|
42
|
+
.option('showAppsembleLogin', {
|
|
43
|
+
description: 'Whether the Appsemble password login method should be shown',
|
|
44
|
+
type: 'boolean',
|
|
45
|
+
})
|
|
46
|
+
.option('enableSelfRegistration', {
|
|
47
|
+
description: 'Whether new users should be able to register themselves',
|
|
48
|
+
type: 'boolean',
|
|
49
|
+
})
|
|
50
|
+
.option('force', {
|
|
51
|
+
description: 'Whether the lock property should be ignored',
|
|
52
|
+
type: 'boolean',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
export async function handler({ clientCredentials, id, remote, ...args }) {
|
|
56
|
+
await authenticate(remote, 'apps:write', clientCredentials);
|
|
57
|
+
logger.info(`Patching app ${id}`);
|
|
58
|
+
await patchApp({
|
|
59
|
+
...args,
|
|
60
|
+
id,
|
|
61
|
+
remote,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=patch.js.map
|
package/commands/app/publish.js
CHANGED
|
@@ -27,6 +27,9 @@ export function builder(yargs) {
|
|
|
27
27
|
.option('maskable-icon', {
|
|
28
28
|
describe: 'The maskable icon to upload. By default "maskable-icon.png" in the app directory is used.',
|
|
29
29
|
coerce: coerceFile,
|
|
30
|
+
})
|
|
31
|
+
.option('variant', {
|
|
32
|
+
describe: 'The shared variant to use instead.',
|
|
30
33
|
})
|
|
31
34
|
.option('visibility', {
|
|
32
35
|
describe: 'Visibility of the app in the public app store.',
|
package/commands/app/update.js
CHANGED
|
@@ -29,6 +29,9 @@ export function builder(yargs) {
|
|
|
29
29
|
.option('maskable-icon', {
|
|
30
30
|
describe: 'The maskable icon to upload. By default "maskable-icon.png" in the app directory is used.',
|
|
31
31
|
coerce: coerceFile,
|
|
32
|
+
})
|
|
33
|
+
.option('variant', {
|
|
34
|
+
describe: 'The shared variant to use instead.',
|
|
32
35
|
})
|
|
33
36
|
.option('visibility', {
|
|
34
37
|
describe: 'Visibility of the app in the public app store.',
|
package/commands/serve.js
CHANGED
|
@@ -56,7 +56,7 @@ export function builder(yargs) {
|
|
|
56
56
|
export async function handler(argv) {
|
|
57
57
|
setArgv(argv);
|
|
58
58
|
const appPath = join(process.cwd(), argv.path);
|
|
59
|
-
const [, ,
|
|
59
|
+
const [, , appsembleApp] = await traverseAppDirectory(appPath, new FormData());
|
|
60
60
|
const appRoles = appsembleApp.definition.roles;
|
|
61
61
|
const passedUserRole = argv['user-role'];
|
|
62
62
|
if (passedUserRole && !(appRoles === null || appRoles === void 0 ? void 0 : appRoles.includes(passedUserRole))) {
|
package/lib/app.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
/// <reference types="node" resolution-mode="require"/>
|
|
3
3
|
import { type ReadStream } from 'node:fs';
|
|
4
|
-
import { type App, type
|
|
4
|
+
import { type App, type AppLock, type AppsembleRC, type AppVisibility } from '@appsemble/types';
|
|
5
5
|
import FormData from 'form-data';
|
|
6
6
|
interface PublishAppParams {
|
|
7
7
|
/**
|
|
@@ -20,6 +20,10 @@ interface PublishAppParams {
|
|
|
20
20
|
* The path in which the App YAML is located.
|
|
21
21
|
*/
|
|
22
22
|
path: string;
|
|
23
|
+
/**
|
|
24
|
+
* The shared variant to use instead.
|
|
25
|
+
*/
|
|
26
|
+
variant?: string;
|
|
23
27
|
/**
|
|
24
28
|
* Visibility of the app in the public app store.
|
|
25
29
|
*/
|
|
@@ -103,6 +107,10 @@ interface UpdateAppParams {
|
|
|
103
107
|
* The path in which the App YAML is located.
|
|
104
108
|
*/
|
|
105
109
|
path: string;
|
|
110
|
+
/**
|
|
111
|
+
* The shared variant to use instead.
|
|
112
|
+
*/
|
|
113
|
+
variant?: string;
|
|
106
114
|
/**
|
|
107
115
|
* Visibility of the app in the public app store.
|
|
108
116
|
*/
|
|
@@ -164,11 +172,10 @@ interface UpdateAppParams {
|
|
|
164
172
|
* Traverses an app directory and appends the files it finds to the given FormData object.
|
|
165
173
|
*
|
|
166
174
|
* @param path The path of the app directory to traverse.
|
|
167
|
-
* @param context The Context to use from `.appsemblerc.yaml`.
|
|
168
175
|
* @param formData The FormData object to append the results into..
|
|
169
176
|
* @returns The context from `.appsemblerc.yaml` if a match was found.
|
|
170
177
|
*/
|
|
171
|
-
export declare function traverseAppDirectory(path: string,
|
|
178
|
+
export declare function traverseAppDirectory(path: string, formData: FormData): Promise<[AppsembleRC, string, App]>;
|
|
172
179
|
/**
|
|
173
180
|
* Export an app as a zip file.
|
|
174
181
|
*
|
|
@@ -203,13 +210,13 @@ export declare function writeAppMessages(path: string, languages: string[], veri
|
|
|
203
210
|
*
|
|
204
211
|
* @param argv The command line options used for updating the app.
|
|
205
212
|
*/
|
|
206
|
-
export declare function updateApp({
|
|
213
|
+
export declare function updateApp({ clientCredentials, context, force, path, ...options }: UpdateAppParams): Promise<void>;
|
|
207
214
|
/**
|
|
208
215
|
* Publish a new app.
|
|
209
216
|
*
|
|
210
217
|
* @param options The options to use for publishing an app.
|
|
211
218
|
*/
|
|
212
|
-
export declare function publishApp({
|
|
219
|
+
export declare function publishApp({ clientCredentials, context, dryRun, modifyContext, path, ...options }: PublishAppParams): Promise<void>;
|
|
213
220
|
/**
|
|
214
221
|
* Resolve the app’s ID and remote based on the app context.
|
|
215
222
|
*
|
|
@@ -220,4 +227,59 @@ export declare function publishApp({ assets, clientCredentials, context, dryRun,
|
|
|
220
227
|
* @returns The resolved app ID and remote.
|
|
221
228
|
*/
|
|
222
229
|
export declare function resolveAppIdAndRemote(appPath: string, name: string, defaultRemote: string, defaultAppId: number): Promise<[number, string]>;
|
|
230
|
+
interface PatchAppParams {
|
|
231
|
+
/**
|
|
232
|
+
* The path at which the app is hosted.
|
|
233
|
+
*/
|
|
234
|
+
path?: string;
|
|
235
|
+
/**
|
|
236
|
+
* Whether the locked property should be ignored.
|
|
237
|
+
*/
|
|
238
|
+
force?: boolean;
|
|
239
|
+
/**
|
|
240
|
+
* The ID of the app to update.
|
|
241
|
+
*/
|
|
242
|
+
id?: number;
|
|
243
|
+
/**
|
|
244
|
+
* Visibility of the app in the public app store.
|
|
245
|
+
*/
|
|
246
|
+
visibility?: AppVisibility;
|
|
247
|
+
/**
|
|
248
|
+
* The remote server to create the app on.
|
|
249
|
+
*/
|
|
250
|
+
remote: string;
|
|
251
|
+
/**
|
|
252
|
+
* Whether the App should be marked as a template.
|
|
253
|
+
*/
|
|
254
|
+
template?: boolean;
|
|
255
|
+
/**
|
|
256
|
+
* Whether the App should be used in demo mode.
|
|
257
|
+
*/
|
|
258
|
+
demoMode?: boolean;
|
|
259
|
+
/**
|
|
260
|
+
* The background color to use for the icon in opaque contexts.
|
|
261
|
+
*/
|
|
262
|
+
iconBackground?: string;
|
|
263
|
+
/**
|
|
264
|
+
* Whether or not the app definition is exposed for display in Appsemble Studio.
|
|
265
|
+
*/
|
|
266
|
+
showAppDefinition?: boolean;
|
|
267
|
+
/**
|
|
268
|
+
* Whether the Appsemble password login method should be shown.
|
|
269
|
+
*/
|
|
270
|
+
showAppsembleLogin?: boolean;
|
|
271
|
+
/**
|
|
272
|
+
* Whether the Appsemble OAuth2 login method should be shown.
|
|
273
|
+
*/
|
|
274
|
+
showAppsembleOAuth2Login?: boolean;
|
|
275
|
+
/**
|
|
276
|
+
* Whether new users should be able to register themselves.
|
|
277
|
+
*/
|
|
278
|
+
enableSelfRegistration?: boolean;
|
|
279
|
+
/**
|
|
280
|
+
* Whether the app is currently locked.
|
|
281
|
+
*/
|
|
282
|
+
locked?: AppLock;
|
|
283
|
+
}
|
|
284
|
+
export declare function patchApp({ id, remote, ...options }: PatchAppParams): Promise<void>;
|
|
223
285
|
export {};
|
package/lib/app.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { cpSync, createReadStream, createWriteStream, existsSync } from 'node:fs';
|
|
2
|
-
import { mkdir, readdir,
|
|
2
|
+
import { mkdir, readdir, rm, stat } from 'node:fs/promises';
|
|
3
3
|
import { basename, dirname, join, parse, relative, resolve } from 'node:path';
|
|
4
4
|
import { inspect } from 'node:util';
|
|
5
|
-
import { applyAppVariant, AppsembleError, authenticate,
|
|
5
|
+
import { applyAppVariant, AppsembleError, authenticate, logger, opendirSafe, readData, writeData, } from '@appsemble/node-utils';
|
|
6
6
|
import { extractAppMessages, has, normalizeBlockName } from '@appsemble/utils';
|
|
7
7
|
import axios from 'axios';
|
|
8
8
|
import fg from 'fast-glob';
|
|
@@ -20,27 +20,38 @@ import { publishResourcesRecursively } from './resource.js';
|
|
|
20
20
|
* Traverses an app directory and appends the files it finds to the given FormData object.
|
|
21
21
|
*
|
|
22
22
|
* @param path The path of the app directory to traverse.
|
|
23
|
-
* @param context The Context to use from `.appsemblerc.yaml`.
|
|
24
23
|
* @param formData The FormData object to append the results into..
|
|
25
24
|
* @returns The context from `.appsemblerc.yaml` if a match was found.
|
|
26
25
|
*/
|
|
27
|
-
export async function traverseAppDirectory(path,
|
|
28
|
-
let discoveredContext;
|
|
26
|
+
export async function traverseAppDirectory(path, formData) {
|
|
29
27
|
let rc;
|
|
30
|
-
let iconPath;
|
|
31
|
-
let maskableIconPath;
|
|
32
28
|
let yaml;
|
|
33
29
|
let controllerPath;
|
|
34
30
|
let controllerBuildConfig;
|
|
35
31
|
let controllerBuildResult;
|
|
36
32
|
let controllerCode;
|
|
37
33
|
let controllerImplementations;
|
|
38
|
-
|
|
34
|
+
let supportedLanguages = new Set();
|
|
35
|
+
try {
|
|
36
|
+
const languageFiles = await readdir(join(path, 'i18n'));
|
|
37
|
+
supportedLanguages = new Set(languageFiles.map((lang) => lang.split('.json')[0].toLowerCase()));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
logger.warn(`Could not read ${join(path, 'i18n')}. No supported languages found.`);
|
|
41
|
+
}
|
|
39
42
|
const gatheredData = {
|
|
40
43
|
screenshotUrls: [],
|
|
41
44
|
};
|
|
42
45
|
logger.info(`Traversing directory for App files in ${path} 🕵`);
|
|
43
46
|
await opendirSafe(path, async (filepath, filestat) => {
|
|
47
|
+
if (filestat.isFile() && filestat.name.toLowerCase().startsWith('readme')) {
|
|
48
|
+
logger.info(`Adding ${filepath}`);
|
|
49
|
+
formData.append('readmes', createReadStream(filepath));
|
|
50
|
+
if (filestat.name.toLowerCase().startsWith('readme.md')) {
|
|
51
|
+
gatheredData.readmeUrl = basename(filepath);
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
44
55
|
switch (filestat.name.toLowerCase()) {
|
|
45
56
|
case '.appsemblerc.yaml':
|
|
46
57
|
logger.info(`Reading app settings from ${filepath}`);
|
|
@@ -49,10 +60,6 @@ export async function traverseAppDirectory(path, context, formData) {
|
|
|
49
60
|
formData.append('iconBackground', rc.iconBackground);
|
|
50
61
|
gatheredData.iconBackground = rc.iconBackground;
|
|
51
62
|
}
|
|
52
|
-
if (context && has(rc === null || rc === void 0 ? void 0 : rc.context, context)) {
|
|
53
|
-
discoveredContext = rc.context[context];
|
|
54
|
-
logger.verbose(`Using context: ${inspect(discoveredContext, { colors: true })}`);
|
|
55
|
-
}
|
|
56
63
|
break;
|
|
57
64
|
case 'app-definition.yaml': {
|
|
58
65
|
logger.info(`Using app definition from ${filepath}`);
|
|
@@ -67,27 +74,23 @@ export async function traverseAppDirectory(path, context, formData) {
|
|
|
67
74
|
}
|
|
68
75
|
case 'icon.png':
|
|
69
76
|
case 'icon.svg':
|
|
70
|
-
iconPath = filepath;
|
|
71
77
|
gatheredData.iconUrl = filepath;
|
|
72
78
|
return;
|
|
73
79
|
case 'maskable-icon.png':
|
|
74
|
-
maskableIconPath = filepath;
|
|
75
|
-
return;
|
|
76
|
-
case 'readme.md':
|
|
77
|
-
logger.info(`Including longDescription from ${filepath}`);
|
|
78
|
-
formData.append('longDescription', await readFile(filepath, 'utf8'));
|
|
79
|
-
gatheredData.longDescription = await readFile(filepath, 'utf8');
|
|
80
80
|
return;
|
|
81
81
|
case 'screenshots':
|
|
82
82
|
return opendirSafe(filepath, (screenshotPath, screenshotStat) => {
|
|
83
|
+
if (screenshotStat.isDirectory() &&
|
|
84
|
+
!['screenshots', ...supportedLanguages].includes(screenshotStat.name)) {
|
|
85
|
+
logger.warn(`Unsupported directory name at ${screenshotPath}. Skipping...`);
|
|
86
|
+
}
|
|
83
87
|
if (screenshotStat.isFile()) {
|
|
84
88
|
const screenshotDirectoryPath = dirname(screenshotPath);
|
|
85
89
|
const screenshotDirectoryName = basename(screenshotDirectoryPath);
|
|
86
|
-
const screenshotName = basename(screenshotPath);
|
|
87
90
|
if (!['screenshots', ...supportedLanguages].includes(screenshotDirectoryName)) {
|
|
88
|
-
logger.warn(`Unsupported directory name at ${screenshotPath}. Skipping...`);
|
|
89
91
|
return;
|
|
90
92
|
}
|
|
93
|
+
const screenshotName = basename(screenshotPath);
|
|
91
94
|
const language = supportedLanguages.has(screenshotDirectoryName)
|
|
92
95
|
? screenshotDirectoryName
|
|
93
96
|
: 'unspecified';
|
|
@@ -124,12 +127,40 @@ export async function traverseAppDirectory(path, context, formData) {
|
|
|
124
127
|
gatheredData.controllerImplementations = controllerImplementations;
|
|
125
128
|
break;
|
|
126
129
|
default:
|
|
127
|
-
|
|
130
|
+
break;
|
|
128
131
|
}
|
|
129
132
|
});
|
|
130
133
|
if (yaml === undefined) {
|
|
131
134
|
throw new AppsembleError('No app definition found');
|
|
132
135
|
}
|
|
136
|
+
return [rc, yaml, gatheredData];
|
|
137
|
+
}
|
|
138
|
+
async function retrieveContext(path, context) {
|
|
139
|
+
let rc;
|
|
140
|
+
let discoveredContext;
|
|
141
|
+
let iconPath;
|
|
142
|
+
let maskableIconPath;
|
|
143
|
+
await opendirSafe(path, async (filepath, filestat) => {
|
|
144
|
+
switch (filestat.name.toLowerCase()) {
|
|
145
|
+
case '.appsemblerc.yaml':
|
|
146
|
+
logger.info(`Reading app settings from ${filepath}`);
|
|
147
|
+
[rc] = await readData(filepath);
|
|
148
|
+
if (context && has(rc === null || rc === void 0 ? void 0 : rc.context, context)) {
|
|
149
|
+
discoveredContext = rc.context[context];
|
|
150
|
+
logger.verbose(`Using context: ${inspect(discoveredContext, { colors: true })}`);
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
case 'icon.png':
|
|
154
|
+
case 'icon.svg':
|
|
155
|
+
iconPath = filepath;
|
|
156
|
+
return;
|
|
157
|
+
case 'maskable-icon.png':
|
|
158
|
+
maskableIconPath = filepath;
|
|
159
|
+
break;
|
|
160
|
+
default:
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
133
164
|
discoveredContext || (discoveredContext = {});
|
|
134
165
|
discoveredContext.icon = discoveredContext.icon
|
|
135
166
|
? resolve(path, discoveredContext.icon)
|
|
@@ -137,7 +168,7 @@ export async function traverseAppDirectory(path, context, formData) {
|
|
|
137
168
|
discoveredContext.maskableIcon = discoveredContext.maskableIcon
|
|
138
169
|
? resolve(path, discoveredContext.maskableIcon)
|
|
139
170
|
: maskableIconPath;
|
|
140
|
-
return
|
|
171
|
+
return discoveredContext;
|
|
141
172
|
}
|
|
142
173
|
/**
|
|
143
174
|
* Export an app as a zip file.
|
|
@@ -425,38 +456,45 @@ This block version is not used in the app`);
|
|
|
425
456
|
*
|
|
426
457
|
* @param argv The command line options used for updating the app.
|
|
427
458
|
*/
|
|
428
|
-
export async function updateApp({
|
|
429
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
|
|
459
|
+
export async function updateApp({ clientCredentials, context, force, path, ...options }) {
|
|
460
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
|
430
461
|
const file = await stat(path);
|
|
431
462
|
const formData = new FormData();
|
|
432
|
-
|
|
463
|
+
const appsembleContext = await retrieveContext(path, context);
|
|
464
|
+
let yaml;
|
|
465
|
+
let filename = relative(process.cwd(), path);
|
|
466
|
+
const variant = (_b = (_a = appsembleContext.variant) !== null && _a !== void 0 ? _a : options.variant) !== null && _b !== void 0 ? _b : context;
|
|
433
467
|
let appVariantPath = path;
|
|
434
|
-
if (existsSync(join(path, 'variants',
|
|
435
|
-
await applyAppVariant(path,
|
|
436
|
-
appVariantPath = join(dirname(path), `${basename(path)}-${
|
|
468
|
+
if (variant && existsSync(join(path, 'variants', variant))) {
|
|
469
|
+
await applyAppVariant(path, variant);
|
|
470
|
+
appVariantPath = join(dirname(path), `${basename(path)}-${variant}`);
|
|
437
471
|
}
|
|
438
472
|
else {
|
|
439
|
-
logger.warn(`App variant ${
|
|
473
|
+
logger.warn(`App variant ${variant} is not defined in ${join(path, 'variants')}. Using default app.`);
|
|
440
474
|
}
|
|
441
475
|
if (file.isFile()) {
|
|
442
476
|
const [, data] = await readData(appVariantPath);
|
|
477
|
+
yaml = data;
|
|
443
478
|
formData.append('yaml', data);
|
|
444
479
|
}
|
|
445
480
|
else {
|
|
446
|
-
[
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
const
|
|
451
|
-
const
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
const
|
|
456
|
-
const
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
const
|
|
481
|
+
[, yaml] = await traverseAppDirectory(appVariantPath, formData);
|
|
482
|
+
filename = join(filename, 'app-definition.yaml');
|
|
483
|
+
}
|
|
484
|
+
const remote = (_c = appsembleContext.remote) !== null && _c !== void 0 ? _c : options.remote;
|
|
485
|
+
const id = (_d = appsembleContext.id) !== null && _d !== void 0 ? _d : options.id;
|
|
486
|
+
const template = (_f = (_e = appsembleContext.template) !== null && _e !== void 0 ? _e : options.template) !== null && _f !== void 0 ? _f : false;
|
|
487
|
+
const assetsClonable = (_h = (_g = appsembleContext.assetsClonable) !== null && _g !== void 0 ? _g : options.assetsClonable) !== null && _h !== void 0 ? _h : false;
|
|
488
|
+
const demoMode = (_k = (_j = appsembleContext.demoMode) !== null && _j !== void 0 ? _j : options.demoMode) !== null && _k !== void 0 ? _k : false;
|
|
489
|
+
const visibility = (_l = appsembleContext.visibility) !== null && _l !== void 0 ? _l : options.visibility;
|
|
490
|
+
const iconBackground = (_m = appsembleContext.iconBackground) !== null && _m !== void 0 ? _m : options.iconBackground;
|
|
491
|
+
const icon = (_o = options.icon) !== null && _o !== void 0 ? _o : appsembleContext.icon;
|
|
492
|
+
const maskableIcon = (_p = options.maskableIcon) !== null && _p !== void 0 ? _p : appsembleContext.maskableIcon;
|
|
493
|
+
const sentryDsn = (_q = appsembleContext.sentryDsn) !== null && _q !== void 0 ? _q : options.sentryDsn;
|
|
494
|
+
const sentryEnvironment = (_r = appsembleContext.sentryEnvironment) !== null && _r !== void 0 ? _r : options.sentryEnvironment;
|
|
495
|
+
const googleAnalyticsId = (_s = appsembleContext.googleAnalyticsId) !== null && _s !== void 0 ? _s : options.googleAnalyticsId;
|
|
496
|
+
const resources = (_t = appsembleContext.resources) !== null && _t !== void 0 ? _t : options.resources;
|
|
497
|
+
const assets = (_u = appsembleContext.assets) !== null && _u !== void 0 ? _u : options.assets;
|
|
460
498
|
const { appLock } = appsembleContext;
|
|
461
499
|
logger.info(`App id: ${id}`);
|
|
462
500
|
logger.verbose(`App remote: ${remote}`);
|
|
@@ -474,12 +512,12 @@ export async function updateApp({ assets, clientCredentials, context, force, pat
|
|
|
474
512
|
formData.append('iconBackground', iconBackground);
|
|
475
513
|
if (icon) {
|
|
476
514
|
const realIcon = typeof icon === 'string' ? createReadStream(icon) : icon;
|
|
477
|
-
logger.info(`Using icon from ${(
|
|
515
|
+
logger.info(`Using icon from ${(_v = realIcon.path) !== null && _v !== void 0 ? _v : 'stdin'}`);
|
|
478
516
|
formData.append('icon', realIcon);
|
|
479
517
|
}
|
|
480
518
|
if (maskableIcon) {
|
|
481
519
|
const realIcon = typeof maskableIcon === 'string' ? createReadStream(maskableIcon) : maskableIcon;
|
|
482
|
-
logger.info(`Using maskable icon from ${(
|
|
520
|
+
logger.info(`Using maskable icon from ${(_w = realIcon.path) !== null && _w !== void 0 ? _w : 'stdin'}`);
|
|
483
521
|
formData.append('maskableIcon', realIcon);
|
|
484
522
|
}
|
|
485
523
|
if (sentryDsn) {
|
|
@@ -512,19 +550,16 @@ export async function updateApp({ assets, clientCredentials, context, force, pat
|
|
|
512
550
|
}
|
|
513
551
|
let data;
|
|
514
552
|
try {
|
|
515
|
-
data = await axios
|
|
516
|
-
.patch(`/api/apps/${id}`, formData, { baseURL: remote })
|
|
517
|
-
.then((r) => r.data);
|
|
553
|
+
({ data } = await axios.patch(`/api/apps/${id}`, formData, { baseURL: remote }));
|
|
518
554
|
}
|
|
519
555
|
catch (error) {
|
|
520
556
|
if (!axios.isAxiosError(error)) {
|
|
521
557
|
throw error;
|
|
522
558
|
}
|
|
523
|
-
if (((
|
|
559
|
+
if (((_y = (_x = error.response) === null || _x === void 0 ? void 0 : _x.data) === null || _y === void 0 ? void 0 : _y.message) !== 'App validation failed') {
|
|
524
560
|
throw error;
|
|
525
561
|
}
|
|
526
|
-
|
|
527
|
-
process.exit(1);
|
|
562
|
+
throw new AppsembleError(printAxiosError(filename, yaml, error.response.data.data.errors));
|
|
528
563
|
}
|
|
529
564
|
if (file.isDirectory()) {
|
|
530
565
|
// After uploading the app, upload block styles and messages if they are available
|
|
@@ -552,21 +587,22 @@ export async function updateApp({ assets, clientCredentials, context, force, pat
|
|
|
552
587
|
*
|
|
553
588
|
* @param options The options to use for publishing an app.
|
|
554
589
|
*/
|
|
555
|
-
export async function publishApp({
|
|
556
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
|
|
590
|
+
export async function publishApp({ clientCredentials, context, dryRun, modifyContext, path, ...options }) {
|
|
591
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
|
|
557
592
|
const file = await stat(path);
|
|
558
593
|
const formData = new FormData();
|
|
559
|
-
|
|
594
|
+
const appsembleContext = await retrieveContext(path, context);
|
|
560
595
|
let rc;
|
|
561
596
|
let yaml;
|
|
562
597
|
let filename = relative(process.cwd(), path);
|
|
598
|
+
const variant = (_b = (_a = appsembleContext.variant) !== null && _a !== void 0 ? _a : options.variant) !== null && _b !== void 0 ? _b : context;
|
|
563
599
|
let appVariantPath = path;
|
|
564
|
-
if (existsSync(join(path, 'variants',
|
|
565
|
-
await applyAppVariant(path,
|
|
566
|
-
appVariantPath = join(dirname(path), `${basename(path)}-${
|
|
600
|
+
if (variant && existsSync(join(path, 'variants', variant))) {
|
|
601
|
+
await applyAppVariant(path, variant);
|
|
602
|
+
appVariantPath = join(dirname(path), `${basename(path)}-${variant}`);
|
|
567
603
|
}
|
|
568
604
|
else {
|
|
569
|
-
logger.warn(`App variant ${
|
|
605
|
+
logger.warn(`App variant ${variant} is not defined in ${join(path, 'variants')}. Using default app.`);
|
|
570
606
|
}
|
|
571
607
|
if (file.isFile()) {
|
|
572
608
|
// Assuming file is App YAML
|
|
@@ -575,21 +611,23 @@ export async function publishApp({ assets, clientCredentials, context, dryRun, m
|
|
|
575
611
|
formData.append('yaml', data);
|
|
576
612
|
}
|
|
577
613
|
else {
|
|
578
|
-
[
|
|
614
|
+
[rc, yaml] = await traverseAppDirectory(appVariantPath, formData);
|
|
579
615
|
filename = join(filename, 'app-definition.yaml');
|
|
580
616
|
}
|
|
581
|
-
const remote = (
|
|
582
|
-
const organizationId = (
|
|
583
|
-
const template = (
|
|
584
|
-
const assetsClonable = (
|
|
585
|
-
const demoMode = (
|
|
586
|
-
const visibility = (
|
|
587
|
-
const iconBackground = (
|
|
588
|
-
const icon = (
|
|
589
|
-
const maskableIcon = (
|
|
590
|
-
const sentryDsn = (
|
|
591
|
-
const sentryEnvironment = (
|
|
592
|
-
const googleAnalyticsId = (
|
|
617
|
+
const remote = (_c = appsembleContext.remote) !== null && _c !== void 0 ? _c : options.remote;
|
|
618
|
+
const organizationId = (_d = appsembleContext.organization) !== null && _d !== void 0 ? _d : options.organization;
|
|
619
|
+
const template = (_f = (_e = appsembleContext.template) !== null && _e !== void 0 ? _e : options.template) !== null && _f !== void 0 ? _f : false;
|
|
620
|
+
const assetsClonable = (_h = (_g = appsembleContext.assetsClonable) !== null && _g !== void 0 ? _g : options.assetsClonable) !== null && _h !== void 0 ? _h : false;
|
|
621
|
+
const demoMode = (_k = (_j = appsembleContext.demoMode) !== null && _j !== void 0 ? _j : options.demoMode) !== null && _k !== void 0 ? _k : false;
|
|
622
|
+
const visibility = (_l = appsembleContext.visibility) !== null && _l !== void 0 ? _l : options.visibility;
|
|
623
|
+
const iconBackground = (_m = appsembleContext.iconBackground) !== null && _m !== void 0 ? _m : options.iconBackground;
|
|
624
|
+
const icon = (_o = options.icon) !== null && _o !== void 0 ? _o : appsembleContext.icon;
|
|
625
|
+
const maskableIcon = (_p = options.maskableIcon) !== null && _p !== void 0 ? _p : appsembleContext.maskableIcon;
|
|
626
|
+
const sentryDsn = (_q = appsembleContext.sentryDsn) !== null && _q !== void 0 ? _q : options.sentryDsn;
|
|
627
|
+
const sentryEnvironment = (_r = appsembleContext.sentryEnvironment) !== null && _r !== void 0 ? _r : options.sentryEnvironment;
|
|
628
|
+
const googleAnalyticsId = (_s = appsembleContext.googleAnalyticsId) !== null && _s !== void 0 ? _s : options.googleAnalyticsId;
|
|
629
|
+
const assets = (_t = appsembleContext.assets) !== null && _t !== void 0 ? _t : options.assets;
|
|
630
|
+
const resources = (_u = appsembleContext.resources) !== null && _u !== void 0 ? _u : options.resources;
|
|
593
631
|
const appLock = appsembleContext.appLock || 'unlocked';
|
|
594
632
|
logger.verbose(`App remote: ${remote}`);
|
|
595
633
|
logger.verbose(`App organization: ${organizationId}`);
|
|
@@ -607,12 +645,12 @@ export async function publishApp({ assets, clientCredentials, context, dryRun, m
|
|
|
607
645
|
formData.append('locked', appLock);
|
|
608
646
|
if (icon) {
|
|
609
647
|
const realIcon = typeof icon === 'string' ? createReadStream(icon) : icon;
|
|
610
|
-
logger.info(`Using icon from ${(
|
|
648
|
+
logger.info(`Using icon from ${(_v = realIcon.path) !== null && _v !== void 0 ? _v : 'stdin'}`);
|
|
611
649
|
formData.append('icon', realIcon);
|
|
612
650
|
}
|
|
613
651
|
if (maskableIcon) {
|
|
614
652
|
const realIcon = typeof maskableIcon === 'string' ? createReadStream(maskableIcon) : maskableIcon;
|
|
615
|
-
logger.info(`Using maskable icon from ${(
|
|
653
|
+
logger.info(`Using maskable icon from ${(_w = realIcon.path) !== null && _w !== void 0 ? _w : 'stdin'}`);
|
|
616
654
|
formData.append('maskableIcon', realIcon);
|
|
617
655
|
}
|
|
618
656
|
if (sentryDsn) {
|
|
@@ -642,7 +680,7 @@ export async function publishApp({ assets, clientCredentials, context, dryRun, m
|
|
|
642
680
|
if (!axios.isAxiosError(error)) {
|
|
643
681
|
throw error;
|
|
644
682
|
}
|
|
645
|
-
if (((
|
|
683
|
+
if (((_y = (_x = error.response) === null || _x === void 0 ? void 0 : _x.data) === null || _y === void 0 ? void 0 : _y.message) !== 'App validation failed') {
|
|
646
684
|
throw error;
|
|
647
685
|
}
|
|
648
686
|
throw new AppsembleError(printAxiosError(filename, yaml, error.response.data.data.errors));
|
|
@@ -673,7 +711,7 @@ export async function publishApp({ assets, clientCredentials, context, dryRun, m
|
|
|
673
711
|
logger.info(`Successfully published app ${data.definition.name}! 🙌`);
|
|
674
712
|
logger.info(`App URL: ${protocol}//${data.path}.${data.OrganizationId}.${host}`);
|
|
675
713
|
logger.info(`App store page: ${new URL(`/apps/${data.id}`, remote)}`);
|
|
676
|
-
const rcCollections = rc.context[context].collections;
|
|
714
|
+
const rcCollections = (_z = rc.context[context]) === null || _z === void 0 ? void 0 : _z.collections;
|
|
677
715
|
if (Array.isArray(rcCollections)) {
|
|
678
716
|
for (const collectionId of rcCollections) {
|
|
679
717
|
try {
|
|
@@ -687,7 +725,7 @@ export async function publishApp({ assets, clientCredentials, context, dryRun, m
|
|
|
687
725
|
}
|
|
688
726
|
}
|
|
689
727
|
}
|
|
690
|
-
if (appVariantPath !== path) {
|
|
728
|
+
if (appVariantPath !== path && modifyContext && !dryRun) {
|
|
691
729
|
const [defaultAppRc] = await readData(join(path, '.appsemblerc.yaml'));
|
|
692
730
|
defaultAppRc.context[context] = rc.context[context];
|
|
693
731
|
await writeData(join(path, '.appsemblerc.yaml'), defaultAppRc, { sort: false });
|
|
@@ -724,4 +762,68 @@ export async function resolveAppIdAndRemote(appPath, name, defaultRemote, defaul
|
|
|
724
762
|
}
|
|
725
763
|
return [id, coerceRemote(resolvedRemote)];
|
|
726
764
|
}
|
|
765
|
+
export async function patchApp({ id, remote, ...options }) {
|
|
766
|
+
const formData = new FormData();
|
|
767
|
+
if (options.path !== undefined) {
|
|
768
|
+
logger.info(`Setting app path to ${options.path}`);
|
|
769
|
+
formData.append('path', options.path);
|
|
770
|
+
}
|
|
771
|
+
if (options.force !== undefined) {
|
|
772
|
+
formData.append('force', String(options.force));
|
|
773
|
+
}
|
|
774
|
+
if (options.demoMode !== undefined) {
|
|
775
|
+
logger.info(`Setting app demo mode to ${options.demoMode}`);
|
|
776
|
+
formData.append('demoMode', String(options.demoMode));
|
|
777
|
+
}
|
|
778
|
+
if (options.template !== undefined) {
|
|
779
|
+
logger.info(`Setting template to ${options.template}`);
|
|
780
|
+
formData.append('template', String(options.template));
|
|
781
|
+
}
|
|
782
|
+
if (options.visibility !== undefined) {
|
|
783
|
+
logger.info(`Setting app visibility to ${options.visibility}`);
|
|
784
|
+
formData.append('visibility', options.visibility);
|
|
785
|
+
}
|
|
786
|
+
if (options.iconBackground !== undefined) {
|
|
787
|
+
logger.info(`Setting app icon background to ${options.iconBackground}`);
|
|
788
|
+
formData.append('iconBackground', options.iconBackground);
|
|
789
|
+
}
|
|
790
|
+
if (options.showAppDefinition !== undefined) {
|
|
791
|
+
logger.info(`Setting showAppDefinition to ${options.showAppDefinition}`);
|
|
792
|
+
formData.append('showAppDefinition', String(options.showAppDefinition));
|
|
793
|
+
}
|
|
794
|
+
if (options.showAppsembleLogin !== undefined) {
|
|
795
|
+
logger.info(`Setting showAppsembleLogin to ${options.showAppsembleLogin}`);
|
|
796
|
+
formData.append('showAppsembleLogin', String(options.showAppsembleLogin));
|
|
797
|
+
}
|
|
798
|
+
if (options.showAppsembleOAuth2Login !== undefined) {
|
|
799
|
+
logger.info(`Setting showAppsembleOAuth2Login to ${options.showAppsembleOAuth2Login}`);
|
|
800
|
+
formData.append('showAppsembleOAuth2Login', String(options.showAppsembleOAuth2Login));
|
|
801
|
+
}
|
|
802
|
+
if (options.enableSelfRegistration !== undefined) {
|
|
803
|
+
logger.info(`Setting enableSelfRegistration to ${options.enableSelfRegistration}`);
|
|
804
|
+
formData.append('enableSelfRegistration', String(options.enableSelfRegistration));
|
|
805
|
+
}
|
|
806
|
+
try {
|
|
807
|
+
if (!formData.getLengthSync() && options.locked === undefined) {
|
|
808
|
+
logger.warn('Nothing to patch. Need at least one additional argument to patch app.');
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
if (formData.getLengthSync()) {
|
|
812
|
+
const { data } = await axios.patch(`/api/apps/${id}`, formData);
|
|
813
|
+
logger.info(`Successfully updated app with id ${id}`);
|
|
814
|
+
const { host, protocol } = new URL(remote);
|
|
815
|
+
logger.info(`App URL: ${protocol}//${data.path}.${data.OrganizationId}.${host}`);
|
|
816
|
+
}
|
|
817
|
+
if (options.locked !== undefined) {
|
|
818
|
+
await axios.post(`/api/apps/${id}/lock`, {
|
|
819
|
+
locked: options.locked,
|
|
820
|
+
});
|
|
821
|
+
logger.info(`Successfully set app lock value for app with id ${id} to ${options.locked}`);
|
|
822
|
+
}
|
|
823
|
+
logger.info(`App store page: ${new URL(`/apps/${id}`, remote)}`);
|
|
824
|
+
}
|
|
825
|
+
catch (error) {
|
|
826
|
+
logger.error(error);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
727
829
|
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/app.test.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { authenticate } from '@appsemble/node-utils';
|
|
2
|
+
import { authorizeClientCredentials, createServer, createTestUser, models, setArgv, useTestDatabase, } from '@appsemble/server';
|
|
3
|
+
import { setTestApp } from 'axios-test-instance';
|
|
4
|
+
import { hash } from 'bcrypt';
|
|
5
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { patchApp } from './app.js';
|
|
7
|
+
import { initAxios } from './initAxios.js';
|
|
8
|
+
const { App, Organization, OrganizationMember } = models;
|
|
9
|
+
const argv = { host: 'http://localhost', secret: 'test', aesSecret: 'testSecret' };
|
|
10
|
+
let user;
|
|
11
|
+
let organization;
|
|
12
|
+
let testApp;
|
|
13
|
+
async function authorizeCLI(scopes) {
|
|
14
|
+
const OAuth2AuthorizationCode = await authorizeClientCredentials(scopes);
|
|
15
|
+
const { id, secret } = OAuth2AuthorizationCode;
|
|
16
|
+
await OAuth2AuthorizationCode.update({ secret: await hash(secret, 10) });
|
|
17
|
+
await authenticate(testApp.defaults.baseURL, scopes, `${id}:${secret}`);
|
|
18
|
+
}
|
|
19
|
+
useTestDatabase(import.meta);
|
|
20
|
+
beforeAll(() => {
|
|
21
|
+
vi.useFakeTimers();
|
|
22
|
+
setArgv(argv);
|
|
23
|
+
});
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
vi.clearAllTimers();
|
|
26
|
+
vi.setSystemTime(0);
|
|
27
|
+
const server = await createServer();
|
|
28
|
+
testApp = await setTestApp(server);
|
|
29
|
+
initAxios({ remote: testApp.defaults.baseURL });
|
|
30
|
+
user = await createTestUser();
|
|
31
|
+
organization = await Organization.create({
|
|
32
|
+
id: 'testorganization',
|
|
33
|
+
name: 'Test Organization',
|
|
34
|
+
});
|
|
35
|
+
await OrganizationMember.create({
|
|
36
|
+
OrganizationId: organization.id,
|
|
37
|
+
UserId: user.id,
|
|
38
|
+
role: 'Owner',
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
afterAll(() => {
|
|
42
|
+
vi.useRealTimers();
|
|
43
|
+
});
|
|
44
|
+
describe('patchApp', () => {
|
|
45
|
+
it('should apply patches to app', async () => {
|
|
46
|
+
const patches = {
|
|
47
|
+
demoMode: true,
|
|
48
|
+
enableSelfRegistration: true,
|
|
49
|
+
iconBackground: '#FFFFFF',
|
|
50
|
+
locked: 'fullLock',
|
|
51
|
+
path: 'updated-path',
|
|
52
|
+
showAppDefinition: true,
|
|
53
|
+
showAppsembleLogin: true,
|
|
54
|
+
showAppsembleOAuth2Login: true,
|
|
55
|
+
template: true,
|
|
56
|
+
visibility: 'private',
|
|
57
|
+
};
|
|
58
|
+
const app = await App.create({
|
|
59
|
+
path: 'test-app',
|
|
60
|
+
definition: { name: 'Test App', defaultPage: 'Test Page' },
|
|
61
|
+
vapidPublicKey: 'a',
|
|
62
|
+
vapidPrivateKey: 'b',
|
|
63
|
+
visibility: 'public',
|
|
64
|
+
OrganizationId: organization.id,
|
|
65
|
+
}, { raw: true });
|
|
66
|
+
await authorizeCLI('apps:write');
|
|
67
|
+
await patchApp({
|
|
68
|
+
...patches,
|
|
69
|
+
remote: testApp.defaults.baseURL,
|
|
70
|
+
id: app.id,
|
|
71
|
+
});
|
|
72
|
+
await app.reload();
|
|
73
|
+
expect(app.dataValues).toStrictEqual(expect.objectContaining({ ...app.dataValues, ...patches }));
|
|
74
|
+
expect(app.dataValues).toMatchInlineSnapshot(`
|
|
75
|
+
{
|
|
76
|
+
"OrganizationId": "testorganization",
|
|
77
|
+
"controllerCode": null,
|
|
78
|
+
"controllerImplementations": null,
|
|
79
|
+
"coreStyle": null,
|
|
80
|
+
"created": 1970-01-01T00:00:00.000Z,
|
|
81
|
+
"definition": {
|
|
82
|
+
"defaultPage": "Test Page",
|
|
83
|
+
"name": "Test App",
|
|
84
|
+
},
|
|
85
|
+
"deleted": null,
|
|
86
|
+
"demoMode": true,
|
|
87
|
+
"domain": null,
|
|
88
|
+
"emailHost": null,
|
|
89
|
+
"emailName": null,
|
|
90
|
+
"emailPassword": null,
|
|
91
|
+
"emailPort": 587,
|
|
92
|
+
"emailSecure": true,
|
|
93
|
+
"emailUser": null,
|
|
94
|
+
"enableSelfRegistration": true,
|
|
95
|
+
"enableUnsecuredServiceSecrets": false,
|
|
96
|
+
"googleAnalyticsID": null,
|
|
97
|
+
"icon": null,
|
|
98
|
+
"iconBackground": "#FFFFFF",
|
|
99
|
+
"id": 1,
|
|
100
|
+
"locked": "fullLock",
|
|
101
|
+
"maskableIcon": null,
|
|
102
|
+
"path": "updated-path",
|
|
103
|
+
"scimEnabled": false,
|
|
104
|
+
"scimToken": null,
|
|
105
|
+
"sentryDsn": null,
|
|
106
|
+
"sentryEnvironment": null,
|
|
107
|
+
"sharedStyle": null,
|
|
108
|
+
"showAppDefinition": true,
|
|
109
|
+
"showAppsembleLogin": true,
|
|
110
|
+
"showAppsembleOAuth2Login": true,
|
|
111
|
+
"sslCertificate": null,
|
|
112
|
+
"sslKey": null,
|
|
113
|
+
"template": true,
|
|
114
|
+
"updated": 1970-01-01T00:00:00.000Z,
|
|
115
|
+
"vapidPrivateKey": "b",
|
|
116
|
+
"vapidPublicKey": "a",
|
|
117
|
+
"visibility": "private",
|
|
118
|
+
}
|
|
119
|
+
`);
|
|
120
|
+
});
|
|
121
|
+
it.each `
|
|
122
|
+
key | from | to
|
|
123
|
+
${'demoMode'} | ${false} | ${true}
|
|
124
|
+
${'demoMode'} | ${true} | ${false}
|
|
125
|
+
${'enableSelfRegistration'} | ${false} | ${true}
|
|
126
|
+
${'enableSelfRegistration'} | ${true} | ${false}
|
|
127
|
+
${'iconBackground'} | ${''} | ${'#FFFFFF'}
|
|
128
|
+
${'path'} | ${'before'} | ${'after'}
|
|
129
|
+
${'locked'} | ${'unlocked'} | ${'studioLock'}
|
|
130
|
+
${'locked'} | ${'unlocked'} | ${'fullLock'}
|
|
131
|
+
${'locked'} | ${'fullLock'} | ${'unlocked'}
|
|
132
|
+
${'showAppDefinition'} | ${false} | ${true}
|
|
133
|
+
${'showAppDefinition'} | ${true} | ${false}
|
|
134
|
+
${'showAppsembleLogin'} | ${false} | ${true}
|
|
135
|
+
${'showAppsembleLogin'} | ${true} | ${false}
|
|
136
|
+
${'showAppsembleOAuth2Login'} | ${false} | ${true}
|
|
137
|
+
${'showAppsembleOAuth2Login'} | ${true} | ${false}
|
|
138
|
+
${'template'} | ${false} | ${true}
|
|
139
|
+
${'template'} | ${true} | ${false}
|
|
140
|
+
${'visibility'} | ${'public'} | ${'unlisted'}
|
|
141
|
+
${'visibility'} | ${'public'} | ${'private'}
|
|
142
|
+
${'visibility'} | ${'private'} | ${'public'}
|
|
143
|
+
`('should patch $key from `$from` to `$to`', async ({ from, key, to }) => {
|
|
144
|
+
const app = await App.create({
|
|
145
|
+
path: 'test-app',
|
|
146
|
+
definition: { name: 'Test App', defaultPage: 'Test Page' },
|
|
147
|
+
vapidPublicKey: 'a',
|
|
148
|
+
vapidPrivateKey: 'b',
|
|
149
|
+
visibility: 'public',
|
|
150
|
+
OrganizationId: organization.id,
|
|
151
|
+
[key]: from,
|
|
152
|
+
}, { raw: true });
|
|
153
|
+
await authorizeCLI('apps:write');
|
|
154
|
+
await patchApp({
|
|
155
|
+
[key]: to,
|
|
156
|
+
remote: testApp.defaults.baseURL,
|
|
157
|
+
id: app.id,
|
|
158
|
+
});
|
|
159
|
+
expect(app.dataValues[key]).toStrictEqual(from);
|
|
160
|
+
await app.reload();
|
|
161
|
+
expect(app.dataValues[key]).toStrictEqual(to);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
//# sourceMappingURL=app.test.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appsemble/cli",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.10",
|
|
4
4
|
"description": "The CLI for developing with Appsemble apps and blocks",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"app",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"test": "vitest"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@appsemble/node-utils": "0.27.
|
|
46
|
-
"@appsemble/types": "0.27.
|
|
47
|
-
"@appsemble/utils": "0.27.
|
|
45
|
+
"@appsemble/node-utils": "0.27.10",
|
|
46
|
+
"@appsemble/types": "0.27.10",
|
|
47
|
+
"@appsemble/utils": "0.27.10",
|
|
48
48
|
"@fortawesome/fontawesome-common-types": "^6.0.0",
|
|
49
49
|
"@koa/cors": "^4.0.0",
|
|
50
50
|
"@odata/parser": "^0.2.0",
|
|
@@ -87,7 +87,10 @@
|
|
|
87
87
|
"yargs": "^17.0.0"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
|
-
"@appsemble/types": "0.27.
|
|
90
|
+
"@appsemble/types": "0.27.10",
|
|
91
|
+
"@appsemble/server": "0.27.10",
|
|
92
|
+
"bcrypt": "^5.0.0",
|
|
93
|
+
"axios-test-instance": "^7.0.0",
|
|
91
94
|
"@types/concat-stream": "^2.0.0",
|
|
92
95
|
"@types/inquirer": "^9.0.0",
|
|
93
96
|
"@types/normalize-path": "^3.0.0",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
export async function getAppReadmes({ app }) {
|
|
3
|
+
const readme = await readFile(`${app.path}/readmes/${app.readmeUrl}`);
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
id: 0,
|
|
7
|
+
file: readme,
|
|
8
|
+
},
|
|
9
|
+
];
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=getAppReadmes.js.map
|
package/server/options/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyAppServiceSecrets, checkRole, createAppAsset, createAppResourcesWithAssets, createSettings, createTheme, deleteAppAsset, deleteAppResource, email, getApp, getAppAssets, getAppBlockStyles, getAppDetails, getAppIcon, getAppMembers, getAppMessages, getAppResource, getAppResources, getAppScreenshots, getAppStyles, getAppTeams, getAppUrl, getAppUserInfo, getBlockAsset, getBlockMessages, getBlocksAssetsPaths, getCsp, getDbUpdated, getHost, getTheme, parseQuery, reloadUser, sendNotifications, updateAppResource, verifyResourceActionPermission, } from './index.js';
|
|
1
|
+
import { applyAppServiceSecrets, checkRole, createAppAsset, createAppResourcesWithAssets, createSettings, createTheme, deleteAppAsset, deleteAppResource, email, getApp, getAppAssets, getAppBlockStyles, getAppDetails, getAppIcon, getAppMembers, getAppMessages, getAppReadmes, getAppResource, getAppResources, getAppScreenshots, getAppStyles, getAppTeams, getAppUrl, getAppUserInfo, getBlockAsset, getBlockMessages, getBlocksAssetsPaths, getCsp, getDbUpdated, getHost, getTheme, parseQuery, reloadUser, sendNotifications, updateAppResource, verifyResourceActionPermission, } from './index.js';
|
|
2
2
|
export const options = {
|
|
3
3
|
applyAppServiceSecrets,
|
|
4
4
|
checkRole,
|
|
@@ -16,6 +16,7 @@ export const options = {
|
|
|
16
16
|
getAppIcon,
|
|
17
17
|
getAppMembers,
|
|
18
18
|
getAppMessages,
|
|
19
|
+
getAppReadmes,
|
|
19
20
|
getAppResource,
|
|
20
21
|
getAppResources,
|
|
21
22
|
getAppScreenshots,
|
|
@@ -6,7 +6,7 @@ pages:
|
|
|
6
6
|
- name: Example Page A
|
|
7
7
|
blocks:
|
|
8
8
|
- type: action-button
|
|
9
|
-
version: 0.27.
|
|
9
|
+
version: 0.27.10
|
|
10
10
|
parameters:
|
|
11
11
|
icon: arrow-right
|
|
12
12
|
actions:
|
|
@@ -17,7 +17,7 @@ pages:
|
|
|
17
17
|
- name: Example Page B
|
|
18
18
|
blocks:
|
|
19
19
|
- type: action-button
|
|
20
|
-
version: 0.27.
|
|
20
|
+
version: 0.27.10
|
|
21
21
|
parameters:
|
|
22
22
|
icon: arrow-left
|
|
23
23
|
actions:
|