@contentful/app-scripts 1.9.0 → 1.10.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/bin/app-scripts CHANGED
@@ -29,6 +29,7 @@ async function runCommand(command, options) {
29
29
  .option('--token [accessToken]', 'Your content management access token')
30
30
  .option('--comment [comment]', 'Optional comment for the created bundle')
31
31
  .option('--skip-activation', 'A Boolean flag to skip automatic activation')
32
+ .option('--host [host]', 'Contentful domain to use')
32
33
  .action(async (options) => {
33
34
  await runCommand(upload, options);
34
35
  });
@@ -40,6 +41,7 @@ async function runCommand(command, options) {
40
41
  .option('--organization-id [orgId]', 'The id of your organization')
41
42
  .option('--definition-id [defId]', 'The id of your apps definition')
42
43
  .option('--token [accessToken]', 'Your content management access token')
44
+ .option('--host [host]', 'Contentful domain to use')
43
45
  .action(async (options) => {
44
46
  await runCommand(activate, options);
45
47
  });
@@ -59,14 +61,14 @@ async function runCommand(command, options) {
59
61
  .option('--definition-id [defId]', 'The id of your apps definition')
60
62
  .option('--token [accessToken]', 'Your content management access token')
61
63
  .option('--keep [keepAmount]', 'The amount of bundles that should remain')
62
- .option('--host [host]', 'Contentful CMA-endpoint to use')
64
+ .option('--host [host]', 'Contentful domain to use')
63
65
  .action(async (options) => {
64
66
  await runCommand(cleanup, options);
65
67
  });
66
68
 
67
69
  program.hook('preAction', (thisCommand) => {
68
- track({ command: thisCommand.args[0], ci: `${thisCommand._optionValues.ci}` })
69
- })
70
+ track({ command: thisCommand.args[0], ci: `${thisCommand._optionValues.ci}` });
71
+ });
70
72
 
71
73
  await program.parseAsync(process.argv);
72
74
  })().catch((e) => {
@@ -3,9 +3,9 @@ const chalk = require('chalk');
3
3
  const { throwError } = require('../utils');
4
4
  const { createClient } = require('contentful-management');
5
5
 
6
- async function activateBundle({ accessToken, organization, definition, bundleId }) {
6
+ async function activateBundle({ accessToken, organization, definition, bundleId, host }) {
7
7
  const activationSpinner = ora('Activating your bundle').start();
8
- const plainClient = createClient({ accessToken }, { type: 'plain' });
8
+ const plainClient = createClient({ accessToken, host }, { type: 'plain' });
9
9
  const defaultLocations = [{ location: 'dialog' }];
10
10
 
11
11
  const currentDefinition = await plainClient.appDefinition.get({
@@ -10,7 +10,8 @@ const mockedSettings = {
10
10
  };
11
11
 
12
12
  describe('activate-bundle', () => {
13
- let activateBundle, clientMock, updateStub;
13
+ // eslint-disable-next-line no-unused-vars
14
+ let activateBundle, clientMock, updateStub, createClientArgs;
14
15
  beforeEach(() => {
15
16
  stub(console, 'log');
16
17
  });
@@ -23,7 +24,7 @@ describe('activate-bundle', () => {
23
24
  const definitionMock = {
24
25
  src: 'src',
25
26
  bundle: undefined,
26
- locations: []
27
+ locations: [],
27
28
  };
28
29
 
29
30
  beforeEach(() => {
@@ -37,7 +38,8 @@ describe('activate-bundle', () => {
37
38
 
38
39
  ({ activateBundle } = proxyquire('./activate-bundle', {
39
40
  'contentful-management': {
40
- createClient: () => {
41
+ createClient: (...args) => {
42
+ createClientArgs = args;
41
43
  return clientMock;
42
44
  },
43
45
  },
@@ -60,4 +62,8 @@ describe('activate-bundle', () => {
60
62
  await activateBundle(mockedSettings);
61
63
  assert.strictEqual(throwErrorStub.called, true);
62
64
  });
65
+ it('supports custom defined host domain', async () => {
66
+ await activateBundle({ ...mockedSettings, host: 'jane.doe.com' });
67
+ assert.strictEqual(createClientArgs[0].host, 'jane.doe.com');
68
+ });
63
69
  });
@@ -4,7 +4,7 @@ const inquirer = require('inquirer');
4
4
  const { getAppInfo } = require('../get-app-info');
5
5
 
6
6
  async function buildBundleActivateSettings(options) {
7
- let bundleId = options.bundleId;
7
+ let { bundleId, host } = options;
8
8
  if (!bundleId) {
9
9
  const prompts = await inquirer.prompt([
10
10
  {
@@ -22,6 +22,7 @@ async function buildBundleActivateSettings(options) {
22
22
  return {
23
23
  ...appInfo,
24
24
  bundleId,
25
+ host,
25
26
  };
26
27
  }
27
28
 
@@ -20,6 +20,7 @@ async function getActivateSettingsArgs(options) {
20
20
  ...appInfo,
21
21
  bundleId: options.bundleId,
22
22
  comment: options.comment,
23
+ host: options.host,
23
24
  };
24
25
  } catch (err) {
25
26
  console.log(`
@@ -7,14 +7,15 @@ const { getActionsManifest } = require('../utils');
7
7
  async function buildAppUploadSettings(options) {
8
8
  const actionsManifest = getActionsManifest();
9
9
  const prompts = [];
10
- if (!options.bundleDir) {
10
+ const { bundleDir, comment, skipActivation, host } = options;
11
+ if (!bundleDir) {
11
12
  prompts.push({
12
13
  name: 'bundleDirectory',
13
14
  message: `Bundle directory, if not default:`,
14
15
  default: './build',
15
16
  });
16
17
  }
17
- if (!options.comment) {
18
+ if (!comment) {
18
19
  prompts.push({
19
20
  name: 'comment',
20
21
  message: `Add a comment to the created bundle:`,
@@ -27,9 +28,10 @@ async function buildAppUploadSettings(options) {
27
28
  const appInfo = await getAppInfo(options);
28
29
  // Add app-config & dialog automatically
29
30
  return {
30
- bundleDirectory: options.bundleDir,
31
- skipActivation: !!options.skipActivation,
32
- comment: options.comment,
31
+ bundleDirectory: bundleDir,
32
+ skipActivation: !!skipActivation,
33
+ comment,
34
+ host,
33
35
  actions: actionsManifest,
34
36
  ...appUploadSettings,
35
37
  ...appInfo,
@@ -6,12 +6,12 @@ const { createClient } = require('contentful-management');
6
6
  const { createAppUpload } = require('./create-app-upload');
7
7
 
8
8
  async function createAppBundleFromUpload(settings, appUploadId) {
9
+ const { accessToken, host, userAgentApplication, comment, actions } = settings;
9
10
  const clientSpinner = ora('Verifying your upload...').start();
10
11
  const client = createClient({
11
- accessToken: settings.accessToken,
12
- application: settings.userAgentApplication
13
- ? settings.userAgentApplication
14
- : 'contentful.app-scripts',
12
+ accessToken,
13
+ host,
14
+ application: userAgentApplication ? userAgentApplication : 'contentful.app-scripts',
15
15
  });
16
16
  const organization = await client.getOrganization(settings.organization.value);
17
17
  const appDefinition = await organization.getAppDefinition(settings.definition.value);
@@ -22,8 +22,8 @@ async function createAppBundleFromUpload(settings, appUploadId) {
22
22
  try {
23
23
  appBundle = await appDefinition.createAppBundle({
24
24
  appUploadId,
25
- comment: settings.comment && settings.comment.length > 0 ? settings.comment : undefined,
26
- actions: settings.actions,
25
+ comment: comment && comment.length > 0 ? comment : undefined,
26
+ actions,
27
27
  });
28
28
  } catch (err) {
29
29
  showCreationError('app upload', err.message);
@@ -10,7 +10,8 @@ const mockedSettings = {
10
10
  };
11
11
 
12
12
  describe('createAppBundleFromUpload', () => {
13
- let createAppBundleFromUpload, clientMock;
13
+ // eslint-disable-next-line no-unused-vars
14
+ let createAppBundleFromUpload, clientMock, createClientArgs;
14
15
 
15
16
  beforeEach(() => {
16
17
  stub(console, 'log');
@@ -32,7 +33,8 @@ describe('createAppBundleFromUpload', () => {
32
33
 
33
34
  ({ createAppBundleFromUpload } = proxyquire('./create-app-bundle', {
34
35
  'contentful-management': {
35
- createClient: () => {
36
+ createClient: (...args) => {
37
+ createClientArgs = args;
36
38
  return clientMock;
37
39
  },
38
40
  },
@@ -52,10 +54,14 @@ describe('createAppBundleFromUpload', () => {
52
54
 
53
55
  assert(console.log.calledWith(match(/Creation error:/)));
54
56
  });
57
+ it('supports custom defined host domain creating appbundle from upload', async () => {
58
+ await createAppBundleFromUpload({ ...mockedSettings, host: 'jane.doe.com' });
59
+ assert.strictEqual(createClientArgs[0].host, 'jane.doe.com');
60
+ });
55
61
  });
56
62
 
57
63
  describe('createAppBundleFromSettings', () => {
58
- let createAppBundleFromSettings, clientMock, uploadMock;
64
+ let createAppBundleFromSettings, clientMock, uploadMock, createClientArgs;
59
65
 
60
66
  beforeEach(() => {
61
67
  stub(console, 'log');
@@ -79,7 +85,8 @@ describe('createAppBundleFromSettings', () => {
79
85
 
80
86
  ({ createAppBundleFromSettings } = proxyquire('./create-app-bundle', {
81
87
  'contentful-management': {
82
- createClient: () => {
88
+ createClient: (...args) => {
89
+ createClientArgs = args;
83
90
  return clientMock;
84
91
  },
85
92
  },
@@ -96,4 +103,8 @@ describe('createAppBundleFromSettings', () => {
96
103
  await createAppBundleFromSettings(mockedSettings);
97
104
  assert(console.log.calledWith(match(/Creation error:/)));
98
105
  });
106
+ it('supports custom defined host domain creating appbundle from settings', async () => {
107
+ await createAppBundleFromSettings({ ...mockedSettings, host: 'jane.doe.com' });
108
+ assert.strictEqual(createClientArgs[0].host, 'jane.doe.com');
109
+ });
99
110
  });
@@ -14,16 +14,18 @@ const requiredOptions = {
14
14
  async function getUploadSettingsArgs(options) {
15
15
  const validateSpinner = ora('Validating your input...').start();
16
16
  const actionsManifest = getActionsManifest();
17
+ const { bundleDir, comment, skipActivation, host, userAgentApplication } = options;
17
18
 
18
19
  try {
19
20
  validateArguments(requiredOptions, options, 'upload');
20
21
  const appInfo = await getAppInfo(options);
21
22
  return {
22
23
  ...appInfo,
23
- bundleDirectory: options.bundleDir,
24
- skipActivation: options.skipActivation,
25
- comment: options.comment,
26
- userAgentApplication: options.userAgentApplication,
24
+ bundleDirectory: bundleDir,
25
+ skipActivation,
26
+ comment,
27
+ host,
28
+ userAgentApplication,
27
29
  actions: actionsManifest,
28
30
  };
29
31
  } catch (err) {
package/lib/utils.js CHANGED
@@ -15,7 +15,7 @@ const throwValidationException = (subject, message, details) => {
15
15
 
16
16
  const isValidNetwork = (address) => {
17
17
  const addressRegex =
18
- /^(?:localhost|(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}|(?:\d{1,3}\.){3}\d{1,3}|(\[(?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}\]|(?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}))(?::\d{1,5})?$/;
18
+ /^(?:localhost|(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(\[(?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}\]|(?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}))(?::\d{1,5})?$/;
19
19
  return addressRegex.test(address);
20
20
  };
21
21
 
@@ -94,17 +94,16 @@ function getActionsManifest() {
94
94
  console.log('');
95
95
 
96
96
  const actions = manifest.actions.map((action) => {
97
- const allowedNetworks = Array.isArray(action.allowedNetworks)
98
- ? allowedNetworks.map(stripProtocol)
97
+ const allowNetworks = Array.isArray(action.allowNetworks)
98
+ ? action.allowNetworks.map(stripProtocol)
99
99
  : [];
100
100
 
101
- const hasInvalidNetwork = allowedNetworks.find((netWork) => !isValidNetwork(netWork));
102
-
101
+ const hasInvalidNetwork = allowNetworks.find((netWork) => !isValidNetwork(netWork));
103
102
  if (hasInvalidNetwork) {
104
103
  console.log(
105
104
  `${chalk.red(
106
105
  'Error:'
107
- )} Invalid IP address ${hasInvalidNetwork} found in the allowedNetworks array for action "${
106
+ )} Invalid IP address ${hasInvalidNetwork} found in the allowNetworks array for action "${
108
107
  action.name
109
108
  }".`
110
109
  );
@@ -112,10 +111,14 @@ function getActionsManifest() {
112
111
  process.exit(1);
113
112
  }
114
113
 
114
+ // EntryFile is not used but we do want to strip it from action
115
+ // eslint-disable-next-line no-unused-vars
116
+ const { entryFile: _, ...actionWithoutEntryFile } = action;
117
+
115
118
  return {
116
119
  parameters: [],
117
- ...action,
118
- allowedNetworks,
120
+ ...actionWithoutEntryFile,
121
+ allowNetworks,
119
122
  };
120
123
  });
121
124
 
@@ -0,0 +1,198 @@
1
+ const assert = require('assert');
2
+ const { stub } = require('sinon');
3
+ const proxyquire = require('proxyquire');
4
+
5
+ const { isValidNetwork, stripProtocol } = require('./utils');
6
+
7
+ describe('isValidIpAddress', () => {
8
+ it('returns true for a valid IP address', () => {
9
+ const result = isValidNetwork('192.168.0.1');
10
+ assert.strictEqual(result, true);
11
+ });
12
+
13
+ it('returns true for a valid domain', () => {
14
+ const result = isValidNetwork('google.com');
15
+ assert.strictEqual(result, true);
16
+ });
17
+
18
+ it('returns true for a valid ipv6 address', () => {
19
+ const result = isValidNetwork('2001:0db8:85a3:0000:0000:8a2e:0370:7334');
20
+ assert.strictEqual(result, true);
21
+ });
22
+
23
+ it('returns true for a valid domain with port', () => {
24
+ const result = isValidNetwork('google.com:4000');
25
+ assert.strictEqual(result, true);
26
+ });
27
+
28
+ it('returns true for a valid IP address with port', () => {
29
+ const result = isValidNetwork('192.168.0.1:2000');
30
+ assert.strictEqual(result, true);
31
+ });
32
+
33
+ it('returns false for an invalid IP address', () => {
34
+ const result = isValidNetwork('not an ip address');
35
+ assert.strictEqual(result, false);
36
+ });
37
+
38
+ it('returns false for an invalid IP address', () => {
39
+ const result = isValidNetwork('427.0.0.1');
40
+ assert.strictEqual(result, false);
41
+ });
42
+
43
+ it('returns true for an valid ipv6 address with port', () => {
44
+ const result = isValidNetwork('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:443');
45
+ assert.strictEqual(result, true);
46
+ });
47
+ });
48
+
49
+ describe('removeProtocolFromUrl', () => {
50
+ it('returns the host of a URL without a protocol', () => {
51
+ const result = stripProtocol('http://example.com');
52
+ assert.strictEqual(result, 'example.com');
53
+ });
54
+
55
+ it('returns valid ipv6 address', () => {
56
+ const result = stripProtocol('http://[2001:0db8:85a3:0000:0000:8a2e:0370]:7334');
57
+ assert.strictEqual(result, '[2001:0db8:85a3:0000:0000:8a2e:0370]:7334');
58
+ });
59
+
60
+ it('returns valid domain', () => {
61
+ const result = stripProtocol('example.com');
62
+ assert.strictEqual(result, 'example.com');
63
+ });
64
+
65
+ it('returns valid domain with port', () => {
66
+ const result = stripProtocol('example.com:40');
67
+ assert.strictEqual(result, 'example.com:40');
68
+ });
69
+ });
70
+
71
+ describe('getActionsManifest', () => {
72
+ let fs, exitStub, consoleLog, chalk;
73
+ let DEFAULT_MANIFEST_PATH = 'path/to/manifest';
74
+
75
+ const actionMock = {
76
+ name: 'name',
77
+ category: 'Custom',
78
+ description: 'descriptoin',
79
+ type: 'function',
80
+ path: 'actions/mock.js',
81
+ entryFile: './actions/mock.ts',
82
+ parameters: [],
83
+ allowNetworks: ['127.0.0.1', 'some.domain.tld'],
84
+ };
85
+ // eslint-disable-next-line no-unused-vars
86
+ const { entryFile: _, ...resultMock } = actionMock;
87
+
88
+ fs = {
89
+ existsSync: stub(),
90
+ readFileSync: stub(),
91
+ };
92
+ chalk = {
93
+ bold: stub(),
94
+ red: stub(),
95
+ };
96
+
97
+ let { getActionsManifest } = proxyquire('./utils', { fs, chalk });
98
+
99
+ beforeEach(() => {
100
+ exitStub = stub(process, 'exit');
101
+ consoleLog = stub(console, 'log');
102
+ });
103
+ afterEach(() => {
104
+ exitStub.restore();
105
+ consoleLog.restore();
106
+ });
107
+
108
+ it('should return undefined if manifest does not exist', () => {
109
+ fs.existsSync.returns(false);
110
+
111
+ const result = getActionsManifest(DEFAULT_MANIFEST_PATH);
112
+
113
+ assert.equal(result, undefined);
114
+ });
115
+
116
+ it('should return undefined if manifest has no actions', () => {
117
+ fs.existsSync.returns(true);
118
+ fs.readFileSync.returns(JSON.stringify({ actions: [] }));
119
+
120
+ const result = getActionsManifest(DEFAULT_MANIFEST_PATH);
121
+ assert.equal(result, undefined);
122
+ });
123
+
124
+ it('should return an array of actions if manifest is valid', () => {
125
+ fs.existsSync.returns(true);
126
+ fs.readFileSync.returns(
127
+ JSON.stringify({
128
+ actions: [actionMock],
129
+ })
130
+ );
131
+
132
+ const result = getActionsManifest();
133
+
134
+ assert.deepEqual(result, [resultMock]);
135
+ assert.ok(consoleLog.called);
136
+ });
137
+
138
+ it('should strip the protocol when a domain has a protocol in allowNetworks', () => {
139
+ const mockAction = {
140
+ ...actionMock,
141
+ allowNetworks: ['http://some.domain.tld'],
142
+ };
143
+ // eslint-disable-next-line no-unused-vars
144
+ const { entryFile: _, ...resultMock } = mockAction;
145
+ fs.existsSync.returns(true);
146
+ fs.readFileSync.returns(
147
+ JSON.stringify({
148
+ actions: [mockAction],
149
+ })
150
+ );
151
+
152
+ const result = getActionsManifest();
153
+
154
+ assert.deepEqual(result, [{ ...resultMock, allowNetworks: ['some.domain.tld'] }]);
155
+ assert.ok(consoleLog.called);
156
+ });
157
+
158
+ it('should return an array of actions without entryFile prop if manifest is valid', () => {
159
+ fs.existsSync.returns(true);
160
+ fs.readFileSync.returns(
161
+ JSON.stringify({
162
+ actions: [actionMock],
163
+ })
164
+ );
165
+
166
+ const result = getActionsManifest();
167
+
168
+ assert.notDeepEqual(result, [actionMock]);
169
+ });
170
+
171
+ it('should exit with error if invalid network is found in allowNetworks', () => {
172
+ fs.existsSync.returns(true);
173
+ fs.readFileSync.returns(
174
+ JSON.stringify({
175
+ actions: [
176
+ {
177
+ name: 'action1',
178
+ entryFile: 'entry1',
179
+ allowNetworks: ['412.1.1.1'],
180
+ },
181
+ ],
182
+ })
183
+ );
184
+
185
+ getActionsManifest();
186
+
187
+ assert.ok(exitStub.calledOnceWith(1));
188
+ });
189
+
190
+ it('should exit with error if manifest is invalid JSON', () => {
191
+ fs.existsSync.returns(true);
192
+ fs.readFileSync.throws();
193
+
194
+ getActionsManifest();
195
+
196
+ assert.ok(exitStub.calledOnceWith(1));
197
+ });
198
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/app-scripts",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "A collection of scripts for building Contentful Apps",
5
5
  "author": "Contentful GmbH",
6
6
  "license": "MIT",
@@ -61,5 +61,5 @@
61
61
  "open": "8.4.2",
62
62
  "ora": "5.4.1"
63
63
  },
64
- "gitHead": "63817dc3be779cc5bf2258d955618069bbeecbe5"
64
+ "gitHead": "f07638b628190b97813d2af4fa391b879f5f32e5"
65
65
  }
@@ -1,62 +0,0 @@
1
- const assert = require('assert');
2
-
3
- const { isValidNetwork, stripProtocol } = require('./utils');
4
-
5
- describe('isValidIpAddress', () => {
6
- it('returns true for a valid IP address', () => {
7
- const result = isValidNetwork('192.168.0.1');
8
- assert.strictEqual(result, true);
9
- });
10
-
11
- it('returns true for a valid domain', () => {
12
- const result = isValidNetwork('google.com');
13
- assert.strictEqual(result, true);
14
- });
15
-
16
- it('returns true for a valid ipv6 address', () => {
17
- const result = isValidNetwork('2001:0db8:85a3:0000:0000:8a2e:0370:7334');
18
- assert.strictEqual(result, true);
19
- });
20
-
21
- it('returns true for a valid domain with port', () => {
22
- const result = isValidNetwork('google.com:4000');
23
- assert.strictEqual(result, true);
24
- });
25
-
26
- it('returns true for a valid IP address with port', () => {
27
- const result = isValidNetwork('192.168.0.1:2000');
28
- assert.strictEqual(result, true);
29
- });
30
-
31
- it('returns false for an invalid IP address', () => {
32
- const result = isValidNetwork('not an ip address');
33
- assert.strictEqual(result, false);
34
- });
35
-
36
- it('returns true for an valid ipv6 address with port', () => {
37
- const result = isValidNetwork('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:443');
38
- assert.strictEqual(result, true);
39
- });
40
- });
41
-
42
- describe('removeProtocolFromUrl', () => {
43
- it('returns the host of a URL without a protocol', () => {
44
- const result = stripProtocol('http://example.com');
45
- assert.strictEqual(result, 'example.com');
46
- });
47
-
48
- it('returns valid ipv6 address', () => {
49
- const result = stripProtocol('http://[2001:0db8:85a3:0000:0000:8a2e:0370]:7334');
50
- assert.strictEqual(result, '[2001:0db8:85a3:0000:0000:8a2e:0370]:7334');
51
- });
52
-
53
- it('returns valid domain', () => {
54
- const result = stripProtocol('example.com');
55
- assert.strictEqual(result, 'example.com');
56
- });
57
-
58
- it('returns valid domain with port', () => {
59
- const result = stripProtocol('example.com:40');
60
- assert.strictEqual(result, 'example.com:40');
61
- });
62
- });