@contentful/app-scripts 1.32.2 → 1.32.4-alpha.14

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.
Files changed (37) hide show
  1. package/README.md +64 -4
  2. package/lib/activate/build-bundle-activate-settings.js +4 -3
  3. package/lib/bin.js +21 -9
  4. package/lib/build-functions/build-functions.d.ts +0 -1
  5. package/lib/build-functions/build-functions.js +5 -10
  6. package/lib/clean-up/build-clean-up-settings.js +4 -3
  7. package/lib/create-app-definition/create-app-definition.js +6 -4
  8. package/lib/index.d.ts +1 -0
  9. package/lib/index.js +3 -1
  10. package/lib/install/install.js +19 -10
  11. package/lib/open/open-settings.d.ts +0 -1
  12. package/lib/open/open-settings.js +20 -11
  13. package/lib/types.d.ts +1 -11
  14. package/lib/upload/build-upload-settings.js +5 -6
  15. package/lib/upload/create-app-bundle.js +3 -3
  16. package/lib/upload/get-upload-settings-args.js +1 -3
  17. package/lib/upload/validate-bundle.d.ts +1 -1
  18. package/lib/upload/validate-bundle.js +2 -8
  19. package/lib/upsert-actions/client.d.ts +4 -0
  20. package/lib/upsert-actions/client.js +37 -0
  21. package/lib/upsert-actions/get-cli-args.d.ts +2 -0
  22. package/lib/upsert-actions/get-cli-args.js +40 -0
  23. package/lib/upsert-actions/index.d.ts +5 -0
  24. package/lib/upsert-actions/index.js +18 -0
  25. package/lib/upsert-actions/make-cma-payload.d.ts +2 -0
  26. package/lib/upsert-actions/make-cma-payload.js +39 -0
  27. package/lib/upsert-actions/prompt-interactive-args.d.ts +2 -0
  28. package/lib/upsert-actions/prompt-interactive-args.js +36 -0
  29. package/lib/upsert-actions/types.d.ts +52 -0
  30. package/lib/upsert-actions/types.js +2 -0
  31. package/lib/upsert-actions/upsert-actions.d.ts +16 -0
  32. package/lib/upsert-actions/upsert-actions.js +80 -0
  33. package/lib/upsert-actions/validation.d.ts +9 -0
  34. package/lib/upsert-actions/validation.js +77 -0
  35. package/lib/utils.d.ts +7 -4
  36. package/lib/utils.js +22 -9
  37. package/package.json +6 -6
package/README.md CHANGED
@@ -177,6 +177,16 @@ You can also execute this command without the argument if the environment variab
177
177
  > $ CONTENTFUL_APP_DEF_ID=some-definition-id npx --no-install @contentful/app-scripts open-settings
178
178
  > ```
179
179
 
180
+ **Options:**
181
+
182
+ | Argument | Description | Default value |
183
+ | ----------------- | -------------------------------------------- | -------------------- |
184
+ | |
185
+ | `--definition-id` | The ID of the app to which to add the bundle |
186
+ | `--host` | (optional) Contentful CMA-endpoint to use | `api.contentful.com` |
187
+
188
+ **Note:** You can also pass all arguments in interactive mode to skip being asked for it.
189
+
180
190
  ### Clean up bundles
181
191
 
182
192
  Allows you to clean the list of previous bundles. It fetches the list and deletes all bundles except the 50 newest ones.
@@ -246,6 +256,16 @@ By default, the script will install the app into the default host URL: `app.cont
246
256
  > $ npx --no-install @contentful/app-scripts install --definition-id some-definition-id --host api.eu.contentful.com
247
257
  > ```
248
258
 
259
+ **Options:**
260
+
261
+ | Argument | Description | Default value |
262
+ | ----------------- | -------------------------------------------- | -------------------- |
263
+ | |
264
+ | `--definition-id` | The ID of the app to which to add the bundle |
265
+ | `--host` | (optional) Contentful CMA-endpoint to use | `api.contentful.com` |
266
+
267
+ **Note:** You can also pass all arguments in interactive mode to skip being asked for it.
268
+
249
269
  ### Tracking
250
270
 
251
271
  We gather depersonalized usage data of our CLI tools in order to improve experience. If you do not want your data to be gathered, you can opt out by providing an env variable `DISABLE_ANALYTICS` set to any value:
@@ -286,7 +306,47 @@ When passing the `--ci` argument adding all variables as arguments is required
286
306
  **Options:**
287
307
 
288
308
  Options:
289
- -e, --esbuild-config <path> custom esbuild config file path
290
- -m, --manifest-file <path> Contentful app manifest file path
291
- -w, --watch watch for changes
292
- -h, --help display help for command
309
+ -e, --esbuild-config <path> custom esbuild config file path
310
+ -m, --manifest-file <path> Contentful app manifest file path
311
+ -w, --watch watch for changes
312
+ -h, --help display help for command
313
+
314
+ ### Upsert App Actions
315
+
316
+ Creates or updates Actions for an App using the configuration in a Contentful App Manifest file. Created resources will be synced back to your manifest file.
317
+
318
+ #### Interactive mode:
319
+
320
+ In the interactive mode, the CLI will ask for all required options.
321
+
322
+ > **Example**
323
+ >
324
+ > ```shell
325
+ > $ npx --no-install @contentful/app-scripts upsert-actions
326
+ > ```
327
+
328
+ #### Non-interactive mode:
329
+
330
+ When passing the `--ci` argument adding all variables as arguments is required.
331
+
332
+ > **Example**
333
+ >
334
+ > ```shell
335
+ > $ npx --no-install @contentful/app-scripts upsert-actions --ci \
336
+ > --manifest-file path/to/contentful-app-manifest.json \
337
+ > --organization-id some-org-id \
338
+ > --definition-id some-app-def-id \
339
+ > --token $MY_CONTENTFUL_PAT
340
+ > ```
341
+
342
+ **Options:**
343
+
344
+ | Argument | Description | Default value |
345
+ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ |
346
+ | `--manifest-file` | The path to the Contentful app manifest file | `contentful-app-manifest.json` |
347
+ | `--organization-id` | The ID of the organization which the app is defined in | |
348
+ | `--definition-id` | The ID of the app to which to add the actions | |
349
+ | `--token` | A personal [access token](https://www.contentful.com/developers/docs/references/content-management-api/#/reference/personal-access-tokens) | |
350
+ | `--host` | (optional) Contentful CMA-endpoint to use | `api.contentful.com` |
351
+
352
+ **Note:** You can also pass all arguments in interactive mode to skip being asked for it.
@@ -23,11 +23,12 @@ async function buildBundleActivateSettings(options) {
23
23
  default: 'api.contentful.com',
24
24
  });
25
25
  }
26
- const appActivateSettings = await inquirer_1.default.prompt(prompts);
27
- const appInfo = await (0, get_app_info_1.getAppInfo)(options);
26
+ const { host: interactiveHost, ...appActivateSettings } = await inquirer_1.default.prompt(prompts);
27
+ const hostValue = host || interactiveHost;
28
+ const appInfo = await (0, get_app_info_1.getAppInfo)({ ...options, host: hostValue });
28
29
  return {
29
30
  bundleId,
30
- host,
31
+ host: hostValue,
31
32
  ...appActivateSettings,
32
33
  ...appInfo,
33
34
  };
package/lib/bin.js CHANGED
@@ -22,11 +22,11 @@ async function runCommand(command, options) {
22
22
  .description('Upload your build folder and create an AppBundle')
23
23
  .option('--bundle-dir [directory]', 'The directory of your build folder')
24
24
  .option('--organization-id [orgId]', 'The id of your organization')
25
- .option('--definition-id [defId]', 'The id of your apps definition')
25
+ .option('--definition-id [defId]', 'The id of your app\'s definition')
26
26
  .option('--token [accessToken]', 'Your content management access token')
27
27
  .option('--comment [comment]', 'Optional comment for the created bundle')
28
28
  .option('--skip-activation', 'A Boolean flag to skip automatic activation')
29
- .option('--host [host]', 'Contentful domain to use')
29
+ .option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
30
30
  .action(async (options) => {
31
31
  await runCommand(index_1.upload, options);
32
32
  });
@@ -35,16 +35,17 @@ async function runCommand(command, options) {
35
35
  .description('Mark an AppBundle as "active" for a given AppDefinition')
36
36
  .option('--bundle-id [bundleId]', 'The id of your bundle')
37
37
  .option('--organization-id [orgId]', 'The id of your organization')
38
- .option('--definition-id [defId]', 'The id of your apps definition')
38
+ .option('--definition-id [defId]', 'The id of your app\'s definition')
39
39
  .option('--token [accessToken]', 'Your content management access token')
40
- .option('--host [host]', 'Contentful domain to use')
40
+ .option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
41
41
  .action(async (options) => {
42
42
  await runCommand(index_1.activate, options);
43
43
  });
44
44
  commander_1.program
45
45
  .command('open-settings')
46
46
  .description('Opens the app editor for a given AppDefinition')
47
- .option('--definition-id [defId]', 'The id of your apps definition')
47
+ .option('--definition-id [defId]', 'The id of your app\'s definition')
48
+ .option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
48
49
  .action(async (options) => {
49
50
  await runCommand(index_1.open, options);
50
51
  });
@@ -52,10 +53,10 @@ async function runCommand(command, options) {
52
53
  .command('bundle-cleanup')
53
54
  .description('Removes old, non-active bundles, only keeps the 50 most recent ones')
54
55
  .option('--organization-id [orgId]', 'The id of your organization')
55
- .option('--definition-id [defId]', 'The id of your apps definition')
56
+ .option('--definition-id [defId]', 'The id of your app\'s definition')
56
57
  .option('--token [accessToken]', 'Your content management access token')
57
58
  .option('--keep [keepAmount]', 'The amount of bundles that should remain')
58
- .option('--host [host]', 'Contentful domain to use')
59
+ .option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
59
60
  .action(async (options) => {
60
61
  await runCommand(index_1.cleanup, options);
61
62
  });
@@ -68,8 +69,8 @@ async function runCommand(command, options) {
68
69
  commander_1.program
69
70
  .command('install')
70
71
  .description('Opens a picker to select the space and environment for installing the app associated with a given AppDefinition')
71
- .option('--definition-id [defId]', 'The id of your apps definition')
72
- .option('--host [host]', 'Contentful domain to use')
72
+ .option('--definition-id [defId]', 'The id of your app\'s definition')
73
+ .option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
73
74
  .action(async (options) => {
74
75
  await runCommand(index_1.install, options);
75
76
  });
@@ -82,6 +83,17 @@ async function runCommand(command, options) {
82
83
  .action(async (options) => {
83
84
  await runCommand(index_1.buildFunctions, options);
84
85
  });
86
+ commander_1.program
87
+ .command('upsert-actions')
88
+ .description('Upsert Action(s) for an App')
89
+ .option('-m, --manifest-file <path>', 'Contentful app manifest file path')
90
+ .option('--organization-id [orgId]', 'The id of your organization')
91
+ .option('--definition-id [defId]', 'The id of your app\'s definition')
92
+ .option('--token [accessToken]', 'Your content management access token')
93
+ .option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
94
+ .action(async (options) => {
95
+ await runCommand(index_1.upsertActions, options);
96
+ });
85
97
  commander_1.program.hook('preAction', (thisCommand) => {
86
98
  (0, index_1.track)({ command: thisCommand.args[0], ci: thisCommand.opts().ci });
87
99
  });
@@ -3,7 +3,6 @@ type ContentfulFunctionToBuild = Omit<ContentfulFunction, 'entryFile'> & {
3
3
  entryFile: string;
4
4
  };
5
5
  export declare const validateFunctions: (manifest: Record<string, any>) => void;
6
- export declare const resolveManifestFile: (options: BuildFunctionsOptions, cwd?: string) => any;
7
6
  export declare const resolveEsBuildConfig: (options: BuildFunctionsOptions, manifest: {
8
7
  functions: ContentfulFunctionToBuild[];
9
8
  }, cwd?: string) => any;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.resolveEsBuildConfig = exports.resolveManifestFile = exports.validateFunctions = void 0;
6
+ exports.resolveEsBuildConfig = exports.validateFunctions = void 0;
7
7
  exports.buildFunctions = buildFunctions;
8
8
  /* eslint-disable @typescript-eslint/no-var-requires */
9
9
  const esbuild_1 = __importDefault(require("esbuild"));
@@ -11,17 +11,18 @@ const path_1 = require("path");
11
11
  const node_modules_polyfill_1 = require("@esbuild-plugins/node-modules-polyfill");
12
12
  const node_globals_polyfill_1 = require("@esbuild-plugins/node-globals-polyfill");
13
13
  const zod_1 = require("zod");
14
+ const utils_1 = require("../utils");
14
15
  const functionManifestSchema = zod_1.z
15
16
  .object({
16
17
  functions: zod_1.z.array(zod_1.z
17
18
  .object({
18
- id: zod_1.z.string(),
19
+ id: zod_1.z.string().regex(utils_1.ID_REGEX, 'Invalid "id" (must only contain alphanumeric characters)'),
19
20
  name: zod_1.z.string(),
20
21
  description: zod_1.z.string(),
21
22
  path: zod_1.z.string(),
22
23
  entryFile: zod_1.z.string(),
23
24
  accepts: zod_1.z.array(zod_1.z.string()),
24
- })
25
+ }, {})
25
26
  .required()),
26
27
  })
27
28
  .required();
@@ -52,12 +53,6 @@ const validateFunctions = (manifest) => {
52
53
  });
53
54
  };
54
55
  exports.validateFunctions = validateFunctions;
55
- const resolveManifestFile = (options, cwd = process.cwd()) => {
56
- return require(options.manifestFile
57
- ? (0, path_1.resolve)(cwd, options.manifestFile)
58
- : (0, path_1.resolve)(cwd, 'contentful-app-manifest.json'));
59
- };
60
- exports.resolveManifestFile = resolveManifestFile;
61
56
  const getEntryPoints = (manifest, cwd = process.cwd()) => {
62
57
  return manifest.functions.reduce((result, contentfulFunction) => {
63
58
  const fileProperties = (0, path_1.parse)(contentfulFunction.path);
@@ -85,7 +80,7 @@ const resolveEsBuildConfig = (options, manifest, cwd = process.cwd()) => {
85
80
  };
86
81
  exports.resolveEsBuildConfig = resolveEsBuildConfig;
87
82
  async function buildFunctions(options) {
88
- const manifest = (0, exports.resolveManifestFile)(options);
83
+ const manifest = (0, utils_1.resolveManifestFile)(options);
89
84
  try {
90
85
  console.log('Building functions');
91
86
  (0, exports.validateFunctions)(manifest);
@@ -22,11 +22,12 @@ async function buildCleanUpSettings(options) {
22
22
  default: constants_1.DEFAULT_CONTENTFUL_API_HOST,
23
23
  });
24
24
  }
25
- const appCleanUpSettings = await (0, inquirer_1.prompt)(prompts);
26
- const appInfo = await (0, get_app_info_1.getAppInfo)(options);
25
+ const { host: interactiveHost, ...appCleanUpSettings } = await (0, inquirer_1.prompt)(prompts);
26
+ const hostValue = host || interactiveHost;
27
+ const appInfo = await (0, get_app_info_1.getAppInfo)({ ...options, host: hostValue });
27
28
  return {
28
29
  keep: keep === undefined ? +appCleanUpSettings.keep : +keep,
29
- host,
30
+ host: hostValue,
30
31
  ...appCleanUpSettings,
31
32
  ...appInfo,
32
33
  };
@@ -42,7 +42,8 @@ function assertValidArguments(accessToken, appDefinitionSettings) {
42
42
  }
43
43
  async function createAppDefinition(accessToken, appDefinitionSettings) {
44
44
  assertValidArguments(accessToken, appDefinitionSettings);
45
- const client = (0, contentful_management_1.createClient)({ accessToken, host: appDefinitionSettings.host });
45
+ const host = appDefinitionSettings.host;
46
+ const client = (0, contentful_management_1.createClient)({ accessToken, host });
46
47
  const organizations = await fetchOrganizations(client);
47
48
  const selectedOrg = await (0, utils_1.selectFromList)(organizations, 'Select an organization for your app:', constants_1.ORG_ID_ENV_KEY);
48
49
  const organizationId = selectedOrg.value;
@@ -92,16 +93,17 @@ async function createAppDefinition(accessToken, appDefinitionSettings) {
92
93
  await (0, cache_credential_1.cacheEnvVars)({
93
94
  [constants_1.APP_DEF_ENV_KEY]: createdAppDefinition.sys.id,
94
95
  });
96
+ const webApp = (0, utils_1.getWebAppHostname)(host);
95
97
  console.log(`
96
98
  ${chalk_1.default.greenBright('Success!')} Created an app definition for ${chalk_1.default.bold(appName)} in ${chalk_1.default.bold(selectedOrg.name)}.
97
99
 
98
- ${chalk_1.default.dim(`NOTE: You can update this app definition in your organization settings:
99
- ${chalk_1.default.underline(`https://app.contentful.com/deeplink?link=org`)}`)}
100
+ ${chalk_1.default.dim(`NOTE: You can update this app definition in your apps settings:
101
+ ${chalk_1.default.underline(`https://${webApp}/deeplink?link=app-definition-list`)}`)}
100
102
 
101
103
  ${chalk_1.default.bold('Next steps:')}
102
104
  1. Run your app with ${chalk_1.default.cyan('`npm start`')} inside of your app folder.
103
105
  2. Install this app definition to one of your spaces by opening:
104
- ${chalk_1.default.underline(`https://app.contentful.com/deeplink?link=apps&id=${createdAppDefinition.sys.id}`)}
106
+ ${chalk_1.default.underline(`https://${webApp}/deeplink?link=apps&id=${createdAppDefinition.sys.id}`)}
105
107
  3. Learn how to build your first Contentful app:
106
108
  ${chalk_1.default.underline(`https://ctfl.io/app-tutorial`)}
107
109
  `);
package/lib/index.d.ts CHANGED
@@ -7,3 +7,4 @@ export { track } from './analytics';
7
7
  export { feedback } from './feedback';
8
8
  export { install } from './install';
9
9
  export { buildFunctions } from './build-functions';
10
+ export { upsertActions } from './upsert-actions';
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildFunctions = exports.install = exports.feedback = exports.track = exports.open = exports.cleanup = exports.activate = exports.upload = exports.createAppDefinition = void 0;
3
+ exports.upsertActions = exports.buildFunctions = exports.install = exports.feedback = exports.track = exports.open = exports.cleanup = exports.activate = exports.upload = exports.createAppDefinition = void 0;
4
4
  var create_app_definition_1 = require("./create-app-definition");
5
5
  Object.defineProperty(exports, "createAppDefinition", { enumerable: true, get: function () { return create_app_definition_1.createAppDefinition; } });
6
6
  var upload_1 = require("./upload");
@@ -19,3 +19,5 @@ var install_1 = require("./install");
19
19
  Object.defineProperty(exports, "install", { enumerable: true, get: function () { return install_1.install; } });
20
20
  var build_functions_1 = require("./build-functions");
21
21
  Object.defineProperty(exports, "buildFunctions", { enumerable: true, get: function () { return build_functions_1.buildFunctions; } });
22
+ var upsert_actions_1 = require("./upsert-actions");
23
+ Object.defineProperty(exports, "upsertActions", { enumerable: true, get: function () { return upsert_actions_1.upsertActions; } });
@@ -8,8 +8,10 @@ const open_1 = __importDefault(require("open"));
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
9
  const inquirer_1 = __importDefault(require("inquirer"));
10
10
  const constants_1 = require("../constants");
11
+ const utils_1 = require("../utils");
11
12
  async function installToEnvironment(options) {
12
13
  let definitionId;
14
+ const prompts = [];
13
15
  if (options.definitionId) {
14
16
  definitionId = options.definitionId;
15
17
  }
@@ -17,15 +19,22 @@ async function installToEnvironment(options) {
17
19
  definitionId = process.env[constants_1.APP_DEF_ENV_KEY];
18
20
  }
19
21
  else {
20
- const prompts = await inquirer_1.default.prompt([
21
- {
22
- name: 'definitionId',
23
- message: `The id of the app:`,
24
- },
25
- ]);
26
- definitionId = prompts.definitionId;
22
+ prompts.push({
23
+ name: 'definitionId',
24
+ message: `The id of the app:`,
25
+ });
27
26
  }
28
- if (!definitionId) {
27
+ if (!options.host) {
28
+ prompts.push({
29
+ name: 'host',
30
+ message: `Contentful CMA endpoint URL:`,
31
+ default: constants_1.DEFAULT_CONTENTFUL_API_HOST,
32
+ });
33
+ }
34
+ const openSettingsOptions = await inquirer_1.default.prompt(prompts);
35
+ const hostValue = options.host || openSettingsOptions?.host;
36
+ const appDefinitionIdValue = definitionId || openSettingsOptions?.definitionId;
37
+ if (!appDefinitionIdValue) {
29
38
  console.log(`
30
39
  ${chalk_1.default.red('Error:')} There was no app-definition defined.
31
40
 
@@ -34,8 +43,8 @@ async function installToEnvironment(options) {
34
43
  `);
35
44
  throw new Error('No app-definition-id');
36
45
  }
37
- const host = options.host || constants_1.DEFAULT_CONTENTFUL_APP_HOST;
38
- const redirectUrl = `https://${host}/deeplink?link=apps`;
46
+ const webApp = (0, utils_1.getWebAppHostname)(hostValue);
47
+ const redirectUrl = `https://${webApp}/deeplink?link=apps`;
39
48
  try {
40
49
  (0, open_1.default)(`${redirectUrl}&id=${definitionId}`);
41
50
  }
@@ -1,3 +1,2 @@
1
1
  import { OpenSettingsOptions } from '../types';
2
- export declare const REDIRECT_URL = "https://app.contentful.com/deeplink?link=app-definition";
3
2
  export declare function openSettings(options: OpenSettingsOptions): Promise<void>;
@@ -3,15 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.REDIRECT_URL = void 0;
7
6
  exports.openSettings = openSettings;
8
7
  const open_1 = __importDefault(require("open"));
9
8
  const chalk_1 = __importDefault(require("chalk"));
10
9
  const inquirer_1 = __importDefault(require("inquirer"));
11
10
  const constants_1 = require("../constants");
12
- exports.REDIRECT_URL = 'https://app.contentful.com/deeplink?link=app-definition';
11
+ const utils_1 = require("../utils");
13
12
  async function openSettings(options) {
14
13
  let definitionId;
14
+ const prompts = [];
15
15
  if (options.definitionId) {
16
16
  definitionId = options.definitionId;
17
17
  }
@@ -19,15 +19,22 @@ async function openSettings(options) {
19
19
  definitionId = process.env[constants_1.APP_DEF_ENV_KEY];
20
20
  }
21
21
  else {
22
- const prompts = await inquirer_1.default.prompt([
23
- {
24
- name: 'definitionId',
25
- message: `The id of the app:`,
26
- },
27
- ]);
28
- definitionId = prompts.definitionId;
22
+ prompts.push({
23
+ name: 'definitionId',
24
+ message: `The id of the app:`,
25
+ });
29
26
  }
30
- if (!definitionId) {
27
+ if (!options.host) {
28
+ prompts.push({
29
+ name: 'host',
30
+ message: `Contentful CMA endpoint URL:`,
31
+ default: constants_1.DEFAULT_CONTENTFUL_API_HOST,
32
+ });
33
+ }
34
+ const openSettingsOptions = await inquirer_1.default.prompt(prompts);
35
+ const hostValue = options.host || openSettingsOptions?.host;
36
+ const appDefinitionIdValue = definitionId || openSettingsOptions?.definitionId;
37
+ if (!appDefinitionIdValue) {
31
38
  console.log(`
32
39
  ${chalk_1.default.red('Error:')} There was no app-definition defined.
33
40
 
@@ -36,8 +43,10 @@ async function openSettings(options) {
36
43
  `);
37
44
  throw new Error('No app-definition-id');
38
45
  }
46
+ const webApp = (0, utils_1.getWebAppHostname)(hostValue);
47
+ const redirectUrl = `https://${webApp}/deeplink?link=app-definition`;
39
48
  try {
40
- (0, open_1.default)(`${exports.REDIRECT_URL}&id=${definitionId}`);
49
+ (0, open_1.default)(`${redirectUrl}&id=${appDefinitionIdValue}`);
41
50
  }
42
51
  catch (err) {
43
52
  console.log(`${chalk_1.default.red('Error:')} Failed to open browser`);
package/lib/types.d.ts CHANGED
@@ -1,15 +1,5 @@
1
1
  import { Definition } from './definition-api';
2
2
  import { Organization } from './organization-api';
3
- export interface FunctionAppAction {
4
- id: string;
5
- name: string;
6
- description: string;
7
- category: 'Custom';
8
- type: 'function';
9
- path: string;
10
- allowNetworks?: string[];
11
- entryFile?: string;
12
- }
13
3
  export interface ContentfulFunction {
14
4
  id: string;
15
5
  name: string;
@@ -49,6 +39,7 @@ export interface CleanupSettings {
49
39
  }
50
40
  export interface OpenSettingsOptions {
51
41
  definitionId?: string;
42
+ host?: string;
52
43
  }
53
44
  export interface InstallOptions {
54
45
  definitionId?: string;
@@ -73,7 +64,6 @@ export interface UploadSettings {
73
64
  skipActivation?: boolean;
74
65
  userAgentApplication?: string;
75
66
  host?: string;
76
- actions?: FunctionAppAction[];
77
67
  functions?: ContentfulFunction[];
78
68
  }
79
69
  export interface BuildFunctionsOptions {
@@ -7,8 +7,7 @@ const get_app_info_1 = require("../get-app-info");
7
7
  const utils_1 = require("../utils");
8
8
  const constants_1 = require("../constants");
9
9
  async function buildAppUploadSettings(options) {
10
- const actionsManifest = (0, utils_1.getEntityFromManifest)('actions');
11
- const functionManifest = (0, utils_1.getEntityFromManifest)('functions');
10
+ const functionManifest = (0, utils_1.getFunctionsFromManifest)();
12
11
  const prompts = [];
13
12
  const { bundleDir, comment, skipActivation, host } = options;
14
13
  if (!bundleDir) {
@@ -41,14 +40,14 @@ async function buildAppUploadSettings(options) {
41
40
  filter: hostProtocolFilter,
42
41
  });
43
42
  }
44
- const { activateBundle, ...appUploadSettings } = await (0, inquirer_1.prompt)(prompts);
45
- const appInfo = await (0, get_app_info_1.getAppInfo)(options);
43
+ const { activateBundle, host: interactiveHost, ...appUploadSettings } = await (0, inquirer_1.prompt)(prompts);
44
+ const hostValue = host || interactiveHost;
45
+ const appInfo = await (0, get_app_info_1.getAppInfo)({ ...options, host: hostValue });
46
46
  return {
47
47
  bundleDirectory: bundleDir,
48
48
  skipActivation: skipActivation === undefined ? !activateBundle : skipActivation,
49
49
  comment,
50
- host,
51
- actions: actionsManifest,
50
+ host: hostValue,
52
51
  functions: functionManifest,
53
52
  ...appUploadSettings,
54
53
  ...appInfo,
@@ -11,7 +11,7 @@ const utils_1 = require("../utils");
11
11
  const contentful_management_1 = require("contentful-management");
12
12
  const create_app_upload_1 = require("./create-app-upload");
13
13
  async function createAppBundleFromUpload(settings, appUploadId) {
14
- const { accessToken, host, userAgentApplication, comment, actions, functions } = settings;
14
+ const { accessToken, host, userAgentApplication, comment, functions } = settings;
15
15
  const clientSpinner = (0, ora_1.default)('Verifying your upload...').start();
16
16
  const client = (0, contentful_management_1.createClient)({
17
17
  accessToken,
@@ -27,7 +27,6 @@ async function createAppBundleFromUpload(settings, appUploadId) {
27
27
  appBundle = await appDefinition.createAppBundle({
28
28
  appUploadId,
29
29
  comment: comment && comment.length > 0 ? comment : undefined,
30
- actions,
31
30
  functions,
32
31
  });
33
32
  }
@@ -59,13 +58,14 @@ async function createAppBundleFromSettings(settings) {
59
58
 
60
59
  Bundle Id: ${chalk_1.default.yellow(appBundle.sys.id)}
61
60
  `);
61
+ const webApp = (0, utils_1.getWebAppHostname)(settings.host);
62
62
  if (settings.skipActivation) {
63
63
  console.log(`
64
64
  ${chalk_1.default.green(`NEXT STEPS:`)}
65
65
 
66
66
  ${chalk_1.default.bold('You can activate this app bundle in your apps settings:')}
67
67
 
68
- ${chalk_1.default.underline('https://app.contentful.com/deeplink?link=app-definition-list')}
68
+ ${chalk_1.default.underline(`https://${webApp}/deeplink?link=app-definition-list`)}
69
69
 
70
70
  ${chalk_1.default.bold('or by simply running the cli command:')}
71
71
 
@@ -17,8 +17,7 @@ const requiredOptions = {
17
17
  };
18
18
  async function getUploadSettingsArgs(options) {
19
19
  const validateSpinner = (0, ora_1.default)('Validating your input...').start();
20
- const actionsManifest = (0, utils_1.getEntityFromManifest)('actions');
21
- const functionManifest = (0, utils_1.getEntityFromManifest)('functions');
20
+ const functionManifest = (0, utils_1.getFunctionsFromManifest)();
22
21
  const { bundleDir, comment, skipActivation, host, userAgentApplication } = options;
23
22
  try {
24
23
  (0, validate_arguments_1.validateArguments)(requiredOptions, options, 'upload');
@@ -30,7 +29,6 @@ async function getUploadSettingsArgs(options) {
30
29
  comment,
31
30
  host,
32
31
  userAgentApplication,
33
- actions: actionsManifest,
34
32
  functions: functionManifest,
35
33
  };
36
34
  }
@@ -1,2 +1,2 @@
1
1
  import { UploadSettings } from '../types';
2
- export declare const validateBundle: (path: string, { functions, actions }: Pick<UploadSettings, "functions" | "actions">) => void;
2
+ export declare const validateBundle: (path: string, { functions }: Pick<UploadSettings, "functions">) => void;
@@ -13,11 +13,11 @@ const ABSOLUTE_PATH_REG_EXP = /(src|href)="\/([^/])([^"]*)+"/g;
13
13
  const fileContainsAbsolutePath = (fileContent) => {
14
14
  return [...fileContent.matchAll(ABSOLUTE_PATH_REG_EXP)].length > 0;
15
15
  };
16
- const validateBundle = (path, { functions, actions }) => {
16
+ const validateBundle = (path, { functions }) => {
17
17
  const buildFolder = path_1.default.join('./', path);
18
18
  const files = fs_1.default.readdirSync(buildFolder, { recursive: true, encoding: 'utf-8' });
19
19
  const entry = getEntryFile(files);
20
- if (!entry && !functions && !actions) {
20
+ if (!entry && !functions) {
21
21
  throw new Error('Ensure your bundle includes a valid index.html file in its root folder, or a valid Contentful Function entrypoint (defined in your contentful-app-manifest.json file).');
22
22
  }
23
23
  if (entry) {
@@ -34,11 +34,5 @@ const validateBundle = (path, { functions, actions }) => {
34
34
  throw new Error(`Function "${functionWithoutEntryFile.id}" is missing its entry file at "${path_1.default.join(buildFolder, functionWithoutEntryFile.path)}".`);
35
35
  }
36
36
  }
37
- if (actions) {
38
- const actionWithoutEntryFile = actions.find(({ path }) => !files.includes(path));
39
- if (actionWithoutEntryFile) {
40
- throw new Error(`Action "${actionWithoutEntryFile.id}" is missing its entry file at "${path_1.default.join(buildFolder, actionWithoutEntryFile.path)}".`);
41
- }
42
- }
43
37
  };
44
38
  exports.validateBundle = validateBundle;
@@ -0,0 +1,4 @@
1
+ import { AppActionProps, CreateAppActionProps, PlainClientAPI } from 'contentful-management';
2
+ export declare function createAction(client: PlainClientAPI, appDefinitionId: string, payload: CreateAppActionProps): Promise<AppActionProps>;
3
+ export declare function getExistingAction(client: PlainClientAPI, appDefinitionId: string, appActionId: string): Promise<AppActionProps | null>;
4
+ export declare function updateAction(client: PlainClientAPI, appDefinitionId: string, appActionId: string, payload: CreateAppActionProps): Promise<AppActionProps>;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAction = createAction;
4
+ exports.getExistingAction = getExistingAction;
5
+ exports.updateAction = updateAction;
6
+ const chalk_1 = require("chalk");
7
+ async function createAction(client, appDefinitionId, payload) {
8
+ const action = await client.appAction.create({
9
+ appDefinitionId,
10
+ }, payload);
11
+ console.log(`
12
+ ${(0, chalk_1.cyan)('Success!')} Created Action ${(0, chalk_1.cyan)(action.name)} with ID ${(0, chalk_1.cyan)(action.sys.id)} for App ${(0, chalk_1.cyan)(appDefinitionId)}.`);
13
+ return action;
14
+ }
15
+ async function getExistingAction(client, appDefinitionId, appActionId) {
16
+ try {
17
+ return await client.appAction.get({
18
+ appDefinitionId,
19
+ appActionId,
20
+ });
21
+ }
22
+ catch (err) {
23
+ if (err.name === 'NotFound') {
24
+ return null;
25
+ }
26
+ throw err;
27
+ }
28
+ }
29
+ async function updateAction(client, appDefinitionId, appActionId, payload) {
30
+ const action = await client.appAction.update({
31
+ appDefinitionId,
32
+ appActionId,
33
+ }, payload);
34
+ console.log(`
35
+ ${(0, chalk_1.cyan)('Success!')} Updated Action ${(0, chalk_1.cyan)(action.name)} with ID ${(0, chalk_1.cyan)(appActionId)} for App ${(0, chalk_1.cyan)(appDefinitionId)}.`);
36
+ return action;
37
+ }
@@ -0,0 +1,2 @@
1
+ import { CreateAppActionSettings } from "./types";
2
+ export declare function getCreateAppActionsArgs(settings: Record<string, any>): Promise<Required<CreateAppActionSettings>>;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getCreateAppActionsArgs = getCreateAppActionsArgs;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ const get_app_info_1 = require("../get-app-info");
10
+ const validate_arguments_1 = require("../validate-arguments");
11
+ const requiredOptions = {
12
+ organizationId: '--organization-id',
13
+ definitionId: '--definition-id',
14
+ token: '--token',
15
+ };
16
+ async function getCreateAppActionsArgs(settings) {
17
+ const validateSpinner = (0, ora_1.default)('Validating your input').start();
18
+ try {
19
+ (0, validate_arguments_1.validateArguments)(requiredOptions, settings, 'upsert-actions');
20
+ const appInfo = await (0, get_app_info_1.getAppInfo)(settings);
21
+ return {
22
+ host: settings.host || 'api.contentful.com',
23
+ manifestFile: settings.manifestFile,
24
+ accessToken: settings.token,
25
+ appDefinitionId: appInfo.definition.value,
26
+ organizationId: appInfo.organization.value,
27
+ };
28
+ }
29
+ catch (err) {
30
+ console.log(`
31
+ ${chalk_1.default.red('Validation failed')}
32
+ ${err.message}
33
+ `);
34
+ // eslint-disable-next-line no-process-exit
35
+ process.exit(1);
36
+ }
37
+ finally {
38
+ validateSpinner.stop();
39
+ }
40
+ }
@@ -0,0 +1,5 @@
1
+ import { type CreateAppActionSettings } from './types';
2
+ export declare const upsertActions: {
3
+ interactive: (options: CreateAppActionSettings) => Promise<void>;
4
+ nonInteractive: (options: CreateAppActionSettings) => Promise<void>;
5
+ };
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.upsertActions = void 0;
4
+ const upsert_actions_1 = require("./upsert-actions");
5
+ const get_cli_args_1 = require("./get-cli-args");
6
+ const prompt_interactive_args_1 = require("./prompt-interactive-args");
7
+ const interactive = async (options) => {
8
+ const settings = await (0, prompt_interactive_args_1.promptCreateAppAction)(options);
9
+ await (0, upsert_actions_1.upsertAppActions)(settings);
10
+ };
11
+ const nonInteractive = async (options) => {
12
+ const settings = await (0, get_cli_args_1.getCreateAppActionsArgs)(options);
13
+ await (0, upsert_actions_1.upsertAppActions)(settings);
14
+ };
15
+ exports.upsertActions = {
16
+ interactive,
17
+ nonInteractive,
18
+ };
@@ -0,0 +1,2 @@
1
+ import { AppActionManifest, CreateAppActionPayload } from './types';
2
+ export declare function makeAppActionCMAPayload(action: AppActionManifest): CreateAppActionPayload;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeAppActionCMAPayload = makeAppActionCMAPayload;
4
+ function makeAppActionCMAPayload(action) {
5
+ const baseProps = {
6
+ name: action.name,
7
+ ...(action.description && { description: action.description }),
8
+ ...(action.id && { id: action.id }),
9
+ };
10
+ const additionalPropsByType = action.type === 'function-invocation'
11
+ ? {
12
+ type: action.type,
13
+ function: {
14
+ sys: {
15
+ id: action.functionId,
16
+ linkType: 'Function',
17
+ type: 'Link',
18
+ },
19
+ },
20
+ }
21
+ : {
22
+ type: action.type,
23
+ url: action.url,
24
+ };
25
+ const additionalPropsByCategory = action.category === 'Custom'
26
+ ? {
27
+ category: action.category,
28
+ parameters: action.parameters,
29
+ }
30
+ : {
31
+ category: action.category,
32
+ };
33
+ const payload = {
34
+ ...baseProps,
35
+ ...additionalPropsByType,
36
+ ...additionalPropsByCategory,
37
+ };
38
+ return payload;
39
+ }
@@ -0,0 +1,2 @@
1
+ import { CreateAppActionSettings } from './types';
2
+ export declare function promptCreateAppAction(options: Record<string, any>): Promise<Required<CreateAppActionSettings>>;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.promptCreateAppAction = promptCreateAppAction;
4
+ const inquirer_1 = require("inquirer");
5
+ const constants_1 = require("../constants");
6
+ const get_app_info_1 = require("../get-app-info");
7
+ async function promptCreateAppAction(options) {
8
+ const { manifestFile, host } = options;
9
+ const prompts = [];
10
+ if (manifestFile === undefined) {
11
+ prompts.push({
12
+ type: 'input',
13
+ name: 'manifestFile',
14
+ message: `Path to your Contentful app manifest file:`,
15
+ default: constants_1.DEFAULT_APP_MANIFEST_PATH,
16
+ });
17
+ }
18
+ if (!host) {
19
+ prompts.push({
20
+ name: 'host',
21
+ message: `Contentful CMA endpoint URL:`,
22
+ default: 'api.contentful.com',
23
+ });
24
+ }
25
+ const { host: interactiveHost, manifestFile: interactiveManifest } = await (0, inquirer_1.prompt)(prompts);
26
+ const hostValue = host || interactiveHost;
27
+ const manifestFileValue = manifestFile || interactiveManifest;
28
+ const appInfo = await (0, get_app_info_1.getAppInfo)({ ...options, host: hostValue });
29
+ return {
30
+ host: hostValue,
31
+ manifestFile: manifestFileValue,
32
+ appDefinitionId: appInfo.definition.value,
33
+ accessToken: appInfo.accessToken,
34
+ organizationId: appInfo.organization.value,
35
+ };
36
+ }
@@ -0,0 +1,52 @@
1
+ import { AppActionCategoryType, AppActionParameterDefinition } from "contentful-management";
2
+ export type BaseAppActionProps = {
3
+ id?: string;
4
+ name: string;
5
+ description?: string;
6
+ };
7
+ export type FunctionAppActionManifestProps = {
8
+ type: 'function-invocation';
9
+ functionId: string;
10
+ };
11
+ export type EndpointAppActionProps = {
12
+ type: 'endpoint';
13
+ url: string;
14
+ };
15
+ export type CustomCategoryAppActionProps = {
16
+ category: 'Custom';
17
+ parameters: AppActionParameterDefinition[];
18
+ };
19
+ export type BuiltInCategoryAppActionProps = {
20
+ category: Omit<AppActionCategoryType, 'Custom'>;
21
+ };
22
+ type CategoryProps = CustomCategoryAppActionProps | BuiltInCategoryAppActionProps;
23
+ export type FunctionAppActionProps = {
24
+ id?: string;
25
+ type: 'function-invocation';
26
+ function: {
27
+ sys: {
28
+ id: string;
29
+ linkType: string;
30
+ type: string;
31
+ };
32
+ };
33
+ };
34
+ export type AppActionManifest = BaseAppActionProps & (FunctionAppActionManifestProps | EndpointAppActionProps) & CategoryProps;
35
+ export type CreateAppActionPayload = BaseAppActionProps & (FunctionAppActionProps | EndpointAppActionProps) & CategoryProps;
36
+ export type AppActionToCreate = {
37
+ actions: AppActionManifest[];
38
+ };
39
+ export type CreateAppActionOptions = AppActionToCreate & {
40
+ organizationId: string;
41
+ appDefinitionId: string;
42
+ accessToken: string;
43
+ host: string;
44
+ };
45
+ export type CreateAppActionSettings = {
46
+ manifestFile?: string;
47
+ organizationId?: string;
48
+ appDefinitionId?: string;
49
+ accessToken?: string;
50
+ host?: string;
51
+ };
52
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,16 @@
1
+ import { AppActionProps, PlainClientAPI } from 'contentful-management';
2
+ import { AppActionManifest, CreateAppActionPayload, CreateAppActionSettings } from './types';
3
+ export declare function doUpsert(client: PlainClientAPI, appDefinitionId: string, payload: CreateAppActionPayload): Promise<AppActionProps>;
4
+ export declare function syncUpsertToManifest(manifestActions: AppActionManifest[], actionsToSync: {
5
+ [i: number]: AppActionManifest;
6
+ }, manifest: Record<string, any>, manifestFile: string): void;
7
+ export declare function processActionManifests(actions: AppActionManifest[], doUpsert: (payload: CreateAppActionPayload) => Promise<AppActionProps>): Promise<{
8
+ actionsToSync: {
9
+ [i: number]: AppActionManifest;
10
+ };
11
+ errors: {
12
+ details: any;
13
+ path: (string | number)[];
14
+ }[];
15
+ }>;
16
+ export declare function upsertAppActions(settings: Required<CreateAppActionSettings>): Promise<void>;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.doUpsert = doUpsert;
7
+ exports.syncUpsertToManifest = syncUpsertToManifest;
8
+ exports.processActionManifests = processActionManifests;
9
+ exports.upsertAppActions = upsertAppActions;
10
+ const chalk_1 = require("chalk");
11
+ const contentful_management_1 = require("contentful-management");
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ const ora_1 = __importDefault(require("ora"));
14
+ const utils_1 = require("../utils");
15
+ const client_1 = require("./client");
16
+ const make_cma_payload_1 = require("./make-cma-payload");
17
+ const validation_1 = require("./validation");
18
+ async function doUpsert(client, appDefinitionId, payload) {
19
+ if (payload.id) {
20
+ const existingAction = await (0, client_1.getExistingAction)(client, appDefinitionId, payload.id);
21
+ if (existingAction) {
22
+ const { id, ...update } = payload;
23
+ return (0, client_1.updateAction)(client, appDefinitionId, id, update);
24
+ }
25
+ else if (!existingAction && payload.type === 'endpoint') {
26
+ throw new Error(`Action with id ${payload.id} not found. Endpoint actions may not set a custom ID.`);
27
+ }
28
+ }
29
+ return (0, client_1.createAction)(client, appDefinitionId, payload);
30
+ }
31
+ function syncUpsertToManifest(manifestActions, actionsToSync, manifest, manifestFile) {
32
+ const actions = manifestActions.map((action, i) => {
33
+ const syncedAction = actionsToSync[i];
34
+ return syncedAction || action;
35
+ });
36
+ node_fs_1.default.writeFileSync(manifestFile, JSON.stringify({ ...manifest, actions }, null, 2));
37
+ console.log(`Remote updates synced to your manifest file at ${(0, chalk_1.yellow)(manifestFile)}.`);
38
+ }
39
+ async function processActionManifests(actions, doUpsert) {
40
+ const actionsToSync = {};
41
+ const errors = [];
42
+ for (const i in actions) {
43
+ const action = actions[i];
44
+ const payload = (0, make_cma_payload_1.makeAppActionCMAPayload)(action);
45
+ try {
46
+ const appAction = await doUpsert(payload);
47
+ actionsToSync[i] = {
48
+ ...action,
49
+ id: appAction.sys.id,
50
+ };
51
+ }
52
+ catch (err) {
53
+ errors.push({ details: err, path: ['actions', i] });
54
+ }
55
+ }
56
+ return { actionsToSync, errors };
57
+ }
58
+ async function upsertAppActions(settings) {
59
+ const { accessToken, appDefinitionId, host, organizationId, manifestFile } = settings;
60
+ const manifest = await (0, utils_1.resolveManifestFile)({ manifestFile });
61
+ const actions = (0, validation_1.validateActionsManifest)(manifest);
62
+ const spinner = (0, ora_1.default)('Creating your app action(s)').start();
63
+ const client = (0, contentful_management_1.createClient)({
64
+ accessToken,
65
+ host,
66
+ }, {
67
+ type: 'plain',
68
+ defaults: {
69
+ organizationId,
70
+ },
71
+ });
72
+ const { actionsToSync, errors } = await processActionManifests(actions, async (payload) => doUpsert(client, appDefinitionId, payload));
73
+ syncUpsertToManifest(actions, actionsToSync, manifest, manifestFile);
74
+ if (errors.length) {
75
+ const error = new Error(`Failed to upsert actions`);
76
+ Object.assign(error, { details: errors.map(({ details }) => details) });
77
+ (0, utils_1.throwError)(error, 'Failed to upsert actions');
78
+ }
79
+ spinner.stop();
80
+ }
@@ -0,0 +1,9 @@
1
+ import { CreateAppActionOptions as UpsertAppActionOptions } from './types';
2
+ export declare const validateId: (id: string) => {
3
+ ok: boolean;
4
+ message: string;
5
+ } | {
6
+ ok: boolean;
7
+ message?: undefined;
8
+ };
9
+ export declare function validateActionsManifest(manifest: Record<string, any>): UpsertAppActionOptions['actions'];
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.validateId = void 0;
7
+ exports.validateActionsManifest = validateActionsManifest;
8
+ const zod_1 = __importDefault(require("zod"));
9
+ const utils_1 = require("../utils");
10
+ const parametersSchema = zod_1.default
11
+ .array(zod_1.default.object({
12
+ id: zod_1.default.string(),
13
+ name: zod_1.default.string(),
14
+ description: zod_1.default.string().optional(),
15
+ type: zod_1.default.enum(['Symbol', 'Enum', 'Number', 'Boolean']),
16
+ required: zod_1.default.boolean(),
17
+ default: zod_1.default.union([zod_1.default.string(), zod_1.default.number(), zod_1.default.boolean()]).optional(),
18
+ }));
19
+ const validateId = (id) => {
20
+ if (!utils_1.ID_REGEX.test(id)) {
21
+ return {
22
+ ok: false,
23
+ message: `Invalid "id" (must only contain alphanumeric characters). Received: ${id}.`,
24
+ };
25
+ }
26
+ return { ok: true };
27
+ };
28
+ exports.validateId = validateId;
29
+ function validateActionsManifest(manifest) {
30
+ if (!manifest.actions) {
31
+ throw new Error('Invalid App Action manifest: missing "actions" field');
32
+ }
33
+ const { actions } = manifest;
34
+ if (!Array.isArray(actions)) {
35
+ throw new Error('Invalid App Action manifest: "actions" must be an array');
36
+ }
37
+ const errors = actions.reduce((acc, action) => {
38
+ if (!action.name) {
39
+ acc.push(new Error('Invalid App Action manifest: Actions must define a "name".'));
40
+ }
41
+ if (!action.type) {
42
+ acc.push(new Error('Invalid App Action manifest: Actions must define a "type".'));
43
+ }
44
+ if (!action.category) {
45
+ acc.push(new Error('Invalid App Action manifest: Actions must define a "category".'));
46
+ }
47
+ if (action.type === 'function-invocation' && (!action.functionId || action.url)) {
48
+ acc.push(new Error('Invalid App Action manifest: "function-invocation" Actions must define a "functionId" and may not target a "url".'));
49
+ }
50
+ if (action.type === 'endpoint' && (!action.url || action.functionId)) {
51
+ acc.push(new Error('Invalid App Action manifest: "endpoint" Actions must define a "url" and may not target a "functionId".'));
52
+ }
53
+ if (action.id) {
54
+ const { ok, message } = (0, exports.validateId)(action.id);
55
+ if (!ok) {
56
+ acc.push(new Error(`Invalid App Action manifest: ${message}`));
57
+ }
58
+ }
59
+ if (action.category !== 'Custom' && action.parameters) {
60
+ acc.push(new Error('Invalid App Action manifest: native Action categories may not define "parameters"'));
61
+ }
62
+ if (action.category === 'Custom' && !action.parameters) {
63
+ acc.push(new Error('Invalid App Action manifest: "Custom" Action categories must define "parameters"'));
64
+ }
65
+ if (action.category === 'Custom' && action.parameters) {
66
+ const parametersValidationResult = parametersSchema.safeParse(action.parameters);
67
+ if (!parametersValidationResult.success) {
68
+ acc.push(new Error(`Invalid App Action manifest: invalid "parameters" - ${JSON.stringify(parametersValidationResult.error.errors)}`));
69
+ }
70
+ }
71
+ return acc;
72
+ }, []);
73
+ if (errors.length) {
74
+ throw new Error(errors.map((error) => error.message).join('\n'));
75
+ }
76
+ return actions;
77
+ }
package/lib/utils.d.ts CHANGED
@@ -1,12 +1,15 @@
1
1
  import { Definition } from './definition-api';
2
2
  import { Organization } from './organization-api';
3
- import { ContentfulFunction, FunctionAppAction } from './types';
3
+ import { ContentfulFunction } from './types';
4
4
  export declare const throwValidationException: (subject: string, message?: string, details?: string) => never;
5
5
  export declare const isValidNetwork: (address: string) => boolean;
6
6
  export declare const stripProtocol: (url: string) => string;
7
7
  export declare const showCreationError: (subject: string, message: string) => void;
8
8
  export declare const throwError: (err: Error, message: string) => never;
9
9
  export declare const selectFromList: <T extends Definition | Organization>(list: T[], message: string, cachedOptionEnvVar: string) => Promise<T>;
10
- type Entities<Type> = Type extends 'actions' ? Omit<FunctionAppAction, 'entryFile'>[] : Omit<ContentfulFunction, 'entryFile'>[];
11
- export declare function getEntityFromManifest<Type extends 'actions' | 'functions'>(type: Type): Entities<Type> | undefined;
12
- export {};
10
+ export declare function getFunctionsFromManifest(): Omit<ContentfulFunction, 'entryFile'>[] | undefined;
11
+ export declare function getWebAppHostname(host: string | undefined): string;
12
+ export declare const resolveManifestFile: (options: {
13
+ manifestFile?: string;
14
+ }, cwd?: string) => any;
15
+ export declare const ID_REGEX: RegExp;
package/lib/utils.js CHANGED
@@ -3,12 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.selectFromList = exports.throwError = exports.showCreationError = exports.stripProtocol = exports.isValidNetwork = exports.throwValidationException = void 0;
7
- exports.getEntityFromManifest = getEntityFromManifest;
6
+ exports.ID_REGEX = exports.resolveManifestFile = exports.selectFromList = exports.throwError = exports.showCreationError = exports.stripProtocol = exports.isValidNetwork = exports.throwValidationException = void 0;
7
+ exports.getFunctionsFromManifest = getFunctionsFromManifest;
8
+ exports.getWebAppHostname = getWebAppHostname;
8
9
  const fs_1 = __importDefault(require("fs"));
9
10
  const chalk_1 = __importDefault(require("chalk"));
10
11
  const inquirer_1 = __importDefault(require("inquirer"));
11
12
  const cache_credential_1 = require("./cache-credential");
13
+ const constants_1 = require("./constants");
14
+ const node_path_1 = require("node:path");
12
15
  const DEFAULT_MANIFEST_PATH = './contentful-app-manifest.json';
13
16
  const functionEvents = {
14
17
  appActionCall: 'appaction.call',
@@ -19,7 +22,7 @@ const functionEvents = {
19
22
  resourceTypeMappingEvent: 'graphql.resourcetype.mapping',
20
23
  queryEvent: 'graphql.query',
21
24
  resourceLinksSearchEvent: 'resources.search',
22
- resourceLinksLookupEvent: 'resources.lookup'
25
+ resourceLinksLookupEvent: 'resources.lookup',
23
26
  };
24
27
  const throwValidationException = (subject, message, details) => {
25
28
  console.log(`${chalk_1.default.red('Validation Error:')} Missing or invalid ${subject}.`);
@@ -103,18 +106,18 @@ const selectFromList = async (list, message, cachedOptionEnvVar) => {
103
106
  }
104
107
  };
105
108
  exports.selectFromList = selectFromList;
106
- function getEntityFromManifest(type) {
109
+ function getFunctionsFromManifest() {
107
110
  const isManifestExists = fs_1.default.existsSync(DEFAULT_MANIFEST_PATH);
108
111
  if (!isManifestExists) {
109
112
  return;
110
113
  }
111
114
  try {
112
115
  const manifest = JSON.parse(fs_1.default.readFileSync(DEFAULT_MANIFEST_PATH, { encoding: 'utf8' }));
113
- if (!Array.isArray(manifest[type]) || manifest[type].length === 0) {
116
+ if (!Array.isArray(manifest['functions']) || manifest['functions'].length === 0) {
114
117
  return;
115
118
  }
116
- logProgress(`${type === 'actions' ? 'App Actions' : 'functions'} found in ${chalk_1.default.bold(DEFAULT_MANIFEST_PATH)}.`);
117
- const items = manifest[type].map((item) => {
119
+ logProgress(`functions found in ${chalk_1.default.bold(DEFAULT_MANIFEST_PATH)}.`);
120
+ const items = manifest['functions'].map((item) => {
118
121
  const allowNetworks = Array.isArray(item.allowNetworks)
119
122
  ? item.allowNetworks.map(exports.stripProtocol)
120
123
  : [];
@@ -122,12 +125,12 @@ function getEntityFromManifest(type) {
122
125
  const hasInvalidEvent = accepts?.some((event) => !Object.values(functionEvents).includes(event));
123
126
  const hasInvalidNetwork = allowNetworks.find((netWork) => !(0, exports.isValidNetwork)(netWork));
124
127
  if (hasInvalidNetwork) {
125
- console.log(`${chalk_1.default.red('Error:')} Invalid IP address ${hasInvalidNetwork} found in the allowNetworks array for ${type} "${item.name}".`);
128
+ console.log(`${chalk_1.default.red('Error:')} Invalid IP address ${hasInvalidNetwork} found in the allowNetworks array for Function "${item.name}".`);
126
129
  // eslint-disable-next-line no-process-exit
127
130
  process.exit(1);
128
131
  }
129
132
  if (hasInvalidEvent) {
130
- console.log(`${chalk_1.default.red('Error:')} Invalid events found in the accepts array for ${type} "${item.name}".`);
133
+ console.log(`${chalk_1.default.red('Error:')} Invalid events found in the accepts array for Function "${item.name}".`);
131
134
  // eslint-disable-next-line no-process-exit
132
135
  process.exit(1);
133
136
  }
@@ -148,3 +151,13 @@ function getEntityFromManifest(type) {
148
151
  process.exit(1);
149
152
  }
150
153
  }
154
+ function getWebAppHostname(host) {
155
+ return host && host.includes('api') ? host.replace('api', 'app') : constants_1.DEFAULT_CONTENTFUL_APP_HOST;
156
+ }
157
+ const resolveManifestFile = (options, cwd = process.cwd()) => {
158
+ return require(options.manifestFile
159
+ ? (0, node_path_1.resolve)(cwd, options.manifestFile)
160
+ : (0, node_path_1.resolve)(cwd, 'contentful-app-manifest.json'));
161
+ };
162
+ exports.resolveManifestFile = resolveManifestFile;
163
+ exports.ID_REGEX = /^[a-zA-Z0-9]+$/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/app-scripts",
3
- "version": "1.32.2",
3
+ "version": "1.32.4-alpha.14+bba28d02",
4
4
  "description": "A collection of scripts for building Contentful Apps",
5
5
  "author": "Contentful GmbH",
6
6
  "license": "MIT",
@@ -54,23 +54,23 @@
54
54
  "bottleneck": "2.19.5",
55
55
  "chalk": "4.1.2",
56
56
  "commander": "12.1.0",
57
- "contentful-management": "11.40.3",
57
+ "contentful-management": "11.47.2",
58
58
  "dotenv": "16.4.7",
59
- "esbuild": "^0.24.0",
60
- "ignore": "7.0.0",
59
+ "esbuild": "^0.25.0",
60
+ "ignore": "7.0.3",
61
61
  "inquirer": "8.2.6",
62
62
  "lodash": "4.17.21",
63
63
  "open": "8.4.2",
64
64
  "ora": "5.4.1",
65
65
  "zod": "^3.24.1"
66
66
  },
67
- "gitHead": "5bc6d159d2a14c8df1fc67f5fb30829094146a6b",
67
+ "gitHead": "bba28d0220ba46d0ea9853283ac62588e6a8b882",
68
68
  "devDependencies": {
69
69
  "@types/adm-zip": "0.5.7",
70
70
  "@types/analytics-node": "3.1.14",
71
71
  "@types/chai": "4.3.16",
72
72
  "@types/inquirer": "8.2.1",
73
- "@types/lodash": "4.17.13",
73
+ "@types/lodash": "4.17.15",
74
74
  "@types/mocha": "10.0.10",
75
75
  "@types/proxyquire": "1.3.31",
76
76
  "@types/sinon": "17.0.3",