@appsemble/cli 0.25.2 → 0.27.0

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.25.2/config/assets/logo.svg) Appsemble CLI
1
+ # ![](https://gitlab.com/appsemble/appsemble/-/raw/0.27.0/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.25.2/pipeline.svg)](https://gitlab.com/appsemble/appsemble/-/releases/0.25.2)
6
+ [![GitLab CI](https://gitlab.com/appsemble/appsemble/badges/0.27.0/pipeline.svg)](https://gitlab.com/appsemble/appsemble/-/releases/0.27.0)
7
7
  [![Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io)
8
8
 
9
9
  ## Table of Contents
@@ -169,9 +169,10 @@ To export an app as a zip file, run the following:
169
169
  appsemble app export --id <app-id>
170
170
  ```
171
171
 
172
- Supported arguments for this app are `resources` and `path`. Resources is used to specify whether to
173
- include the resources in the exported file and path is the path of the folder where you want to put
174
- your downloaded file. Path is `./apps` by default and resources is set to `false`.
172
+ Supported arguments for this app are `resources`, `assets` and `path`. Resources is used to specify
173
+ whether to include the resources in the exported file, the same can be applied to assets and path is
174
+ the path of the folder where you want to put your downloaded file. The default path is `./apps`, and
175
+ resources and assets are by default `false`.
175
176
 
176
177
  To import an app from a zip file, use the following command:
177
178
 
@@ -304,5 +305,5 @@ appsemble run-cronjobs --interval 30
304
305
 
305
306
  ## License
306
307
 
307
- [LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.25.2/LICENSE.md) ©
308
+ [LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.27.0/LICENSE.md) ©
308
309
  [Appsemble](https://appsemble.com)
@@ -136,6 +136,7 @@ export async function handler(args) {
136
136
  roles: ['$public'],
137
137
  schema: {
138
138
  type: 'object',
139
+ properties: {},
139
140
  additionalProperties: false,
140
141
  },
141
142
  },
@@ -1,12 +1,13 @@
1
1
  import { type Argv } from 'yargs';
2
2
  import { type BaseArguments } from '../../types.js';
3
3
  interface ExportAppArgs extends BaseArguments {
4
+ assets: boolean;
4
5
  id: number;
5
6
  resources: boolean;
6
7
  path?: string;
7
8
  }
8
9
  export declare const command = "export";
9
- export declare const description = "Export an app as a zip file using app id.\n App definition, styles and messages are exported by default, to export resources, make sure you have suitable permissions and use the '--resources' flag.";
10
+ export declare const description = "Export an app as a zip file using app id.\n App definition, styles, icon and messages are exported by default, to export resources, make sure you have suitable permissions and use the '--resources' flag.\n Similarly for assets use the --assets flag to include assets in the export.";
10
11
  export declare function builder(yargs: Argv): Argv<any>;
11
12
  export declare function handler(args: ExportAppArgs): Promise<void>;
12
13
  export {};
@@ -3,7 +3,8 @@ import { authenticate, logger } from '@appsemble/node-utils';
3
3
  import { exportAppAsZip } from '../../lib/app.js';
4
4
  export const command = 'export';
5
5
  export const description = `Export an app as a zip file using app id.
6
- App definition, styles and messages are exported by default, to export resources, make sure you have suitable permissions and use the '--resources' flag.`;
6
+ App definition, styles, icon and messages are exported by default, to export resources, make sure you have suitable permissions and use the '--resources' flag.
7
+ Similarly for assets use the --assets flag to include assets in the export.`;
7
8
  export function builder(yargs) {
8
9
  return yargs
9
10
  .option('id', {
@@ -12,18 +13,20 @@ export function builder(yargs) {
12
13
  })
13
14
  .option('resources', {
14
15
  describe: 'Boolean representing whether the resources should be exported or not',
15
- default: false,
16
+ })
17
+ .option('assets', {
18
+ describe: 'Boolean representing whether assets should be exported.',
16
19
  })
17
20
  .option('path', {
18
21
  describe: 'Path of the folder where you want to put your exported file.',
19
22
  });
20
23
  }
21
24
  export async function handler(args) {
22
- const { clientCredentials, id: appId, path: outputDirectory, remote, resources } = args;
25
+ const { assets = false, clientCredentials, id: appId, path: outputDirectory, remote, resources = false, } = args;
23
26
  const defaultOutputDirectory = join(process.cwd(), 'apps');
24
27
  await authenticate(remote, 'apps:export', clientCredentials);
25
28
  logger.info(`Exporting app with id: ${appId}`);
26
- await exportAppAsZip(appId, resources, outputDirectory || defaultOutputDirectory, remote);
29
+ await exportAppAsZip(appId, assets, resources, outputDirectory || defaultOutputDirectory, remote);
27
30
  logger.info('Export completed successfully');
28
31
  }
29
32
  //# sourceMappingURL=export.js.map
@@ -40,26 +40,25 @@ export function builder(yargs) {
40
40
  .option('demo-mode', {
41
41
  describe: 'Whether the app should be used in demo mode.',
42
42
  type: 'boolean',
43
- })
44
- .option('seed', {
45
- describe: 'Whether to enable seeding for an app or not.',
46
- implies: ['demo-mode'],
47
43
  })
48
44
  .option('dry-run', {
49
45
  describe: 'Whether the API should be called to run without actually creating the app.',
50
46
  type: 'boolean',
51
47
  })
52
48
  .option('resources', {
53
- describe: 'Whether the resources from the `resources` directory should be created after publishing the app. The names of subdirectories are used as the name of the resource, otherwise the names of top level resource .json files are used instead.',
49
+ describe: 'Whether the resources from the `resources` directory should be created after publishing the app. The names of sub-directories are used as the name of the resource, otherwise the names of top level resource .json files are used instead.',
54
50
  type: 'boolean',
55
51
  })
56
52
  .option('assets', {
57
53
  describe: 'Whether the assets from the `assets` directory should be created after publishing the app.',
58
54
  type: 'boolean',
55
+ default: false,
59
56
  })
60
57
  .option('assets-clonable', {
61
58
  describe: 'Whether published assets should be clonable. Ignored if assets equals false.',
62
59
  type: 'boolean',
60
+ default: false,
61
+ implies: 'assets',
63
62
  })
64
63
  .option('modify-context', {
65
64
  describe: 'If the app context is specified, modify it for the current context to include the id of the published app.',
@@ -13,7 +13,9 @@ interface UpdateAppArguments extends BaseArguments {
13
13
  id: number;
14
14
  template: boolean;
15
15
  demoMode: boolean;
16
- seed: boolean;
16
+ resources: boolean;
17
+ assets: boolean;
18
+ assetsClonable: boolean;
17
19
  force: boolean;
18
20
  visibility: AppVisibility;
19
21
  sentryDsn: string;
@@ -44,9 +44,20 @@ export function builder(yargs) {
44
44
  describe: 'Whether the app should be used in demo mode.',
45
45
  type: 'boolean',
46
46
  })
47
- .option('seed', {
48
- describe: 'Whether to enable seeding for an app or not.',
49
- implies: ['demo-mode'],
47
+ .option('resources', {
48
+ describe: 'Whether the resources from the `resources` directory should replace the seed resources of the app being updated. The names of sub-directories are used as the name of the resource, otherwise the names of top level resource .json files are used instead.',
49
+ type: 'boolean',
50
+ })
51
+ .option('assets', {
52
+ describe: 'Whether the assets from the `assets` directory should replace the seed assets of the app being updated.',
53
+ type: 'boolean',
54
+ default: false,
55
+ })
56
+ .option('assets-clonable', {
57
+ describe: 'Whether published assets should be clonable. Ignored if assets equals false.',
58
+ type: 'boolean',
59
+ default: false,
60
+ implies: 'assets',
50
61
  })
51
62
  .option('force', {
52
63
  describe: 'Whether the lock property should be ignored.',
@@ -8,9 +8,10 @@ interface PublishAssetArguments extends BaseArguments {
8
8
  context: string;
9
9
  app: string;
10
10
  clonable: boolean;
11
+ seed: boolean;
11
12
  }
12
13
  export declare const command = "publish <paths...>";
13
14
  export declare const description = "Publish assets for an existing app.";
14
15
  export declare function builder(yargs: Argv): Argv<any>;
15
- export declare function handler({ app, appId, clientCredentials, clonable, context, name, paths, remote, useFileName, }: PublishAssetArguments): Promise<void>;
16
+ export declare function handler({ app, appId, clientCredentials, clonable, context, name, paths, remote, seed, useFileName, }: PublishAssetArguments): Promise<void>;
16
17
  export {};
@@ -35,9 +35,13 @@ export function builder(yargs) {
35
35
  type: 'boolean',
36
36
  default: false,
37
37
  describe: 'If true, all published assets will be clonable',
38
+ })
39
+ .option('seed', {
40
+ describe: 'If true, published resources will be used as seed',
41
+ default: false,
38
42
  });
39
43
  }
40
- export async function handler({ app, appId, clientCredentials, clonable, context, name, paths, remote, useFileName, }) {
44
+ export async function handler({ app, appId, clientCredentials, clonable, context, name, paths, remote, seed, useFileName, }) {
41
45
  const [resolvedAppId, resolvedRemote] = await resolveAppIdAndRemote(app, context, remote, appId);
42
46
  await authenticate(resolvedRemote, 'assets:write', clientCredentials);
43
47
  const normalizedPaths = paths.map((path) => normalizePath(path));
@@ -52,6 +56,7 @@ export async function handler({ app, appId, clientCredentials, clonable, context
52
56
  name: name || (useFileName ? parse(path).name : null),
53
57
  appId: resolvedAppId,
54
58
  path,
59
+ seed,
55
60
  remote: resolvedRemote,
56
61
  clonable,
57
62
  });
@@ -6,9 +6,10 @@ interface PublishResourceArguments extends BaseArguments {
6
6
  appId: number;
7
7
  context: string;
8
8
  app: string;
9
+ seed: boolean;
9
10
  }
10
11
  export declare const command = "publish <resource-name> <paths...>";
11
12
  export declare const description = "Publish resources based on a specified JSON file or directory.";
12
13
  export declare function builder(yargs: Argv): Argv<any>;
13
- export declare function handler({ app, appId, clientCredentials, context, paths, remote, resourceName, }: PublishResourceArguments): Promise<void>;
14
+ export declare function handler({ app, appId, clientCredentials, context, paths, remote, resourceName, seed, }: PublishResourceArguments): Promise<void>;
14
15
  export {};
@@ -27,9 +27,13 @@ export function builder(yargs) {
27
27
  .option('context', {
28
28
  describe: 'If specified, use the specified context from .appsemblerc.yaml',
29
29
  demandOption: 'app',
30
+ })
31
+ .option('seed', {
32
+ describe: 'If true, published resources will be used as seed',
33
+ default: false,
30
34
  });
31
35
  }
32
- export async function handler({ app, appId, clientCredentials, context, paths, remote, resourceName, }) {
36
+ export async function handler({ app, appId, clientCredentials, context, paths, remote, resourceName, seed, }) {
33
37
  const [resolvedAppId, resolvedRemote] = await resolveAppIdAndRemote(app, context, remote, appId);
34
38
  await authenticate(resolvedRemote, 'resources:write', clientCredentials);
35
39
  const normalizedPaths = paths.map((path) => normalizePath(path));
@@ -43,8 +47,9 @@ export async function handler({ app, appId, clientCredentials, context, paths, r
43
47
  await publishResource({
44
48
  type: resourceName,
45
49
  appId: resolvedAppId,
46
- path,
47
50
  remote: resolvedRemote,
51
+ path,
52
+ seed,
48
53
  });
49
54
  }
50
55
  }
package/commands/serve.js CHANGED
@@ -120,7 +120,7 @@ export async function handler(argv) {
120
120
  };
121
121
  }));
122
122
  const localBlocksPromises = localBlocksConfigs.map(async (blockConfig) => {
123
- await buildBlock(blockConfig);
123
+ await buildBlock(blockConfig, 'development');
124
124
  const [, blockImplementations] = await makeProjectPayload(blockConfig);
125
125
  return {
126
126
  ...blockImplementations,
package/lib/app.d.ts CHANGED
@@ -120,9 +120,17 @@ interface UpdateAppParams {
120
120
  */
121
121
  demoMode: boolean;
122
122
  /**
123
- * Whether the App should seed resources and assets in demo mode.
123
+ * Whether resources from the `resources` directory should be published after publishing the app.
124
124
  */
125
- seed: boolean;
125
+ resources: boolean;
126
+ /**
127
+ * Whether assets from the `assets` directory should be published after publishing the app.
128
+ */
129
+ assets: boolean;
130
+ /**
131
+ * Whether published assets should be clonable. Ignored if `assets` equals false.
132
+ */
133
+ assetsClonable: boolean;
126
134
  /**
127
135
  * Whether the locked property should be ignored.
128
136
  */
@@ -165,11 +173,12 @@ export declare function traverseAppDirectory(path: string, context: string, form
165
173
  * Export an app as a zip file.
166
174
  *
167
175
  * @param appId Id of the app to be exported.
176
+ * @param assets Boolean representing whether to include assets in the exported zip file.
168
177
  * @param resources Boolean representing whether to include resources in the zip file.
169
178
  * @param path Path of the folder where you want to put your downloaded file.
170
179
  * @param remote The remote to fetch the app from.
171
180
  */
172
- export declare function exportAppAsZip(appId: number, resources: boolean, path: string, remote: string): Promise<void>;
181
+ export declare function exportAppAsZip(appId: number, assets: boolean, resources: boolean, path: string, remote: string): Promise<void>;
173
182
  /**
174
183
  * Upload messages for an app.
175
184
  *
@@ -179,6 +188,8 @@ export declare function exportAppAsZip(appId: number, resources: boolean, path:
179
188
  * @param force Whether or not to force the update for locked apps.
180
189
  */
181
190
  export declare function uploadMessages(path: string, appId: number, remote: string, force: boolean): Promise<void>;
191
+ export declare function publishSeedResources(path: string, app: App, remote: string): Promise<void>;
192
+ export declare function publishSeedAssets(path: string, app: App, remote: string, assetsClonable: boolean): Promise<void>;
182
193
  /**
183
194
  * @param path The path to the app directory.
184
195
  * @param languages A list of languages for which translations should be added in addition to the
@@ -192,13 +203,13 @@ export declare function writeAppMessages(path: string, languages: string[], veri
192
203
  *
193
204
  * @param argv The command line options used for updating the app.
194
205
  */
195
- export declare function updateApp({ clientCredentials, context, force, path, ...options }: UpdateAppParams): Promise<void>;
206
+ export declare function updateApp({ assets, clientCredentials, context, force, path, resources, ...options }: UpdateAppParams): Promise<void>;
196
207
  /**
197
208
  * Publish a new app.
198
209
  *
199
210
  * @param options The options to use for publishing an app.
200
211
  */
201
- export declare function publishApp({ assets, assetsClonable, clientCredentials, context, dryRun, modifyContext, path, resources, ...options }: PublishAppParams): Promise<void>;
212
+ export declare function publishApp({ assets, clientCredentials, context, dryRun, modifyContext, path, resources, ...options }: PublishAppParams): Promise<void>;
202
213
  /**
203
214
  * Resolve the app’s ID and remote based on the app context.
204
215
  *
package/lib/app.js CHANGED
@@ -1,14 +1,13 @@
1
1
  import { createReadStream, createWriteStream, existsSync } from 'node:fs';
2
- import { mkdir, readdir, readFile, stat } from 'node:fs/promises';
3
- import { basename, join, parse, relative, resolve } from 'node:path';
2
+ import { mkdir, readdir, readFile, rm, stat } from 'node:fs/promises';
3
+ import { basename, dirname, join, parse, relative, resolve } from 'node:path';
4
4
  import { inspect } from 'node:util';
5
- import { AppsembleError, authenticate, logger, opendirSafe, readData, writeData, } from '@appsemble/node-utils';
5
+ import { applyAppVariant, AppsembleError, authenticate, logger, opendirSafe, readData, writeData, } from '@appsemble/node-utils';
6
6
  import { extractAppMessages, has, normalizeBlockName } from '@appsemble/utils';
7
7
  import axios from 'axios';
8
8
  import fg from 'fast-glob';
9
9
  import FormData from 'form-data';
10
10
  import normalizePath from 'normalize-path';
11
- import { parse as parseYaml } from 'yaml';
12
11
  import { publishAsset } from './asset.js';
13
12
  import { traverseBlockThemes } from './block.js';
14
13
  import { coerceRemote } from './coercers.js';
@@ -131,14 +130,15 @@ export async function traverseAppDirectory(path, context, formData) {
131
130
  * Export an app as a zip file.
132
131
  *
133
132
  * @param appId Id of the app to be exported.
133
+ * @param assets Boolean representing whether to include assets in the exported zip file.
134
134
  * @param resources Boolean representing whether to include resources in the zip file.
135
135
  * @param path Path of the folder where you want to put your downloaded file.
136
136
  * @param remote The remote to fetch the app from.
137
137
  */
138
- export async function exportAppAsZip(appId, resources, path, remote) {
138
+ export async function exportAppAsZip(appId, assets, resources, path, remote) {
139
139
  const app = await axios.get(`/api/apps/${appId}`);
140
140
  const { name } = app.data.definition;
141
- const response = await axios.get(`/api/apps/${appId}/export?resources=${resources}`, {
141
+ const response = await axios.get(`/api/apps/${appId}/export?resources=${resources}&assets=${assets}`, {
142
142
  baseURL: remote,
143
143
  responseType: 'stream',
144
144
  });
@@ -175,6 +175,88 @@ export async function uploadMessages(path, appId, remote, force) {
175
175
  logger.info(`Successfully uploaded messages for language “${language.language}” 🎉`);
176
176
  }
177
177
  }
178
+ export async function publishSeedResources(path, app, remote) {
179
+ var _a, _b;
180
+ const resourcesPath = join(path, 'resources');
181
+ logger.info(`Publishing seed resources from ${resourcesPath}`);
182
+ if (existsSync(resourcesPath)) {
183
+ logger.info(`Deleting existing seed resources from app ${app.id}`);
184
+ await axios.delete(`/api/apps/${app.id}/seed-resources`);
185
+ try {
186
+ const resourceFiles = await readdir(resourcesPath, { withFileTypes: true });
187
+ const resourcesToPublish = [];
188
+ const publishedResourcesIds = {};
189
+ for (const resource of resourceFiles) {
190
+ if (resource.isFile()) {
191
+ const { name } = parse(resource.name);
192
+ resourcesToPublish.push({
193
+ appId: app.id,
194
+ path: join(resourcesPath, resource.name),
195
+ definition: (_a = app.definition.resources) === null || _a === void 0 ? void 0 : _a[name],
196
+ type: name,
197
+ });
198
+ }
199
+ else if (resource.isDirectory()) {
200
+ const subDirectoryResources = await readdir(join(resourcesPath, resource.name), {
201
+ withFileTypes: true,
202
+ });
203
+ for (const subResource of subDirectoryResources.filter((s) => s.isFile())) {
204
+ resourcesToPublish.push({
205
+ appId: app.id,
206
+ path: join(resourcesPath, resource.name, subResource.name),
207
+ definition: (_b = app.definition.resources) === null || _b === void 0 ? void 0 : _b[resource.name],
208
+ type: resource.name,
209
+ });
210
+ }
211
+ }
212
+ }
213
+ await publishResourcesRecursively({
214
+ seed: true,
215
+ remote,
216
+ resourcesToPublish,
217
+ publishedResourcesIds,
218
+ });
219
+ }
220
+ catch (error) {
221
+ logger.error('Something went wrong when publishing seed resources:');
222
+ logger.error(error);
223
+ }
224
+ }
225
+ else {
226
+ logger.warn(`Missing resources directory in ${path}. Skipping...`);
227
+ }
228
+ }
229
+ export async function publishSeedAssets(path, app, remote, assetsClonable) {
230
+ const assetsPath = join(path, 'assets');
231
+ logger.info(`Publishing seed assets from ${assetsPath}`);
232
+ if (existsSync(assetsPath)) {
233
+ logger.info(`Deleting existing seed assets from app ${app.id}`);
234
+ await axios.delete(`/api/apps/${app.id}/seed-assets`);
235
+ const assetFiles = await readdir(assetsPath);
236
+ const normalizedPaths = assetFiles.map((assetFile) => normalizePath(join(assetsPath, assetFile)));
237
+ const files = await fg(normalizedPaths, { absolute: true, onlyFiles: true });
238
+ try {
239
+ logger.info(`Publishing ${files.length} asset(s)`);
240
+ for (const assetFilePath of files) {
241
+ await publishAsset({
242
+ name: parse(assetFilePath).name,
243
+ appId: app.id,
244
+ path: assetFilePath,
245
+ remote,
246
+ seed: true,
247
+ clonable: assetsClonable,
248
+ });
249
+ }
250
+ }
251
+ catch (error) {
252
+ logger.error('Something went wrong when creating assets:');
253
+ logger.error(error);
254
+ }
255
+ }
256
+ else {
257
+ logger.warn(`Missing assets directory in ${path}. Skipping...`);
258
+ }
259
+ }
178
260
  /**
179
261
  * @param path The path to the app directory.
180
262
  * @param languages A list of languages for which translations should be added in addition to the
@@ -330,32 +412,38 @@ This block version is not used in the app`);
330
412
  *
331
413
  * @param argv The command line options used for updating the app.
332
414
  */
333
- export async function updateApp({ clientCredentials, context, force, path, ...options }) {
334
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
415
+ export async function updateApp({ assets, clientCredentials, context, force, path, resources, ...options }) {
416
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
335
417
  const file = await stat(path);
336
418
  const formData = new FormData();
337
419
  let appsembleContext;
420
+ let appVariantPath = path;
421
+ if (existsSync(join(path, 'variants', context))) {
422
+ await applyAppVariant(path, context);
423
+ appVariantPath = join(dirname(path), `${basename(path)}-${context}`);
424
+ }
425
+ else {
426
+ logger.warn(`App variant ${context} is not defined. Using default app.`);
427
+ }
338
428
  if (file.isFile()) {
339
- const [, data] = await readData(path);
429
+ const [, data] = await readData(appVariantPath);
340
430
  formData.append('yaml', data);
341
431
  }
342
432
  else {
343
- [appsembleContext] = await traverseAppDirectory(path, context, formData);
433
+ [appsembleContext] = await traverseAppDirectory(appVariantPath, context, formData);
344
434
  }
345
435
  const remote = (_a = appsembleContext.remote) !== null && _a !== void 0 ? _a : options.remote;
346
436
  const id = (_b = appsembleContext.id) !== null && _b !== void 0 ? _b : options.id;
347
437
  const template = (_d = (_c = appsembleContext.template) !== null && _c !== void 0 ? _c : options.template) !== null && _d !== void 0 ? _d : false;
348
- const demoMode = (_f = (_e = appsembleContext.demoMode) !== null && _e !== void 0 ? _e : options.demoMode) !== null && _f !== void 0 ? _f : false;
349
- // TODO: seed depends on demoMode, this is caused by bad database normalization.
350
- // Consider using a property called `type` on the App model instead.
351
- const seed = (_j = (_h = (_g = appsembleContext.seed) !== null && _g !== void 0 ? _g : options.seed) !== null && _h !== void 0 ? _h : options.demoMode) !== null && _j !== void 0 ? _j : false;
352
- const visibility = (_k = appsembleContext.visibility) !== null && _k !== void 0 ? _k : options.visibility;
353
- const iconBackground = (_l = appsembleContext.iconBackground) !== null && _l !== void 0 ? _l : options.iconBackground;
354
- const icon = (_m = options.icon) !== null && _m !== void 0 ? _m : appsembleContext.icon;
355
- const maskableIcon = (_o = options.maskableIcon) !== null && _o !== void 0 ? _o : appsembleContext.maskableIcon;
356
- const sentryDsn = (_p = appsembleContext.sentryDsn) !== null && _p !== void 0 ? _p : options.sentryDsn;
357
- const sentryEnvironment = (_q = appsembleContext.sentryEnvironment) !== null && _q !== void 0 ? _q : options.sentryEnvironment;
358
- const googleAnalyticsId = (_r = appsembleContext.googleAnalyticsId) !== null && _r !== void 0 ? _r : options.googleAnalyticsId;
438
+ const assetsClonable = (_f = (_e = appsembleContext.assetsClonable) !== null && _e !== void 0 ? _e : options.assetsClonable) !== null && _f !== void 0 ? _f : false;
439
+ const demoMode = (_h = (_g = appsembleContext.demoMode) !== null && _g !== void 0 ? _g : options.demoMode) !== null && _h !== void 0 ? _h : false;
440
+ const visibility = (_j = appsembleContext.visibility) !== null && _j !== void 0 ? _j : options.visibility;
441
+ const iconBackground = (_k = appsembleContext.iconBackground) !== null && _k !== void 0 ? _k : options.iconBackground;
442
+ const icon = (_l = options.icon) !== null && _l !== void 0 ? _l : appsembleContext.icon;
443
+ const maskableIcon = (_m = options.maskableIcon) !== null && _m !== void 0 ? _m : appsembleContext.maskableIcon;
444
+ const sentryDsn = (_o = appsembleContext.sentryDsn) !== null && _o !== void 0 ? _o : options.sentryDsn;
445
+ const sentryEnvironment = (_p = appsembleContext.sentryEnvironment) !== null && _p !== void 0 ? _p : options.sentryEnvironment;
446
+ const googleAnalyticsId = (_q = appsembleContext.googleAnalyticsId) !== null && _q !== void 0 ? _q : options.googleAnalyticsId;
359
447
  const { appLock } = appsembleContext;
360
448
  logger.info(`App id: ${id}`);
361
449
  logger.verbose(`App remote: ${remote}`);
@@ -369,17 +457,16 @@ export async function updateApp({ clientCredentials, context, force, path, ...op
369
457
  formData.append('force', String(force));
370
458
  formData.append('template', String(template));
371
459
  formData.append('demoMode', String(demoMode));
372
- formData.append('seed', String(seed));
373
460
  formData.append('visibility', visibility);
374
461
  formData.append('iconBackground', iconBackground);
375
462
  if (icon) {
376
463
  const realIcon = typeof icon === 'string' ? createReadStream(icon) : icon;
377
- logger.info(`Using icon from ${(_s = realIcon.path) !== null && _s !== void 0 ? _s : 'stdin'}`);
464
+ logger.info(`Using icon from ${(_r = realIcon.path) !== null && _r !== void 0 ? _r : 'stdin'}`);
378
465
  formData.append('icon', realIcon);
379
466
  }
380
467
  if (maskableIcon) {
381
468
  const realIcon = typeof maskableIcon === 'string' ? createReadStream(maskableIcon) : maskableIcon;
382
- logger.info(`Using maskable icon from ${(_t = realIcon.path) !== null && _t !== void 0 ? _t : 'stdin'}`);
469
+ logger.info(`Using maskable icon from ${(_s = realIcon.path) !== null && _s !== void 0 ? _s : 'stdin'}`);
383
470
  formData.append('maskableIcon', realIcon);
384
471
  }
385
472
  if (sentryDsn) {
@@ -393,7 +480,11 @@ export async function updateApp({ clientCredentials, context, force, path, ...op
393
480
  logger.info('Using Google Analytics');
394
481
  formData.append('googleAnalyticsID', googleAnalyticsId);
395
482
  }
396
- await authenticate(remote, 'apps:write', clientCredentials);
483
+ let authScope = 'apps:write';
484
+ if (resources) {
485
+ authScope += ' resources:write';
486
+ }
487
+ await authenticate(remote, authScope, clientCredentials);
397
488
  if (appLock) {
398
489
  logger.info(`Setting AppLock to ${appLock}`);
399
490
  try {
@@ -416,7 +507,7 @@ export async function updateApp({ clientCredentials, context, force, path, ...op
416
507
  if (!axios.isAxiosError(error)) {
417
508
  throw error;
418
509
  }
419
- if (((_v = (_u = error.response) === null || _u === void 0 ? void 0 : _u.data) === null || _v === void 0 ? void 0 : _v.message) !== 'App validation failed') {
510
+ if (((_u = (_t = error.response) === null || _t === void 0 ? void 0 : _t.data) === null || _u === void 0 ? void 0 : _u.message) !== 'App validation failed') {
420
511
  throw error;
421
512
  }
422
513
  logger.error(error.response.data);
@@ -424,51 +515,68 @@ export async function updateApp({ clientCredentials, context, force, path, ...op
424
515
  }
425
516
  if (file.isDirectory()) {
426
517
  // After uploading the app, upload block styles and messages if they are available
427
- await traverseBlockThemes(path, data.id, remote, force);
428
- await uploadMessages(path, data.id, remote, force);
518
+ await traverseBlockThemes(appVariantPath, data.id, remote, force);
519
+ await uploadMessages(appVariantPath, data.id, remote, force);
520
+ // After updating the app, publish seed resources and assets if they are available
521
+ if (assets && (data.locked !== 'fullLock' || force)) {
522
+ await publishSeedAssets(appVariantPath, data, remote, assetsClonable);
523
+ }
524
+ if (resources && (data.locked !== 'fullLock' || force)) {
525
+ await publishSeedResources(appVariantPath, data, remote);
526
+ }
429
527
  }
430
528
  const { host, protocol } = new URL(remote);
431
529
  logger.info(`Successfully updated app ${data.definition.name}! 🙌`);
432
530
  logger.info(`App URL: ${protocol}//${data.path}.${data.OrganizationId}.${host}`);
433
531
  logger.info(`App store page: ${new URL(`/apps/${data.id}`, remote)}`);
532
+ if (appVariantPath !== path) {
533
+ await rm(appVariantPath, { recursive: true });
534
+ logger.info(`Removed ${appVariantPath}`);
535
+ }
434
536
  }
435
537
  /**
436
538
  * Publish a new app.
437
539
  *
438
540
  * @param options The options to use for publishing an app.
439
541
  */
440
- export async function publishApp({ assets, assetsClonable, clientCredentials, context, dryRun, modifyContext, path, resources, ...options }) {
441
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
542
+ export async function publishApp({ assets, clientCredentials, context, dryRun, modifyContext, path, resources, ...options }) {
543
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
442
544
  const file = await stat(path);
443
545
  const formData = new FormData();
444
546
  let appsembleContext;
445
547
  let rc;
446
548
  let yaml;
447
549
  let filename = relative(process.cwd(), path);
550
+ let appVariantPath = path;
551
+ if (existsSync(join(path, 'variants', context))) {
552
+ await applyAppVariant(path, context);
553
+ appVariantPath = join(dirname(path), `${basename(path)}-${context}`);
554
+ }
555
+ else {
556
+ logger.warn(`App variant ${context} is not defined. Using default app.`);
557
+ }
448
558
  if (file.isFile()) {
449
559
  // Assuming file is App YAML
450
- const [, data] = await readData(path);
560
+ const [, data] = await readData(appVariantPath);
451
561
  yaml = data;
452
562
  formData.append('yaml', data);
453
563
  }
454
564
  else {
455
- [appsembleContext, rc, yaml] = await traverseAppDirectory(path, context, formData);
565
+ [appsembleContext, rc, yaml] = await traverseAppDirectory(appVariantPath, context, formData);
456
566
  filename = join(filename, 'app-definition.yaml');
457
567
  }
458
568
  const remote = (_a = appsembleContext.remote) !== null && _a !== void 0 ? _a : options.remote;
459
569
  const organizationId = (_b = appsembleContext.organization) !== null && _b !== void 0 ? _b : options.organization;
460
570
  const template = (_d = (_c = appsembleContext.template) !== null && _c !== void 0 ? _c : options.template) !== null && _d !== void 0 ? _d : false;
461
- const demoMode = (_f = (_e = appsembleContext.demoMode) !== null && _e !== void 0 ? _e : options.demoMode) !== null && _f !== void 0 ? _f : false;
462
- // TODO: seed depends on demoMode, this is caused by bad database normalization.
463
- // Consider using a property called `type` on the App model instead.
464
- const seed = (_j = (_h = (_g = appsembleContext.seed) !== null && _g !== void 0 ? _g : options.seed) !== null && _h !== void 0 ? _h : options.demoMode) !== null && _j !== void 0 ? _j : false;
465
- const visibility = (_k = appsembleContext.visibility) !== null && _k !== void 0 ? _k : options.visibility;
466
- const iconBackground = (_l = appsembleContext.iconBackground) !== null && _l !== void 0 ? _l : options.iconBackground;
467
- const icon = (_m = options.icon) !== null && _m !== void 0 ? _m : appsembleContext.icon;
468
- const maskableIcon = (_o = options.maskableIcon) !== null && _o !== void 0 ? _o : appsembleContext.maskableIcon;
469
- const sentryDsn = (_p = appsembleContext.sentryDsn) !== null && _p !== void 0 ? _p : options.sentryDsn;
470
- const sentryEnvironment = (_q = appsembleContext.sentryEnvironment) !== null && _q !== void 0 ? _q : options.sentryEnvironment;
471
- const googleAnalyticsId = (_r = appsembleContext.googleAnalyticsId) !== null && _r !== void 0 ? _r : options.googleAnalyticsId;
571
+ const assetsClonable = (_f = (_e = appsembleContext.assetsClonable) !== null && _e !== void 0 ? _e : options.assetsClonable) !== null && _f !== void 0 ? _f : false;
572
+ const demoMode = (_h = (_g = appsembleContext.demoMode) !== null && _g !== void 0 ? _g : options.demoMode) !== null && _h !== void 0 ? _h : false;
573
+ const visibility = (_j = appsembleContext.visibility) !== null && _j !== void 0 ? _j : options.visibility;
574
+ const iconBackground = (_k = appsembleContext.iconBackground) !== null && _k !== void 0 ? _k : options.iconBackground;
575
+ const icon = (_l = options.icon) !== null && _l !== void 0 ? _l : appsembleContext.icon;
576
+ const maskableIcon = (_m = options.maskableIcon) !== null && _m !== void 0 ? _m : appsembleContext.maskableIcon;
577
+ const sentryDsn = (_o = appsembleContext.sentryDsn) !== null && _o !== void 0 ? _o : options.sentryDsn;
578
+ const sentryEnvironment = (_p = appsembleContext.sentryEnvironment) !== null && _p !== void 0 ? _p : options.sentryEnvironment;
579
+ const googleAnalyticsId = (_q = appsembleContext.googleAnalyticsId) !== null && _q !== void 0 ? _q : options.googleAnalyticsId;
472
580
  const appLock = appsembleContext.appLock || 'unlocked';
473
581
  logger.verbose(`App remote: ${remote}`);
474
582
  logger.verbose(`App organization: ${organizationId}`);
@@ -481,18 +589,17 @@ export async function publishApp({ assets, assetsClonable, clientCredentials, co
481
589
  formData.append('OrganizationId', organizationId);
482
590
  formData.append('template', String(template));
483
591
  formData.append('demoMode', String(demoMode));
484
- formData.append('seed', String(seed));
485
592
  formData.append('visibility', visibility);
486
593
  formData.append('iconBackground', iconBackground);
487
594
  formData.append('locked', appLock);
488
595
  if (icon) {
489
596
  const realIcon = typeof icon === 'string' ? createReadStream(icon) : icon;
490
- logger.info(`Using icon from ${(_s = realIcon.path) !== null && _s !== void 0 ? _s : 'stdin'}`);
597
+ logger.info(`Using icon from ${(_r = realIcon.path) !== null && _r !== void 0 ? _r : 'stdin'}`);
491
598
  formData.append('icon', realIcon);
492
599
  }
493
600
  if (maskableIcon) {
494
601
  const realIcon = typeof maskableIcon === 'string' ? createReadStream(maskableIcon) : maskableIcon;
495
- logger.info(`Using maskable icon from ${(_t = realIcon.path) !== null && _t !== void 0 ? _t : 'stdin'}`);
602
+ logger.info(`Using maskable icon from ${(_s = realIcon.path) !== null && _s !== void 0 ? _s : 'stdin'}`);
496
603
  formData.append('maskableIcon', realIcon);
497
604
  }
498
605
  if (sentryDsn) {
@@ -522,7 +629,7 @@ export async function publishApp({ assets, assetsClonable, clientCredentials, co
522
629
  if (!axios.isAxiosError(error)) {
523
630
  throw error;
524
631
  }
525
- if (((_v = (_u = error.response) === null || _u === void 0 ? void 0 : _u.data) === null || _v === void 0 ? void 0 : _v.message) !== 'App validation failed') {
632
+ if (((_u = (_t = error.response) === null || _t === void 0 ? void 0 : _t.data) === null || _u === void 0 ? void 0 : _u.message) !== 'App validation failed') {
526
633
  throw error;
527
634
  }
528
635
  throw new AppsembleError(printAxiosError(filename, yaml, error.response.data.data.errors));
@@ -533,83 +640,42 @@ export async function publishApp({ assets, assetsClonable, clientCredentials, co
533
640
  return;
534
641
  }
535
642
  if (file.isDirectory() && !dryRun) {
536
- // After uploading the app, upload block styles and messages if they are available
537
- await traverseBlockThemes(path, data.id, remote, false);
538
- await uploadMessages(path, data.id, remote, false);
539
- if (assets && existsSync(join(path, 'assets'))) {
540
- const assetsPath = join(path, 'assets');
541
- const assetFiles = await readdir(assetsPath);
542
- const normalizedPaths = assetFiles.map((assetFile) => normalizePath(join(assetsPath, assetFile)));
543
- const files = await fg(normalizedPaths, { absolute: true, onlyFiles: true });
544
- try {
545
- logger.info(`Publishing ${files.length} asset(s)`);
546
- for (const assetFilePath of files) {
547
- await publishAsset({
548
- name: parse(assetFilePath).name,
549
- appId: data.id,
550
- path: assetFilePath,
551
- remote,
552
- clonable: assetsClonable,
553
- });
554
- }
555
- }
556
- catch (error) {
557
- logger.error('Something went wrong when creating assets:');
558
- logger.error(error);
559
- }
643
+ // After uploading the app, upload block styles, messages if they are available
644
+ await traverseBlockThemes(appVariantPath, data.id, remote, false);
645
+ await uploadMessages(appVariantPath, data.id, remote, false);
646
+ // After uploading the app, publish seed resources and assets if they are available
647
+ if (assets) {
648
+ await publishSeedAssets(appVariantPath, data, remote, assetsClonable);
560
649
  }
561
- if (resources && existsSync(join(path, 'resources'))) {
562
- const appDefinition = parseYaml(yaml, { maxAliasCount: 10000 });
563
- const resourcePath = join(path, 'resources');
564
- try {
565
- const resourceFiles = await readdir(resourcePath, { withFileTypes: true });
566
- const resourcesToPublish = [];
567
- const publishedResourcesIds = {};
568
- for (const resource of resourceFiles) {
569
- if (resource.isFile()) {
570
- const { name } = parse(resource.name);
571
- resourcesToPublish.push({
572
- appId: data.id,
573
- path: join(resourcePath, resource.name),
574
- definition: appDefinition.resources[name],
575
- type: name,
576
- });
577
- }
578
- else if (resource.isDirectory()) {
579
- const subDirectoryResources = await readdir(join(resourcePath, resource.name), {
580
- withFileTypes: true,
581
- });
582
- for (const subResource of subDirectoryResources.filter((s) => s.isFile())) {
583
- resourcesToPublish.push({
584
- appId: data.id,
585
- path: join(resourcePath, resource.name, subResource.name),
586
- definition: appDefinition.resources[resource.name],
587
- type: resource.name,
588
- });
589
- }
590
- }
591
- }
592
- await publishResourcesRecursively({
593
- remote,
594
- resourcesToPublish,
595
- publishedResourcesIds,
596
- });
597
- }
598
- catch (error) {
599
- logger.error('Something went wrong when creating resources:');
600
- logger.error(error);
601
- }
650
+ if (resources) {
651
+ await publishSeedResources(appVariantPath, data, remote);
602
652
  }
603
653
  }
604
654
  if (modifyContext && appsembleContext && context && !dryRun) {
605
655
  rc.context[context].id = data.id;
606
- await writeData(join(path, '.appsemblerc.yaml'), rc, { sort: false });
656
+ await writeData(join(appVariantPath, '.appsemblerc.yaml'), rc, { sort: false });
607
657
  logger.info(`Updated .appsemblerc: Set context.${context}.id to ${data.id}`);
608
658
  }
609
659
  const { host, protocol } = new URL(remote);
610
660
  logger.info(`Successfully published app ${data.definition.name}! 🙌`);
611
661
  logger.info(`App URL: ${protocol}//${data.path}.${data.OrganizationId}.${host}`);
612
662
  logger.info(`App store page: ${new URL(`/apps/${data.id}`, remote)}`);
663
+ const rcCollections = rc.context[context].collections;
664
+ if (Array.isArray(rcCollections)) {
665
+ for (const collectionId of rcCollections) {
666
+ await axios.post(`/api/appCollections/${collectionId}/apps`, { AppId: data.id }, {
667
+ baseURL: remote,
668
+ });
669
+ logger.info(`Successfully added app to collection ${collectionId}`);
670
+ }
671
+ }
672
+ if (appVariantPath !== path) {
673
+ const [defaultAppRc] = await readData(join(path, '.appsemblerc.yaml'));
674
+ defaultAppRc.context[context] = rc.context[context];
675
+ await writeData(join(path, '.appsemblerc.yaml'), defaultAppRc, { sort: false });
676
+ await rm(appVariantPath, { recursive: true });
677
+ logger.info(`Removed ${appVariantPath}`);
678
+ }
613
679
  }
614
680
  /**
615
681
  * Resolve the app’s ID and remote based on the app context.
package/lib/asset.d.ts CHANGED
@@ -16,10 +16,14 @@ interface PublishAssetParams {
16
16
  * The remote server to publish the asset on.
17
17
  */
18
18
  remote: string;
19
+ /**
20
+ * Whether the published asset should be used as seed.
21
+ */
22
+ seed: boolean;
19
23
  /**
20
24
  * Whether the asset should be clonable.
21
25
  */
22
26
  clonable: boolean;
23
27
  }
24
- export declare function publishAsset({ appId, clonable, name, path, remote, }: PublishAssetParams): Promise<void>;
28
+ export declare function publishAsset({ appId, clonable, name, path, remote, seed, }: PublishAssetParams): Promise<void>;
25
29
  export {};
package/lib/asset.js CHANGED
@@ -3,7 +3,7 @@ import { logger } from '@appsemble/node-utils';
3
3
  import { normalize } from '@appsemble/utils';
4
4
  import axios from 'axios';
5
5
  import FormData from 'form-data';
6
- export async function publishAsset({ appId, clonable, name, path, remote, }) {
6
+ export async function publishAsset({ appId, clonable, name, path, remote, seed, }) {
7
7
  const formData = new FormData();
8
8
  const file = createReadStream(path);
9
9
  formData.append('file', file);
@@ -11,7 +11,8 @@ export async function publishAsset({ appId, clonable, name, path, remote, }) {
11
11
  if (name) {
12
12
  formData.append('name', normalize(name));
13
13
  }
14
- const { data: { id }, } = await axios.post(`/api/apps/${appId}/assets`, formData, { baseURL: remote });
14
+ const endpoint = `/api/apps/${appId}/${seed ? 'seed-' : ''}assets`;
15
+ const { data: { id }, } = await axios.post(endpoint, formData, { baseURL: remote });
15
16
  logger.info(`Published asset ${id}${name ? ` with name ${normalize(name)}` : ''}`);
16
17
  }
17
18
  //# sourceMappingURL=asset.js.map
package/lib/block.d.ts CHANGED
@@ -12,7 +12,7 @@ import { type Stats } from 'webpack';
12
12
  * @param buildConfig The config of the block to build.
13
13
  * @returns The Webpack stats object.
14
14
  */
15
- export declare function buildBlock(buildConfig: ProjectBuildConfig): Promise<Stats>;
15
+ export declare function buildBlock(buildConfig: ProjectBuildConfig, env?: 'development' | 'production'): Promise<Stats>;
16
16
  interface DeleteBlockVersionParams {
17
17
  organization: string;
18
18
  blockName: string;
package/lib/block.js CHANGED
@@ -19,8 +19,8 @@ import { processCss } from './processCss.js';
19
19
  * @param buildConfig The config of the block to build.
20
20
  * @returns The Webpack stats object.
21
21
  */
22
- export async function buildBlock(buildConfig) {
23
- const conf = await getProjectWebpackConfig(buildConfig, 'production', join(buildConfig.dir, buildConfig.output));
22
+ export async function buildBlock(buildConfig, env) {
23
+ const conf = await getProjectWebpackConfig(buildConfig, env || 'production', join(buildConfig.dir, buildConfig.output));
24
24
  if (existsSync(conf.output.path)) {
25
25
  logger.warn(`Removing ${conf.output.path}`);
26
26
  await rm(conf.output.path, { force: true, recursive: true });
@@ -28,7 +28,7 @@ export async function buildBlock(buildConfig) {
28
28
  logger.info(`Building ${buildConfig.name}@${buildConfig.version} 🔨`);
29
29
  const compiler = webpack(conf);
30
30
  return new Promise((resolve, reject) => {
31
- compiler.run((err, stats) => {
31
+ const callback = (err, stats) => {
32
32
  if (err) {
33
33
  reject(err);
34
34
  }
@@ -39,7 +39,13 @@ export async function buildBlock(buildConfig) {
39
39
  logger.verbose(stats.toString({ colors: true }));
40
40
  resolve(stats);
41
41
  }
42
- });
42
+ };
43
+ if (env === 'development') {
44
+ compiler.watch({ ignored: /node_modules/ }, callback);
45
+ }
46
+ else {
47
+ compiler.run(callback);
48
+ }
43
49
  });
44
50
  }
45
51
  export async function deleteBlock({ blockName, blockVersion, organization, remote, }) {
package/lib/resource.d.ts CHANGED
@@ -34,6 +34,10 @@ interface PublishResourceParams {
34
34
  * The remote server to publish the resource on.
35
35
  */
36
36
  remote: string;
37
+ /**
38
+ * Whether the published resource should be used as seed.
39
+ */
40
+ seed: boolean;
37
41
  /**
38
42
  * The definition of the resource.
39
43
  */
@@ -54,6 +58,10 @@ interface PublishResourcesRecursivelyParams {
54
58
  * The remote server to publish the resource on.
55
59
  */
56
60
  remote: string;
61
+ /**
62
+ * Whether the published resources should be used as seed.
63
+ */
64
+ seed: boolean;
57
65
  /**
58
66
  * All resources that need to be published.
59
67
  */
@@ -63,7 +71,7 @@ interface PublishResourcesRecursivelyParams {
63
71
  */
64
72
  publishedResourcesIds: Record<string, number[]>;
65
73
  }
66
- export declare function publishResource({ appId, definition, path, publishedResourcesIds, remote, type, }: PublishResourceParams): Promise<number[]>;
67
- export declare function publishResourcesRecursively({ publishedResourcesIds, remote, resourcesToPublish, }: PublishResourcesRecursivelyParams): Promise<Record<string, number[]>>;
74
+ export declare function publishResource({ appId, definition, path, publishedResourcesIds, remote, seed, type, }: PublishResourceParams): Promise<number[]>;
75
+ export declare function publishResourcesRecursively({ publishedResourcesIds, remote, resourcesToPublish, seed, }: PublishResourcesRecursivelyParams): Promise<Record<string, number[]>>;
68
76
  export declare function updateResource({ appId, path, remote, resourceName, }: UpdateResourceParams): Promise<void>;
69
77
  export {};
package/lib/resource.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { AppsembleError, logger, readData } from '@appsemble/node-utils';
3
3
  import axios from 'axios';
4
- export async function publishResource({ appId, definition, path, publishedResourcesIds = {}, remote, type, }) {
4
+ export async function publishResource({ appId, definition, path, publishedResourcesIds = {}, remote, seed, type, }) {
5
5
  const csv = path.endsWith('.csv');
6
6
  let resources;
7
7
  if (csv) {
@@ -16,15 +16,16 @@ export async function publishResource({ appId, definition, path, publishedResour
16
16
  }
17
17
  logger.info(`Publishing resource(s) from ${path}`);
18
18
  let data;
19
+ const endpoint = `/api/apps/${appId}/${seed ? 'seed-' : ''}resources/${type}`;
19
20
  if (csv) {
20
- const response = await axios.post(`/api/apps/${appId}/resources/${type}`, resources, {
21
+ const response = await axios.post(endpoint, resources, {
21
22
  baseURL: remote,
22
23
  headers: { 'content-type': 'text/csv' },
23
24
  });
24
25
  data = response.data;
25
26
  }
26
27
  else {
27
- const response = await axios.post(`/api/apps/${appId}/resources/${type}`, resources.map((resource) => {
28
+ const response = await axios.post(endpoint, resources.map((resource) => {
28
29
  var _a, _b, _c;
29
30
  const resourceWithReferences = { ...resource };
30
31
  for (const [referencedProperty, resourceReference] of Object.entries((_a = definition === null || definition === void 0 ? void 0 : definition.references) !== null && _a !== void 0 ? _a : {})) {
@@ -45,7 +46,7 @@ export async function publishResource({ appId, definition, path, publishedResour
45
46
  .join('\n')}`);
46
47
  return ids;
47
48
  }
48
- export async function publishResourcesRecursively({ publishedResourcesIds, remote, resourcesToPublish, }) {
49
+ export async function publishResourcesRecursively({ publishedResourcesIds, remote, resourcesToPublish, seed, }) {
49
50
  let updatedPublishedResourcesIds = { ...publishedResourcesIds };
50
51
  for (const resourceToPublish of resourcesToPublish) {
51
52
  const resourceReferences = resourceToPublish.definition.references;
@@ -56,9 +57,10 @@ export async function publishResourcesRecursively({ publishedResourcesIds, remot
56
57
  if (referencedResourcesToPublish.length &&
57
58
  !updatedPublishedResourcesIds[referencedResourceType]) {
58
59
  const referencedPublishedResourcesIds = await publishResourcesRecursively({
59
- remote,
60
60
  resourcesToPublish: referencedResourcesToPublish,
61
61
  publishedResourcesIds: updatedPublishedResourcesIds,
62
+ remote,
63
+ seed,
62
64
  });
63
65
  updatedPublishedResourcesIds = {
64
66
  ...updatedPublishedResourcesIds,
@@ -72,6 +74,7 @@ export async function publishResourcesRecursively({ publishedResourcesIds, remot
72
74
  ...resourceToPublish,
73
75
  publishedResourcesIds: updatedPublishedResourcesIds,
74
76
  remote,
77
+ seed,
75
78
  });
76
79
  }
77
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appsemble/cli",
3
- "version": "0.25.2",
3
+ "version": "0.27.0",
4
4
  "description": "The CLI for developing with Appsemble apps and blocks",
5
5
  "keywords": [
6
6
  "app",
@@ -42,9 +42,9 @@
42
42
  "test": "vitest"
43
43
  },
44
44
  "dependencies": {
45
- "@appsemble/node-utils": "0.25.2",
46
- "@appsemble/types": "0.25.2",
47
- "@appsemble/utils": "0.25.2",
45
+ "@appsemble/node-utils": "0.27.0",
46
+ "@appsemble/types": "0.27.0",
47
+ "@appsemble/utils": "0.27.0",
48
48
  "@fortawesome/fontawesome-common-types": "^6.0.0",
49
49
  "@koa/cors": "^4.0.0",
50
50
  "@odata/parser": "^0.2.0",
@@ -87,7 +87,7 @@
87
87
  "yargs": "^17.0.0"
88
88
  },
89
89
  "devDependencies": {
90
- "@appsemble/types": "0.25.2",
90
+ "@appsemble/types": "0.27.0",
91
91
  "@types/concat-stream": "^2.0.0",
92
92
  "@types/inquirer": "^9.0.0",
93
93
  "@types/normalize-path": "^3.0.0",
@@ -33,4 +33,3 @@ export * from './applyAppServiceSecrets.js';
33
33
  export * from './getAppMembers.js';
34
34
  export * from './getAppTeams.js';
35
35
  export * from './getAppUserInfo.js';
36
- export * from './checkSeededResources.js';
@@ -33,5 +33,4 @@ export * from './applyAppServiceSecrets.js';
33
33
  export * from './getAppMembers.js';
34
34
  export * from './getAppTeams.js';
35
35
  export * from './getAppUserInfo.js';
36
- export * from './checkSeededResources.js';
37
36
  //# sourceMappingURL=index.js.map
@@ -1,8 +1,7 @@
1
- import { applyAppServiceSecrets, checkRole, checkSeededResources, createAppAsset, createAppResourcesWithAssets, createSettings, createTheme, deleteAppAsset, deleteAppResource, email, getApp, getAppAssets, getAppBlockStyles, getAppDetails, getAppIcon, getAppMembers, getAppMessages, getAppResource, getAppResources, getAppScreenshots, getAppStyles, getAppTeams, getAppUrl, getAppUserInfo, getBlockAsset, getBlockMessages, getBlocksAssetsPaths, getCsp, getDbUpdated, getHost, getTheme, parseQuery, reloadUser, sendNotifications, updateAppResource, verifyResourceActionPermission, } from './index.js';
1
+ import { applyAppServiceSecrets, checkRole, createAppAsset, createAppResourcesWithAssets, createSettings, createTheme, deleteAppAsset, deleteAppResource, email, getApp, getAppAssets, getAppBlockStyles, getAppDetails, getAppIcon, getAppMembers, getAppMessages, getAppResource, getAppResources, getAppScreenshots, getAppStyles, getAppTeams, getAppUrl, getAppUserInfo, getBlockAsset, getBlockMessages, getBlocksAssetsPaths, getCsp, getDbUpdated, getHost, getTheme, parseQuery, reloadUser, sendNotifications, updateAppResource, verifyResourceActionPermission, } from './index.js';
2
2
  export const options = {
3
3
  applyAppServiceSecrets,
4
4
  checkRole,
5
- checkSeededResources,
6
5
  createAppAsset,
7
6
  createAppResourcesWithAssets,
8
7
  createSettings,
@@ -6,7 +6,7 @@ pages:
6
6
  - name: Example Page A
7
7
  blocks:
8
8
  - type: action-button
9
- version: 0.25.2
9
+ version: 0.27.0
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.25.2
20
+ version: 0.27.0
21
21
  parameters:
22
22
  icon: arrow-left
23
23
  actions:
@@ -2,7 +2,7 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@appsemble/sdk": "0.25.2",
5
+ "@appsemble/sdk": "0.27.0",
6
6
  "mini-jsx": "^4.0.0"
7
7
  }
8
8
  }
@@ -2,8 +2,8 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@appsemble/preact": "0.25.2",
6
- "@appsemble/sdk": "0.25.2",
5
+ "@appsemble/preact": "0.27.0",
6
+ "@appsemble/sdk": "0.27.0",
7
7
  "preact": "^10.0.0"
8
8
  }
9
9
  }
@@ -2,6 +2,6 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@appsemble/sdk": "0.25.2"
5
+ "@appsemble/sdk": "0.27.0"
6
6
  }
7
7
  }
@@ -1 +0,0 @@
1
- export declare function checkSeededResources(): Promise<boolean>;
@@ -1,4 +0,0 @@
1
- export function checkSeededResources() {
2
- return Promise.resolve(false);
3
- }
4
- //# sourceMappingURL=checkSeededResources.js.map