Skip to content

Commit 03cd1b7

Browse files
Setup scripts for managing spaces and app defns [INTEG-2837] (#9950)
* space setup scripts * separate appDef for staging and production apps * feat: pull variables from .env file * refactor: converting files to ts * fix: .npmrc file * feat: merge complete * fix: linting * fix: unused import, backticks instead of concatenation * refactor: improve team space membership checks * feat: add teardown functionality for cleanup after setup * feat: add example environment variables * refactor: moving dependencies to scripts folder * docs: update README with environment variable setup * feat: add TypeScript support * docs: update README with teardown function instructions and testing details --------- Co-authored-by: ryunsong-contentful <ryun.song@contentful.com>
1 parent 3733e36 commit 03cd1b7

19 files changed

+3218
-7934
lines changed

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
engine-strict = true
2+
@contentful:registry=https://registry.npmjs.org

package-lock.json

Lines changed: 2079 additions & 7934 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
22
"name": "@contentful/apps",
33
"private": true,
4+
"type": "module",
45
"engines": {
56
"node": ">=16.0.0",
67
"npm": ">=8.0.0"
78
},
89
"devDependencies": {
910
"@sentry/cli": "^2.14.4",
11+
"@types/node": "^24.0.13",
1012
"eslint": "^7.32.0",
1113
"husky": "^8.0.0",
1214
"lint-staged": "^13.0.0",

scripts/.env-example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CONTENTFUL_ACCESS_TOKEN =
2+
CONTENTFUL_ORGANIZATION_ID =
3+
CONTENTFUL_TEAM_ID =
4+
CONTENTFUL_APP_NAME =

scripts/README.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Contentful Space Setup Script
2+
3+
This script automates the complete setup of Contentful spaces for your app, including app definition creation, space creation, team assignments, and app installations.
4+
5+
## What This Script Does
6+
7+
1. **Creates App Definitions** (staging and production) in your Contentful organization
8+
2. **Creates Two Spaces**: Production and Staging environments
9+
3. **Assigns Teams** to both spaces with admin privileges
10+
4. **Installs the App** in both spaces
11+
5. **Provides Configuration Links** to finish app setup
12+
13+
## ⚙️ Configuration
14+
15+
### Environment Variables Setup
16+
17+
1. **Copy the example environment file:**
18+
19+
```bash
20+
cp .env-example .env
21+
```
22+
23+
2. **Fill in your Contentful credentials in `.env`:**
24+
```bash
25+
CONTENTFUL_ACCESS_TOKEN=your_access_token_here
26+
CONTENTFUL_ORGANIZATION_ID=your_org_id_here
27+
CONTENTFUL_TEAM_ID=your_team_id_here
28+
CONTENTFUL_APP_NAME=Your App Name
29+
CONTENTFUL_ENVIRONMENT=master
30+
```
31+
32+
### 🔑 How to Get Required IDs
33+
34+
#### Access Token (Content Management API)
35+
36+
1. Go to [Contentful Web App](https://app.contentful.com)
37+
2. Navigate to Settings → API keys → Content management tokens
38+
3. Generate a new personal access token
39+
4. Copy the token to `CONTENTFUL_ACCESS_TOKEN` in your `.env` file
40+
41+
#### Organization ID
42+
43+
1. In Contentful, go to your organization settings
44+
2. The organization ID is in the URL: `app.contentful.com/account/organizations/{ORGANIZATION_ID}`
45+
3. Copy this ID to `CONTENTFUL_ORGANIZATION_ID` in your `.env` file
46+
47+
#### Team ID
48+
49+
1. Go to Settings → Teams
50+
2. Click on the team you want to assign
51+
3. The team ID is in the URL: `app.contentful.com/account/organizations/{ORG_ID}/teams/{TEAM_ID}`
52+
4. Copy this ID to `CONTENTFUL_TEAM_ID` in your `.env` file
53+
54+
## Testing
55+
56+
### Teardown Function
57+
58+
For testing purposes, the script includes a teardown function that completely removes all created resources.
59+
60+
#### How to Enable Teardown
61+
62+
1. **Uncomment the teardown section** at the bottom of the file `scripts/setup.ts`:
63+
64+
```typescript
65+
// FOR TESTING - uncomment to undo setup
66+
await teardown({
67+
client,
68+
organizationId,
69+
spaceIdStaging: stagingSpace.sys.id,
70+
spaceIdProduction: productionSpace.sys.id,
71+
appDefinitionIdStaging: appDefinitionStaging.sys.id,
72+
appDefinitionIdProduction: appDefinitionProduction.sys.id,
73+
});
74+
```
75+
76+
2. **Run the setup script** as normal:
77+
```bash
78+
npm run setup
79+
```
80+
81+
## How to Run
82+
83+
1. **Navigate to the scripts directory:**
84+
85+
```bash
86+
cd scripts
87+
```
88+
89+
2. **Install dependencies:**
90+
91+
```bash
92+
npm install
93+
```
94+
95+
3. **Configure your `.env` file** (see Configuration section above)
96+
97+
4. **Run the setup script:**
98+
```bash
99+
npm run setup
100+
```
101+
102+
## What Gets Created
103+
104+
### App Definitions
105+
106+
- **Staging**: `{appName} (staging)`
107+
- **Production**: `{appName} (production)`
108+
- **Location**: Your Contentful organization
109+
- **Status**: Created but needs configuration
110+
111+
### Spaces
112+
113+
- **Production**: `{appName} (production)`
114+
- **Staging**: `{appName} (staging)`
115+
- **Team Access**: Admin privileges for all members in specified team
116+
- **App Installation**: Your app installed in both spaces
117+
118+
## 📝 Next Steps
119+
120+
After running the script successfully:
121+
122+
1. **Configure your app definitions** using the provided links
123+
2. **Set up app locations** (entry field, page, etc.)
124+
3. **Configure app parameters** if needed

scripts/actions/addTeamToSpace.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { createCMAClient } from './createCMAClient.ts';
2+
3+
interface AddTeamToSpaceProps {
4+
client?: any;
5+
spaceId: string;
6+
teamId: string;
7+
}
8+
9+
export async function addTeamToSpace({ client, spaceId, teamId }: AddTeamToSpaceProps) {
10+
try {
11+
if (!client) {
12+
client = await createCMAClient();
13+
}
14+
const space = await client.getSpace(spaceId);
15+
16+
// Check if team already has access
17+
const existingMemberships = await space.getTeamSpaceMemberships();
18+
const teamHasSpaceAccess = existingMemberships.items.some(
19+
(membership) => membership.sys.team.sys.id === teamId
20+
);
21+
22+
if (teamHasSpaceAccess) {
23+
console.log(`👥 Team already has access to space - no action taken.`);
24+
return;
25+
}
26+
27+
// Create team space membership with admin privileges
28+
const teamSpaceMembership = await space.createTeamSpaceMembership(teamId, {
29+
admin: true,
30+
roles: [],
31+
});
32+
33+
console.log(
34+
`\n👥 Team assigned to space ${space.name} (${space.sys.id}) successfully: ${teamSpaceMembership.sys.id}`
35+
);
36+
return teamSpaceMembership;
37+
} catch (error) {
38+
console.error(`❌ Failed to assign team to space: ${error.message}`);
39+
throw error;
40+
}
41+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { createCMAClient } from './createCMAClient.ts';
2+
interface CreateAppDefinitionProps {
3+
client?: any;
4+
organizationId: string;
5+
appName: string;
6+
}
7+
8+
export async function createAppDefinition({
9+
client,
10+
organizationId,
11+
appName,
12+
}: CreateAppDefinitionProps) {
13+
if (!client) {
14+
client = await createCMAClient();
15+
}
16+
17+
const organization = await client.getOrganization(organizationId);
18+
const appDefinition = await organization.createAppDefinition({
19+
name: appName,
20+
});
21+
22+
console.log(
23+
`\n🚀 App definition created: ${appDefinition.name} (${appDefinition.sys.id}) in organization: ${organization.name} (${organization.sys.id})`
24+
);
25+
26+
return appDefinition;
27+
}

scripts/actions/createCMAClient.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import contentful from 'contentful-management';
2+
3+
export async function createCMAClient() {
4+
if (!process.env.CONTENTFUL_ACCESS_TOKEN) {
5+
throw new Error('Cannot find CMA token');
6+
}
7+
8+
const client = contentful.createClient({
9+
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
10+
});
11+
12+
return client;
13+
}

scripts/actions/createSpace.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { createCMAClient } from './createCMAClient.ts';
2+
3+
interface CreateSpaceOptions {
4+
client?: any;
5+
organizationId: string;
6+
spaceName: string;
7+
}
8+
9+
export async function createSpace({ client, organizationId, spaceName }: CreateSpaceOptions) {
10+
if (!client) {
11+
client = await createCMAClient();
12+
}
13+
14+
const space = await client.createSpace({ name: spaceName }, organizationId);
15+
16+
console.log(`\n🌐 Space created! Name: ${space.name}, ID: ${space.sys.id}`);
17+
18+
return space;
19+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { createCMAClient } from './createCMAClient.ts';
2+
3+
interface DeleteAppDefinitionProps {
4+
client?: any;
5+
organizationId: string;
6+
appDefinitionId: string;
7+
}
8+
9+
export async function deleteAppDefinition({
10+
client,
11+
organizationId,
12+
appDefinitionId,
13+
}: DeleteAppDefinitionProps) {
14+
if (!client) {
15+
client = await createCMAClient();
16+
}
17+
18+
const org = await client.getOrganization(organizationId);
19+
const appDefinition = await org.getAppDefinition(appDefinitionId);
20+
21+
await appDefinition.delete();
22+
23+
console.log(`\n🗑️ AppDefinition '${appDefinitionId}' deleted successfully!`);
24+
}

0 commit comments

Comments
 (0)