@contentful/app-scripts 2.1.1 → 2.1.3

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/lib/analytics.js CHANGED
@@ -19,7 +19,9 @@ function track({ command, ci }) {
19
19
  const client = new analytics_node_1.Analytics({
20
20
  writeKey: SEGMENT_WRITE_KEY,
21
21
  });
22
- client.on('error', (err) => { });
22
+ client.on('error', (err) => {
23
+ /* noop */
24
+ });
23
25
  client.track({
24
26
  event: 'app-cli-app-scripts',
25
27
  properties: {
package/lib/bin.js CHANGED
@@ -22,7 +22,7 @@ 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 app\'s 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')
@@ -35,7 +35,7 @@ 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 app\'s definition')
38
+ .option('--definition-id [defId]', "The id of your app's definition")
39
39
  .option('--token [accessToken]', 'Your content management access token')
40
40
  .option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
41
41
  .action(async (options) => {
@@ -44,7 +44,7 @@ async function runCommand(command, options) {
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 app\'s definition')
47
+ .option('--definition-id [defId]', "The id of your app's definition")
48
48
  .option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
49
49
  .action(async (options) => {
50
50
  await runCommand(index_1.open, options);
@@ -53,7 +53,7 @@ async function runCommand(command, options) {
53
53
  .command('bundle-cleanup')
54
54
  .description('Removes old, non-active bundles, only keeps the 50 most recent ones')
55
55
  .option('--organization-id [orgId]', 'The id of your organization')
56
- .option('--definition-id [defId]', 'The id of your app\'s definition')
56
+ .option('--definition-id [defId]', "The id of your app's definition")
57
57
  .option('--token [accessToken]', 'Your content management access token')
58
58
  .option('--keep [keepAmount]', 'The amount of bundles that should remain')
59
59
  .option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
@@ -69,7 +69,7 @@ async function runCommand(command, options) {
69
69
  commander_1.program
70
70
  .command('install')
71
71
  .description('Opens a picker to select the space and environment for installing the app associated with a given AppDefinition')
72
- .option('--definition-id [defId]', 'The id of your app\'s definition')
72
+ .option('--definition-id [defId]', "The id of your app's definition")
73
73
  .option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
74
74
  .action(async (options) => {
75
75
  await runCommand(index_1.install, options);
@@ -97,7 +97,7 @@ async function runCommand(command, options) {
97
97
  .description('Upsert Action(s) for an App')
98
98
  .option('-m, --manifest-file <path>', 'Contentful app manifest file path')
99
99
  .option('--organization-id [orgId]', 'The id of your organization')
100
- .option('--definition-id [defId]', 'The id of your app\'s definition')
100
+ .option('--definition-id [defId]', "The id of your app's definition")
101
101
  .option('--token [accessToken]', 'Your content management access token')
102
102
  .option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
103
103
  .action(async (options) => {
@@ -1,3 +1,4 @@
1
1
  import { GenerateFunctionSettings } from '../types';
2
2
  export declare function buildGenerateFunctionSettingsInteractive(): Promise<GenerateFunctionSettings>;
3
+ export declare function validateArguments(options: GenerateFunctionSettings): void;
3
4
  export declare function buildGenerateFunctionSettingsCLI(options: GenerateFunctionSettings): Promise<GenerateFunctionSettings>;
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.buildGenerateFunctionSettingsInteractive = buildGenerateFunctionSettingsInteractive;
7
+ exports.validateArguments = validateArguments;
7
8
  exports.buildGenerateFunctionSettingsCLI = buildGenerateFunctionSettingsCLI;
8
9
  const inquirer_1 = __importDefault(require("inquirer"));
9
10
  const path_1 = __importDefault(require("path"));
@@ -12,6 +13,7 @@ const constants_1 = require("./constants");
12
13
  const ora_1 = __importDefault(require("ora"));
13
14
  const chalk_1 = __importDefault(require("chalk"));
14
15
  const logger_1 = require("./logger");
16
+ const types_1 = require("./types");
15
17
  async function buildGenerateFunctionSettingsInteractive() {
16
18
  const baseSettings = await inquirer_1.default.prompt([
17
19
  {
@@ -19,9 +21,7 @@ async function buildGenerateFunctionSettingsInteractive() {
19
21
  message: `Function name (${path_1.default.basename(process.cwd())}):`,
20
22
  },
21
23
  ]);
22
- if (constants_1.BANNED_FUNCTION_NAMES.includes(baseSettings.name)) {
23
- throw new Error(`Invalid function name: ${baseSettings.name}`);
24
- }
24
+ validateFunctionName(baseSettings);
25
25
  const filteredSources = await (0, get_github_folder_names_1.getGithubFolderNames)();
26
26
  const sourceSpecificSettings = await inquirer_1.default.prompt([
27
27
  {
@@ -45,25 +45,38 @@ async function buildGenerateFunctionSettingsInteractive() {
45
45
  baseSettings.language = sourceSpecificSettings.language;
46
46
  return baseSettings;
47
47
  }
48
+ function validateFunctionName(baseSettings) {
49
+ if (constants_1.BANNED_FUNCTION_NAMES.includes(baseSettings.name)) {
50
+ throw new types_1.ValidationError(chalk_1.default.red(`Invalid function name: ${baseSettings.name} is not allowed.`));
51
+ }
52
+ else if (!(/^[a-z0-9]+$/i.test(baseSettings.name))) {
53
+ throw new types_1.ValidationError(chalk_1.default.red(`Invalid function name: ${baseSettings.name}. Note that function names must be alphanumeric.`));
54
+ }
55
+ }
48
56
  function validateArguments(options) {
49
57
  const requiredParams = ['name', 'example', 'language'];
50
58
  if (!requiredParams.every((key) => key in options)) {
51
- throw new Error('You must specify a function name, an example, and a language');
59
+ throw new types_1.ValidationError(chalk_1.default.red('You must specify a function name, an example, and a language'));
52
60
  }
53
- if (constants_1.BANNED_FUNCTION_NAMES.includes(options.name)) {
54
- throw new Error(`Invalid function name: ${options.name}`);
61
+ validateFunctionName(options);
62
+ // Check if the language is valid
63
+ if (!constants_1.ACCEPTED_LANGUAGES.includes(options.language)) {
64
+ (0, logger_1.warn)(`Invalid language: ${options.language}. Defaulting to TypeScript.`);
65
+ options.language = 'typescript';
55
66
  }
56
67
  // Convert options to lowercase and trim whitespace
57
68
  for (const key in options) {
58
69
  const optionKey = key;
59
70
  const value = options[optionKey].toLowerCase().trim();
60
71
  if (optionKey === 'language') {
61
- // Assert that the value is of type Language
62
72
  options[optionKey] = value;
63
73
  }
64
- else {
74
+ else if (optionKey === 'example') {
65
75
  options[optionKey] = value;
66
76
  }
77
+ else { // don't want to lowercase function names
78
+ options[optionKey] = options[optionKey].trim();
79
+ }
67
80
  }
68
81
  }
69
82
  async function buildGenerateFunctionSettingsCLI(options) {
@@ -74,16 +87,9 @@ async function buildGenerateFunctionSettingsCLI(options) {
74
87
  // Check if the source exists
75
88
  const filteredSources = await (0, get_github_folder_names_1.getGithubFolderNames)();
76
89
  if (!filteredSources.includes(options.example)) {
77
- throw new Error(`Invalid example name: ${options.example}. Please choose from: ${filteredSources.join(', ')}`);
78
- }
79
- // Check if the language is valid
80
- if (!constants_1.ACCEPTED_LANGUAGES.includes(options.language)) {
81
- (0, logger_1.warn)(`Invalid language: ${options.language}. Defaulting to TypeScript.`);
82
- settings.language = 'typescript';
83
- }
84
- else {
85
- settings.language = options.language;
90
+ throw new types_1.ValidationError(`Invalid example name: ${options.example}. Please choose from: ${filteredSources.join(', ')}`);
86
91
  }
92
+ settings.language = options.language;
87
93
  settings.example = options.example;
88
94
  settings.name = options.name;
89
95
  return settings;
@@ -91,10 +97,9 @@ async function buildGenerateFunctionSettingsCLI(options) {
91
97
  catch (err) {
92
98
  console.log(`
93
99
  ${chalk_1.default.red('Validation failed')}
94
- ${err.message}
95
100
  `);
96
101
  // eslint-disable-next-line no-process-exit
97
- process.exit(1);
102
+ throw err;
98
103
  }
99
104
  finally {
100
105
  validateSpinner.stop();
@@ -41,11 +41,11 @@ async function cloneFunction(localPath, settings) {
41
41
  }
42
42
  catch (e) {
43
43
  (0, logger_1.error)(`Failed to clone function ${(0, logger_1.highlight)(chalk_1.default.cyan(settings.name))}`, e);
44
- process.exit(1);
44
+ throw Error(chalk_1.default.red('Failed to clone function ') + (0, logger_1.highlight)(chalk_1.default.cyan(settings.name)));
45
45
  }
46
46
  }
47
47
  function getCloneURL(settings) {
48
- return `${constants_1.REPO_URL}/${settings.example}/${settings.language}`; // this is the default for template
48
+ return `${constants_1.REPO_URL}/${settings.example}/${settings.language}`;
49
49
  }
50
50
  async function touchupAppManifest(localPath, settings, renameFunctionFile) {
51
51
  const appManifest = JSON.parse(node_fs_1.default.readFileSync(`${localPath}/${constants_1.CONTENTFUL_APP_MANIFEST}`, 'utf-8'));
@@ -4,7 +4,7 @@ exports.BANNED_FUNCTION_NAMES = exports.CONTENTFUL_FUNCTIONS_EXAMPLE_REPO_PATH =
4
4
  exports.EXAMPLES_PATH = 'contentful/apps/function-examples/';
5
5
  exports.APP_MANIFEST = 'app-manifest.json';
6
6
  exports.CONTENTFUL_APP_MANIFEST = 'contentful-app-manifest.json';
7
- exports.IGNORED_CLONED_FILES = [exports.APP_MANIFEST, `package.json`];
7
+ exports.IGNORED_CLONED_FILES = [exports.APP_MANIFEST, exports.CONTENTFUL_APP_MANIFEST, `package.json`];
8
8
  exports.REPO_URL = 'https://github.com/contentful/apps/function-examples';
9
9
  exports.ACCEPTED_EXAMPLE_FOLDERS = [
10
10
  'appevent-handler',
@@ -3,13 +3,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateFunction = void 0;
4
4
  const build_generate_function_settings_1 = require("./build-generate-function-settings");
5
5
  const create_function_1 = require("./create-function");
6
+ const types_1 = require("./types");
6
7
  const interactive = async () => {
7
- const generateFunctionSettings = await (0, build_generate_function_settings_1.buildGenerateFunctionSettingsInteractive)();
8
- return (0, create_function_1.create)(generateFunctionSettings);
8
+ try {
9
+ const generateFunctionSettings = await (0, build_generate_function_settings_1.buildGenerateFunctionSettingsInteractive)();
10
+ return (0, create_function_1.create)(generateFunctionSettings);
11
+ }
12
+ catch (e) {
13
+ if (e instanceof types_1.ValidationError) {
14
+ console.error(e.message);
15
+ }
16
+ else {
17
+ console.error(e);
18
+ }
19
+ }
9
20
  };
10
21
  const nonInteractive = async (options) => {
11
- const generateFunctionSettings = await (0, build_generate_function_settings_1.buildGenerateFunctionSettingsCLI)(options);
12
- return (0, create_function_1.create)(generateFunctionSettings);
22
+ try {
23
+ const generateFunctionSettings = await (0, build_generate_function_settings_1.buildGenerateFunctionSettingsCLI)(options);
24
+ return (0, create_function_1.create)(generateFunctionSettings);
25
+ }
26
+ catch (e) {
27
+ if (e instanceof types_1.ValidationError) {
28
+ console.error(e.message);
29
+ }
30
+ else {
31
+ console.error(e);
32
+ }
33
+ }
13
34
  };
14
35
  exports.generateFunction = {
15
36
  interactive,
@@ -2,3 +2,6 @@ export declare class HTTPResponseError extends Error {
2
2
  }
3
3
  export declare class InvalidTemplateError extends Error {
4
4
  }
5
+ export declare class ValidationError extends Error {
6
+ constructor(message: string);
7
+ }
@@ -1,9 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InvalidTemplateError = exports.HTTPResponseError = void 0;
3
+ exports.ValidationError = exports.InvalidTemplateError = exports.HTTPResponseError = void 0;
4
4
  class HTTPResponseError extends Error {
5
5
  }
6
6
  exports.HTTPResponseError = HTTPResponseError;
7
7
  class InvalidTemplateError extends Error {
8
8
  }
9
9
  exports.InvalidTemplateError = InvalidTemplateError;
10
+ class ValidationError extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "ValidationError";
14
+ }
15
+ }
16
+ exports.ValidationError = ValidationError;
@@ -1,3 +1,4 @@
1
1
  import { UploadSettings } from '../types';
2
2
  export declare function createAppBundleFromUpload(settings: UploadSettings, appUploadId: string): Promise<import("contentful-management").AppBundle | null>;
3
+ export declare function processCreateAppBundleError(err: any): any;
3
4
  export declare function createAppBundleFromSettings(settings: UploadSettings): Promise<void | import("contentful-management").AppBundle>;
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createAppBundleFromUpload = createAppBundleFromUpload;
7
+ exports.processCreateAppBundleError = processCreateAppBundleError;
7
8
  exports.createAppBundleFromSettings = createAppBundleFromSettings;
8
9
  const chalk_1 = __importDefault(require("chalk"));
9
10
  const ora_1 = __importDefault(require("ora"));
@@ -31,21 +32,36 @@ async function createAppBundleFromUpload(settings, appUploadId) {
31
32
  });
32
33
  }
33
34
  catch (err) {
34
- try {
35
- const message = JSON.parse(err.message);
36
- if (message['status'] == 403 && message.details?.reasons) {
37
- (0, utils_1.showCreationError)('app upload', message['details']['reasons']);
35
+ (0, utils_1.showCreationError)('app upload', processCreateAppBundleError(err));
36
+ }
37
+ bundleSpinner.stop();
38
+ return appBundle;
39
+ }
40
+ function processCreateAppBundleError(err) {
41
+ try {
42
+ const message = JSON.parse(err.message);
43
+ const reasons = message.details?.reasons;
44
+ if (message.status !== 403 || !reasons) {
45
+ return err.message;
46
+ }
47
+ if (message['status'] == 403 && message.details?.reasons) {
48
+ if (reasons.includes('Not entitled to App Functions.')) {
49
+ return 'Your app seems to be using App Functions, which your organization is not entitled to. Remove your app function, or upgrade your account to proceed with your app upload.';
50
+ }
51
+ else if (reasons.includes('App Functions beta not enabled.')) {
52
+ return 'Your app seems to be using App Functions, which your organization has not enabled in the Preview Center. In the Contentful web app, go to the Account Menu → Preview Center → App Functions to enable and proceed with your app upload.';
38
53
  }
39
54
  else {
40
- (0, utils_1.showCreationError)('app upload', message);
55
+ return reasons;
41
56
  }
42
57
  }
43
- catch (e) {
44
- (0, utils_1.showCreationError)('app upload', err.message);
58
+ else {
59
+ return reasons;
45
60
  }
46
61
  }
47
- bundleSpinner.stop();
48
- return appBundle;
62
+ catch (e) {
63
+ return err.message;
64
+ }
49
65
  }
50
66
  async function createAppBundleFromSettings(settings) {
51
67
  let appUpload = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/app-scripts",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "A collection of scripts for building Contentful Apps",
5
5
  "author": "Contentful GmbH",
6
6
  "license": "MIT",
@@ -25,7 +25,7 @@
25
25
  "contentful-app-scripts": "lib/bin.js"
26
26
  },
27
27
  "scripts": {
28
- "prettier": "prettier **/*.js --write --ignore-path .gitignore",
28
+ "prettier": "prettier **/*.ts --write --ignore-path .gitignore",
29
29
  "lint": "eslint ./src",
30
30
  "lint:fix": "npm run lint -- --fix",
31
31
  "test": "ts-mocha \"./{,!(node_modules)/**/}*.test.ts\"",
@@ -55,7 +55,7 @@
55
55
  "bottleneck": "2.19.5",
56
56
  "chalk": "4.1.2",
57
57
  "commander": "12.1.0",
58
- "contentful-management": "11.47.3",
58
+ "contentful-management": "11.48.0",
59
59
  "dotenv": "16.4.7",
60
60
  "esbuild": "^0.25.0",
61
61
  "ignore": "7.0.3",
@@ -67,7 +67,7 @@
67
67
  "tiged": "^2.12.7",
68
68
  "zod": "^3.24.1"
69
69
  },
70
- "gitHead": "c86c0866476bf0cae4f72ff8730c2ff9c120a593",
70
+ "gitHead": "19544e247831ead71d56506298ed3335aa852d4a",
71
71
  "devDependencies": {
72
72
  "@types/adm-zip": "0.5.7",
73
73
  "@types/analytics-node": "3.1.14",