Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
376 changes: 258 additions & 118 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "contentstack-cli-tsgen",
"description": "Generate TypeScript typings from a Stack.",
"version": "2.2.2",
"version": "2.3.0",
"author": "Michael Davis",
"bugs": "https://github.com/Contentstack-Solutions/contentstack-cli-tsgen/issues",
"dependencies": {
"@contentstack/cli-command": "^1.2.13",
"@contentstack/cli-utilities": "^1.5.2",
"@gql2ts/from-schema": "^2.0.0-4",
"lodash": "^4.17.20",
"prettier": "^2.0.5",
"tslib": "^1.13.0"
Expand Down
55 changes: 39 additions & 16 deletions src/commands/tsgen.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Command} from '@contentstack/cli-command'
import {FlagInput, flags} from '@contentstack/cli-utilities'
import {getGlobalFields, stackConnect, StackConnectionConfig} from '../lib/stack/client'
import {getGlobalFields, stackConnect, StackConnectionConfig, generateGraphQLTypeDef} from '../lib/stack/client'
import {ContentType} from '../lib/stack/schema'
import tsgenRunner from '../lib/tsgen/runner'

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

static flags: FlagInput = {
Expand Down Expand Up @@ -56,6 +58,17 @@ export default class TypeScriptCodeGeneratorCommand extends Command {
description: 'include system fields in generated types',
default: false,
}),

'api-type': flags.string({
default: 'rest',
multiple: false,
options: ['rest', 'graphql'],
description: '[Optional] Please enter api type to generate type definitions',
}),

namespace: flags.string({
description: 'Please provide namespace to organize generated types',
}),
};

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

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

const [client, globalFields] = await Promise.all([stackConnect(this.deliveryAPIClient.Stack, config, this.cdaHost), getGlobalFields(config, this.cdaHost)])

let schemas: ContentType[] = []
if (client.types?.length) {
if ((globalFields as any)?.global_fields?.length) {
schemas = schemas.concat((globalFields as any).global_fields as ContentType)
schemas = schemas.map(schema => ({
...schema,
schema_type: 'global_field',
}))
if (flags['api-type'] === 'graphql') {
const result = await generateGraphQLTypeDef(config, outputPath, namespace)
if (result) {
this.log(`Wrote graphql schema type definitions to '${result.outputPath}'.`)
} else {
this.log('No schema exist in the Stack.')
}
schemas = schemas.concat(client.types)
const result = await tsgenRunner(outputPath, schemas, prefix, includeDocumentation, includeSystemFields)
this.log(`Wrote ${result.definitions} Content Types to '${result.outputPath}'.`)
} else {
this.log('No Content Types exist in the Stack.')
const [client, globalFields] = await Promise.all([stackConnect(this.deliveryAPIClient.Stack, config, this.cdaHost), getGlobalFields(config, this.cdaHost)])

let schemas: ContentType[] = []
if (client.types?.length) {
if ((globalFields as any)?.global_fields?.length) {
schemas = schemas.concat((globalFields as any).global_fields as ContentType)
schemas = schemas.map(schema => ({
...schema,
schema_type: 'global_field',
}))
}
schemas = schemas.concat(client.types)
const result = await tsgenRunner(outputPath, schemas, prefix, includeDocumentation, includeSystemFields)
this.log(`Wrote ${result.definitions} Content Types to '${result.outputPath}'.`)
} else {
this.log('No Content Types exist in the Stack.')
}
}
} catch (error) {
} catch (error: any) {
this.error(error as any, {exit: 1})
}
}
Expand Down
1 change: 1 addition & 0 deletions src/graphQL/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './queries'
98 changes: 98 additions & 0 deletions src/graphQL/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const introspectionQuery = `query IntrospectionQuery{
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type {
...TypeRef
}
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}`

export {introspectionQuery}
69 changes: 63 additions & 6 deletions src/lib/stack/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import * as fs from 'fs'
import * as http from 'https'
import * as async from 'async'
import {ContentTypeCollection} from 'contentstack'
import {HttpClient, cliux} from '@contentstack/cli-utilities'
import {schemaToInterfaces, generateNamespace} from '@gql2ts/from-schema'

import {introspectionQuery} from '../../graphQL'

type RegionUrlMap = {
[prop: string]: string;
Expand All @@ -11,7 +16,16 @@ const REGION_URL_MAPPING: RegionUrlMap = {
us: 'cdn.contentstack.io',
eu: 'eu-cdn.contentstack.com',
'azure-na': 'azure-na-cdn.contentstack.com',
'azure-eu': 'azure-eu-cdn.contentstack.com'
'azure-eu': 'azure-eu-cdn.contentstack.com',
}

const GRAPHQL_REGION_URL_MAPPING: RegionUrlMap = {
na: 'https://graphql.contentstack.com/stacks',
us: 'https://graphql.contentstack.com/stacks',
eu: 'https://eu-graphql.contentstack.com/stacks',
'azure-na': 'https://azure-na-graphql.contentstack.com/stacks',
'azure-eu': 'https://azure-eu-graphql.contentstack.com',
stage: 'https://stag-graphql.csnonprod.com/stacks',
}

export type StackConnectionConfig = {
Expand All @@ -32,11 +46,11 @@ const queryParams = {
export async function stackConnect(client: any, config: StackConnectionConfig, cdaHost: string) {
try {
const clientParams: {
api_key: string,
delivery_token: string,
environment: string,
region: string,
branch?: string
api_key: string;
delivery_token: string;
environment: string;
region: string;
branch?: string;
} = {
api_key: config.apiKey,
delivery_token: config.token,
Expand Down Expand Up @@ -139,3 +153,46 @@ export async function getGlobalFields(config: StackConnectionConfig, cdaHost: st
throw new Error('Could not connect to the stack. Please check your credentials.')
}
}

export async function generateGraphQLTypeDef(config: StackConnectionConfig, outPath: string, namespace: string) {
const spinner = cliux.loaderV2('Fetching graphql schema...')
try {
if (!GRAPHQL_REGION_URL_MAPPING[config.region]) {
throw new Error(`GraphQL content delivery api not available for '${config.region}' region`)
}

const query = {
environment: config.environment,
}
const headers: any = {
access_token: config.token,
}
if (config.branch) {
headers.branch = config.branch
}

// Generate graphql schema with introspection query
const url = `${GRAPHQL_REGION_URL_MAPPING[config.region]}/${config.apiKey}`
const result = await new HttpClient()
.headers(headers)
.queryParams(query)
.post(url, {query: introspectionQuery})

cliux.loaderV2('', spinner)

let schema: string;
if(namespace){
schema = generateNamespace(namespace, result?.data)
}else{
schema = schemaToInterfaces(result?.data)
}
fs.writeFileSync(outPath, schema)

return {
outputPath: outPath,
}
} catch (error: any) {
cliux.loaderV2('', spinner)
throw error
}
}
4 changes: 2 additions & 2 deletions src/lib/stack/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export type GlobalField = {
reference_to: string;
schema: Schema;
schema_type?: string;
_version?: number
_version?: number;
} & FieldOptions;

export type ReferenceField = {
Expand Down Expand Up @@ -62,4 +62,4 @@ export type ContentType = {
reference_to?: string;
data_type?: string;
schema_type?: string;
} & Identifier;
} & Identifier;