@appsemble/cli 0.40.8 → 0.40.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 +3 -3
- package/commands/app/update.js +1 -1
- package/lib/app.d.ts +10 -1
- package/lib/app.js +82 -31
- package/package.json +7 -7
- package/templates/apps/empty/app-definition.yaml +2 -2
- package/templates/blocks/mini-jsx/package.json +1 -1
- package/templates/blocks/preact/package.json +2 -2
- package/templates/blocks/vanilla/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
#  Appsemble CLI
|
|
2
2
|
|
|
3
3
|
> Manage apps and blocks from the command line.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@appsemble/cli)
|
|
6
|
-
[](https://gitlab.com/appsemble/appsemble/-/releases/0.40.10)
|
|
7
7
|
[](https://prettier.io)
|
|
8
8
|
|
|
9
9
|
## Table of Contents
|
|
@@ -271,5 +271,5 @@ appsemble run-cronjobs --interval 30
|
|
|
271
271
|
|
|
272
272
|
## License
|
|
273
273
|
|
|
274
|
-
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.40.
|
|
274
|
+
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.40.10/LICENSE.md) ©
|
|
275
275
|
[Appsemble](https://appsemble.com)
|
package/commands/app/update.js
CHANGED
|
@@ -60,7 +60,7 @@ export function builder(yargs) {
|
|
|
60
60
|
type: 'boolean',
|
|
61
61
|
})
|
|
62
62
|
.option('assets', {
|
|
63
|
-
describe: 'Whether the assets
|
|
63
|
+
describe: 'Whether the assets of the app being updated should be synced with the `assets` directory. Unchanged assets are kept.',
|
|
64
64
|
type: 'boolean',
|
|
65
65
|
default: false,
|
|
66
66
|
})
|
package/lib/app.d.ts
CHANGED
|
@@ -38,7 +38,16 @@ interface DeleteAppArgs {
|
|
|
38
38
|
export declare function deleteApp({ clientCredentials, id, remote }: DeleteAppArgs): Promise<void>;
|
|
39
39
|
export declare function publishSeedResources(path: string, app: App, remote: string): Promise<void>;
|
|
40
40
|
export declare function publishSeedAppMembers(path: string, app: App, remote: string): Promise<void>;
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Read the seed asset files of an app.
|
|
43
|
+
*
|
|
44
|
+
* @param assetsPath The path to the assets directory.
|
|
45
|
+
* @returns The absolute file paths by the normalized name of the asset, or null if the directory
|
|
46
|
+
* doesn't exist.
|
|
47
|
+
* @throws AppsembleError if a file normalizes to an empty name, or two files to the same name.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getSeedAssetFiles(assetsPath: string): Promise<Map<string, string> | null>;
|
|
50
|
+
export declare function publishSeedAssets(path: string, files: Map<string, string> | null, app: App, remote: string, assetsClonable: boolean): Promise<void>;
|
|
42
51
|
export declare function publishAppConfig(path: string, app: App, remote: string): Promise<void>;
|
|
43
52
|
/**
|
|
44
53
|
* @param path The path to the app directory.
|
package/lib/app.js
CHANGED
|
@@ -2,8 +2,8 @@ import { createReadStream, createWriteStream, existsSync } from 'node:fs';
|
|
|
2
2
|
import { mkdir, readdir, readFile, 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 { normalizeBlockName } from '@appsemble/lang-sdk';
|
|
6
|
-
import { applyAppVariant, AppsembleError, authenticate, logger, opendirSafe, preProcessCSV, readData, writeData, } from '@appsemble/node-utils';
|
|
5
|
+
import { normalize, normalizeBlockName, } from '@appsemble/lang-sdk';
|
|
6
|
+
import { applyAppVariant, AppsembleError, authenticate, hashFile, logger, opendirSafe, preProcessCSV, readData, writeData, } from '@appsemble/node-utils';
|
|
7
7
|
import { APP_VALIDATION_FAILED, } from '@appsemble/types';
|
|
8
8
|
import { extractAppMessages, has, normalizeLocale } from '@appsemble/utils';
|
|
9
9
|
import axios from 'axios';
|
|
@@ -363,40 +363,89 @@ export async function publishSeedAppMembers(path, app, remote) {
|
|
|
363
363
|
logger.warn(`Missing members directory in ${path}. Skipping...`);
|
|
364
364
|
}
|
|
365
365
|
}
|
|
366
|
-
|
|
366
|
+
/**
|
|
367
|
+
* Read the seed asset files of an app.
|
|
368
|
+
*
|
|
369
|
+
* @param assetsPath The path to the assets directory.
|
|
370
|
+
* @returns The absolute file paths by the normalized name of the asset, or null if the directory
|
|
371
|
+
* doesn't exist.
|
|
372
|
+
* @throws AppsembleError if a file normalizes to an empty name, or two files to the same name.
|
|
373
|
+
*/
|
|
374
|
+
export async function getSeedAssetFiles(assetsPath) {
|
|
375
|
+
if (!existsSync(assetsPath)) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
const assetFiles = await readdir(assetsPath);
|
|
379
|
+
const normalizedPaths = assetFiles.map((assetFile) => normalizePath(join(assetsPath, assetFile)));
|
|
380
|
+
const files = await fg(normalizedPaths, { absolute: true, onlyFiles: true });
|
|
381
|
+
const filesByName = new Map();
|
|
382
|
+
for (const file of files) {
|
|
383
|
+
const name = normalize(parse(file).name);
|
|
384
|
+
if (!name) {
|
|
385
|
+
throw new AppsembleError(`Asset file ${basename(file)} normalizes to an empty name`);
|
|
386
|
+
}
|
|
387
|
+
const collision = filesByName.get(name);
|
|
388
|
+
if (collision) {
|
|
389
|
+
throw new AppsembleError(`Asset files ${basename(collision)} and ${basename(file)} both normalize to ${name}`);
|
|
390
|
+
}
|
|
391
|
+
filesByName.set(name, file);
|
|
392
|
+
}
|
|
393
|
+
return filesByName;
|
|
394
|
+
}
|
|
395
|
+
export async function publishSeedAssets(path, files, app, remote, assetsClonable) {
|
|
367
396
|
const assetsPath = join(path, 'assets');
|
|
368
397
|
logger.info(`Publishing seed assets from ${assetsPath}`);
|
|
369
|
-
if (
|
|
370
|
-
logger.
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
398
|
+
if (!files) {
|
|
399
|
+
logger.warn(`Missing assets directory in ${path}. Skipping...`);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const assetsUrl = `/api/apps/${app.id}/assets`;
|
|
403
|
+
try {
|
|
404
|
+
let serverAssets = [];
|
|
405
|
+
if (app.demoMode) {
|
|
406
|
+
// The asset endpoints only reach the ephemeral copies of a demo app, and the cleanup job
|
|
407
|
+
// gives those new ids on every run, so their seed assets can't be compared one by one.
|
|
408
|
+
logger.info(`Deleting existing seed assets from app ${app.id}`);
|
|
409
|
+
await axios.delete(assetsUrl, { data: [], baseURL: remote, params: { seed: true } });
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
({ data: serverAssets } = await axios.get(assetsUrl, {
|
|
374
413
|
baseURL: remote,
|
|
375
|
-
params: {
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
await
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
seed: true,
|
|
389
|
-
clonable: assetsClonable,
|
|
390
|
-
});
|
|
414
|
+
params: { named: true },
|
|
415
|
+
}));
|
|
416
|
+
}
|
|
417
|
+
const serverAssetsByName = new Map(serverAssets.map((asset) => [asset.name, asset]));
|
|
418
|
+
for (const [name, file] of files) {
|
|
419
|
+
const serverAsset = serverAssetsByName.get(name);
|
|
420
|
+
if (serverAsset) {
|
|
421
|
+
if (serverAsset.sha256 === (await hashFile(file)) &&
|
|
422
|
+
serverAsset.clonable === assetsClonable) {
|
|
423
|
+
logger.info(`Keeping unchanged asset ${name}`);
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
await axios.delete(assetsUrl, { data: [serverAsset.id], baseURL: remote });
|
|
391
427
|
}
|
|
428
|
+
await publishAsset({
|
|
429
|
+
name,
|
|
430
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
431
|
+
appId: app.id,
|
|
432
|
+
path: file,
|
|
433
|
+
remote,
|
|
434
|
+
seed: true,
|
|
435
|
+
clonable: assetsClonable,
|
|
436
|
+
});
|
|
392
437
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
438
|
+
const removedSeedAssetIds = serverAssets
|
|
439
|
+
.filter(({ name, seed }) => seed && !files.has(name))
|
|
440
|
+
.map(({ id }) => id);
|
|
441
|
+
if (removedSeedAssetIds.length) {
|
|
442
|
+
logger.info(`Deleting ${removedSeedAssetIds.length} seed asset(s) without a local file`);
|
|
443
|
+
await axios.delete(assetsUrl, { data: removedSeedAssetIds, baseURL: remote });
|
|
396
444
|
}
|
|
397
445
|
}
|
|
398
|
-
|
|
399
|
-
logger.
|
|
446
|
+
catch (error) {
|
|
447
|
+
logger.error('Something went wrong when creating assets:');
|
|
448
|
+
logger.error(error);
|
|
400
449
|
}
|
|
401
450
|
}
|
|
402
451
|
function parseValueFromDefinition(value) {
|
|
@@ -899,6 +948,7 @@ export async function publishApp({ clientCredentials, context, dryRun, modifyCon
|
|
|
899
948
|
logger.info(`Setting TOTP to ${totp}`);
|
|
900
949
|
formData.append('totp', totp);
|
|
901
950
|
}
|
|
951
|
+
const seedAssetFiles = assets && file.isDirectory() ? await getSeedAssetFiles(join(appVariantPath, 'assets')) : null;
|
|
902
952
|
let authScope = 'apps:write';
|
|
903
953
|
if (resources) {
|
|
904
954
|
authScope += ' resources:write';
|
|
@@ -939,7 +989,7 @@ export async function publishApp({ clientCredentials, context, dryRun, modifyCon
|
|
|
939
989
|
await publishAppConfig(appVariantPath, data, remote);
|
|
940
990
|
// After uploading the app, publish seed resources and assets if they are available
|
|
941
991
|
if (assets) {
|
|
942
|
-
await publishSeedAssets(appVariantPath, data, remote, assetsClonable);
|
|
992
|
+
await publishSeedAssets(appVariantPath, seedAssetFiles, data, remote, assetsClonable);
|
|
943
993
|
}
|
|
944
994
|
if (resources) {
|
|
945
995
|
await publishSeedResources(appVariantPath, data, remote);
|
|
@@ -1119,6 +1169,7 @@ export async function updateApp({ clientCredentials, context, force, path, ...op
|
|
|
1119
1169
|
logger.info(`Setting TOTP to ${totp}`);
|
|
1120
1170
|
formData.append('totp', totp);
|
|
1121
1171
|
}
|
|
1172
|
+
const seedAssetFiles = assets && file.isDirectory() ? await getSeedAssetFiles(join(appVariantPath, 'assets')) : null;
|
|
1122
1173
|
let authScope = 'apps:write';
|
|
1123
1174
|
if (resources) {
|
|
1124
1175
|
authScope += ' resources:write';
|
|
@@ -1163,7 +1214,7 @@ export async function updateApp({ clientCredentials, context, force, path, ...op
|
|
|
1163
1214
|
await publishAppConfig(appVariantPath, data, remote);
|
|
1164
1215
|
// After updating the app, publish seed resources and assets if they are available
|
|
1165
1216
|
if (assets && (data.locked !== 'fullLock' || force)) {
|
|
1166
|
-
await publishSeedAssets(appVariantPath, data, remote, assetsClonable);
|
|
1217
|
+
await publishSeedAssets(appVariantPath, seedAssetFiles, data, remote, assetsClonable);
|
|
1167
1218
|
}
|
|
1168
1219
|
if (resources && (data.locked !== 'fullLock' || force)) {
|
|
1169
1220
|
await publishSeedResources(appVariantPath, data, remote);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appsemble/cli",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.10",
|
|
4
4
|
"description": "The CLI for developing with Appsemble apps and blocks",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"app",
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"test": "vitest"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@appsemble/node-utils": "0.40.
|
|
47
|
-
"@appsemble/types": "0.40.
|
|
48
|
-
"@appsemble/lang-sdk": "0.40.
|
|
49
|
-
"@appsemble/utils": "0.40.
|
|
46
|
+
"@appsemble/node-utils": "0.40.10",
|
|
47
|
+
"@appsemble/types": "0.40.10",
|
|
48
|
+
"@appsemble/lang-sdk": "0.40.10",
|
|
49
|
+
"@appsemble/utils": "0.40.10",
|
|
50
50
|
"@fortawesome/fontawesome-common-types": "^6.0.0",
|
|
51
51
|
"@inquirer/prompts": "^8.0.0",
|
|
52
52
|
"axios": "^1.0.0",
|
|
@@ -77,8 +77,8 @@
|
|
|
77
77
|
"yargs": "^17.0.0"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
-
"@appsemble/types": "0.40.
|
|
81
|
-
"@appsemble/server": "0.40.
|
|
80
|
+
"@appsemble/types": "0.40.10",
|
|
81
|
+
"@appsemble/server": "0.40.10",
|
|
82
82
|
"@types/concat-stream": "2.0.3",
|
|
83
83
|
"@types/normalize-path": "3.0.2",
|
|
84
84
|
"@types/postcss-import": "14.0.3",
|
|
@@ -6,7 +6,7 @@ pages:
|
|
|
6
6
|
- name: Example Page A
|
|
7
7
|
blocks:
|
|
8
8
|
- type: action-button
|
|
9
|
-
version: 0.40.
|
|
9
|
+
version: 0.40.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.40.
|
|
20
|
+
version: 0.40.10
|
|
21
21
|
parameters:
|
|
22
22
|
icon: arrow-left
|
|
23
23
|
actions:
|