Skip to content

Commit

Permalink
feat(create-contentful-studio-experiences): add more validation to CL…
Browse files Browse the repository at this point in the history
…I tool [ALT-991] (#674)
  • Loading branch information
primeinteger committed Jun 27, 2024
1 parent adf2ec3 commit aacdffb
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 176 deletions.
1 change: 1 addition & 0 deletions packages/create-contentful-studio-experiences/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
# CLI generated project (default path)
studio-experiences-react-app
31 changes: 16 additions & 15 deletions packages/create-contentful-studio-experiences/README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
# create-contentful-studio-experiences

## Description

This package is a CLI tool to bootstrap a [React, Vite & TypeScript](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) app that renders [Studio Experiences](https://www.contentful.com/developers/docs/experiences/what-are-experiences/).

## Prerequisites

1. A Contentful Space that has Experiences Enabled. See: [How to enable Experiences in a space](https://www.contentful.com/help/enable-spaces-for-experiences/).
2. You must not have an Experiences Content type already configured for your space. For Studio Experiences to work correctly, a maximum of one experiences content type must be defined. As many individual experiences as required (within your subscription plan/billing) can be created.

## Getting started

To create a Contentful Studio Experiences project, run this command and follow the prompts.

```bash
npm init contentful-studio-experiences
cd <studio-experiences-react-app>
```

Navigate to your project directory and start the server. After that, you can manage your Experiences in Contentful.

```bash
cd studio-experiences-react-app
npm run dev
```

## CLI steps
## CLI Steps

1. Project Type - Currently supports React.
2. React Type - Currently supports Vite + React + TypeScript.
3. Installation Path - The generated project will be installed here.
4. _Optional_ Contentful Space - Note: only spaces that have already been "Experiences" enabled are eligible. See: [How to enable Experiences in a space](https://www.contentful.com/help/enable-spaces-for-experiences/).
4. Connect to Contentful Space _(Optional)_ - Note: only spaces that have Studio Experiences enabled are eligible. See: [How to enable Experiences in a space](https://www.contentful.com/help/enable-spaces-for-experiences/).
- Select your Contentful Organization.
- Select or Create an CLI API Token.
5. Experience Content Type - Enter a name for your Experiences Content Type.
- Creates your Experience Content Type.
- Creates a Demo Experience Entry to help you get started.
6. Content Preview Environment - Configures the Content Preview Settings for Studio Experiences canvas to preview your local project.
- Select your Contentful Space.
- Select or Create an API key.
- Content Type will be created for Experiences if needed.
- Demo Experience Entry will be created (unless one exists).
- Content Preview Settings will be configured if needed.
5. Project Output - The React project will be created, incorporating the [Studio Experiences SDK](https://www.contentful.com/developers/docs/experiences/set-up-experiences-sdk/#usage).

## CLI Options

Expand Down
150 changes: 79 additions & 71 deletions packages/create-contentful-studio-experiences/src/ctflClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import open from 'open';
import { EnvFileData } from './models.js';
import { GetmanySpaceEnablementsReturn } from './types.js';
import { getExperienceEntryDemoReqBody, getExperienceContentTypeReqBody } from './content.js';

const defaultLocale = 'en-US';
const baseUrl = process.env.BASE_URL || 'https://api.contentful.com';

export class CtflClient {
Expand Down Expand Up @@ -78,21 +79,33 @@ export class CtflClient {
this.apiKey!.previewAccessToken = previewAccessToken;
}

/**
* @description - retrieve Enablements
*/
async getManySpaceEnablements({ orgId }: { orgId: string }) {
const getAllEnablementsUrl = `/organizations/${orgId}/space_enablements`;

try {
const response = await this.apiCall<GetmanySpaceEnablementsReturn>(getAllEnablementsUrl, {
async getSpaceEnablements(orgId: string) {
type GetSpaceEnablementsReturn = {
items: {
sys: { space: { sys: { id: string } } };
studioExperiences: {
enabled: boolean;
};
}[];
};
const enablements = await this.apiCall<GetSpaceEnablementsReturn>(
`/organizations/${orgId}/space_enablements`,
{
method: 'GET',
});
},
);
return enablements.items;
}

return response.items;
} catch (error) {
throw new Error(`Unable to retrieve Space Enablements. error: ${error}`);
}
async getContentEntry(slug: string, contentTypeId: string) {
type GetContentEntriesReturn = { items: { sys: { id: string } }[] };
const entries = await this.apiCall<GetContentEntriesReturn>(
`/spaces/${this.space?.id}/environments/master/entries?content_type=${contentTypeId}&fields.slug.${defaultLocale}=${slug}&limit=1`,
{
method: 'GET',
},
);
return entries.items[0]?.sys.id || undefined;
}

async createContentEntry(title: string, slug: string, contentTypeId: string) {
Expand All @@ -113,43 +126,69 @@ export class CtflClient {
await this.apiCall(
`/spaces/${this.space?.id}/environments/master/entries/${entryId}/published`,
{
method: 'put',
method: 'PUT',
headers: {
'x-contentful-version': '1',
},
},
);
return entryId;
}

async getExistingExperienceType() {
type GetContentTypesReturn = {
items: {
name: string;
sys: { id: string };
metadata: { annotations: { ContentType: { sys: { id: string } }[] } };
}[];
};
return await this.apiCall<GetContentTypesReturn>(
`/spaces/${this.space?.id}/environments/master/content_types`,
{
method: 'GET',
},
).then((val) => {
for (const item of val.items) {
if (
item.metadata?.annotations?.ContentType?.some(
(ct) => ct.sys.id === 'Contentful:ExperienceType',
)
) {
// Return the first Content Type found (currently there can be only one Experience Type per space)
return {
id: item.sys.id,
name: item.name,
};
}
}
});
}

async createContentType(contentTypeName: string, contentTypeId: string) {
try {
// Create Content Type
await this.apiCall(
`/spaces/${this.space?.id}/environments/master/content_types/${contentTypeId}`,
{
headers: {
'x-contentful-version': '0',
},
body: JSON.stringify(getExperienceContentTypeReqBody(contentTypeName)),
method: 'PUT',
// Create Content Type
await this.apiCall(
`/spaces/${this.space?.id}/environments/master/content_types/${contentTypeId}`,
{
headers: {
'x-contentful-version': '0',
},
);
body: JSON.stringify(getExperienceContentTypeReqBody(contentTypeName)),
method: 'PUT',
},
);

// Publish Content Type
await this.apiCall(
`/spaces/${this.space?.id}/environments/master/content_types/${contentTypeId}/published`,
{
headers: {
'x-contentful-version': '1',
},
method: 'PUT',
body: null,
// Publish Content Type
await this.apiCall(
`/spaces/${this.space?.id}/environments/master/content_types/${contentTypeId}/published`,
{
headers: {
'x-contentful-version': '1',
},
);
} catch (error) {
console.error(error);
throw error;
}
method: 'PUT',
body: null,
},
);
}

async createPreviewEnvironment(port: string, contentTypeName: string, contentTypeId: string) {
Expand Down Expand Up @@ -194,28 +233,6 @@ export class CtflClient {
return previewEnvironments;
}

async createSpace(name: string, orgId: string) {
type SpaceReturn = { name: string; sys: { id: string } };
const space = await this.apiCall<SpaceReturn>('/spaces', {
method: 'POST',
headers: {
'X-Contentful-Organization': orgId,
},
body: JSON.stringify({
name: name,
defaultLocale: 'en-US',
}),
}).then((res) => {
this.space = {
name: res.name,
id: res.sys.id,
};
return this.space;
});

return space;
}

async deleteAuthToken() {
if (this.authToken && this.authTokenCreatedFromApi) {
type KeysReturn = { items: { sys: { id: string; redactedValue: string } }[] };
Expand All @@ -239,14 +256,6 @@ export class CtflClient {
}
}

async deleteSpace() {
if (this.space) {
await this.apiCall(`/spaces/${this.space.id}`, {
method: 'DELETE',
});
}
}

async getAuthToken() {
const APP_ID = '9f86a1d54f3d6f85c159468f5919d6e5d27716b3ed68fd01bd534e3dea2df864';
const REDIRECT_URI = 'https://www.contentful.com/developers/cli-oauth-page/';
Expand Down Expand Up @@ -319,7 +328,6 @@ export class CtflClient {

if (!response.ok) {
console.log('[ ctflClient.ts ] apiCall() not OK response => ', await response.json());

throw new Error(response.statusText);
}

Expand Down
Loading

0 comments on commit aacdffb

Please sign in to comment.