Skip to content

Commit a292360

Browse files
authored
fix: support host option for all commands [EXT-5971] (#2325)
* fix: pass host value to getAppInfo for interactive modes * fix: use correct hostname in success messages * feat: update open settings and install to support host * chore: update README * chore: update host description to subdomain
1 parent b76050f commit a292360

File tree

15 files changed

+231
-87
lines changed

15 files changed

+231
-87
lines changed

packages/contentful--app-scripts/README.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,16 @@ You can also execute this command without the argument if the environment variab
177177
> $ CONTENTFUL_APP_DEF_ID=some-definition-id npx --no-install @contentful/app-scripts open-settings
178178
> ```
179179
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+
180190
### Clean up bundles
181191
182192
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
246256
> $ npx --no-install @contentful/app-scripts install --definition-id some-definition-id --host api.eu.contentful.com
247257
> ```
248258
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+
249269
### Tracking
250270
251271
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,7 @@ When passing the `--ci` argument adding all variables as arguments is required
286306
**Options:**
287307
288308
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

packages/contentful--app-scripts/src/activate/build-bundle-activate-settings.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getAppInfo } from '../get-app-info';
33
import { ActivateOptions, ActivateSettings } from '../types';
44

55
export async function buildBundleActivateSettings(
6-
options: ActivateOptions,
6+
options: ActivateOptions
77
): Promise<ActivateSettings> {
88
const { bundleId, host } = options;
99
const prompts = [];
@@ -23,12 +23,13 @@ export async function buildBundleActivateSettings(
2323
});
2424
}
2525

26-
const appActivateSettings = await inquirer.prompt(prompts);
27-
const appInfo = await getAppInfo(options);
26+
const { host: interactiveHost, ...appActivateSettings } = await inquirer.prompt(prompts);
27+
const hostValue = host || interactiveHost;
28+
const appInfo = await getAppInfo({ ...options, host: hostValue });
2829

2930
return {
3031
bundleId,
31-
host,
32+
host: hostValue,
3233
...appActivateSettings,
3334
...appInfo,
3435
};

packages/contentful--app-scripts/src/bin.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ async function runCommand(command: Command, options?: any) {
4848
.option('--token [accessToken]', 'Your content management access token')
4949
.option('--comment [comment]', 'Optional comment for the created bundle')
5050
.option('--skip-activation', 'A Boolean flag to skip automatic activation')
51-
.option('--host [host]', 'Contentful domain to use')
51+
.option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
5252
.action(async (options) => {
5353
await runCommand(upload, options);
5454
});
@@ -60,7 +60,7 @@ async function runCommand(command: Command, options?: any) {
6060
.option('--organization-id [orgId]', 'The id of your organization')
6161
.option('--definition-id [defId]', 'The id of your apps definition')
6262
.option('--token [accessToken]', 'Your content management access token')
63-
.option('--host [host]', 'Contentful domain to use')
63+
.option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
6464
.action(async (options) => {
6565
await runCommand(activate, options);
6666
});
@@ -69,6 +69,7 @@ async function runCommand(command: Command, options?: any) {
6969
.command('open-settings')
7070
.description('Opens the app editor for a given AppDefinition')
7171
.option('--definition-id [defId]', 'The id of your apps definition')
72+
.option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
7273
.action(async (options) => {
7374
await runCommand(open, options);
7475
});
@@ -80,7 +81,7 @@ async function runCommand(command: Command, options?: any) {
8081
.option('--definition-id [defId]', 'The id of your apps definition')
8182
.option('--token [accessToken]', 'Your content management access token')
8283
.option('--keep [keepAmount]', 'The amount of bundles that should remain')
83-
.option('--host [host]', 'Contentful domain to use')
84+
.option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
8485
.action(async (options) => {
8586
await runCommand(cleanup, options);
8687
});
@@ -98,7 +99,7 @@ async function runCommand(command: Command, options?: any) {
9899
'Opens a picker to select the space and environment for installing the app associated with a given AppDefinition'
99100
)
100101
.option('--definition-id [defId]', 'The id of your apps definition')
101-
.option('--host [host]', 'Contentful domain to use')
102+
.option('--host [host]', 'Contentful subdomain to use, e.g. "api.contentful.com"')
102103
.action(async (options) => {
103104
await runCommand(install, options);
104105
});

packages/contentful--app-scripts/src/clean-up/build-clean-up-settings.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@ export async function buildCleanUpSettings(options: CleanupOptions): Promise<Cle
2323
});
2424
}
2525

26-
const appCleanUpSettings = await prompt(prompts);
27-
const appInfo = await getAppInfo(options);
26+
const { host: interactiveHost, ...appCleanUpSettings } = await prompt(prompts);
27+
const hostValue = host || interactiveHost;
28+
const appInfo = await getAppInfo({ ...options, host: hostValue });
2829

2930
return {
3031
keep: keep === undefined ? +appCleanUpSettings.keep : +keep,
31-
host,
32+
host: hostValue,
3233
...appCleanUpSettings,
3334
...appInfo,
3435
};

packages/contentful--app-scripts/src/create-app-definition/create-app-definition.test.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,16 @@ describe('createAppDefinition', () => {
6565
selectFromListMock.returns({ name: 'name', value: organizationId });
6666

6767
await assert.rejects(() => subject(token, { locations: [] }));
68-
assert((console.log as SinonStub).calledWith(match(/Something went wrong while creating the app definition/)));
68+
assert(
69+
(console.log as SinonStub).calledWith(
70+
match(/Something went wrong while creating the app definition/)
71+
)
72+
);
6973
});
7074

71-
it('logs success message', async () => {
75+
it('logs success message with default host', async () => {
7276
const appId = 'appId';
73-
const orgSettingsLink = 'https://app.contentful.com/deeplink?link=org';
77+
const orgSettingsLink = 'https://app.contentful.com/deeplink?link=app-definition-list';
7478
const appLink = `https://app.contentful.com/deeplink?link=apps&id=${appId}`;
7579
const tutorialLink = 'https://ctfl.io/app-tutorial';
7680

@@ -93,6 +97,33 @@ describe('createAppDefinition', () => {
9397
assert.deepStrictEqual(cachedEnvVarsMock.args[0][0], { [APP_DEF_ENV_KEY]: 'appId' });
9498
});
9599

100+
it('logs success message with EU host option', async () => {
101+
const appId = 'appId';
102+
const orgSettingsLink = 'https://app.eu.contentful.com/deeplink?link=app-definition-list';
103+
const appLink = `https://app.eu.contentful.com/deeplink?link=apps&id=${appId}`;
104+
const tutorialLink = 'https://ctfl.io/app-tutorial';
105+
106+
clientMock.getOrganization = stub().resolves({
107+
createAppDefinition: stub().resolves({ sys: { id: 'appId' } }),
108+
});
109+
clientMock.getOrganizations = stub().resolves({
110+
items: [{ name: 'name', sys: { id: organizationId } }],
111+
});
112+
selectFromListMock.returns({ name: 'name', value: organizationId });
113+
114+
await assert.doesNotReject(() =>
115+
subject(token, { locations: [], host: 'api.eu.contentful.com' })
116+
);
117+
118+
const loggedMessage = (console.log as SinonStub).getCall(0).args[0];
119+
120+
assert(loggedMessage.includes('Success'));
121+
assert(loggedMessage.includes(orgSettingsLink));
122+
assert(loggedMessage.includes(appLink));
123+
assert(loggedMessage.includes(tutorialLink));
124+
assert.deepStrictEqual(cachedEnvVarsMock.args[0][0], { [APP_DEF_ENV_KEY]: 'appId' });
125+
});
126+
96127
it('sets default src if any frontend location is specified', async () => {
97128
const createAppDefinitionStub = stub().resolves({ sys: { id: 'testId' } });
98129
clientMock.getOrganization = stub().resolves({

packages/contentful--app-scripts/src/create-app-definition/create-app-definition.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ClientAPI, createClient } from 'contentful-management';
44
import chalk from 'chalk';
55
import { isString, isPlainObject, has } from 'lodash';
66

7-
import { throwValidationException, selectFromList } from '../utils';
7+
import { throwValidationException, selectFromList, getWebAppHostname } from '../utils';
88
import { cacheEnvVars } from '../cache-credential';
99
import { ORG_ID_ENV_KEY, APP_DEF_ENV_KEY } from '../constants';
1010
import { AppDefinitionSettings } from './build-app-definition-settings';
@@ -62,7 +62,8 @@ export async function createAppDefinition(
6262
) {
6363
assertValidArguments(accessToken, appDefinitionSettings);
6464

65-
const client = createClient({ accessToken, host: appDefinitionSettings.host });
65+
const host = appDefinitionSettings.host;
66+
const client = createClient({ accessToken, host });
6667
const organizations = await fetchOrganizations(client);
6768

6869
const selectedOrg = await selectFromList(
@@ -100,7 +101,7 @@ export async function createAppDefinition(
100101
return {
101102
location,
102103
};
103-
})
104+
});
104105
const hasFrontendLocation = locations.some(({ location }) => location !== 'dialog');
105106
const body = {
106107
name: appName,
@@ -123,20 +124,20 @@ export async function createAppDefinition(
123124
[APP_DEF_ENV_KEY]: createdAppDefinition.sys.id,
124125
});
125126

127+
const webApp = getWebAppHostname(host);
128+
126129
console.log(`
127130
${chalk.greenBright('Success!')} Created an app definition for ${chalk.bold(
128131
appName
129132
)} in ${chalk.bold(selectedOrg.name)}.
130133
131-
${chalk.dim(`NOTE: You can update this app definition in your organization settings:
132-
${chalk.underline(`https://app.contentful.com/deeplink?link=org`)}`)}
134+
${chalk.dim(`NOTE: You can update this app definition in your apps settings:
135+
${chalk.underline(`https://${webApp}/deeplink?link=app-definition-list`)}`)}
133136
134137
${chalk.bold('Next steps:')}
135138
1. Run your app with ${chalk.cyan('`npm start`')} inside of your app folder.
136139
2. Install this app definition to one of your spaces by opening:
137-
${chalk.underline(
138-
`https://app.contentful.com/deeplink?link=apps&id=${createdAppDefinition.sys.id}`
139-
)}
140+
${chalk.underline(`https://${webApp}/deeplink?link=apps&id=${createdAppDefinition.sys.id}`)}
140141
3. Learn how to build your first Contentful app:
141142
${chalk.underline(`https://ctfl.io/app-tutorial`)}
142143
`);

packages/contentful--app-scripts/src/install/install.test.ts

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { SinonStub, stub } from 'sinon';
22
import assert from 'assert';
3-
import { APP_DEF_ENV_KEY } from '../constants';
3+
import {
4+
APP_DEF_ENV_KEY,
5+
DEFAULT_CONTENTFUL_API_HOST,
6+
DEFAULT_CONTENTFUL_APP_HOST,
7+
} from '../constants';
48
import proxyquire from 'proxyquire';
59

610
const TEST_DEF_ID = 'test-def-id';
7-
const TEST_HOST = 'test.host.com';
811

912
describe('install', () => {
1013
let subject: typeof import('./install').installToEnvironment,
@@ -29,33 +32,40 @@ describe('install', () => {
2932
}));
3033
});
3134

32-
it('works with an app ID option passed', () => {
33-
subject({ definitionId: TEST_DEF_ID });
35+
it('works with both host and app ID options passed', async () => {
36+
await subject({ host: DEFAULT_CONTENTFUL_API_HOST, definitionId: TEST_DEF_ID });
3437
assert(
35-
installMock.calledWith(`https://app.contentful.com/deeplink?link=apps&id=${TEST_DEF_ID}`)
38+
installMock.calledWith(
39+
`https://${DEFAULT_CONTENTFUL_APP_HOST}/deeplink?link=apps&id=${TEST_DEF_ID}`
40+
)
3641
);
3742
});
3843

39-
it('works with both host and app ID options passed', () => {
40-
subject({ host: TEST_HOST, definitionId: TEST_DEF_ID });
41-
assert(installMock.calledWith(`https://${TEST_HOST}/deeplink?link=apps&id=${TEST_DEF_ID}`));
42-
});
43-
44-
it('shows prompt when no app definition is provided', () => {
44+
it('shows prompt when no options are provided', () => {
4545
subject({});
4646
assert.strictEqual(inquirerMock.called, true);
4747
});
4848

49-
it('shows prompt when host is provided, but no app definition', () => {
50-
subject({ host: TEST_HOST });
51-
assert.strictEqual(inquirerMock.called, true);
49+
it('throws an error when no app definition is defined', async () => {
50+
try {
51+
await subject({});
52+
} catch (err) {
53+
assert.strictEqual(err.message, 'No app-definition-id');
54+
}
5255
});
5356

54-
it('works with env variable set', () => {
57+
it('works with env variable set', async () => {
5558
process.env[APP_DEF_ENV_KEY] = TEST_DEF_ID;
56-
subject({});
59+
await subject({});
5760
assert(
5861
installMock.calledWith(`https://app.contentful.com/deeplink?link=apps&id=${TEST_DEF_ID}`)
5962
);
6063
});
64+
65+
it('works with EU host option passed', async () => {
66+
await subject({ definitionId: TEST_DEF_ID, host: 'api.eu.contentful.com' });
67+
assert(
68+
installMock.calledWith(`https://app.eu.contentful.com/deeplink?link=apps&id=${TEST_DEF_ID}`)
69+
);
70+
});
6171
});

packages/contentful--app-scripts/src/install/install.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,38 @@
11
import open from 'open';
22
import chalk from 'chalk';
33
import inquirer from 'inquirer';
4-
import { APP_DEF_ENV_KEY, DEFAULT_CONTENTFUL_APP_HOST } from '../constants';
4+
import { APP_DEF_ENV_KEY, DEFAULT_CONTENTFUL_API_HOST } from '../constants';
55
import { InstallOptions } from '../types';
6+
import { getWebAppHostname } from '../utils';
67

78
export async function installToEnvironment(options: InstallOptions) {
89
let definitionId;
10+
const prompts = [];
11+
912
if (options.definitionId) {
1013
definitionId = options.definitionId;
1114
} else if (process.env[APP_DEF_ENV_KEY]) {
1215
definitionId = process.env[APP_DEF_ENV_KEY];
1316
} else {
14-
const prompts = await inquirer.prompt([
15-
{
16-
name: 'definitionId',
17-
message: `The id of the app:`,
18-
},
19-
]);
20-
definitionId = prompts.definitionId;
17+
prompts.push({
18+
name: 'definitionId',
19+
message: `The id of the app:`,
20+
});
21+
}
22+
23+
if (!options.host) {
24+
prompts.push({
25+
name: 'host',
26+
message: `Contentful CMA endpoint URL:`,
27+
default: DEFAULT_CONTENTFUL_API_HOST,
28+
});
2129
}
2230

23-
if (!definitionId) {
31+
const openSettingsOptions = await inquirer.prompt(prompts);
32+
const hostValue = options.host || openSettingsOptions?.host;
33+
const appDefinitionIdValue = definitionId || openSettingsOptions?.definitionId;
34+
35+
if (!appDefinitionIdValue) {
2436
console.log(`
2537
${chalk.red('Error:')} There was no app-definition defined.
2638
@@ -30,8 +42,8 @@ export async function installToEnvironment(options: InstallOptions) {
3042
throw new Error('No app-definition-id');
3143
}
3244

33-
const host = options.host || DEFAULT_CONTENTFUL_APP_HOST;
34-
const redirectUrl = `https://${host}/deeplink?link=apps`;
45+
const webApp = getWebAppHostname(hostValue);
46+
const redirectUrl = `https://${webApp}/deeplink?link=apps`;
3547

3648
try {
3749
open(`${redirectUrl}&id=${definitionId}`);

0 commit comments

Comments
 (0)