@appsemble/cli 0.27.11 → 0.27.12

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.11/config/assets/logo.svg) Appsemble CLI
1
+ # ![](https://gitlab.com/appsemble/appsemble/-/raw/0.27.12/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.11/pipeline.svg)](https://gitlab.com/appsemble/appsemble/-/releases/0.27.11)
6
+ [![GitLab CI](https://gitlab.com/appsemble/appsemble/badges/0.27.12/pipeline.svg)](https://gitlab.com/appsemble/appsemble/-/releases/0.27.12)
7
7
  [![Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io)
8
8
 
9
9
  ## Table of Contents
@@ -323,5 +323,5 @@ appsemble run-cronjobs --interval 30
323
323
 
324
324
  ## License
325
325
 
326
- [LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.27.11/LICENSE.md) ©
326
+ [LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.27.12/LICENSE.md) ©
327
327
  [Appsemble](https://appsemble.com)
package/commands/serve.js CHANGED
@@ -9,7 +9,7 @@ import axios from 'axios';
9
9
  import csvToJson from 'csvtojson';
10
10
  import FormData from 'form-data';
11
11
  import globalCacheDir from 'global-cache-dir';
12
- import { traverseAppDirectory } from '../lib/app.js';
12
+ import { parseValues, traverseAppDirectory } from '../lib/app.js';
13
13
  import { buildBlock } from '../lib/block.js';
14
14
  import { getProjectBuildConfig, getProjectWebpackConfig } from '../lib/config.js';
15
15
  import { createApiServer, createStaticServer } from '../lib/createServers.js';
@@ -207,6 +207,24 @@ export async function handler(argv) {
207
207
  const { name } = parse(stat.name);
208
208
  await Resource.bulkCreate(resources, name, true);
209
209
  }, { allowMissing: true });
210
+ const appVariables = [];
211
+ const configPath = join(appPath, 'config');
212
+ if (existsSync(configPath)) {
213
+ const variablesPath = join(configPath, 'variables.json');
214
+ if (existsSync(variablesPath)) {
215
+ const [readAppVariables] = (await readData(variablesPath));
216
+ if (readAppVariables.length) {
217
+ for (const appVariable of readAppVariables) {
218
+ const { name, value } = appVariable;
219
+ const [parsedValues, missingValues] = parseValues('variable', name, [{ value }]);
220
+ if (missingValues) {
221
+ continue;
222
+ }
223
+ appVariables.push({ name, ...parsedValues });
224
+ }
225
+ }
226
+ }
227
+ }
210
228
  const stubbedApp = {
211
229
  ...appsembleApp,
212
230
  id: appId,
@@ -221,6 +239,7 @@ export async function handler(argv) {
221
239
  appsembleApp: stubbedApp,
222
240
  appBlocks,
223
241
  appMessages,
242
+ appVariables,
224
243
  ...(appSecurity
225
244
  ? {
226
245
  appMembers,
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 AppLock, type AppsembleContext, type AppsembleRC, type AppVisibility } from '@appsemble/types';
4
+ import { type App, type AppLock, type AppsembleContext, type AppsembleRC, type AppVisibility, type ValueFromDefinition, type ValueFromProcess } from '@appsemble/types';
5
5
  import FormData from 'form-data';
6
6
  /**
7
7
  * Traverses an app directory and appends the files it finds to the given FormData object.
@@ -33,6 +33,8 @@ export declare function exportAppAsZip(appId: number, assets: boolean, resources
33
33
  export declare function uploadMessages(path: string, appId: number, remote: string, force: boolean): Promise<void>;
34
34
  export declare function publishSeedResources(path: string, app: App, remote: string): Promise<void>;
35
35
  export declare function publishSeedAssets(path: string, app: App, remote: string, assetsClonable: boolean): Promise<void>;
36
+ export declare function parseValues(type: string, name: string, valuesToParse: Record<string, ValueFromDefinition>[]): [Record<string, ValueFromProcess>, boolean];
37
+ export declare function publishAppConfig(path: string, app: App, remote: string): Promise<void>;
36
38
  /**
37
39
  * @param path The path to the app directory.
38
40
  * @param languages A list of languages for which translations should be added in addition to the
package/lib/app.js CHANGED
@@ -318,6 +318,182 @@ export async function publishSeedAssets(path, app, remote, assetsClonable) {
318
318
  logger.warn(`Missing assets directory in ${path}. Skipping...`);
319
319
  }
320
320
  }
321
+ function parseValueFromDefinition(value) {
322
+ let parsed = value;
323
+ if (typeof parsed === 'string' && parsed.includes('{{') && parsed.includes('}}')) {
324
+ parsed = parsed.replaceAll(/{{\s*([^\s{}]+)\s*}}/g, (match, variable) => process.env[variable.trim()] || match);
325
+ }
326
+ return parsed;
327
+ }
328
+ export function parseValues(type, name, valuesToParse) {
329
+ const parsedValues = {};
330
+ let missingValues = false;
331
+ for (const valueToParse of valuesToParse) {
332
+ const [key, value] = Object.entries(valueToParse)[0];
333
+ parsedValues[key] = parseValueFromDefinition(value);
334
+ if (!parsedValues[key]) {
335
+ logger.error(`Missing ${key} value for ${type} ${name}`);
336
+ missingValues = true;
337
+ }
338
+ }
339
+ return [parsedValues, missingValues];
340
+ }
341
+ export async function publishAppConfig(path, app, remote) {
342
+ const configPath = join(path, 'config');
343
+ logger.info(`Publishing app config from ${configPath}`);
344
+ if (existsSync(configPath)) {
345
+ try {
346
+ logger.info(`Deleting existing variables from app ${app.id}`);
347
+ await axios.delete(`/api/apps/${app.id}/variables`, { baseURL: remote });
348
+ const appVariablesPath = join(configPath, 'variables.json');
349
+ if (existsSync(appVariablesPath)) {
350
+ const [appVariables] = (await readData(appVariablesPath));
351
+ if (appVariables.length) {
352
+ logger.info(`Publishing ${appVariables.length} app variable(s)`);
353
+ for (const appVariable of appVariables) {
354
+ const { name, value } = appVariable;
355
+ const [parsedValues, missingValues] = parseValues('variable', name, [{ value }]);
356
+ if (missingValues) {
357
+ continue;
358
+ }
359
+ await axios.post(`/api/apps/${app.id}/variables`, { name, ...parsedValues }, { baseURL: remote });
360
+ }
361
+ }
362
+ }
363
+ }
364
+ catch (error) {
365
+ logger.error('Something went wrong when creating app variables:');
366
+ logger.error(error);
367
+ }
368
+ try {
369
+ logger.info(`Deleting existing service secrets from app ${app.id}`);
370
+ await axios.delete(`/api/apps/${app.id}/secrets/service`, { baseURL: remote });
371
+ const serviceSecretsPath = join(configPath, 'secrets', 'service.json');
372
+ if (existsSync(serviceSecretsPath)) {
373
+ const [serviceSecrets] = (await readData(serviceSecretsPath));
374
+ if (serviceSecrets.length) {
375
+ logger.info(`Publishing ${serviceSecrets.length} app service secret(s)`);
376
+ for (const serviceSecret of serviceSecrets) {
377
+ const { identifier, name, secret, urlPatterns, ...rest } = serviceSecret;
378
+ const [parsedValues, missingValues] = parseValues('service secret', name, [
379
+ { secret },
380
+ { identifier },
381
+ { urlPatterns },
382
+ ]);
383
+ if (missingValues) {
384
+ continue;
385
+ }
386
+ await axios.post(`/api/apps/${app.id}/secrets/service`, { name, ...parsedValues, ...rest }, { baseURL: remote });
387
+ }
388
+ }
389
+ }
390
+ }
391
+ catch (error) {
392
+ logger.error('Something went wrong when creating app service secrets:');
393
+ logger.error(error);
394
+ }
395
+ try {
396
+ logger.info(`Deleting existing saml secrets from app ${app.id}`);
397
+ await axios.delete(`/api/apps/${app.id}/secrets/saml`, { baseURL: remote });
398
+ const samlSecretsPath = join(configPath, 'secrets', 'saml.json');
399
+ if (existsSync(samlSecretsPath)) {
400
+ const [samlSecrets] = (await readData(samlSecretsPath));
401
+ if (samlSecrets.length) {
402
+ logger.info(`Publishing ${samlSecrets.length} app saml secret(s)`);
403
+ for (const samlSecret of samlSecrets) {
404
+ const { entityId, idpCertificate, name, ssoUrl, ...rest } = samlSecret;
405
+ const [parsedValues, missingValues] = parseValues('saml secret', name, [
406
+ { entityId },
407
+ { idpCertificate },
408
+ { ssoUrl },
409
+ ]);
410
+ if (missingValues) {
411
+ continue;
412
+ }
413
+ await axios.post(`/api/apps/${app.id}/secrets/saml`, { name, ...parsedValues, ...rest }, { baseURL: remote });
414
+ }
415
+ }
416
+ }
417
+ }
418
+ catch (error) {
419
+ logger.error('Something went wrong when creating app saml secrets:');
420
+ logger.error(error);
421
+ }
422
+ try {
423
+ logger.info(`Deleting existing oauth2 secrets from app ${app.id}`);
424
+ await axios.delete(`/api/apps/${app.id}/secrets/oauth2`, { baseURL: remote });
425
+ const oauth2SecretsPath = join(configPath, 'secrets', 'oauth2.json');
426
+ if (existsSync(oauth2SecretsPath)) {
427
+ const [oauth2Secrets] = (await readData(oauth2SecretsPath));
428
+ if (oauth2Secrets.length) {
429
+ logger.info(`Publishing ${oauth2Secrets.length} app oauth2 secrets`);
430
+ for (const oauth2Secret of oauth2Secrets) {
431
+ const { authorizationUrl, clientId, clientSecret, name, tokenUrl, userInfoUrl, ...rest } = oauth2Secret;
432
+ const [parsedValues, missingValues] = parseValues('oauth2 secret', name, [
433
+ { authorizationUrl },
434
+ { tokenUrl },
435
+ { userInfoUrl },
436
+ { clientId },
437
+ { clientSecret },
438
+ ]);
439
+ if (missingValues) {
440
+ continue;
441
+ }
442
+ await axios.post(`/api/apps/${app.id}/secrets/oauth2`, { name, ...parsedValues, ...rest }, { baseURL: remote });
443
+ }
444
+ }
445
+ }
446
+ }
447
+ catch (error) {
448
+ logger.error('Something went wrong when creating app oauth2 secrets:');
449
+ logger.error(error);
450
+ }
451
+ try {
452
+ const sslSecretPath = join(configPath, 'secrets', 'ssl.json');
453
+ if (existsSync(sslSecretPath)) {
454
+ const [sslSecret] = (await readData(sslSecretPath));
455
+ if (sslSecret) {
456
+ logger.info('Publishing app ssl secret');
457
+ const { certificate, key } = sslSecret;
458
+ const [parsedValues, missingValues] = parseValues('ssl secret', '', [
459
+ { certificate },
460
+ { key },
461
+ ]);
462
+ if (missingValues) {
463
+ return;
464
+ }
465
+ await axios.put(`/api/apps/${app.id}/secrets/ssl`, { ...parsedValues }, { baseURL: remote });
466
+ }
467
+ }
468
+ }
469
+ catch (error) {
470
+ logger.error('Something went wrong when creating app ssl secret:');
471
+ logger.error(error);
472
+ }
473
+ try {
474
+ const scimSecretPath = join(configPath, 'secrets', 'scim.json');
475
+ if (existsSync(scimSecretPath)) {
476
+ const [scimSecret] = (await readData(scimSecretPath));
477
+ if (scimSecret) {
478
+ logger.info('Publishing app scim secret');
479
+ const { enabled, token } = scimSecret;
480
+ const [parsedValues, missingValues] = parseValues('scim secret', '', [{ token }]);
481
+ if (missingValues) {
482
+ return;
483
+ }
484
+ await axios.patch(`/api/apps/${app.id}/secrets/scim`, { enabled, ...parsedValues }, { baseURL: remote });
485
+ }
486
+ }
487
+ }
488
+ catch (error) {
489
+ logger.error('Something went wrong when creating app scim secret:');
490
+ logger.error(error);
491
+ }
492
+ }
493
+ else {
494
+ logger.warn(`Missing config directory in ${path}. Skipping...`);
495
+ }
496
+ }
321
497
  /**
322
498
  * @param path The path to the app directory.
323
499
  * @param languages A list of languages for which translations should be added in addition to the
@@ -616,6 +792,7 @@ export async function publishApp({ clientCredentials, context, dryRun, modifyCon
616
792
  // After uploading the app, upload block styles, messages if they are available
617
793
  await traverseBlockThemes(appVariantPath, data.id, remote, false);
618
794
  await uploadMessages(appVariantPath, data.id, remote, false);
795
+ await publishAppConfig(appVariantPath, data, remote);
619
796
  // After uploading the app, publish seed resources and assets if they are available
620
797
  if (assets) {
621
798
  await publishSeedAssets(appVariantPath, data, remote, assetsClonable);
@@ -776,6 +953,7 @@ export async function updateApp({ clientCredentials, context, force, path, ...op
776
953
  // After uploading the app, upload block styles and messages if they are available
777
954
  await traverseBlockThemes(appVariantPath, data.id, remote, force);
778
955
  await uploadMessages(appVariantPath, data.id, remote, force);
956
+ await publishAppConfig(appVariantPath, data, remote);
779
957
  // After updating the app, publish seed resources and assets if they are available
780
958
  if (assets && (data.locked !== 'fullLock' || force)) {
781
959
  await publishSeedAssets(appVariantPath, data, remote, assetsClonable);
package/lib/app.test.js CHANGED
@@ -6,7 +6,7 @@ import { hash } from 'bcrypt';
6
6
  import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
7
7
  import { patchApp, publishApp, updateApp } from './app.js';
8
8
  import { initAxios } from './initAxios.js';
9
- const { App, AppBlockStyle, AppCollection, AppCollectionApp, AppMessages, AppScreenshot, Asset, BlockVersion, Organization, OrganizationMember, Resource, } = models;
9
+ const { App, AppBlockStyle, AppCollection, AppCollectionApp, AppMessages, AppOAuth2Secret, AppSamlSecret, AppScreenshot, AppServiceSecret, AppVariable, Asset, BlockVersion, Organization, OrganizationMember, Resource, } = models;
10
10
  const argv = { host: 'http://localhost', secret: 'test', aesSecret: 'testSecret' };
11
11
  let user;
12
12
  let organization;
@@ -633,6 +633,142 @@ describe('publishApp', () => {
633
633
  await readFixture('apps/test/variants/tux/assets/small-tux.png'),
634
634
  ]);
635
635
  });
636
+ it('should publish app variables and secrets', async () => {
637
+ vi.useRealTimers();
638
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
639
+ process.env.TEST_VARIABLE_1 = 'test-variable-1';
640
+ process.env.TEST_VARIABLE_2 = 'test-variable-2';
641
+ process.env.TEST_SECRET = 'test-secret';
642
+ process.env.TEST_SERVICE_URL_PATTERNS = 'http://localhost:1234';
643
+ process.env.TEST_SERVICE_IDENTIFIER = 'Authorization';
644
+ process.env.TEST_SERVICE_SECRET = 'secret';
645
+ process.env.TEST_SAML_IDP_CERTIFICATE = 'certificate';
646
+ process.env.TEST_SAML_ENTITY_ID = 'http://localhost:1234';
647
+ process.env.TEST_SAML_SSO_URL = 'http://localhost:1234';
648
+ process.env.TEST_OAUTH2_AUTHORIZATION_URL = 'http://localhost:1234';
649
+ process.env.TEST_OAUTH2_TOKEN_URL = 'http://localhost:1234';
650
+ process.env.TEST_OAUTH2_USER_INFO_URL = 'http://localhost:1234';
651
+ process.env.TEST_OAUTH2_CLIENT_ID = 'id';
652
+ process.env.TEST_OAUTH2_CLIENT_SECRET = 'secret';
653
+ process.env.TEST_SCIM_TOKEN = 'token';
654
+ process.env.TEST_SSL_KEY = 'key';
655
+ process.env.TEST_SSL_CERTIFICATE = 'certificate';
656
+ await publishApp({
657
+ path: resolveFixture('apps/test'),
658
+ organization: organization.id,
659
+ remote: testApp.defaults.baseURL,
660
+ clientCredentials,
661
+ visibility: 'unlisted',
662
+ iconBackground: '#ffffff',
663
+ });
664
+ vi.useFakeTimers();
665
+ const app = await App.findOne({
666
+ attributes: ['scimEnabled', 'scimToken', 'sslKey', 'sslCertificate'],
667
+ where: {
668
+ id: 1,
669
+ },
670
+ });
671
+ expect({
672
+ scimEnabled: app.scimEnabled,
673
+ scimToken: app.scimToken,
674
+ sslKey: app.sslKey,
675
+ sslCertificate: app.sslCertificate,
676
+ }).toStrictEqual({
677
+ scimEnabled: true,
678
+ scimToken: expect.any(Buffer),
679
+ sslKey: 'key',
680
+ sslCertificate: 'certificate',
681
+ });
682
+ const appVariables = await AppVariable.findAll({
683
+ attributes: ['name', 'value'],
684
+ where: {
685
+ AppId: 1,
686
+ },
687
+ });
688
+ expect(appVariables[0].toJSON()).toStrictEqual({
689
+ name: 'test-variable-inline',
690
+ value: 'inline',
691
+ });
692
+ expect(appVariables[1].toJSON()).toStrictEqual({
693
+ name: 'test-variable',
694
+ value: 'test-variable-1.test-variable-2',
695
+ });
696
+ const appServiceSecret = await AppServiceSecret.findOne({
697
+ attributes: ['name', 'authenticationMethod', 'urlPatterns', 'identifier', 'secret'],
698
+ where: {
699
+ AppId: 1,
700
+ },
701
+ });
702
+ expect(appServiceSecret.toJSON()).toStrictEqual({
703
+ name: 'test-service-secret',
704
+ authenticationMethod: 'custom-header',
705
+ urlPatterns: 'http://localhost:1234',
706
+ identifier: 'Authorization',
707
+ secret: expect.any(Buffer),
708
+ });
709
+ const appSamlSecret = await AppSamlSecret.findOne({
710
+ attributes: [
711
+ 'name',
712
+ 'icon',
713
+ 'nameAttribute',
714
+ 'emailAttribute',
715
+ 'idpCertificate',
716
+ 'entityId',
717
+ 'ssoUrl',
718
+ ],
719
+ where: {
720
+ AppId: 1,
721
+ },
722
+ });
723
+ expect(appSamlSecret.toJSON()).toStrictEqual({
724
+ emailAttribute: 'email',
725
+ entityId: 'http://localhost:1234',
726
+ icon: 'redhat',
727
+ id: 1,
728
+ idpCertificate: 'certificate',
729
+ name: 'test-saml-secret',
730
+ nameAttribute: 'name',
731
+ ssoUrl: 'http://localhost:1234',
732
+ spCertificate: expect.any(String),
733
+ });
734
+ const appOauth2Secret = await AppOAuth2Secret.findOne({
735
+ attributes: [
736
+ 'name',
737
+ 'icon',
738
+ 'scope',
739
+ 'remapper',
740
+ 'authorizationUrl',
741
+ 'tokenUrl',
742
+ 'userInfoUrl',
743
+ 'clientId',
744
+ 'clientSecret',
745
+ ],
746
+ where: {
747
+ AppId: 1,
748
+ },
749
+ });
750
+ expect({
751
+ authorizationUrl: appOauth2Secret.authorizationUrl,
752
+ clientId: appOauth2Secret.clientId,
753
+ clientSecret: appOauth2Secret.clientSecret,
754
+ icon: appOauth2Secret.icon,
755
+ name: appOauth2Secret.name,
756
+ remapper: appOauth2Secret.remapper,
757
+ scope: appOauth2Secret.scope,
758
+ tokenUrl: appOauth2Secret.tokenUrl,
759
+ userInfoUrl: appOauth2Secret.userInfoUrl,
760
+ }).toStrictEqual({
761
+ authorizationUrl: 'http://localhost:1234',
762
+ clientId: 'id',
763
+ clientSecret: 'secret',
764
+ icon: 'redhat',
765
+ name: 'test-oauth2-secret',
766
+ remapper: [{ prop: 'email' }],
767
+ scope: 'email openid profile',
768
+ tokenUrl: 'http://localhost:1234',
769
+ userInfoUrl: 'http://localhost:1234',
770
+ });
771
+ });
636
772
  });
637
773
  describe('updateApp', () => {
638
774
  let app;
@@ -1226,6 +1362,143 @@ describe('updateApp', () => {
1226
1362
  await readFixture('apps/test/variants/tux/assets/small-tux.png'),
1227
1363
  ]);
1228
1364
  });
1365
+ it('should update app variables and secrets', async () => {
1366
+ vi.useRealTimers();
1367
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
1368
+ process.env.TEST_VARIABLE_1 = 'test-variable-1';
1369
+ process.env.TEST_VARIABLE_2 = 'test-variable-2';
1370
+ process.env.TEST_SECRET = 'test-secret';
1371
+ process.env.TEST_SERVICE_URL_PATTERNS = 'http://localhost:1234';
1372
+ process.env.TEST_SERVICE_IDENTIFIER = 'Authorization';
1373
+ process.env.TEST_SERVICE_SECRET = 'secret';
1374
+ process.env.TEST_SAML_IDP_CERTIFICATE = 'certificate';
1375
+ process.env.TEST_SAML_ENTITY_ID = 'http://localhost:1234';
1376
+ process.env.TEST_SAML_SSO_URL = 'http://localhost:1234';
1377
+ process.env.TEST_OAUTH2_AUTHORIZATION_URL = 'http://localhost:1234';
1378
+ process.env.TEST_OAUTH2_TOKEN_URL = 'http://localhost:1234';
1379
+ process.env.TEST_OAUTH2_USER_INFO_URL = 'http://localhost:1234';
1380
+ process.env.TEST_OAUTH2_CLIENT_ID = 'id';
1381
+ process.env.TEST_OAUTH2_CLIENT_SECRET = 'secret';
1382
+ process.env.TEST_SCIM_TOKEN = 'token';
1383
+ process.env.TEST_SSL_KEY = 'key';
1384
+ process.env.TEST_SSL_CERTIFICATE = 'certificate';
1385
+ await updateApp({
1386
+ id: 1,
1387
+ path: resolveFixture('apps/test'),
1388
+ remote: testApp.defaults.baseURL,
1389
+ clientCredentials,
1390
+ visibility: 'unlisted',
1391
+ iconBackground: '#ffffff',
1392
+ force: true,
1393
+ });
1394
+ vi.useFakeTimers();
1395
+ const updatedApp = await App.findOne({
1396
+ attributes: ['scimEnabled', 'scimToken', 'sslKey', 'sslCertificate'],
1397
+ where: {
1398
+ id: 1,
1399
+ },
1400
+ });
1401
+ expect({
1402
+ scimEnabled: updatedApp.scimEnabled,
1403
+ scimToken: updatedApp.scimToken,
1404
+ sslKey: updatedApp.sslKey,
1405
+ sslCertificate: updatedApp.sslCertificate,
1406
+ }).toStrictEqual({
1407
+ scimEnabled: true,
1408
+ scimToken: expect.any(Buffer),
1409
+ sslKey: 'key',
1410
+ sslCertificate: 'certificate',
1411
+ });
1412
+ const appVariables = await AppVariable.findAll({
1413
+ attributes: ['name', 'value'],
1414
+ where: {
1415
+ AppId: 1,
1416
+ },
1417
+ });
1418
+ expect(appVariables[0].toJSON()).toStrictEqual({
1419
+ name: 'test-variable-inline',
1420
+ value: 'inline',
1421
+ });
1422
+ expect(appVariables[1].toJSON()).toStrictEqual({
1423
+ name: 'test-variable',
1424
+ value: 'test-variable-1.test-variable-2',
1425
+ });
1426
+ const appServiceSecret = await AppServiceSecret.findOne({
1427
+ attributes: ['name', 'authenticationMethod', 'urlPatterns', 'identifier', 'secret'],
1428
+ where: {
1429
+ AppId: 1,
1430
+ },
1431
+ });
1432
+ expect(appServiceSecret.toJSON()).toStrictEqual({
1433
+ name: 'test-service-secret',
1434
+ authenticationMethod: 'custom-header',
1435
+ urlPatterns: 'http://localhost:1234',
1436
+ identifier: 'Authorization',
1437
+ secret: expect.any(Buffer),
1438
+ });
1439
+ const appSamlSecret = await AppSamlSecret.findOne({
1440
+ attributes: [
1441
+ 'name',
1442
+ 'icon',
1443
+ 'nameAttribute',
1444
+ 'emailAttribute',
1445
+ 'idpCertificate',
1446
+ 'entityId',
1447
+ 'ssoUrl',
1448
+ ],
1449
+ where: {
1450
+ AppId: 1,
1451
+ },
1452
+ });
1453
+ expect(appSamlSecret.toJSON()).toStrictEqual({
1454
+ emailAttribute: 'email',
1455
+ entityId: 'http://localhost:1234',
1456
+ icon: 'redhat',
1457
+ id: 1,
1458
+ idpCertificate: 'certificate',
1459
+ name: 'test-saml-secret',
1460
+ nameAttribute: 'name',
1461
+ ssoUrl: 'http://localhost:1234',
1462
+ spCertificate: expect.any(String),
1463
+ });
1464
+ const appOauth2Secret = await AppOAuth2Secret.findOne({
1465
+ attributes: [
1466
+ 'name',
1467
+ 'icon',
1468
+ 'scope',
1469
+ 'remapper',
1470
+ 'authorizationUrl',
1471
+ 'tokenUrl',
1472
+ 'userInfoUrl',
1473
+ 'clientId',
1474
+ 'clientSecret',
1475
+ ],
1476
+ where: {
1477
+ AppId: 1,
1478
+ },
1479
+ });
1480
+ expect({
1481
+ authorizationUrl: appOauth2Secret.authorizationUrl,
1482
+ clientId: appOauth2Secret.clientId,
1483
+ clientSecret: appOauth2Secret.clientSecret,
1484
+ icon: appOauth2Secret.icon,
1485
+ name: appOauth2Secret.name,
1486
+ remapper: appOauth2Secret.remapper,
1487
+ scope: appOauth2Secret.scope,
1488
+ tokenUrl: appOauth2Secret.tokenUrl,
1489
+ userInfoUrl: appOauth2Secret.userInfoUrl,
1490
+ }).toStrictEqual({
1491
+ authorizationUrl: 'http://localhost:1234',
1492
+ clientId: 'id',
1493
+ clientSecret: 'secret',
1494
+ icon: 'redhat',
1495
+ name: 'test-oauth2-secret',
1496
+ remapper: [{ prop: 'email' }],
1497
+ scope: 'email openid profile',
1498
+ tokenUrl: 'http://localhost:1234',
1499
+ userInfoUrl: 'http://localhost:1234',
1500
+ });
1501
+ });
1229
1502
  });
1230
1503
  describe('patchApp', () => {
1231
1504
  it('should apply patches to app', async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appsemble/cli",
3
- "version": "0.27.11",
3
+ "version": "0.27.12",
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.11",
46
- "@appsemble/types": "0.27.11",
47
- "@appsemble/utils": "0.27.11",
45
+ "@appsemble/node-utils": "0.27.12",
46
+ "@appsemble/types": "0.27.12",
47
+ "@appsemble/utils": "0.27.12",
48
48
  "@fortawesome/fontawesome-common-types": "^6.0.0",
49
49
  "@koa/cors": "^4.0.0",
50
50
  "@odata/parser": "^0.2.0",
@@ -87,8 +87,8 @@
87
87
  "yargs": "^17.0.0"
88
88
  },
89
89
  "devDependencies": {
90
- "@appsemble/types": "0.27.11",
91
- "@appsemble/server": "0.27.11",
90
+ "@appsemble/types": "0.27.12",
91
+ "@appsemble/server": "0.27.12",
92
92
  "bcrypt": "^5.0.0",
93
93
  "axios-test-instance": "^7.0.0",
94
94
  "@types/concat-stream": "^2.0.0",
@@ -0,0 +1,3 @@
1
+ import { type GetAppSubEntityParams } from '@appsemble/node-utils';
2
+ import { type AppConfigEntry as AppVariableInterface } from '@appsemble/types';
3
+ export declare function getAppVariables({ context, }: GetAppSubEntityParams): Promise<AppVariableInterface[]>;
@@ -0,0 +1,4 @@
1
+ export function getAppVariables({ context, }) {
2
+ return Promise.resolve(context.appVariables);
3
+ }
4
+ //# sourceMappingURL=getAppVariables.js.map
@@ -34,3 +34,4 @@ export * from './getAppMembers.js';
34
34
  export * from './getAppTeams.js';
35
35
  export * from './getAppUserInfo.js';
36
36
  export * from './getAppReadmes.js';
37
+ export * from './getAppVariables.js';
@@ -34,4 +34,5 @@ export * from './getAppMembers.js';
34
34
  export * from './getAppTeams.js';
35
35
  export * from './getAppUserInfo.js';
36
36
  export * from './getAppReadmes.js';
37
+ export * from './getAppVariables.js';
37
38
  //# 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, getAppReadmes, 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, getAppVariables, 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,
@@ -24,6 +24,7 @@ export const options = {
24
24
  getAppTeams,
25
25
  getAppUrl,
26
26
  getAppUserInfo,
27
+ getAppVariables,
27
28
  getBlockAsset,
28
29
  getBlockMessages,
29
30
  getBlocksAssetsPaths,
@@ -6,7 +6,7 @@ pages:
6
6
  - name: Example Page A
7
7
  blocks:
8
8
  - type: action-button
9
- version: 0.27.11
9
+ version: 0.27.12
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.11
20
+ version: 0.27.12
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.11",
5
+ "@appsemble/sdk": "0.27.12",
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.11",
6
- "@appsemble/sdk": "0.27.11",
5
+ "@appsemble/preact": "0.27.12",
6
+ "@appsemble/sdk": "0.27.12",
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.11"
5
+ "@appsemble/sdk": "0.27.12"
6
6
  }
7
7
  }