Skip to content

Commit 57992e7

Browse files
authored
Merge pull request #90 from Contentstack-Solutions/staging
Merge staging to master
2 parents d8d4872 + f799ff3 commit 57992e7

File tree

13 files changed

+1657
-214
lines changed

13 files changed

+1657
-214
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
/yarn.lock
88
node_modules
99
.vscode/
10+
oclif.manifest.json

package-lock.json

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

package.json

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,34 @@
11
{
22
"name": "contentstack-cli-tsgen",
33
"description": "Generate TypeScript typings from a Stack.",
4-
"version": "2.2.2",
4+
"version": "2.3.0",
55
"author": "Michael Davis",
66
"bugs": "https://github.com/Contentstack-Solutions/contentstack-cli-tsgen/issues",
77
"dependencies": {
8-
"@contentstack/cli-command": "^1.2.13",
9-
"@contentstack/cli-utilities": "^1.5.2",
8+
"@contentstack/cli-command": "^1.2.17",
9+
"@contentstack/cli-utilities": "^1.5.10",
10+
"@gql2ts/from-schema": "^2.0.0-4",
1011
"lodash": "^4.17.20",
1112
"prettier": "^2.0.5",
1213
"tslib": "^1.13.0"
1314
},
1415
"devDependencies": {
1516
"@oclif/plugin-help": "^3.2.0",
17+
"@oclif/test": "^3.1.12",
1618
"@types/async": "^3.2.18",
19+
"@types/chai": "^4.3.11",
1720
"@types/jest": "^26.0.14",
1821
"@types/lodash": "^4.14.162",
22+
"@types/mocha": "^10.0.6",
1923
"@types/node": "^10.17.28",
2024
"async": "^3.2.4",
25+
"chai": "^5.0.0",
2126
"eslint": "^5.16.0",
2227
"eslint-config-oclif": "^3.1.0",
2328
"eslint-config-oclif-typescript": "^0.1.0",
2429
"globby": "^10.0.2",
2530
"jest": "^26.5.3",
31+
"mocha": "^10.2.0",
2632
"oclif": "^3.7.0",
2733
"ts-jest": "^26.4.1",
2834
"ts-node": "^10.9.1",
@@ -56,8 +62,9 @@
5662
"postpack": "rm -f oclif.manifest.json",
5763
"posttest": "eslint . --ext .ts --config .eslintrc",
5864
"prepack": "rm -rf lib && tsc -b && oclif manifest && oclif readme",
59-
"test": "jest",
60-
"version": "oclif readme && git add README.md"
65+
"test": "jest --testPathPattern=tests",
66+
"version": "oclif readme && git add README.md",
67+
"test:unit": "mocha --forbid-only -r ts-node/register \"test/**/*.test.ts\""
6168
},
6269
"csdxConfig": {
6370
"shortCommandName": {

src/commands/tsgen.ts

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Command} from '@contentstack/cli-command'
22
import {FlagInput, flags} from '@contentstack/cli-utilities'
3-
import {getGlobalFields, stackConnect, StackConnectionConfig} from '../lib/stack/client'
3+
import {getGlobalFields, stackConnect, StackConnectionConfig, generateGraphQLTypeDef} from '../lib/stack/client'
44
import {ContentType} from '../lib/stack/schema'
55
import tsgenRunner from '../lib/tsgen/runner'
66

@@ -11,6 +11,8 @@ export default class TypeScriptCodeGeneratorCommand extends Command {
1111
'$ csdx tsgen -a "delivery token alias" -o "contentstack/generated.d.ts"',
1212
'$ csdx tsgen -a "delivery token alias" -o "contentstack/generated.d.ts" -p "I"',
1313
'$ csdx tsgen -a "delivery token alias" -o "contentstack/generated.d.ts" --no-doc',
14+
'$ csdx tsgen -a "delivery token alias" -o "contentstack/generated.d.ts" --api-type graphql',
15+
'$ csdx tsgen -a "delivery token alias" -o "contentstack/generated.d.ts" --api-type graphql --namespace "GraphQL" ',
1416
];
1517

1618
static flags: FlagInput = {
@@ -56,6 +58,17 @@ export default class TypeScriptCodeGeneratorCommand extends Command {
5658
description: 'include system fields in generated types',
5759
default: false,
5860
}),
61+
62+
'api-type': flags.string({
63+
default: 'rest',
64+
multiple: false,
65+
options: ['rest', 'graphql'],
66+
description: '[Optional] Please enter an API type to generate the type definitions.',
67+
}),
68+
69+
namespace: flags.string({
70+
description: '[Optional]Please enter a namespace for the GraphQL API type to organize the generated types.',
71+
}),
5972
};
6073

6174
async run() {
@@ -68,6 +81,7 @@ export default class TypeScriptCodeGeneratorCommand extends Command {
6881
const outputPath = flags.output
6982
const branch = flags.branch
7083
const includeSystemFields = flags['include-system-fields']
84+
const namespace = flags.namespace
7185

7286
if (token.type !== 'delivery') {
7387
this.warn('Possibly using a management token. You may not be able to connect to your Stack. Please use a delivery token.')
@@ -85,24 +99,33 @@ export default class TypeScriptCodeGeneratorCommand extends Command {
8599
branch: branch || null,
86100
}
87101

88-
const [client, globalFields] = await Promise.all([stackConnect(this.deliveryAPIClient.Stack, config, this.cdaHost), getGlobalFields(config, this.cdaHost)])
89-
90-
let schemas: ContentType[] = []
91-
if (client.types?.length) {
92-
if ((globalFields as any)?.global_fields?.length) {
93-
schemas = schemas.concat((globalFields as any).global_fields as ContentType)
94-
schemas = schemas.map(schema => ({
95-
...schema,
96-
schema_type: 'global_field',
97-
}))
102+
if (flags['api-type'] === 'graphql') {
103+
const result = await generateGraphQLTypeDef(config, outputPath, namespace)
104+
if (result) {
105+
this.log(`Successfully added the GraphQL schema type definitions to '${result.outputPath}'.`)
106+
} else {
107+
this.log('No schema found in the stack! Please use a valid stack.')
98108
}
99-
schemas = schemas.concat(client.types)
100-
const result = await tsgenRunner(outputPath, schemas, prefix, includeDocumentation, includeSystemFields)
101-
this.log(`Wrote ${result.definitions} Content Types to '${result.outputPath}'.`)
102109
} else {
103-
this.log('No Content Types exist in the Stack.')
110+
const [client, globalFields] = await Promise.all([stackConnect(this.deliveryAPIClient.Stack, config, this.cdaHost), getGlobalFields(config, this.cdaHost)])
111+
112+
let schemas: ContentType[] = []
113+
if (client.types?.length) {
114+
if ((globalFields as any)?.global_fields?.length) {
115+
schemas = schemas.concat((globalFields as any).global_fields as ContentType)
116+
schemas = schemas.map(schema => ({
117+
...schema,
118+
schema_type: 'global_field',
119+
}))
120+
}
121+
schemas = schemas.concat(client.types)
122+
const result = await tsgenRunner(outputPath, schemas, prefix, includeDocumentation, includeSystemFields)
123+
this.log(`Wrote ${result.definitions} Content Types to '${result.outputPath}'.`)
124+
} else {
125+
this.log('No Content Types exist in the Stack.')
126+
}
104127
}
105-
} catch (error) {
128+
} catch (error: any) {
106129
this.error(error as any, {exit: 1})
107130
}
108131
}

src/graphQL/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './queries'

src/graphQL/queries.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
const introspectionQuery = `query IntrospectionQuery{
2+
__schema {
3+
queryType {
4+
name
5+
}
6+
mutationType {
7+
name
8+
}
9+
subscriptionType {
10+
name
11+
}
12+
types {
13+
...FullType
14+
}
15+
directives {
16+
name
17+
description
18+
locations
19+
args {
20+
...InputValue
21+
}
22+
}
23+
}
24+
}
25+
fragment FullType on __Type {
26+
kind
27+
name
28+
description
29+
fields(includeDeprecated: true) {
30+
name
31+
description
32+
args {
33+
...InputValue
34+
}
35+
type {
36+
...TypeRef
37+
}
38+
isDeprecated
39+
deprecationReason
40+
}
41+
inputFields {
42+
...InputValue
43+
}
44+
interfaces {
45+
...TypeRef
46+
}
47+
enumValues(includeDeprecated: true) {
48+
name
49+
description
50+
isDeprecated
51+
deprecationReason
52+
}
53+
possibleTypes {
54+
...TypeRef
55+
}
56+
}
57+
fragment InputValue on __InputValue {
58+
name
59+
description
60+
type {
61+
...TypeRef
62+
}
63+
defaultValue
64+
}
65+
fragment TypeRef on __Type {
66+
kind
67+
name
68+
ofType {
69+
kind
70+
name
71+
ofType {
72+
kind
73+
name
74+
ofType {
75+
kind
76+
name
77+
ofType {
78+
kind
79+
name
80+
ofType {
81+
kind
82+
name
83+
ofType {
84+
kind
85+
name
86+
ofType {
87+
kind
88+
name
89+
}
90+
}
91+
}
92+
}
93+
}
94+
}
95+
}
96+
}`
97+
98+
export {introspectionQuery}

src/lib/stack/client.ts

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
import * as fs from 'fs'
12
import * as http from 'https'
23
import * as async from 'async'
3-
import {ContentTypeCollection} from 'contentstack'
4+
import * as path from 'path'
5+
import { ContentTypeCollection } from 'contentstack'
6+
import {HttpClient, cliux, configHandler} from '@contentstack/cli-utilities'
7+
import {schemaToInterfaces, generateNamespace} from '@gql2ts/from-schema'
8+
9+
import {introspectionQuery} from '../../graphQL'
410

511
type RegionUrlMap = {
612
[prop: string]: string;
@@ -11,7 +17,17 @@ const REGION_URL_MAPPING: RegionUrlMap = {
1117
us: 'cdn.contentstack.io',
1218
eu: 'eu-cdn.contentstack.com',
1319
'azure-na': 'azure-na-cdn.contentstack.com',
14-
'azure-eu': 'azure-eu-cdn.contentstack.com'
20+
'azure-eu': 'azure-eu-cdn.contentstack.com',
21+
'gcp-na': 'gcp-na-cdn.contentstack.com'
22+
}
23+
24+
const GRAPHQL_REGION_URL_MAPPING: RegionUrlMap = {
25+
na: 'https://graphql.contentstack.com/stacks',
26+
us: 'https://graphql.contentstack.com/stacks',
27+
eu: 'https://eu-graphql.contentstack.com/stacks',
28+
'azure-na': 'https://azure-na-graphql.contentstack.com/stacks',
29+
'azure-eu': 'https://azure-eu-graphql.contentstack.com/stacks',
30+
'gcp-na': 'https://gcp-na-graphql.contentstack.com/stacks'
1531
}
1632

1733
export type StackConnectionConfig = {
@@ -32,11 +48,12 @@ const queryParams = {
3248
export async function stackConnect(client: any, config: StackConnectionConfig, cdaHost: string) {
3349
try {
3450
const clientParams: {
35-
api_key: string,
36-
delivery_token: string,
37-
environment: string,
38-
region: string,
39-
branch?: string
51+
api_key: string;
52+
delivery_token: string;
53+
environment: string;
54+
region: string;
55+
branch?: string;
56+
early_access?: string[];
4057
} = {
4158
api_key: config.apiKey,
4259
delivery_token: config.token,
@@ -46,6 +63,12 @@ export async function stackConnect(client: any, config: StackConnectionConfig, c
4663
if (config.branch) {
4764
clientParams.branch = config.branch
4865
}
66+
67+
const earlyAccessHeaders = configHandler.get(`earlyAccessHeaders`);
68+
if (earlyAccessHeaders && Object.keys(earlyAccessHeaders).length > 0) {
69+
clientParams.early_access = Object.values(earlyAccessHeaders);
70+
}
71+
4972
// eslint-disable-next-line new-cap
5073
const stack = client(clientParams)
5174
// check and update host if doesn't exists in REGION_URL_MAPPING
@@ -139,3 +162,51 @@ export async function getGlobalFields(config: StackConnectionConfig, cdaHost: st
139162
throw new Error('Could not connect to the stack. Please check your credentials.')
140163
}
141164
}
165+
166+
export async function generateGraphQLTypeDef(config: StackConnectionConfig, outputFile: string, namespace: string) {
167+
const spinner = cliux.loaderV2('Fetching graphql schema...')
168+
try {
169+
if (!GRAPHQL_REGION_URL_MAPPING[config.region]) {
170+
throw new Error(`GraphQL content delivery api unavailable for '${config.region}' region`)
171+
}
172+
173+
const query = {
174+
environment: config.environment,
175+
}
176+
const headers: any = {
177+
access_token: config.token,
178+
}
179+
if (config.branch) {
180+
headers.branch = config.branch
181+
}
182+
183+
// Generate graphql schema with introspection query
184+
const url = `${GRAPHQL_REGION_URL_MAPPING[config.region]}/${config.apiKey}`
185+
const result = await new HttpClient()
186+
.headers(headers)
187+
.queryParams(query)
188+
.post(url, {query: introspectionQuery})
189+
190+
cliux.loaderV2('', spinner)
191+
192+
let schema: string
193+
if (namespace) {
194+
schema = generateNamespace(namespace, result?.data)
195+
} else {
196+
schema = schemaToInterfaces(result?.data)
197+
}
198+
199+
//Create and write type def in file
200+
const outputPath = path.resolve(process.cwd(), outputFile)
201+
const dirName = path.dirname(outputPath)
202+
fs.mkdirSync(dirName, {recursive: true})
203+
fs.writeFileSync(outputPath, schema)
204+
205+
return {
206+
outputPath: outputPath,
207+
}
208+
} catch (error: any) {
209+
cliux.loaderV2('', spinner)
210+
throw error
211+
}
212+
}

src/lib/stack/schema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export type GlobalField = {
2929
reference_to: string;
3030
schema: Schema;
3131
schema_type?: string;
32-
_version?: number
32+
_version?: number;
3333
} & FieldOptions;
3434

3535
export type ReferenceField = {
@@ -62,4 +62,4 @@ export type ContentType = {
6262
reference_to?: string;
6363
data_type?: string;
6464
schema_type?: string;
65-
} & Identifier;
65+
} & Identifier;

test/helpers/init.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const path = require('path')
2+
process.env.TS_NODE_PROJECT = path.resolve('test/tsconfig.json')
3+
process.env.NODE_ENV = 'development'
4+
5+
global.oclif = global.oclif || {}
6+
global.oclif.columns = 80
7+

test/tsconfig.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"extends": "../tsconfig",
3+
"compilerOptions": {
4+
"noEmit": true,
5+
"resolveJsonModule": true,
6+
"esModuleInterop": true,
7+
},
8+
"references": [
9+
{"path": ".."}
10+
],
11+
}

0 commit comments

Comments
 (0)