@appsemble/cli 0.27.10 → 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 +3 -3
- package/__fixtures__/apps/test/README.md +1 -0
- package/commands/serve.js +21 -2
- package/lib/app.d.ts +74 -75
- package/lib/app.js +333 -124
- package/lib/app.test.js +1462 -3
- package/package.json +6 -6
- package/server/options/getAppVariables.d.ts +3 -0
- package/server/options/getAppVariables.js +4 -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/lib/app.js
CHANGED
|
@@ -20,12 +20,16 @@ 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`.
|
|
23
24
|
* @param formData The FormData object to append the results into..
|
|
24
25
|
* @returns The context from `.appsemblerc.yaml` if a match was found.
|
|
25
26
|
*/
|
|
26
|
-
export async function traverseAppDirectory(path, formData) {
|
|
27
|
+
export async function traverseAppDirectory(path, context, formData) {
|
|
27
28
|
let rc;
|
|
29
|
+
let discoveredContext;
|
|
28
30
|
let yaml;
|
|
31
|
+
let iconPath;
|
|
32
|
+
let maskableIconPath;
|
|
29
33
|
let controllerPath;
|
|
30
34
|
let controllerBuildConfig;
|
|
31
35
|
let controllerBuildResult;
|
|
@@ -60,6 +64,10 @@ export async function traverseAppDirectory(path, formData) {
|
|
|
60
64
|
formData.append('iconBackground', rc.iconBackground);
|
|
61
65
|
gatheredData.iconBackground = rc.iconBackground;
|
|
62
66
|
}
|
|
67
|
+
if (context && has(rc === null || rc === void 0 ? void 0 : rc.context, context)) {
|
|
68
|
+
discoveredContext = rc.context[context];
|
|
69
|
+
logger.verbose(`Using context: ${inspect(discoveredContext, { colors: true })}`);
|
|
70
|
+
}
|
|
63
71
|
break;
|
|
64
72
|
case 'app-definition.yaml': {
|
|
65
73
|
logger.info(`Using app definition from ${filepath}`);
|
|
@@ -74,10 +82,12 @@ export async function traverseAppDirectory(path, formData) {
|
|
|
74
82
|
}
|
|
75
83
|
case 'icon.png':
|
|
76
84
|
case 'icon.svg':
|
|
85
|
+
iconPath = filepath;
|
|
77
86
|
gatheredData.iconUrl = filepath;
|
|
78
87
|
return;
|
|
79
88
|
case 'maskable-icon.png':
|
|
80
|
-
|
|
89
|
+
maskableIconPath = filepath;
|
|
90
|
+
break;
|
|
81
91
|
case 'screenshots':
|
|
82
92
|
return opendirSafe(filepath, (screenshotPath, screenshotStat) => {
|
|
83
93
|
if (screenshotStat.isDirectory() &&
|
|
@@ -133,7 +143,14 @@ export async function traverseAppDirectory(path, formData) {
|
|
|
133
143
|
if (yaml === undefined) {
|
|
134
144
|
throw new AppsembleError('No app definition found');
|
|
135
145
|
}
|
|
136
|
-
|
|
146
|
+
discoveredContext || (discoveredContext = {});
|
|
147
|
+
discoveredContext.icon = discoveredContext.icon
|
|
148
|
+
? resolve(path, discoveredContext.icon)
|
|
149
|
+
: iconPath;
|
|
150
|
+
discoveredContext.maskableIcon = discoveredContext.maskableIcon
|
|
151
|
+
? resolve(path, discoveredContext.maskableIcon)
|
|
152
|
+
: maskableIconPath;
|
|
153
|
+
return [discoveredContext, rc, yaml, gatheredData];
|
|
137
154
|
}
|
|
138
155
|
async function retrieveContext(path, context) {
|
|
139
156
|
let rc;
|
|
@@ -301,6 +318,182 @@ export async function publishSeedAssets(path, app, remote, assetsClonable) {
|
|
|
301
318
|
logger.warn(`Missing assets directory in ${path}. Skipping...`);
|
|
302
319
|
}
|
|
303
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
|
+
}
|
|
304
497
|
/**
|
|
305
498
|
* @param path The path to the app directory.
|
|
306
499
|
* @param languages A list of languages for which translations should be added in addition to the
|
|
@@ -452,15 +645,45 @@ This block version is not used in the app`);
|
|
|
452
645
|
}
|
|
453
646
|
}
|
|
454
647
|
/**
|
|
455
|
-
*
|
|
648
|
+
* Resolve the app’s ID and remote based on the app context.
|
|
456
649
|
*
|
|
457
|
-
* @param
|
|
650
|
+
* @param appPath The path to the app.
|
|
651
|
+
* @param name Which context to use in the AppsembleRC file.
|
|
652
|
+
* @param defaultRemote The remote to fall back to.
|
|
653
|
+
* @param defaultAppId The app ID to fall back to.
|
|
654
|
+
* @returns The resolved app ID and remote.
|
|
458
655
|
*/
|
|
459
|
-
export async function
|
|
460
|
-
var _a
|
|
656
|
+
export async function resolveAppIdAndRemote(appPath, name, defaultRemote, defaultAppId) {
|
|
657
|
+
var _a;
|
|
658
|
+
let id = defaultAppId;
|
|
659
|
+
let resolvedRemote = defaultRemote;
|
|
660
|
+
if (appPath) {
|
|
661
|
+
const rcPath = join(appPath, '.appsemblerc.yaml');
|
|
662
|
+
const [rc] = await readData(rcPath);
|
|
663
|
+
const context = (_a = rc.context) === null || _a === void 0 ? void 0 : _a[name];
|
|
664
|
+
if (!defaultAppId) {
|
|
665
|
+
id = context === null || context === void 0 ? void 0 : context.id;
|
|
666
|
+
}
|
|
667
|
+
if (context.remote) {
|
|
668
|
+
resolvedRemote = context.remote;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (id == null) {
|
|
672
|
+
throw new AppsembleError(`App ID was not found in context.${name}.id nor in --app-id`);
|
|
673
|
+
}
|
|
674
|
+
return [id, coerceRemote(resolvedRemote)];
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Publish a new app.
|
|
678
|
+
*
|
|
679
|
+
* @param options The options to use for publishing an app.
|
|
680
|
+
*/
|
|
681
|
+
export async function publishApp({ clientCredentials, context, dryRun, modifyContext, path, ...options }) {
|
|
682
|
+
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, _0;
|
|
461
683
|
const file = await stat(path);
|
|
462
684
|
const formData = new FormData();
|
|
463
685
|
const appsembleContext = await retrieveContext(path, context);
|
|
686
|
+
let rc;
|
|
464
687
|
let yaml;
|
|
465
688
|
let filename = relative(process.cwd(), path);
|
|
466
689
|
const variant = (_b = (_a = appsembleContext.variant) !== null && _a !== void 0 ? _a : options.variant) !== null && _b !== void 0 ? _b : context;
|
|
@@ -473,16 +696,24 @@ export async function updateApp({ clientCredentials, context, force, path, ...op
|
|
|
473
696
|
logger.warn(`App variant ${variant} is not defined in ${join(path, 'variants')}. Using default app.`);
|
|
474
697
|
}
|
|
475
698
|
if (file.isFile()) {
|
|
699
|
+
// Assuming file is App YAML
|
|
476
700
|
const [, data] = await readData(appVariantPath);
|
|
477
701
|
yaml = data;
|
|
478
702
|
formData.append('yaml', data);
|
|
479
703
|
}
|
|
480
704
|
else {
|
|
481
|
-
|
|
705
|
+
let appsembleVariantContext;
|
|
706
|
+
[appsembleVariantContext, rc, yaml] = await traverseAppDirectory(appVariantPath, context, formData);
|
|
707
|
+
if (appsembleVariantContext.icon) {
|
|
708
|
+
appsembleContext.icon = appsembleVariantContext.icon;
|
|
709
|
+
}
|
|
710
|
+
if (appsembleVariantContext.maskableIcon) {
|
|
711
|
+
appsembleContext.maskableIcon = appsembleVariantContext.maskableIcon;
|
|
712
|
+
}
|
|
482
713
|
filename = join(filename, 'app-definition.yaml');
|
|
483
714
|
}
|
|
484
715
|
const remote = (_c = appsembleContext.remote) !== null && _c !== void 0 ? _c : options.remote;
|
|
485
|
-
const
|
|
716
|
+
const organizationId = (_d = appsembleContext.organization) !== null && _d !== void 0 ? _d : options.organization;
|
|
486
717
|
const template = (_f = (_e = appsembleContext.template) !== null && _e !== void 0 ? _e : options.template) !== null && _f !== void 0 ? _f : false;
|
|
487
718
|
const assetsClonable = (_h = (_g = appsembleContext.assetsClonable) !== null && _g !== void 0 ? _g : options.assetsClonable) !== null && _h !== void 0 ? _h : false;
|
|
488
719
|
const demoMode = (_k = (_j = appsembleContext.demoMode) !== null && _j !== void 0 ? _j : options.demoMode) !== null && _k !== void 0 ? _k : false;
|
|
@@ -493,23 +724,23 @@ export async function updateApp({ clientCredentials, context, force, path, ...op
|
|
|
493
724
|
const sentryDsn = (_q = appsembleContext.sentryDsn) !== null && _q !== void 0 ? _q : options.sentryDsn;
|
|
494
725
|
const sentryEnvironment = (_r = appsembleContext.sentryEnvironment) !== null && _r !== void 0 ? _r : options.sentryEnvironment;
|
|
495
726
|
const googleAnalyticsId = (_s = appsembleContext.googleAnalyticsId) !== null && _s !== void 0 ? _s : options.googleAnalyticsId;
|
|
496
|
-
const
|
|
497
|
-
const
|
|
498
|
-
const
|
|
499
|
-
logger.info(`App id: ${id}`);
|
|
727
|
+
const assets = (_t = appsembleContext.assets) !== null && _t !== void 0 ? _t : options.assets;
|
|
728
|
+
const resources = (_u = appsembleContext.resources) !== null && _u !== void 0 ? _u : options.resources;
|
|
729
|
+
const appLock = appsembleContext.appLock || 'unlocked';
|
|
500
730
|
logger.verbose(`App remote: ${remote}`);
|
|
731
|
+
logger.verbose(`App organization: ${organizationId}`);
|
|
501
732
|
logger.verbose(`App is template: ${inspect(template, { colors: true })}`);
|
|
502
733
|
logger.verbose(`App visibility: ${visibility}`);
|
|
503
734
|
logger.verbose(`Icon background: ${iconBackground}`);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
throw new AppsembleError('The app id must be passed as a command line flag or in the context');
|
|
735
|
+
if (!organizationId) {
|
|
736
|
+
throw new AppsembleError('An organization id must be passed as a command line flag or in the context');
|
|
507
737
|
}
|
|
508
|
-
formData.append('
|
|
738
|
+
formData.append('OrganizationId', organizationId);
|
|
509
739
|
formData.append('template', String(template));
|
|
510
740
|
formData.append('demoMode', String(demoMode));
|
|
511
741
|
formData.append('visibility', visibility);
|
|
512
742
|
formData.append('iconBackground', iconBackground);
|
|
743
|
+
formData.append('locked', appLock);
|
|
513
744
|
if (icon) {
|
|
514
745
|
const realIcon = typeof icon === 'string' ? createReadStream(icon) : icon;
|
|
515
746
|
logger.info(`Using icon from ${(_v = realIcon.path) !== null && _v !== void 0 ? _v : 'stdin'}`);
|
|
@@ -536,21 +767,12 @@ export async function updateApp({ clientCredentials, context, force, path, ...op
|
|
|
536
767
|
authScope += ' resources:write';
|
|
537
768
|
}
|
|
538
769
|
await authenticate(remote, authScope, clientCredentials);
|
|
539
|
-
if (appLock) {
|
|
540
|
-
logger.info(`Setting AppLock to ${appLock}`);
|
|
541
|
-
try {
|
|
542
|
-
await axios.post(`/api/apps/${id}/lock`, {
|
|
543
|
-
locked: appLock,
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
catch (error) {
|
|
547
|
-
logger.error(error);
|
|
548
|
-
process.exit(1);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
770
|
let data;
|
|
552
771
|
try {
|
|
553
|
-
({ data } = await axios.
|
|
772
|
+
({ data } = await axios.post('/api/apps', formData, {
|
|
773
|
+
baseURL: remote,
|
|
774
|
+
params: { dryRun: dryRun || undefined },
|
|
775
|
+
}));
|
|
554
776
|
}
|
|
555
777
|
catch (error) {
|
|
556
778
|
if (!axios.isAxiosError(error)) {
|
|
@@ -561,38 +783,65 @@ export async function updateApp({ clientCredentials, context, force, path, ...op
|
|
|
561
783
|
}
|
|
562
784
|
throw new AppsembleError(printAxiosError(filename, yaml, error.response.data.data.errors));
|
|
563
785
|
}
|
|
564
|
-
if (
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
786
|
+
if (dryRun) {
|
|
787
|
+
logger.info('Skipped uploading block themes and app messages.');
|
|
788
|
+
logger.info('Successfully ran dry run. The app should be valid when creating it.');
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (file.isDirectory() && !dryRun) {
|
|
792
|
+
// After uploading the app, upload block styles, messages if they are available
|
|
793
|
+
await traverseBlockThemes(appVariantPath, data.id, remote, false);
|
|
794
|
+
await uploadMessages(appVariantPath, data.id, remote, false);
|
|
795
|
+
await publishAppConfig(appVariantPath, data, remote);
|
|
796
|
+
// After uploading the app, publish seed resources and assets if they are available
|
|
797
|
+
if (assets) {
|
|
570
798
|
await publishSeedAssets(appVariantPath, data, remote, assetsClonable);
|
|
571
799
|
}
|
|
572
|
-
if (resources
|
|
800
|
+
if (resources) {
|
|
573
801
|
await publishSeedResources(appVariantPath, data, remote);
|
|
574
802
|
}
|
|
575
803
|
}
|
|
804
|
+
if (modifyContext && appsembleContext && context && !dryRun) {
|
|
805
|
+
rc.context[context].id = data.id;
|
|
806
|
+
await writeData(join(appVariantPath, '.appsemblerc.yaml'), rc, { sort: false });
|
|
807
|
+
logger.info(`Updated .appsemblerc: Set context.${context}.id to ${data.id}`);
|
|
808
|
+
}
|
|
576
809
|
const { host, protocol } = new URL(remote);
|
|
577
|
-
logger.info(`Successfully
|
|
810
|
+
logger.info(`Successfully published app ${data.definition.name}! 🙌`);
|
|
578
811
|
logger.info(`App URL: ${protocol}//${data.path}.${data.OrganizationId}.${host}`);
|
|
579
812
|
logger.info(`App store page: ${new URL(`/apps/${data.id}`, remote)}`);
|
|
580
|
-
|
|
813
|
+
const rcCollections = (_0 = (_z = rc === null || rc === void 0 ? void 0 : rc.context) === null || _z === void 0 ? void 0 : _z[context]) === null || _0 === void 0 ? void 0 : _0.collections;
|
|
814
|
+
if (Array.isArray(rcCollections)) {
|
|
815
|
+
for (const collectionId of rcCollections) {
|
|
816
|
+
try {
|
|
817
|
+
await axios.post(`/api/appCollections/${collectionId}/apps`, { AppId: data.id }, {
|
|
818
|
+
baseURL: remote,
|
|
819
|
+
});
|
|
820
|
+
logger.info(`Successfully added app to collection ${collectionId}`);
|
|
821
|
+
}
|
|
822
|
+
catch (error) {
|
|
823
|
+
logger.error(error);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (appVariantPath !== path && modifyContext && !dryRun) {
|
|
828
|
+
const [defaultAppRc] = await readData(join(path, '.appsemblerc.yaml'));
|
|
829
|
+
defaultAppRc.context[context] = rc.context[context];
|
|
830
|
+
await writeData(join(path, '.appsemblerc.yaml'), defaultAppRc, { sort: false });
|
|
581
831
|
await rm(appVariantPath, { recursive: true });
|
|
582
832
|
logger.info(`Removed ${appVariantPath}`);
|
|
583
833
|
}
|
|
584
834
|
}
|
|
585
835
|
/**
|
|
586
|
-
*
|
|
836
|
+
* Update an existing app.
|
|
587
837
|
*
|
|
588
|
-
* @param
|
|
838
|
+
* @param argv The command line options used for updating the app.
|
|
589
839
|
*/
|
|
590
|
-
export async function
|
|
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
|
|
840
|
+
export async function updateApp({ clientCredentials, context, force, path, ...options }) {
|
|
841
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
|
592
842
|
const file = await stat(path);
|
|
593
843
|
const formData = new FormData();
|
|
594
844
|
const appsembleContext = await retrieveContext(path, context);
|
|
595
|
-
let rc;
|
|
596
845
|
let yaml;
|
|
597
846
|
let filename = relative(process.cwd(), path);
|
|
598
847
|
const variant = (_b = (_a = appsembleContext.variant) !== null && _a !== void 0 ? _a : options.variant) !== null && _b !== void 0 ? _b : context;
|
|
@@ -605,17 +854,23 @@ export async function publishApp({ clientCredentials, context, dryRun, modifyCon
|
|
|
605
854
|
logger.warn(`App variant ${variant} is not defined in ${join(path, 'variants')}. Using default app.`);
|
|
606
855
|
}
|
|
607
856
|
if (file.isFile()) {
|
|
608
|
-
// Assuming file is App YAML
|
|
609
857
|
const [, data] = await readData(appVariantPath);
|
|
610
858
|
yaml = data;
|
|
611
859
|
formData.append('yaml', data);
|
|
612
860
|
}
|
|
613
861
|
else {
|
|
614
|
-
|
|
862
|
+
let appsembleVariantContext;
|
|
863
|
+
[appsembleVariantContext, , yaml] = await traverseAppDirectory(appVariantPath, context, formData);
|
|
864
|
+
if (appsembleVariantContext.icon) {
|
|
865
|
+
appsembleContext.icon = appsembleVariantContext.icon;
|
|
866
|
+
}
|
|
867
|
+
if (appsembleVariantContext.maskableIcon) {
|
|
868
|
+
appsembleContext.maskableIcon = appsembleVariantContext.maskableIcon;
|
|
869
|
+
}
|
|
615
870
|
filename = join(filename, 'app-definition.yaml');
|
|
616
871
|
}
|
|
617
872
|
const remote = (_c = appsembleContext.remote) !== null && _c !== void 0 ? _c : options.remote;
|
|
618
|
-
const
|
|
873
|
+
const id = (_d = appsembleContext.id) !== null && _d !== void 0 ? _d : options.id;
|
|
619
874
|
const template = (_f = (_e = appsembleContext.template) !== null && _e !== void 0 ? _e : options.template) !== null && _f !== void 0 ? _f : false;
|
|
620
875
|
const assetsClonable = (_h = (_g = appsembleContext.assetsClonable) !== null && _g !== void 0 ? _g : options.assetsClonable) !== null && _h !== void 0 ? _h : false;
|
|
621
876
|
const demoMode = (_k = (_j = appsembleContext.demoMode) !== null && _j !== void 0 ? _j : options.demoMode) !== null && _k !== void 0 ? _k : false;
|
|
@@ -626,23 +881,23 @@ export async function publishApp({ clientCredentials, context, dryRun, modifyCon
|
|
|
626
881
|
const sentryDsn = (_q = appsembleContext.sentryDsn) !== null && _q !== void 0 ? _q : options.sentryDsn;
|
|
627
882
|
const sentryEnvironment = (_r = appsembleContext.sentryEnvironment) !== null && _r !== void 0 ? _r : options.sentryEnvironment;
|
|
628
883
|
const googleAnalyticsId = (_s = appsembleContext.googleAnalyticsId) !== null && _s !== void 0 ? _s : options.googleAnalyticsId;
|
|
629
|
-
const
|
|
630
|
-
const
|
|
631
|
-
const appLock = appsembleContext
|
|
884
|
+
const resources = (_t = appsembleContext.resources) !== null && _t !== void 0 ? _t : options.resources;
|
|
885
|
+
const assets = (_u = appsembleContext.assets) !== null && _u !== void 0 ? _u : options.assets;
|
|
886
|
+
const { appLock } = appsembleContext;
|
|
887
|
+
logger.info(`App id: ${id}`);
|
|
632
888
|
logger.verbose(`App remote: ${remote}`);
|
|
633
|
-
logger.verbose(`App organization: ${organizationId}`);
|
|
634
889
|
logger.verbose(`App is template: ${inspect(template, { colors: true })}`);
|
|
635
890
|
logger.verbose(`App visibility: ${visibility}`);
|
|
636
891
|
logger.verbose(`Icon background: ${iconBackground}`);
|
|
637
|
-
|
|
638
|
-
|
|
892
|
+
logger.verbose(`Force update: ${inspect(force, { colors: true })}`);
|
|
893
|
+
if (!id) {
|
|
894
|
+
throw new AppsembleError('The app id must be passed as a command line flag or in the context');
|
|
639
895
|
}
|
|
640
|
-
formData.append('
|
|
896
|
+
formData.append('force', String(force));
|
|
641
897
|
formData.append('template', String(template));
|
|
642
898
|
formData.append('demoMode', String(demoMode));
|
|
643
899
|
formData.append('visibility', visibility);
|
|
644
900
|
formData.append('iconBackground', iconBackground);
|
|
645
|
-
formData.append('locked', appLock);
|
|
646
901
|
if (icon) {
|
|
647
902
|
const realIcon = typeof icon === 'string' ? createReadStream(icon) : icon;
|
|
648
903
|
logger.info(`Using icon from ${(_v = realIcon.path) !== null && _v !== void 0 ? _v : 'stdin'}`);
|
|
@@ -669,12 +924,21 @@ export async function publishApp({ clientCredentials, context, dryRun, modifyCon
|
|
|
669
924
|
authScope += ' resources:write';
|
|
670
925
|
}
|
|
671
926
|
await authenticate(remote, authScope, clientCredentials);
|
|
927
|
+
if (appLock) {
|
|
928
|
+
logger.info(`Setting AppLock to ${appLock}`);
|
|
929
|
+
try {
|
|
930
|
+
await axios.post(`/api/apps/${id}/lock`, {
|
|
931
|
+
locked: appLock,
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
catch (error) {
|
|
935
|
+
logger.error(error);
|
|
936
|
+
process.exit(1);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
672
939
|
let data;
|
|
673
940
|
try {
|
|
674
|
-
({ data } = await axios.
|
|
675
|
-
baseURL: remote,
|
|
676
|
-
params: { dryRun: dryRun || undefined },
|
|
677
|
-
}));
|
|
941
|
+
({ data } = await axios.patch(`/api/apps/${id}`, formData, { baseURL: remote }));
|
|
678
942
|
}
|
|
679
943
|
catch (error) {
|
|
680
944
|
if (!axios.isAxiosError(error)) {
|
|
@@ -685,83 +949,28 @@ export async function publishApp({ clientCredentials, context, dryRun, modifyCon
|
|
|
685
949
|
}
|
|
686
950
|
throw new AppsembleError(printAxiosError(filename, yaml, error.response.data.data.errors));
|
|
687
951
|
}
|
|
688
|
-
if (
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
await traverseBlockThemes(appVariantPath, data.id, remote, false);
|
|
696
|
-
await uploadMessages(appVariantPath, data.id, remote, false);
|
|
697
|
-
// After uploading the app, publish seed resources and assets if they are available
|
|
698
|
-
if (assets) {
|
|
952
|
+
if (file.isDirectory()) {
|
|
953
|
+
// After uploading the app, upload block styles and messages if they are available
|
|
954
|
+
await traverseBlockThemes(appVariantPath, data.id, remote, force);
|
|
955
|
+
await uploadMessages(appVariantPath, data.id, remote, force);
|
|
956
|
+
await publishAppConfig(appVariantPath, data, remote);
|
|
957
|
+
// After updating the app, publish seed resources and assets if they are available
|
|
958
|
+
if (assets && (data.locked !== 'fullLock' || force)) {
|
|
699
959
|
await publishSeedAssets(appVariantPath, data, remote, assetsClonable);
|
|
700
960
|
}
|
|
701
|
-
if (resources) {
|
|
961
|
+
if (resources && (data.locked !== 'fullLock' || force)) {
|
|
702
962
|
await publishSeedResources(appVariantPath, data, remote);
|
|
703
963
|
}
|
|
704
964
|
}
|
|
705
|
-
if (modifyContext && appsembleContext && context && !dryRun) {
|
|
706
|
-
rc.context[context].id = data.id;
|
|
707
|
-
await writeData(join(appVariantPath, '.appsemblerc.yaml'), rc, { sort: false });
|
|
708
|
-
logger.info(`Updated .appsemblerc: Set context.${context}.id to ${data.id}`);
|
|
709
|
-
}
|
|
710
965
|
const { host, protocol } = new URL(remote);
|
|
711
|
-
logger.info(`Successfully
|
|
966
|
+
logger.info(`Successfully updated app ${data.definition.name}! 🙌`);
|
|
712
967
|
logger.info(`App URL: ${protocol}//${data.path}.${data.OrganizationId}.${host}`);
|
|
713
968
|
logger.info(`App store page: ${new URL(`/apps/${data.id}`, remote)}`);
|
|
714
|
-
|
|
715
|
-
if (Array.isArray(rcCollections)) {
|
|
716
|
-
for (const collectionId of rcCollections) {
|
|
717
|
-
try {
|
|
718
|
-
await axios.post(`/api/appCollections/${collectionId}/apps`, { AppId: data.id }, {
|
|
719
|
-
baseURL: remote,
|
|
720
|
-
});
|
|
721
|
-
logger.info(`Successfully added app to collection ${collectionId}`);
|
|
722
|
-
}
|
|
723
|
-
catch (error) {
|
|
724
|
-
logger.error(error);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
if (appVariantPath !== path && modifyContext && !dryRun) {
|
|
729
|
-
const [defaultAppRc] = await readData(join(path, '.appsemblerc.yaml'));
|
|
730
|
-
defaultAppRc.context[context] = rc.context[context];
|
|
731
|
-
await writeData(join(path, '.appsemblerc.yaml'), defaultAppRc, { sort: false });
|
|
969
|
+
if (appVariantPath !== path) {
|
|
732
970
|
await rm(appVariantPath, { recursive: true });
|
|
733
971
|
logger.info(`Removed ${appVariantPath}`);
|
|
734
972
|
}
|
|
735
973
|
}
|
|
736
|
-
/**
|
|
737
|
-
* Resolve the app’s ID and remote based on the app context.
|
|
738
|
-
*
|
|
739
|
-
* @param appPath The path to the app.
|
|
740
|
-
* @param name Which context to use in the AppsembleRC file.
|
|
741
|
-
* @param defaultRemote The remote to fall back to.
|
|
742
|
-
* @param defaultAppId The app ID to fall back to.
|
|
743
|
-
* @returns The resolved app ID and remote.
|
|
744
|
-
*/
|
|
745
|
-
export async function resolveAppIdAndRemote(appPath, name, defaultRemote, defaultAppId) {
|
|
746
|
-
var _a;
|
|
747
|
-
let id = defaultAppId;
|
|
748
|
-
let resolvedRemote = defaultRemote;
|
|
749
|
-
if (appPath) {
|
|
750
|
-
const rcPath = join(appPath, '.appsemblerc.yaml');
|
|
751
|
-
const [rc] = await readData(rcPath);
|
|
752
|
-
const context = (_a = rc.context) === null || _a === void 0 ? void 0 : _a[name];
|
|
753
|
-
if (!defaultAppId) {
|
|
754
|
-
id = context === null || context === void 0 ? void 0 : context.id;
|
|
755
|
-
}
|
|
756
|
-
if (context.remote) {
|
|
757
|
-
resolvedRemote = context.remote;
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
if (id == null) {
|
|
761
|
-
throw new AppsembleError(`App ID was not found in context.${name}.id nor in --app-id`);
|
|
762
|
-
}
|
|
763
|
-
return [id, coerceRemote(resolvedRemote)];
|
|
764
|
-
}
|
|
765
974
|
export async function patchApp({ id, remote, ...options }) {
|
|
766
975
|
const formData = new FormData();
|
|
767
976
|
if (options.path !== undefined) {
|