@appsemble/cli 0.23.5 → 0.23.7

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.23.5/config/assets/logo.svg) Appsemble CLI
1
+ # ![](https://gitlab.com/appsemble/appsemble/-/raw/0.23.7/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.23.5/pipeline.svg)](https://gitlab.com/appsemble/appsemble/-/releases/0.23.5)
6
+ [![GitLab CI](https://gitlab.com/appsemble/appsemble/badges/0.23.7/pipeline.svg)](https://gitlab.com/appsemble/appsemble/-/releases/0.23.7)
7
7
  [![Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](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.5/LICENSE.md) ©
307
+ [LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.23.7/LICENSE.md) ©
308
308
  [Appsemble](https://appsemble.com)
@@ -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 { buildBlock, getBlockConfig } from '../../lib/block.js';
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 config = await getBlockConfig(dir);
19
- await buildBlock(config);
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 { getBlockConfig, processBlockMessages } from '../../lib/block.js';
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 config = await getBlockConfig(dir);
26
- logger.info(`Processing ${config.name}`);
27
- await processBlockMessages(config, langs);
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, getBlockConfig, publishBlock } from '../../lib/block.js';
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 config = await getBlockConfig(dir);
32
+ const buildConfig = await getProjectBuildConfig(dir);
31
33
  if (build) {
32
- await buildBlock(config);
34
+ await buildBlock(buildConfig);
33
35
  }
34
- await publishBlock(config, ignoreConflict);
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, getBlockConfig, makePayload } from '../lib/block.js';
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 { loadWebpackConfig } from '../lib/loadWebpackConfig.js';
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 blockConfig = await getBlockConfig(join(process.cwd(), 'blocks', blockName));
113
+ const buildConfig = await getProjectBuildConfig(join(process.cwd(), 'blocks', blockName));
113
114
  return {
114
- ...blockConfig,
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 [, blockManifest] = await makePayload(blockConfig);
121
- blockManifest.version = localIdentifiableBlocks.find((identifiableBlock) => identifiableBlock.type === blockManifest.name).version;
122
- return blockManifest;
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) => loadWebpackConfig(blockConfig, 'development', join(blockConfig.dir, blockConfig.output))));
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 { discoverBlocks } from '../lib/block.js';
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 blocks = await discoverBlocks(process.cwd());
130
- const webpackConfigs = await Promise.all(blocks.map((block) => loadWebpackConfig(block, 'development')));
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
- await authenticate(remote, resources ? 'apps:write resources:write' : 'apps:write', clientCredentials);
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 BlockConfig, type BlockManifest } from '@appsemble/types';
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(config: BlockConfig, languages: string[]): Promise<void>;
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 { createReadStream, existsSync } from 'node:fs';
2
- import { mkdir, readdir, readFile, rm } from 'node:fs/promises';
3
- import { basename, extname, join, relative, resolve as resolvePath } from 'node:path';
4
- import { inspect } from 'node:util';
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 { getBlockConfigFromTypeScript } from './getBlockConfigFromTypeScript.js';
13
- import { loadWebpackConfig } from './loadWebpackConfig.js';
8
+ import { getProjectImplementations, getProjectWebpackConfig } from './config.js';
14
9
  import { processCss } from './processCss.js';
15
- export async function buildBlock(config) {
16
- const conf = await loadWebpackConfig(config, 'production', join(config.dir, config.output));
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 ${config.name}@${config.version} 🔨`);
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(config, languages) {
270
- const path = join(config.dir, 'i18n');
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 } = getBlockConfigFromTypeScript(config);
103
+ const { messages } = getProjectImplementations(buildConfig);
274
104
  if (!messages) {
275
- logger.warn(`Block ${config.name} has no messages.`);
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 ${config.name}.`);
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 { makePayload } from './block.js';
5
- describe('makePayload', () => {
4
+ import { makeProjectPayload } from './project.js';
5
+ describe('makeProjectPayload', () => {
6
6
  it('should create a form-data payload', async () => {
7
- const payload = await makePayload({
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('makePayload/no-icon'),
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 makePayload({
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('makePayload/with-icon'),
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
@@ -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>;