@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 CHANGED
@@ -1,9 +1,9 @@
1
- # ![](https://gitlab.com/appsemble/appsemble/-/raw/0.27.8/config/assets/logo.svg) Appsemble CLI
1
+ # ![](https://gitlab.com/appsemble/appsemble/-/raw/0.27.10/config/assets/logo.svg) Appsemble CLI
2
2
 
3
3
  > Manage apps and blocks from the command line.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@appsemble/cli)](https://www.npmjs.com/package/@appsemble/cli)
6
- [![GitLab CI](https://gitlab.com/appsemble/appsemble/badges/0.27.8/pipeline.svg)](https://gitlab.com/appsemble/appsemble/-/releases/0.27.8)
6
+ [![GitLab CI](https://gitlab.com/appsemble/appsemble/badges/0.27.10/pipeline.svg)](https://gitlab.com/appsemble/appsemble/-/releases/0.27.10)
7
7
  [![Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](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 app are `resources`, `assets` and `path`. Resources is used to specify
173
- whether to include the resources in the exported file, the same can be applied to assets and path is
174
- the path of the folder where you want to put your downloaded file. The default path is `./apps`, and
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.8/LICENSE.md) ©
326
+ [LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.27.10/LICENSE.md) ©
309
327
  [Appsemble](https://appsemble.com)
@@ -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
@@ -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.',
@@ -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 [, , , appsembleApp] = await traverseAppDirectory(appPath, 'development', new FormData());
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 AppsembleContext, type AppsembleRC, type AppVisibility } from '@appsemble/types';
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, context: string, formData: FormData): Promise<[AppsembleContext, AppsembleRC, string, App]>;
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({ assets, clientCredentials, context, force, path, resources, ...options }: UpdateAppParams): Promise<void>;
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({ assets, clientCredentials, context, dryRun, modifyContext, path, resources, ...options }: PublishAppParams): Promise<void>;
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, readFile, rm, stat } from 'node:fs/promises';
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, getSupportedLanguages, logger, opendirSafe, readData, writeData, } from '@appsemble/node-utils';
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, context, formData) {
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
- const supportedLanguages = await getSupportedLanguages();
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
- logger.warn(`Found unused file ${filepath}`);
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 [discoveredContext, rc, yaml, gatheredData];
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({ assets, clientCredentials, context, force, path, resources, ...options }) {
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
- let appsembleContext;
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', context))) {
435
- await applyAppVariant(path, context);
436
- appVariantPath = join(dirname(path), `${basename(path)}-${context}`);
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 ${context} is not defined. Using default app.`);
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
- [appsembleContext] = await traverseAppDirectory(appVariantPath, context, formData);
447
- }
448
- const remote = (_a = appsembleContext.remote) !== null && _a !== void 0 ? _a : options.remote;
449
- const id = (_b = appsembleContext.id) !== null && _b !== void 0 ? _b : options.id;
450
- const template = (_d = (_c = appsembleContext.template) !== null && _c !== void 0 ? _c : options.template) !== null && _d !== void 0 ? _d : false;
451
- const assetsClonable = (_f = (_e = appsembleContext.assetsClonable) !== null && _e !== void 0 ? _e : options.assetsClonable) !== null && _f !== void 0 ? _f : false;
452
- const demoMode = (_h = (_g = appsembleContext.demoMode) !== null && _g !== void 0 ? _g : options.demoMode) !== null && _h !== void 0 ? _h : false;
453
- const visibility = (_j = appsembleContext.visibility) !== null && _j !== void 0 ? _j : options.visibility;
454
- const iconBackground = (_k = appsembleContext.iconBackground) !== null && _k !== void 0 ? _k : options.iconBackground;
455
- const icon = (_l = options.icon) !== null && _l !== void 0 ? _l : appsembleContext.icon;
456
- const maskableIcon = (_m = options.maskableIcon) !== null && _m !== void 0 ? _m : appsembleContext.maskableIcon;
457
- const sentryDsn = (_o = appsembleContext.sentryDsn) !== null && _o !== void 0 ? _o : options.sentryDsn;
458
- const sentryEnvironment = (_p = appsembleContext.sentryEnvironment) !== null && _p !== void 0 ? _p : options.sentryEnvironment;
459
- const googleAnalyticsId = (_q = appsembleContext.googleAnalyticsId) !== null && _q !== void 0 ? _q : options.googleAnalyticsId;
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 ${(_r = realIcon.path) !== null && _r !== void 0 ? _r : 'stdin'}`);
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 ${(_s = realIcon.path) !== null && _s !== void 0 ? _s : 'stdin'}`);
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 (((_u = (_t = error.response) === null || _t === void 0 ? void 0 : _t.data) === null || _u === void 0 ? void 0 : _u.message) !== 'App validation failed') {
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
- logger.error(error.response.data);
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({ assets, clientCredentials, context, dryRun, modifyContext, path, resources, ...options }) {
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
- let appsembleContext;
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', context))) {
565
- await applyAppVariant(path, context);
566
- appVariantPath = join(dirname(path), `${basename(path)}-${context}`);
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 ${context} is not defined. Using default app.`);
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
- [appsembleContext, rc, yaml] = await traverseAppDirectory(appVariantPath, context, formData);
614
+ [rc, yaml] = await traverseAppDirectory(appVariantPath, formData);
579
615
  filename = join(filename, 'app-definition.yaml');
580
616
  }
581
- const remote = (_a = appsembleContext.remote) !== null && _a !== void 0 ? _a : options.remote;
582
- const organizationId = (_b = appsembleContext.organization) !== null && _b !== void 0 ? _b : options.organization;
583
- const template = (_d = (_c = appsembleContext.template) !== null && _c !== void 0 ? _c : options.template) !== null && _d !== void 0 ? _d : false;
584
- const assetsClonable = (_f = (_e = appsembleContext.assetsClonable) !== null && _e !== void 0 ? _e : options.assetsClonable) !== null && _f !== void 0 ? _f : false;
585
- const demoMode = (_h = (_g = appsembleContext.demoMode) !== null && _g !== void 0 ? _g : options.demoMode) !== null && _h !== void 0 ? _h : false;
586
- const visibility = (_j = appsembleContext.visibility) !== null && _j !== void 0 ? _j : options.visibility;
587
- const iconBackground = (_k = appsembleContext.iconBackground) !== null && _k !== void 0 ? _k : options.iconBackground;
588
- const icon = (_l = options.icon) !== null && _l !== void 0 ? _l : appsembleContext.icon;
589
- const maskableIcon = (_m = options.maskableIcon) !== null && _m !== void 0 ? _m : appsembleContext.maskableIcon;
590
- const sentryDsn = (_o = appsembleContext.sentryDsn) !== null && _o !== void 0 ? _o : options.sentryDsn;
591
- const sentryEnvironment = (_p = appsembleContext.sentryEnvironment) !== null && _p !== void 0 ? _p : options.sentryEnvironment;
592
- const googleAnalyticsId = (_q = appsembleContext.googleAnalyticsId) !== null && _q !== void 0 ? _q : options.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 ${(_r = realIcon.path) !== null && _r !== void 0 ? _r : 'stdin'}`);
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 ${(_s = realIcon.path) !== null && _s !== void 0 ? _s : 'stdin'}`);
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 (((_u = (_t = error.response) === null || _t === void 0 ? void 0 : _t.data) === null || _u === void 0 ? void 0 : _u.message) !== 'App validation failed') {
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 {};
@@ -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.8",
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.8",
46
- "@appsemble/types": "0.27.8",
47
- "@appsemble/utils": "0.27.8",
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.8",
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,2 @@
1
+ import { type AppReadme as AppReadmeInterface, type GetAppSubEntityParams } from '@appsemble/node-utils';
2
+ export declare function getAppReadmes({ app }: GetAppSubEntityParams): Promise<AppReadmeInterface[]>;
@@ -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
@@ -33,3 +33,4 @@ export * from './applyAppServiceSecrets.js';
33
33
  export * from './getAppMembers.js';
34
34
  export * from './getAppTeams.js';
35
35
  export * from './getAppUserInfo.js';
36
+ export * from './getAppReadmes.js';
@@ -33,4 +33,5 @@ export * from './applyAppServiceSecrets.js';
33
33
  export * from './getAppMembers.js';
34
34
  export * from './getAppTeams.js';
35
35
  export * from './getAppUserInfo.js';
36
+ export * from './getAppReadmes.js';
36
37
  //# sourceMappingURL=index.js.map
@@ -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.8
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.8
20
+ version: 0.27.10
21
21
  parameters:
22
22
  icon: arrow-left
23
23
  actions:
@@ -2,7 +2,7 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@appsemble/sdk": "0.27.8",
5
+ "@appsemble/sdk": "0.27.10",
6
6
  "mini-jsx": "^4.0.0"
7
7
  }
8
8
  }
@@ -2,8 +2,8 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@appsemble/preact": "0.27.8",
6
- "@appsemble/sdk": "0.27.8",
5
+ "@appsemble/preact": "0.27.10",
6
+ "@appsemble/sdk": "0.27.10",
7
7
  "preact": "^10.0.0"
8
8
  }
9
9
  }
@@ -2,6 +2,6 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@appsemble/sdk": "0.27.8"
5
+ "@appsemble/sdk": "0.27.10"
6
6
  }
7
7
  }