@appsemble/cli 0.23.5 → 0.23.6
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/block/build.js +4 -3
- package/commands/block/extract-messages.js +5 -4
- package/commands/block/publish.js +6 -4
- package/commands/serve.js +11 -8
- package/commands/start.js +3 -4
- package/lib/app.js +23 -1
- package/lib/block.d.ts +9 -34
- package/lib/block.js +25 -195
- package/lib/block.test.js +10 -16
- package/lib/config.d.ts +40 -0
- package/lib/{getBlockConfigFromTypeScript.js → config.js} +164 -68
- package/lib/{getBlockConfigFromTypeScript.test.js → config.test.js} +32 -32
- package/lib/project.d.ts +12 -0
- package/lib/project.js +145 -0
- package/package.json +6 -5
- package/server/options/createSettings.js +19 -4
- package/server/options/getBlockAsset.d.ts +2 -2
- package/server/options/getBlockAsset.js +2 -0
- 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/getBlockConfigFromTypeScript.d.ts +0 -10
- package/lib/loadWebpackConfig.d.ts +0 -15
- package/lib/loadWebpackConfig.js +0 -45
- /package/__fixtures__/{makePayload → makeProjectPayload}/no-icon/output/block.js +0 -0
- /package/__fixtures__/{makePayload → makeProjectPayload}/with-icon/output/block.js +0 -0
- /package/lib/{getBlockConfigFromTypeScript.test.d.ts → config.test.d.ts} +0 -0
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.23.6)
|
|
7
7
|
[](https://prettier.io)
|
|
8
8
|
|
|
9
9
|
## Table of Contents
|
|
@@ -304,5 +304,5 @@ appsemble run-cronjobs --interval 30
|
|
|
304
304
|
|
|
305
305
|
## License
|
|
306
306
|
|
|
307
|
-
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.23.
|
|
307
|
+
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.23.6/LICENSE.md) ©
|
|
308
308
|
[Appsemble](https://appsemble.com)
|
package/commands/block/build.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { logger } from '@appsemble/node-utils';
|
|
2
2
|
import fg from 'fast-glob';
|
|
3
3
|
import normalizePath from 'normalize-path';
|
|
4
|
-
import {
|
|
4
|
+
import { getProjectBuildConfig } from '../../lib/config.js';
|
|
5
|
+
import { buildProject } from '../../lib/project.js';
|
|
5
6
|
export const command = 'build <paths...>';
|
|
6
7
|
export const description = 'Build a block without publishing.';
|
|
7
8
|
export function builder(yargs) {
|
|
@@ -15,8 +16,8 @@ export async function handler({ paths }) {
|
|
|
15
16
|
logger.info(`Building ${directories.length} Blocks`);
|
|
16
17
|
for (const dir of directories) {
|
|
17
18
|
logger.info('');
|
|
18
|
-
const
|
|
19
|
-
await
|
|
19
|
+
const buildConfig = await getProjectBuildConfig(dir);
|
|
20
|
+
await buildProject(buildConfig);
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
//# sourceMappingURL=build.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { logger } from '@appsemble/node-utils';
|
|
2
2
|
import fg from 'fast-glob';
|
|
3
3
|
import normalizePath from 'normalize-path';
|
|
4
|
-
import {
|
|
4
|
+
import { processBlockMessages } from '../../lib/block.js';
|
|
5
|
+
import { getProjectBuildConfig } from '../../lib/config.js';
|
|
5
6
|
export const command = 'extract-messages <paths...>';
|
|
6
7
|
export const description = 'Extract messages from a block.';
|
|
7
8
|
export function builder(yargs) {
|
|
@@ -22,9 +23,9 @@ export async function handler({ languages, paths }) {
|
|
|
22
23
|
logger.info(`Extracting messages from ${directories.length} Blocks`);
|
|
23
24
|
for (const dir of directories) {
|
|
24
25
|
logger.info('');
|
|
25
|
-
const
|
|
26
|
-
logger.info(`Processing ${
|
|
27
|
-
await processBlockMessages(
|
|
26
|
+
const buildConfig = await getProjectBuildConfig(dir);
|
|
27
|
+
logger.info(`Processing ${buildConfig.name}`);
|
|
28
|
+
await processBlockMessages(buildConfig, langs);
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
//# sourceMappingURL=extract-messages.js.map
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { authenticate, logger } from '@appsemble/node-utils';
|
|
2
2
|
import fg from 'fast-glob';
|
|
3
3
|
import normalizePath from 'normalize-path';
|
|
4
|
-
import { buildBlock
|
|
4
|
+
import { buildBlock } from '../../lib/block.js';
|
|
5
|
+
import { getProjectBuildConfig } from '../../lib/config.js';
|
|
6
|
+
import { publishProject } from '../../lib/project.js';
|
|
5
7
|
export const command = 'publish <paths...>';
|
|
6
8
|
export const description = 'Publish a block.';
|
|
7
9
|
export function builder(yargs) {
|
|
@@ -27,11 +29,11 @@ export async function handler({ build, clientCredentials, ignoreConflict, paths,
|
|
|
27
29
|
logger.info(`Publishing ${directories.length} Blocks`);
|
|
28
30
|
for (const dir of directories) {
|
|
29
31
|
logger.info('');
|
|
30
|
-
const
|
|
32
|
+
const buildConfig = await getProjectBuildConfig(dir);
|
|
31
33
|
if (build) {
|
|
32
|
-
await buildBlock(
|
|
34
|
+
await buildBlock(buildConfig);
|
|
33
35
|
}
|
|
34
|
-
await
|
|
36
|
+
await publishProject(buildConfig, '/api/blocks', ignoreConflict);
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
//# sourceMappingURL=publish.js.map
|
package/commands/serve.js
CHANGED
|
@@ -10,9 +10,10 @@ import csvToJson from 'csvtojson';
|
|
|
10
10
|
import FormData from 'form-data';
|
|
11
11
|
import globalCacheDir from 'global-cache-dir';
|
|
12
12
|
import { traverseAppDirectory } from '../lib/app.js';
|
|
13
|
-
import { buildBlock
|
|
13
|
+
import { buildBlock } from '../lib/block.js';
|
|
14
|
+
import { getProjectBuildConfig, getProjectWebpackConfig } from '../lib/config.js';
|
|
14
15
|
import { createApiServer, createStaticServer } from '../lib/createServers.js';
|
|
15
|
-
import {
|
|
16
|
+
import { makeProjectPayload } from '../lib/project.js';
|
|
16
17
|
import { setArgv } from '../server/argv.js';
|
|
17
18
|
import { setAppName } from '../server/db/methods.js';
|
|
18
19
|
import { Resource } from '../server/models/Resource.js';
|
|
@@ -109,17 +110,19 @@ export async function handler(argv) {
|
|
|
109
110
|
}
|
|
110
111
|
const localBlocksConfigs = await Promise.all(localIdentifiableBlocks.map(async (identifiableBlock) => {
|
|
111
112
|
const [organization, blockName] = parseBlockName(identifiableBlock.type);
|
|
112
|
-
const
|
|
113
|
+
const buildConfig = await getProjectBuildConfig(join(process.cwd(), 'blocks', blockName));
|
|
113
114
|
return {
|
|
114
|
-
...
|
|
115
|
+
...buildConfig,
|
|
115
116
|
OrganizationId: organization,
|
|
116
117
|
};
|
|
117
118
|
}));
|
|
118
119
|
const localBlocksPromises = localBlocksConfigs.map(async (blockConfig) => {
|
|
119
120
|
await buildBlock(blockConfig);
|
|
120
|
-
const [,
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
const [, blockImplementations] = await makeProjectPayload(blockConfig);
|
|
122
|
+
return {
|
|
123
|
+
...blockImplementations,
|
|
124
|
+
...blockConfig,
|
|
125
|
+
};
|
|
123
126
|
});
|
|
124
127
|
const cacheDir = await globalCacheDir('appsemble');
|
|
125
128
|
const remoteBlocksPromises = remoteIdentifiableBlocks.map(async (identifiableBlock) => {
|
|
@@ -156,7 +159,7 @@ export async function handler(argv) {
|
|
|
156
159
|
const localBlocks = await Promise.all(localBlocksPromises);
|
|
157
160
|
const remoteBlocks = await Promise.all(remoteBlocksPromises);
|
|
158
161
|
const appBlocks = [...localBlocks, ...remoteBlocks];
|
|
159
|
-
const webpackConfigs = await Promise.all(localBlocksConfigs.map((blockConfig) =>
|
|
162
|
+
const webpackConfigs = await Promise.all(localBlocksConfigs.map((blockConfig) => getProjectWebpackConfig(blockConfig, 'development', join(blockConfig.dir, blockConfig.output))));
|
|
160
163
|
// Get app messages
|
|
161
164
|
const appMessages = [];
|
|
162
165
|
await opendirSafe(join(appPath, 'i18n'), async (path) => {
|
package/commands/start.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { loadWebpackConfig } from '../lib/loadWebpackConfig.js';
|
|
1
|
+
import { getProjectsBuildConfigs, getProjectWebpackConfig } from '../lib/config.js';
|
|
3
2
|
import { serverImport } from '../lib/serverImport.js';
|
|
4
3
|
export const command = 'start';
|
|
5
4
|
export const description = 'Start the Appsemble development server.';
|
|
@@ -126,8 +125,8 @@ export function builder(yargs) {
|
|
|
126
125
|
export async function handler(argv) {
|
|
127
126
|
const { setArgv, start } = await serverImport('setArgv', 'start');
|
|
128
127
|
setArgv(argv);
|
|
129
|
-
const
|
|
130
|
-
const webpackConfigs = await Promise.all(
|
|
128
|
+
const projectsBuildConfigs = await getProjectsBuildConfigs(process.cwd());
|
|
129
|
+
const webpackConfigs = await Promise.all(projectsBuildConfigs.map((projectBuildConfig) => getProjectWebpackConfig(projectBuildConfig, 'development')));
|
|
131
130
|
return start({ webpackConfigs });
|
|
132
131
|
}
|
|
133
132
|
//# sourceMappingURL=start.js.map
|
package/lib/app.js
CHANGED
|
@@ -8,8 +8,10 @@ import axios from 'axios';
|
|
|
8
8
|
import FormData from 'form-data';
|
|
9
9
|
import { traverseBlockThemes } from './block.js';
|
|
10
10
|
import { coerceRemote } from './coercers.js';
|
|
11
|
+
import { getProjectBuildConfig } from './config.js';
|
|
11
12
|
import { printAxiosError } from './output.js';
|
|
12
13
|
import { processCss } from './processCss.js';
|
|
14
|
+
import { buildProject, makeProjectPayload } from './project.js';
|
|
13
15
|
import { publishResource } from './resource.js';
|
|
14
16
|
/**
|
|
15
17
|
* Traverses an app directory and appends the files it finds to the given FormData object.
|
|
@@ -25,6 +27,11 @@ export async function traverseAppDirectory(path, context, formData) {
|
|
|
25
27
|
let iconPath;
|
|
26
28
|
let maskableIconPath;
|
|
27
29
|
let yaml;
|
|
30
|
+
let controllerPath;
|
|
31
|
+
let controllerBuildConfig;
|
|
32
|
+
let controllerBuildResult;
|
|
33
|
+
let controllerCode;
|
|
34
|
+
let controllerImplementations;
|
|
28
35
|
const gatheredData = {
|
|
29
36
|
screenshotUrls: [],
|
|
30
37
|
};
|
|
@@ -89,6 +96,17 @@ export async function traverseAppDirectory(path, context, formData) {
|
|
|
89
96
|
formData.append(`${name}Style`, css);
|
|
90
97
|
gatheredData[`${name}Style`] = css;
|
|
91
98
|
});
|
|
99
|
+
case 'controller':
|
|
100
|
+
controllerPath = join(path, 'controller');
|
|
101
|
+
controllerBuildConfig = await getProjectBuildConfig(controllerPath);
|
|
102
|
+
controllerBuildResult = await buildProject(controllerBuildConfig);
|
|
103
|
+
controllerCode = controllerBuildResult.outputFiles[0].text;
|
|
104
|
+
formData.append('controllerCode', controllerCode);
|
|
105
|
+
gatheredData.controllerCode = controllerCode;
|
|
106
|
+
[, controllerImplementations] = await makeProjectPayload(controllerBuildConfig);
|
|
107
|
+
formData.append('controllerImplementations', JSON.stringify(controllerImplementations));
|
|
108
|
+
gatheredData.controllerImplementations = controllerImplementations;
|
|
109
|
+
break;
|
|
92
110
|
default:
|
|
93
111
|
logger.warn(`Found unused file ${filepath}`);
|
|
94
112
|
}
|
|
@@ -461,7 +479,11 @@ export async function publishApp({ clientCredentials, context, dryRun, modifyCon
|
|
|
461
479
|
logger.info('Using Google Analytics');
|
|
462
480
|
formData.append('googleAnalyticsID', googleAnalyticsId);
|
|
463
481
|
}
|
|
464
|
-
|
|
482
|
+
let authScope = 'apps:write';
|
|
483
|
+
if (resources) {
|
|
484
|
+
authScope += ' resources:write';
|
|
485
|
+
}
|
|
486
|
+
await authenticate(remote, authScope, clientCredentials);
|
|
465
487
|
let data;
|
|
466
488
|
try {
|
|
467
489
|
({ data } = await axios.post('/api/apps', formData, {
|
package/lib/block.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import FormData from 'form-data';
|
|
1
|
+
import { type ProjectBuildConfig } from '@appsemble/types';
|
|
3
2
|
import { type Stats } from 'webpack';
|
|
4
3
|
/**
|
|
5
4
|
* Builds a block using Webpack.
|
|
@@ -7,44 +6,20 @@ import { type Stats } from 'webpack';
|
|
|
7
6
|
* @param config The config of the block to build.
|
|
8
7
|
* @returns The Webpack stats object.
|
|
9
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* Builds a block using Webpack.
|
|
11
|
+
*
|
|
12
|
+
* @param buildConfig The config of the block to build.
|
|
13
|
+
* @returns The Webpack stats object.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildBlock(buildConfig: ProjectBuildConfig): Promise<Stats>;
|
|
10
16
|
interface DeleteBlockVersionParams {
|
|
11
17
|
organization: string;
|
|
12
18
|
blockName: string;
|
|
13
19
|
blockVersion: string;
|
|
14
20
|
remote: string;
|
|
15
21
|
}
|
|
16
|
-
export declare function buildBlock(config: BlockConfig): Promise<Stats>;
|
|
17
22
|
export declare function deleteBlock({ blockName, blockVersion, organization, remote, }: DeleteBlockVersionParams): Promise<void>;
|
|
18
|
-
/**
|
|
19
|
-
* Get the block configuration from a block directory.
|
|
20
|
-
*
|
|
21
|
-
* @param dir The directory in which to search for the configuration file.
|
|
22
|
-
* @returns The block configuration.
|
|
23
|
-
*/
|
|
24
|
-
export declare function getBlockConfig(dir: string): Promise<BlockConfig>;
|
|
25
|
-
/**
|
|
26
|
-
* Discover Appsemble blocks based on workspaces in a monorepo.
|
|
27
|
-
*
|
|
28
|
-
* npm workspaces are supported.
|
|
29
|
-
*
|
|
30
|
-
* @param root The project root in which to find workspaces.
|
|
31
|
-
* @returns Discovered Appsemble blocks.
|
|
32
|
-
*/
|
|
33
|
-
export declare function discoverBlocks(root: string): Promise<BlockConfig[]>;
|
|
34
|
-
/**
|
|
35
|
-
* Configure the payload for a new block version upload.
|
|
36
|
-
*
|
|
37
|
-
* @param config The block configuration
|
|
38
|
-
* @returns The payload that should be sent to the version endpoint.
|
|
39
|
-
*/
|
|
40
|
-
export declare function makePayload(config: BlockConfig): Promise<[FormData, BlockManifest]>;
|
|
41
|
-
/**
|
|
42
|
-
* Publish a new block version.
|
|
43
|
-
*
|
|
44
|
-
* @param config The block configuration
|
|
45
|
-
* @param ignoreConflict Prevent the command from crashing when a conflict has been detected.
|
|
46
|
-
*/
|
|
47
|
-
export declare function publishBlock(config: BlockConfig, ignoreConflict: boolean): Promise<void>;
|
|
48
23
|
/**
|
|
49
24
|
* Uploads an app block theme
|
|
50
25
|
*
|
|
@@ -65,5 +40,5 @@ export declare function uploadAppBlockTheme(filePath: string, organization: stri
|
|
|
65
40
|
* @param force Force update the theme if the app is locked.
|
|
66
41
|
*/
|
|
67
42
|
export declare function traverseBlockThemes(path: string, appId: number, remote: string, force: boolean): Promise<void>;
|
|
68
|
-
export declare function processBlockMessages(
|
|
43
|
+
export declare function processBlockMessages(buildConfig: ProjectBuildConfig, languages: string[]): Promise<void>;
|
|
69
44
|
export {};
|
package/lib/block.js
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdir, readdir,
|
|
3
|
-
import { basename,
|
|
4
|
-
import {
|
|
5
|
-
import { AppsembleError, getWorkspaces, logger, opendirSafe, readData, writeData, } from '@appsemble/node-utils';
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, readdir, rm } from 'node:fs/promises';
|
|
3
|
+
import { basename, join } from 'node:path';
|
|
4
|
+
import { AppsembleError, logger, opendirSafe, readData, writeData } from '@appsemble/node-utils';
|
|
6
5
|
import { compareStrings } from '@appsemble/utils';
|
|
7
6
|
import axios from 'axios';
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
import { cosmiconfig } from 'cosmiconfig';
|
|
10
|
-
import FormData from 'form-data';
|
|
11
7
|
import webpack from 'webpack';
|
|
12
|
-
import {
|
|
13
|
-
import { loadWebpackConfig } from './loadWebpackConfig.js';
|
|
8
|
+
import { getProjectImplementations, getProjectWebpackConfig } from './config.js';
|
|
14
9
|
import { processCss } from './processCss.js';
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Builds a block using Webpack.
|
|
12
|
+
*
|
|
13
|
+
* @param config The config of the block to build.
|
|
14
|
+
* @returns The Webpack stats object.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Builds a block using Webpack.
|
|
18
|
+
*
|
|
19
|
+
* @param buildConfig The config of the block to build.
|
|
20
|
+
* @returns The Webpack stats object.
|
|
21
|
+
*/
|
|
22
|
+
export async function buildBlock(buildConfig) {
|
|
23
|
+
const conf = await getProjectWebpackConfig(buildConfig, 'production', join(buildConfig.dir, buildConfig.output));
|
|
17
24
|
if (existsSync(conf.output.path)) {
|
|
18
25
|
logger.warn(`Removing ${conf.output.path}`);
|
|
19
26
|
await rm(conf.output.path, { force: true, recursive: true });
|
|
20
27
|
}
|
|
21
|
-
logger.info(`Building ${
|
|
28
|
+
logger.info(`Building ${buildConfig.name}@${buildConfig.version} 🔨`);
|
|
22
29
|
const compiler = webpack(conf);
|
|
23
30
|
return new Promise((resolve, reject) => {
|
|
24
31
|
compiler.run((err, stats) => {
|
|
@@ -41,183 +48,6 @@ export async function deleteBlock({ blockName, blockVersion, organization, remot
|
|
|
41
48
|
});
|
|
42
49
|
logger.info(`Successfully deleted ${blockName} block version.`);
|
|
43
50
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Get the block configuration from a block directory.
|
|
46
|
-
*
|
|
47
|
-
* @param dir The directory in which to search for the configuration file.
|
|
48
|
-
* @returns The block configuration.
|
|
49
|
-
*/
|
|
50
|
-
export async function getBlockConfig(dir) {
|
|
51
|
-
const explorer = cosmiconfig('appsemble', { stopDir: dir });
|
|
52
|
-
const found = await explorer.search(dir);
|
|
53
|
-
if (!found) {
|
|
54
|
-
throw new AppsembleError(`No Appsemble configuration file found searching ${dir}`);
|
|
55
|
-
}
|
|
56
|
-
const { config, filepath } = found;
|
|
57
|
-
logger.info(`Found configuration file: ${filepath}`);
|
|
58
|
-
const [pkg] = await readData(join(dir, 'package.json'));
|
|
59
|
-
if (!pkg.private) {
|
|
60
|
-
logger.warn(`It is ${chalk.underline.yellow('highly recommended')} to set “${chalk.green('"private"')}: ${chalk.cyan('true')}” in package.json`);
|
|
61
|
-
}
|
|
62
|
-
let longDescription;
|
|
63
|
-
if (existsSync(join(dir, 'README.md'))) {
|
|
64
|
-
longDescription = await readFile(join(dir, 'README.md'), 'utf8');
|
|
65
|
-
}
|
|
66
|
-
const result = {
|
|
67
|
-
description: pkg.description,
|
|
68
|
-
longDescription,
|
|
69
|
-
name: pkg.name,
|
|
70
|
-
version: pkg.version,
|
|
71
|
-
webpack: undefined,
|
|
72
|
-
...config,
|
|
73
|
-
dir,
|
|
74
|
-
};
|
|
75
|
-
logger.verbose(`Resolved block configuration: ${inspect(result, { colors: true })}`);
|
|
76
|
-
return result;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Discover Appsemble blocks based on workspaces in a monorepo.
|
|
80
|
-
*
|
|
81
|
-
* npm workspaces are supported.
|
|
82
|
-
*
|
|
83
|
-
* @param root The project root in which to find workspaces.
|
|
84
|
-
* @returns Discovered Appsemble blocks.
|
|
85
|
-
*/
|
|
86
|
-
export async function discoverBlocks(root) {
|
|
87
|
-
const dirs = await getWorkspaces(root);
|
|
88
|
-
const manifests = await Promise.all(dirs
|
|
89
|
-
.concat(root)
|
|
90
|
-
.map((path) => getBlockConfig(path))
|
|
91
|
-
// Ignore non-block workspaces.
|
|
92
|
-
.map((p) => p.catch(() => null)));
|
|
93
|
-
return manifests.filter(Boolean);
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Configure the payload for a new block version upload.
|
|
97
|
-
*
|
|
98
|
-
* @param config The block configuration
|
|
99
|
-
* @returns The payload that should be sent to the version endpoint.
|
|
100
|
-
*/
|
|
101
|
-
export async function makePayload(config) {
|
|
102
|
-
const { dir, output } = config;
|
|
103
|
-
const distPath = resolvePath(dir, output);
|
|
104
|
-
const form = new FormData();
|
|
105
|
-
const gatheredData = {};
|
|
106
|
-
const { description, layout, longDescription, name, version, visibility } = config;
|
|
107
|
-
const { actions, events, messages, parameters } = getBlockConfigFromTypeScript(config);
|
|
108
|
-
const files = await readdir(dir);
|
|
109
|
-
const icon = files.find((entry) => entry.match(/^icon\.(png|svg)$/));
|
|
110
|
-
function append(field, value) {
|
|
111
|
-
if (value) {
|
|
112
|
-
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
113
|
-
logger.verbose(`Using ${field}: ${inspect(value, { colors: true, depth: 20 })}`);
|
|
114
|
-
form.append(field, serialized);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
logger.silly(`Skipping parameter ${field}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
append('actions', actions);
|
|
121
|
-
gatheredData.actions = actions;
|
|
122
|
-
append('description', description);
|
|
123
|
-
gatheredData.description = description;
|
|
124
|
-
append('longDescription', longDescription);
|
|
125
|
-
gatheredData.longDescription = longDescription;
|
|
126
|
-
append('events', events);
|
|
127
|
-
gatheredData.events = events;
|
|
128
|
-
append('layout', layout);
|
|
129
|
-
gatheredData.layout = layout;
|
|
130
|
-
if (visibility) {
|
|
131
|
-
append('visibility', visibility);
|
|
132
|
-
gatheredData.visibility = visibility;
|
|
133
|
-
}
|
|
134
|
-
append('name', name);
|
|
135
|
-
gatheredData.name = name;
|
|
136
|
-
append('parameters', parameters);
|
|
137
|
-
gatheredData.parameters = parameters;
|
|
138
|
-
append('version', version);
|
|
139
|
-
gatheredData.version = version;
|
|
140
|
-
if (icon) {
|
|
141
|
-
const iconPath = join(dir, icon);
|
|
142
|
-
logger.info(`Using icon: ${iconPath}`);
|
|
143
|
-
form.append('icon', createReadStream(iconPath));
|
|
144
|
-
gatheredData.iconUrl = basename(iconPath, extname(iconPath));
|
|
145
|
-
}
|
|
146
|
-
if (messages) {
|
|
147
|
-
if (!files.includes('i18n')) {
|
|
148
|
-
throw new AppsembleError('This block has messages defined, but the message files could not be found. Try running extract-messages');
|
|
149
|
-
}
|
|
150
|
-
const messageKeys = Object.keys(messages).sort(compareStrings);
|
|
151
|
-
const messagesResult = {};
|
|
152
|
-
const messagesPath = join(dir, 'i18n');
|
|
153
|
-
const translations = (await readdir(messagesPath)).map((language) => language.toLowerCase());
|
|
154
|
-
if (!translations.includes('en.json')) {
|
|
155
|
-
throw new AppsembleError('Could not find ‘en.json’. Try running extract-messages');
|
|
156
|
-
}
|
|
157
|
-
const duplicates = translations.filter((language, index) => translations.indexOf(language) !== index);
|
|
158
|
-
if (duplicates.length) {
|
|
159
|
-
throw new AppsembleError(`Found duplicate language codes: ‘${duplicates.join('’, ')}`);
|
|
160
|
-
}
|
|
161
|
-
for (const languageFile of translations.filter((t) => t.endsWith('.json'))) {
|
|
162
|
-
const language = basename(languageFile, '.json');
|
|
163
|
-
const languagePath = join(messagesPath, languageFile);
|
|
164
|
-
const [m] = await readData(languagePath);
|
|
165
|
-
const languageKeys = Object.keys(m).sort(compareStrings);
|
|
166
|
-
if (languageKeys.length !== messageKeys.length ||
|
|
167
|
-
languageKeys.some((key) => !messageKeys.includes(key))) {
|
|
168
|
-
throw new AppsembleError(`‘${languagePath}’ contains mismatching message keys. Try running extract-messages`);
|
|
169
|
-
}
|
|
170
|
-
logger.info(`Including ${language} translations from ‘${languagePath}’`);
|
|
171
|
-
messagesResult[language] = m;
|
|
172
|
-
}
|
|
173
|
-
form.append('messages', JSON.stringify(messagesResult));
|
|
174
|
-
gatheredData.messages = messagesResult;
|
|
175
|
-
}
|
|
176
|
-
if (files.includes('examples')) {
|
|
177
|
-
await opendirSafe(join(dir, 'examples'), async (file, stat) => {
|
|
178
|
-
if (!stat.isFile() || !file.endsWith('.yaml')) {
|
|
179
|
-
throw new AppsembleError(`Expected ${file} to be a YAML file`);
|
|
180
|
-
}
|
|
181
|
-
logger.info(`Adding example file ${file}`);
|
|
182
|
-
form.append('examples', await readFile(file, 'utf8'));
|
|
183
|
-
gatheredData.examples = [...(gatheredData.examples || []), basename(file, extname(file))];
|
|
184
|
-
}, { allowMissing: true });
|
|
185
|
-
}
|
|
186
|
-
await opendirSafe(distPath, (fullpath, stat) => {
|
|
187
|
-
if (!stat.isFile()) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const relativePath = relative(distPath, fullpath);
|
|
191
|
-
const realPath = relative(process.cwd(), fullpath);
|
|
192
|
-
logger.info(`Adding file: “${realPath}” as “${relativePath}”`);
|
|
193
|
-
form.append('files', createReadStream(fullpath), {
|
|
194
|
-
filename: encodeURIComponent(relativePath),
|
|
195
|
-
});
|
|
196
|
-
gatheredData.files = [...(gatheredData.files || []), relativePath];
|
|
197
|
-
}, { recursive: true });
|
|
198
|
-
return [form, gatheredData];
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Publish a new block version.
|
|
202
|
-
*
|
|
203
|
-
* @param config The block configuration
|
|
204
|
-
* @param ignoreConflict Prevent the command from crashing when a conflict has been detected.
|
|
205
|
-
*/
|
|
206
|
-
export async function publishBlock(config, ignoreConflict) {
|
|
207
|
-
logger.info(`Publishing ${config.name}@${config.version}…`);
|
|
208
|
-
const [form] = await makePayload(config);
|
|
209
|
-
try {
|
|
210
|
-
await axios.post('/api/blocks', form);
|
|
211
|
-
logger.info(`Successfully published ${config.name}@${config.version} 🎉`);
|
|
212
|
-
}
|
|
213
|
-
catch (err) {
|
|
214
|
-
if (ignoreConflict && axios.isAxiosError(err) && err.response.status === 409) {
|
|
215
|
-
logger.warn(`${config.name}@${config.version} was already published.`);
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
throw err;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
51
|
/**
|
|
222
52
|
* Uploads an app block theme
|
|
223
53
|
*
|
|
@@ -266,13 +96,13 @@ export async function traverseBlockThemes(path, appId, remote, force) {
|
|
|
266
96
|
});
|
|
267
97
|
}, { allowMissing: true });
|
|
268
98
|
}
|
|
269
|
-
export async function processBlockMessages(
|
|
270
|
-
const path = join(
|
|
99
|
+
export async function processBlockMessages(buildConfig, languages) {
|
|
100
|
+
const path = join(buildConfig.dir, 'i18n');
|
|
271
101
|
await mkdir(path, { recursive: true });
|
|
272
102
|
const dir = await readdir(path);
|
|
273
|
-
const { messages } =
|
|
103
|
+
const { messages } = getProjectImplementations(buildConfig);
|
|
274
104
|
if (!messages) {
|
|
275
|
-
logger.warn(`Block ${
|
|
105
|
+
logger.warn(`Block ${buildConfig.name} has no messages.`);
|
|
276
106
|
return;
|
|
277
107
|
}
|
|
278
108
|
const keys = Object.keys(messages).sort(compareStrings);
|
|
@@ -298,7 +128,7 @@ export async function processBlockMessages(config, languages) {
|
|
|
298
128
|
await writeData(langPath, existingMessages);
|
|
299
129
|
logger.info(`Wrote to file ‘${langPath}’`);
|
|
300
130
|
}
|
|
301
|
-
logger.info(`Finished extracting messages for ${
|
|
131
|
+
logger.info(`Finished extracting messages for ${buildConfig.name}.`);
|
|
302
132
|
logger.info('');
|
|
303
133
|
}
|
|
304
134
|
//# sourceMappingURL=block.js.map
|
package/lib/block.test.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { resolveFixture } from '@appsemble/node-utils';
|
|
2
2
|
import concat from 'concat-stream';
|
|
3
3
|
import { describe, expect, it } from 'vitest';
|
|
4
|
-
import {
|
|
5
|
-
describe('
|
|
4
|
+
import { makeProjectPayload } from './project.js';
|
|
5
|
+
describe('makeProjectPayload', () => {
|
|
6
6
|
it('should create a form-data payload', async () => {
|
|
7
|
-
const payload = await
|
|
7
|
+
const payload = await makeProjectPayload({
|
|
8
8
|
webpack: 'webpack.config',
|
|
9
9
|
name: '@org/block',
|
|
10
10
|
output: 'output',
|
|
11
11
|
version: '1.2.3',
|
|
12
|
-
dir: resolveFixture('
|
|
13
|
-
parameters: { type: 'object' },
|
|
14
|
-
actions: { onClick: {} },
|
|
15
|
-
events: { listen: { test: {} } },
|
|
12
|
+
dir: resolveFixture('makeProjectPayload/no-icon'),
|
|
16
13
|
});
|
|
17
14
|
const [formData] = payload;
|
|
18
15
|
const boundary = formData.getBoundary();
|
|
@@ -34,7 +31,7 @@ Content-Disposition: form-data; name="name"\r
|
|
|
34
31
|
--${boundary}\r
|
|
35
32
|
Content-Disposition: form-data; name="parameters"\r
|
|
36
33
|
\r
|
|
37
|
-
{"type":"object"}\r
|
|
34
|
+
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"type":{"type":"object"}},"required":["type"],"additionalProperties":false}\r
|
|
38
35
|
--${boundary}\r
|
|
39
36
|
Content-Disposition: form-data; name="version"\r
|
|
40
37
|
\r
|
|
@@ -49,15 +46,12 @@ export const string = 'no-icon';
|
|
|
49
46
|
`);
|
|
50
47
|
});
|
|
51
48
|
it('should include an icon if one is present', async () => {
|
|
52
|
-
const payload = await
|
|
49
|
+
const payload = await makeProjectPayload({
|
|
53
50
|
webpack: 'webpack.config',
|
|
54
51
|
name: '@org/block',
|
|
55
52
|
output: 'output',
|
|
56
53
|
version: '1.2.3',
|
|
57
|
-
dir: resolveFixture('
|
|
58
|
-
parameters: {},
|
|
59
|
-
actions: {},
|
|
60
|
-
events: {},
|
|
54
|
+
dir: resolveFixture('makeProjectPayload/with-icon'),
|
|
61
55
|
});
|
|
62
56
|
const [formData] = payload;
|
|
63
57
|
const boundary = formData.getBoundary();
|
|
@@ -67,11 +61,11 @@ export const string = 'no-icon';
|
|
|
67
61
|
expect(String(buffer)).toBe(`--${boundary}\r
|
|
68
62
|
Content-Disposition: form-data; name="actions"\r
|
|
69
63
|
\r
|
|
70
|
-
{}\r
|
|
64
|
+
{"onClick":{}}\r
|
|
71
65
|
--${boundary}\r
|
|
72
66
|
Content-Disposition: form-data; name="events"\r
|
|
73
67
|
\r
|
|
74
|
-
{}\r
|
|
68
|
+
{"listen":{"test":{}}}\r
|
|
75
69
|
--${boundary}\r
|
|
76
70
|
Content-Disposition: form-data; name="name"\r
|
|
77
71
|
\r
|
|
@@ -79,7 +73,7 @@ Content-Disposition: form-data; name="name"\r
|
|
|
79
73
|
--${boundary}\r
|
|
80
74
|
Content-Disposition: form-data; name="parameters"\r
|
|
81
75
|
\r
|
|
82
|
-
{}\r
|
|
76
|
+
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"type":{"type":"object"}},"required":["type"],"additionalProperties":false}\r
|
|
83
77
|
--${boundary}\r
|
|
84
78
|
Content-Disposition: form-data; name="version"\r
|
|
85
79
|
\r
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type ProjectBuildConfig, type ProjectImplementations } from '@appsemble/types';
|
|
2
|
+
import { type Configuration } from 'webpack';
|
|
3
|
+
/**
|
|
4
|
+
* Get the build configuration from a project directory.
|
|
5
|
+
*
|
|
6
|
+
* @param dir The directory in which to search for the configuration file.
|
|
7
|
+
* @returns The configuration.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getProjectBuildConfig(dir: string): Promise<ProjectBuildConfig>;
|
|
10
|
+
/**
|
|
11
|
+
* Discover Appsemble projects based on workspaces in a monorepo.
|
|
12
|
+
*
|
|
13
|
+
* Both Lerna and Yarn workspaces are supported.
|
|
14
|
+
*
|
|
15
|
+
* @param root The project root in which to find workspaces.
|
|
16
|
+
* @returns Discovered Appsemble projects.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getProjectsBuildConfigs(root: string): Promise<ProjectBuildConfig[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Generate project implementations from the project metadata and TypeScript project.
|
|
21
|
+
*
|
|
22
|
+
* Uses the .appsemblerc file and the type definitions of the project.
|
|
23
|
+
*
|
|
24
|
+
* @param buildConfig The project build configuration
|
|
25
|
+
* @returns The project implementations from the TypeScript project.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getProjectImplementations(buildConfig: ProjectBuildConfig): ProjectImplementations;
|
|
28
|
+
/**
|
|
29
|
+
* Load a webpack configuration file.
|
|
30
|
+
*
|
|
31
|
+
* A webpack configuration file may export either an webpack configuration object, or a synchronous
|
|
32
|
+
* or asynchronous function which returns a webpack configuration object. This function supports
|
|
33
|
+
* all 3 use cases.
|
|
34
|
+
*
|
|
35
|
+
* @param buildConfig The project build config.
|
|
36
|
+
* @param mode The env that would be passed to webpack by invoking `webpack --env $env`.
|
|
37
|
+
* @param outputPath The path where the build will be output on disk.
|
|
38
|
+
* @returns The webpack configuration as exposed by the webpack configuration file.
|
|
39
|
+
*/
|
|
40
|
+
export declare function getProjectWebpackConfig(buildConfig: ProjectBuildConfig, mode?: 'development' | 'production', outputPath?: string): Promise<Configuration>;
|