├── .gitignore ├── README.md ├── babel.config.js ├── babelx.js ├── bin └── cli ├── img └── logo.png ├── jest.config.js ├── package.json ├── schema.graphql ├── src ├── __testfixtures__ │ ├── simple.graphql │ └── simple.ts ├── __tests__ │ └── graphql2ts.spec.ts ├── cli.ts ├── graphql2ts.ts └── index.ts ├── test └── testUtils.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.sublime-project 3 | *.sublime-workspace 4 | .idea/ 5 | 6 | lib-cov 7 | *.seed 8 | *.log 9 | *.csv 10 | *.dat 11 | *.out 12 | *.pid 13 | *.gz 14 | *.map 15 | 16 | pids 17 | logs 18 | results 19 | test-results 20 | 21 | node_modules 22 | npm-debug.log 23 | 24 | dump.rdb 25 | bundle.js 26 | 27 | build 28 | dist 29 | coverage 30 | .nyc_output 31 | .env 32 | 33 | graphql.*.json 34 | junit.xml 35 | 36 | .vs 37 | 38 | test/globalConfig.json 39 | distTs 40 | 41 | # Random things to ignore 42 | ignore/ 43 | package-lock.json 44 | /yarn-offline-cache 45 | .cache 46 | lib 47 | schema.ts 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graphql2ts 2 | 3 | # ![graphql2ts Logo](/img/logo.png) 4 | 5 | Transform .graphql files to graphql-js typescript code 6 | 7 | ## History 8 | We first had [graphql-js](https://github.com/graphql/graphql-js) implementation, but developers weren't happy with it. 9 | So it was born schema first with [graphql-tools](), [merge-graphql-schemas](https://github.com/Urigo/merge-graphql-schemas), [graphql-modules](https://github.com/Urigo/graphql-modules). 10 | 11 | There is also a codemod to transform your .js files to .graphql files: 12 | [jscodeshift-graphql-files](https://github.com/withspectrum/jscodeshift-graphql-files). 13 | 14 | After a lot of time, schema first showed some scaling problems, [Schema First Problems](https://www.prisma.io/blog/the-problems-of-schema-first-graphql-development-x1mn4cb0tyl3), mostly because it does not colocate schema with resolvers, and code is much more powerful to implement abstraction on top of it. 15 | 16 | This codemod exists to helps us move back to code first approach (graphql-js and related tools). 17 | 18 | It will transform a .graphql file and transform in a graphql-js .ts declartion 19 | 20 | 21 | ## How to run 22 | ```bash 23 | npx graphql2ts myschema.graphql mySchemaOutput.ts 24 | ``` 25 | 26 | ## How to test 27 | ``` 28 | yarn jest 29 | ``` 30 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | '@babel/preset-typescript', 12 | ], 13 | plugins: [ 14 | '@babel/plugin-proposal-object-rest-spread', 15 | '@babel/plugin-proposal-class-properties', 16 | '@babel/plugin-proposal-export-default-from', 17 | '@babel/plugin-proposal-export-namespace-from', 18 | '@babel/plugin-transform-async-to-generator', 19 | '@babel/plugin-proposal-async-generator-functions', 20 | '@babel/plugin-proposal-nullish-coalescing-operator', 21 | '@babel/plugin-proposal-optional-chaining', 22 | ] 23 | }; 24 | -------------------------------------------------------------------------------- /babelx.js: -------------------------------------------------------------------------------- 1 | const config = require('./babel.config'); 2 | 3 | require = require('esm')(module, { 4 | mainFields: ['module'], 5 | force: true, 6 | }); 7 | 8 | require('@babel/register')({ 9 | ...config, 10 | extensions: ['.es6', '.es', '.jsx', '.js', '.mjs', '.ts', '.tsx'], 11 | }); 12 | 13 | require(process.argv[2]); 14 | -------------------------------------------------------------------------------- /bin/cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/cli.js').run(); 4 | -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/graphql2ts/31249e9750073f4021b0fa2ae611a4fe7b9cd666/img/logo.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ["src"], 3 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts|tsx)?$', 4 | }; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql2ts", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "bin": { 7 | "graphql2ts": "./bin/cli" 8 | }, 9 | "dependencies": { 10 | "@babel/generator": "^7.6.2", 11 | "@babel/parser": "^7.6.2", 12 | "ast-types": "^0.13.2", 13 | "graphql": "^14.5.8", 14 | "prettier": "^1.18.2", 15 | "yargs": "^14.0.0" 16 | }, 17 | "devDependencies": { 18 | "@babel/cli": "7.6.2", 19 | "@babel/core": "7.6.2", 20 | "@babel/node": "7.6.2", 21 | "@babel/plugin-proposal-async-generator-functions": "7.2.0", 22 | "@babel/plugin-proposal-class-properties": "7.5.5", 23 | "@babel/plugin-proposal-export-default-from": "7.5.2", 24 | "@babel/plugin-proposal-export-namespace-from": "7.5.2", 25 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4", 26 | "@babel/plugin-proposal-object-rest-spread": "7.6.2", 27 | "@babel/plugin-proposal-optional-chaining": "^7.6.0", 28 | "@babel/plugin-transform-async-to-generator": "7.5.0", 29 | "@babel/plugin-transform-flow-strip-types": "7.4.4", 30 | "@babel/preset-env": "7.6.2", 31 | "@babel/preset-flow": "7.0.0", 32 | "@babel/preset-react": "7.0.0", 33 | "@babel/preset-typescript": "7.6.0", 34 | "@babel/register": "7.6.2", 35 | "@types/graphql": "^14.5.0", 36 | "@types/jscodeshift": "^0.6.3", 37 | "babel-jest": "^24.9.0", 38 | "babel-plugin-jest-hoist": "^24.9.0", 39 | "esm": "^3.2.25", 40 | "jest": "^24.9.0", 41 | "jscodeshift": "^0.6.4", 42 | "typescript": "^3.6.3" 43 | }, 44 | "scripts": { 45 | "start": "node babelx.js ./src/index.ts", 46 | "build": "rm -rf lib/* && babel src --extensions \".es6,.js,.es,.jsx,.mjs,.ts,.tsx\" --ignore __test__,__testfixtures__,*.spec.ts --out-dir lib", 47 | "prepublish": "npm run build", 48 | "test": "jest", 49 | "watch": "babel --extensions \".es6,.js,.es,.jsx,.mjs,.ts,.tsx\" -w -d ./lib ./src" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /schema.graphql: -------------------------------------------------------------------------------- 1 | # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. 2 | # based on relay-test-utils-internal (https://github.com/facebook/relay/blob/master/packages/relay-test-utils-internal/testschema.graphql) 3 | 4 | schema { 5 | query: Query 6 | mutation: Mutation 7 | subscription: Subscription 8 | } 9 | 10 | directive @customDirective(level: Int!) on FIELD 11 | 12 | directive @defer( 13 | label: String! 14 | if: Boolean = true 15 | ) on FRAGMENT_SPREAD | INLINE_FRAGMENT 16 | 17 | directive @stream( 18 | label: String! 19 | initial_count: Int! 20 | if: Boolean = true 21 | ) on FIELD 22 | 23 | type Query { 24 | checkinSearchQuery(query: CheckinSearchInput): CheckinSearchResult 25 | defaultSettings: Settings, 26 | route(waypoints: [WayPoint!]!): Route 27 | items(filter: ItemFilterInput): ItemFilterResult 28 | maybeNode: MaybeNode 29 | neverNode: NeverNode 30 | named: Named 31 | me: User 32 | node(id: ID): Node 33 | node_id_required(id: ID!): Node 34 | nodes(ids: [ID!]): [Node] 35 | settings(environment: Environment): Settings 36 | story: Story 37 | task(number: Int): Task 38 | username(name: String!): Actor 39 | usernames(names: [String!]!): [Actor] 40 | viewer: Viewer 41 | _mutation: Mutation 42 | } 43 | 44 | union MaybeNode = Story | FakeNode | NonNode 45 | 46 | type FakeNode { 47 | id: ID! 48 | } 49 | 50 | type NonNode { 51 | id: String 52 | name: String 53 | } 54 | 55 | union NeverNode = FakeNode | NonNode 56 | 57 | type Task { 58 | title: String 59 | } 60 | 61 | input WayPoint { 62 | lat: String 63 | lon: String 64 | } 65 | 66 | type Route { 67 | steps: [RouteStep] 68 | } 69 | 70 | type RouteStep { 71 | lat: String 72 | lon: String 73 | note: String 74 | } 75 | 76 | type Mutation { 77 | actorSubscribe(input: ActorSubscribeInput): ActorSubscribeResponsePayload 78 | actorNameChange(input: ActorNameChangeInput): ActorNameChangePayload 79 | applicationRequestDeleteAll(input: ApplicationRequestDeleteAllInput): ApplicationRequestDeleteAllResponsePayload 80 | commentCreate(input: CommentCreateInput): CommentCreateResponsePayload 81 | commentDelete(input: CommentDeleteInput): CommentDeleteResponsePayload 82 | feedbackLike(input: FeedbackLikeInput): FeedbackLikeResponsePayload 83 | feedbackLikeSubscribe(input: FeedbackLikeInput): FeedbackLikeResponsePayload 84 | nodeSavedState(input: NodeSaveStateInput): NodeSavedStateResponsePayload 85 | unfriend(input: UnfriendInput): UnfriendResponsePayload 86 | viewerNotificationsUpdateAllSeenState(input: UpdateAllSeenStateInput): ViewerNotificationsUpdateAllSeenStateResponsePayload 87 | setLocation(input: LocationInput): setLocationResponsePayload 88 | } 89 | 90 | type Subscription { 91 | feedbackLikeSubscribe(input: FeedbackLikeInput): FeedbackLikeResponsePayload 92 | commentCreateSubscribe(input: CommentCreateSubscriptionInput): CommentCreateResponsePayload 93 | } 94 | 95 | input ActorSubscribeInput { 96 | clientMutationId: String 97 | subscribeeId: ID 98 | } 99 | 100 | input ActorNameChangeInput { 101 | clientMutationId: String 102 | newName: String 103 | } 104 | 105 | input ApplicationRequestDeleteAllInput { 106 | clientMutationId: String 107 | deletedRequestIds: [ID] 108 | } 109 | 110 | input CommentCreateInput { 111 | clientMutationId: String 112 | feedbackId: ID 113 | feedback: CommentfeedbackFeedback 114 | } 115 | 116 | input CommentfeedbackFeedback { 117 | comment: FeedbackcommentComment 118 | } 119 | 120 | input FeedbackcommentComment { 121 | feedback: CommentfeedbackFeedback 122 | } 123 | 124 | input CommentCreateSubscriptionInput { 125 | clientSubscriptionId: String 126 | feedbackId: ID 127 | text: String 128 | } 129 | 130 | input CommentDeleteInput { 131 | clientMutationId: String 132 | commentId: ID 133 | } 134 | 135 | input FeedbackLikeInput { 136 | clientMutationId: String 137 | feedbackId: ID 138 | } 139 | 140 | input NodeSaveStateInput { 141 | clientMutationId: String 142 | nodeId: ID 143 | } 144 | 145 | input UpdateAllSeenStateInput { 146 | clientMutationId: String 147 | storyIds: [ID] 148 | } 149 | 150 | input LocationInput { 151 | longitude: Float 152 | latitude: Float 153 | } 154 | 155 | type setLocationResponsePayload { 156 | clientMutationId: String 157 | viewer: Viewer 158 | } 159 | 160 | type ActorSubscribeResponsePayload { 161 | clientMutationId: String 162 | subscribee: Actor 163 | } 164 | 165 | type ActorNameChangePayload { 166 | clientMutationId: String 167 | actor: Actor 168 | } 169 | 170 | type ApplicationRequestDeleteAllResponsePayload { 171 | clientMutationId: String 172 | deletedRequestIds: [ID] 173 | } 174 | 175 | type CheckinSearchResult { 176 | query: String 177 | } 178 | 179 | input CheckinSearchInput { 180 | query: String 181 | inputs: [CheckinSearchInput] 182 | } 183 | 184 | type PlainCommentBody { 185 | text: Text 186 | } 187 | 188 | type MarkdownCommentBody { 189 | text: Text 190 | } 191 | 192 | union CommentBody = PlainCommentBody | MarkdownCommentBody 193 | 194 | type Comment implements Node { 195 | actor: Actor 196 | actors: [Actor] 197 | actorCount: Int 198 | address: StreetAddress 199 | allPhones: [Phone] 200 | author: User 201 | backgroundImage: Image 202 | birthdate: Date 203 | body: Text 204 | canViewerComment: Boolean 205 | canViewerLike: Boolean 206 | commentBody(supported: [String!]!): CommentBody 207 | comments(after: ID, before: ID, first: Int, last: Int, orderby: String): CommentsConnection 208 | doesViewerLike: Boolean 209 | emailAddresses: [String] 210 | feedback: Feedback 211 | firstName(if: Boolean, unless: Boolean): String 212 | friends(after: ID, before: ID, first: Int, last: Int, orderby: [String], find: String, isViewerFriend: Boolean, if: Boolean, unless: Boolean, traits: [PersonalityTraits]): FriendsConnection 213 | hometown: Page 214 | id: ID! 215 | lastName: String 216 | likers(first: Int): LikersOfContentConnection 217 | likeSentence: Text 218 | message: Text 219 | name: String 220 | profilePicture(size: [Int], preset: PhotoSize): Image 221 | # This is added in RelayTestSchema 222 | # profilePicture2( 223 | # size: [Int], 224 | # preset: PhotoSize, 225 | # cropPosition: CropPosition, 226 | # fileExtension: FileExtension, 227 | # additionalParameters: JSON 228 | # ): Image 229 | segments(first: Int): Segments 230 | screennames: [Screenname] 231 | subscribeStatus: String 232 | subscribers(first: Int): SubscribersConnection 233 | topLevelComments(first: Int): TopLevelCommentsConnection 234 | tracking: String 235 | url(relative: Boolean, site: String): String 236 | websites: [String] 237 | username: String 238 | viewerSavedState: String 239 | } 240 | 241 | type CommentCreateResponsePayload { 242 | clientMutationId: String 243 | comment: Comment 244 | feedback: Feedback 245 | feedbackCommentEdge: CommentsEdge 246 | viewer: Viewer 247 | } 248 | 249 | type CommentDeleteResponsePayload { 250 | clientMutationId: String 251 | deletedCommentId: ID 252 | feedback: Feedback 253 | } 254 | 255 | type CommentsConnection { 256 | count: Int 257 | edges: [CommentsEdge] 258 | pageInfo: PageInfo 259 | } 260 | 261 | type CommentsEdge { 262 | cursor: String 263 | node: Comment 264 | source: Feedback 265 | } 266 | 267 | type ConfigsConnection { 268 | edges: [ConfigsConnectionEdge] 269 | pageInfo: PageInfo 270 | } 271 | 272 | type ConfigsConnectionEdge { 273 | node: Config 274 | } 275 | 276 | type Config { 277 | name: String 278 | isEnabled: Boolean 279 | } 280 | 281 | type Date { 282 | day: Int 283 | month: Int 284 | year: Int 285 | } 286 | 287 | type Feedback implements Node { 288 | actor: Actor 289 | actors: [Actor] 290 | actorCount: Int 291 | address: StreetAddress 292 | allPhones: [Phone] 293 | author: User 294 | backgroundImage: Image 295 | birthdate: Date 296 | body: Text 297 | canViewerComment: Boolean 298 | canViewerLike: Boolean 299 | comments(after: ID, before: ID, first: Int, last: Int, orderby: String): CommentsConnection 300 | doesViewerLike: Boolean 301 | emailAddresses: [String] 302 | feedback: Feedback 303 | firstName(if: Boolean, unless: Boolean): String 304 | friends(after: ID, before: ID, first: Int, last: Int, orderby: [String], find: String, isViewerFriend: Boolean, if: Boolean, unless: Boolean, traits: [PersonalityTraits]): FriendsConnection 305 | hometown: Page 306 | id: ID! 307 | lastName: String 308 | likers(first: Int): LikersOfContentConnection 309 | likeSentence: Text 310 | message: Text 311 | name: String 312 | profilePicture(size: [Int], preset: PhotoSize): Image 313 | segments(first: Int): Segments 314 | screennames: [Screenname] 315 | subscribeStatus: String 316 | subscribers(first: Int): SubscribersConnection 317 | topLevelComments(orderBy: [TopLevelCommentsOrdering], first: Int): TopLevelCommentsConnection 318 | tracking: String 319 | url(relative: Boolean, site: String): String 320 | websites: [String] 321 | username: String 322 | viewedBy: [Actor] 323 | viewerSavedState: String 324 | } 325 | 326 | type FeedbackLikeResponsePayload { 327 | clientMutationId: String 328 | clientSubscriptionId: String 329 | feedback: Feedback 330 | } 331 | 332 | interface FeedUnit { 333 | actor: Actor 334 | actorCount: Int 335 | feedback: Feedback 336 | id: ID! 337 | message: Text 338 | tracking: String 339 | } 340 | 341 | type FriendsConnection { 342 | count: Int 343 | edges: [FriendsEdge] 344 | pageInfo: PageInfo 345 | } 346 | 347 | type FriendsEdge { 348 | cursor: String 349 | node: User 350 | source: User 351 | } 352 | 353 | type Image { 354 | uri: String 355 | width: Int 356 | height: Int 357 | } 358 | 359 | type LikersOfContentConnection { 360 | count: Int 361 | edges: [LikersEdge] 362 | pageInfo: PageInfo 363 | } 364 | 365 | type LikersEdge { 366 | cursor: String 367 | node: Actor 368 | } 369 | 370 | interface Named { 371 | name: String 372 | } 373 | 374 | type SimpleNamed implements Named { 375 | name: String 376 | } 377 | 378 | type NewsFeedConnection { 379 | edges: [NewsFeedEdge] 380 | pageInfo: PageInfo 381 | } 382 | 383 | type NewsFeedEdge { 384 | cursor: String 385 | node: FeedUnit 386 | sortKey: String 387 | showBeeper: Boolean 388 | } 389 | 390 | interface Node { 391 | actor: Actor 392 | actors: [Actor] 393 | actorCount: Int 394 | address: StreetAddress 395 | allPhones: [Phone] 396 | author: User 397 | backgroundImage: Image 398 | birthdate: Date 399 | body: Text 400 | canViewerComment: Boolean 401 | canViewerLike: Boolean 402 | comments(after: ID, before: ID, first: Int, last: Int, orderby: String): CommentsConnection 403 | doesViewerLike: Boolean 404 | emailAddresses: [String] 405 | feedback: Feedback 406 | firstName(if: Boolean, unless: Boolean): String 407 | friends(after: ID, before: ID, first: Int, last: Int, orderby: [String], find: String, isViewerFriend: Boolean, if: Boolean, unless: Boolean, traits: [PersonalityTraits]): FriendsConnection 408 | hometown: Page 409 | id: ID! 410 | lastName: String 411 | likers(first: Int): LikersOfContentConnection 412 | likeSentence: Text 413 | message: Text 414 | name: String 415 | profilePicture(size: [Int], preset: PhotoSize): Image 416 | segments(first: Int): Segments 417 | screennames: [Screenname] 418 | subscribeStatus: String 419 | subscribers(first: Int): SubscribersConnection 420 | topLevelComments(first: Int): TopLevelCommentsConnection 421 | tracking: String 422 | url(relative: Boolean, site: String): String 423 | websites: [String] 424 | username: String 425 | viewerSavedState: String 426 | } 427 | 428 | interface Actor { 429 | address: StreetAddress 430 | allPhones: [Phone] 431 | birthdate: Date 432 | emailAddresses: [String] 433 | firstName(if: Boolean, unless: Boolean): String 434 | friends(after: ID, before: ID, first: Int, last: Int, orderby: [String], find: String, isViewerFriend: Boolean, if: Boolean, unless: Boolean, traits: [PersonalityTraits]): FriendsConnection 435 | hometown: Page 436 | id: ID! 437 | lastName: String 438 | name: String 439 | nameRenderer(supported: [String!]): UserNameRenderer 440 | nameRenderable(supported: [String!]): UserNameRenderable 441 | profilePicture(size: [Int], preset: PhotoSize): Image 442 | screennames: [Screenname] 443 | subscribeStatus: String 444 | subscribers(first: Int): SubscribersConnection 445 | url(relative: Boolean, site: String): String 446 | websites: [String] 447 | username: String 448 | } 449 | 450 | type NodeSavedStateResponsePayload { 451 | node: Node 452 | } 453 | 454 | type Page implements Node & Actor { 455 | actor: Actor 456 | actors: [Actor] 457 | actorCount: Int 458 | address: StreetAddress 459 | allPhones: [Phone] 460 | author: User 461 | backgroundImage: Image 462 | birthdate: Date 463 | body: Text 464 | canViewerComment: Boolean 465 | canViewerLike: Boolean 466 | comments(after: ID, before: ID, first: Int, last: Int, orderby: String): CommentsConnection 467 | doesViewerLike: Boolean 468 | emailAddresses: [String] 469 | feedback: Feedback 470 | firstName(if: Boolean, unless: Boolean): String 471 | friends(after: ID, before: ID, first: Int, last: Int, orderby: [String], find: String, isViewerFriend: Boolean, if: Boolean, unless: Boolean, traits: [PersonalityTraits]): FriendsConnection 472 | hometown: Page 473 | id: ID! 474 | lastName: String 475 | likers(first: Int): LikersOfContentConnection 476 | likeSentence: Text 477 | message: Text 478 | name: String 479 | nameRenderer(supported: [String!]): UserNameRenderer 480 | nameRenderable(supported: [String!]): UserNameRenderable 481 | profilePicture(size: [Int], preset: PhotoSize): Image 482 | segments(first: Int): Segments 483 | screennames: [Screenname] 484 | subscribeStatus: String 485 | subscribers(first: Int): SubscribersConnection 486 | topLevelComments(first: Int): TopLevelCommentsConnection 487 | tracking: String 488 | url(relative: Boolean, site: String): String 489 | websites: [String] 490 | username: String 491 | viewerSavedState: String 492 | nameWithArgs(capitalize: Boolean!): String 493 | } 494 | 495 | type PageInfo { 496 | hasPreviousPage: Boolean 497 | hasNextPage: Boolean 498 | endCursor: String 499 | startCursor: String 500 | } 501 | 502 | type PendingPostsConnection { 503 | count: Int 504 | edges: [PendingPostsConnectionEdge] 505 | pageInfo: PageInfo 506 | } 507 | 508 | type PendingPostsConnectionEdge { 509 | cursor: String 510 | node: PendingPost 511 | } 512 | 513 | type PendingPost { 514 | text: String 515 | } 516 | 517 | type Phone { 518 | isVerified: Boolean 519 | phoneNumber: PhoneNumber 520 | } 521 | 522 | type PhoneNumber { 523 | displayNumber: String 524 | countryCode: String 525 | } 526 | 527 | type Screenname { 528 | name: String 529 | service: String 530 | } 531 | 532 | type Segments { 533 | edges: SegmentsEdge 534 | } 535 | 536 | type SegmentsEdge { 537 | node: String 538 | } 539 | 540 | type NonNodeStory implements FeedUnit { 541 | actor: Actor 542 | actorCount: Int 543 | feedback: Feedback 544 | id: ID! 545 | message: Text 546 | tracking: String 547 | } 548 | 549 | type PhotoStory implements FeedUnit & Node { 550 | # PhotoStory 551 | photo: Image 552 | 553 | # FeedUnit 554 | canViewerDelete: Boolean 555 | seenState: String 556 | 557 | # Node 558 | actor: Actor 559 | actors: [Actor] 560 | actorCount: Int 561 | address: StreetAddress 562 | allPhones: [Phone] 563 | author: User 564 | backgroundImage: Image 565 | birthdate: Date 566 | body: Text 567 | canViewerComment: Boolean 568 | canViewerLike: Boolean 569 | comments(after: ID, before: ID, first: Int, last: Int, orderby: String): CommentsConnection 570 | doesViewerLike: Boolean 571 | emailAddresses: [String] 572 | feedback: Feedback 573 | firstName(if: Boolean, unless: Boolean): String 574 | friends(after: ID, before: ID, first: Int, last: Int, orderby: [String], find: String, isViewerFriend: Boolean, if: Boolean, unless: Boolean, traits: [PersonalityTraits]): FriendsConnection 575 | hometown: Page 576 | id: ID! 577 | lastName: String 578 | likers(first: Int): LikersOfContentConnection 579 | likeSentence: Text 580 | message: Text 581 | name: String 582 | profilePicture(size: [Int], preset: PhotoSize): Image 583 | segments(first: Int): Segments 584 | screennames: [Screenname] 585 | subscribeStatus: String 586 | subscribers(first: Int): SubscribersConnection 587 | topLevelComments(first: Int): TopLevelCommentsConnection 588 | tracking: String 589 | url(relative: Boolean, site: String): String 590 | websites: [String] 591 | username: String 592 | viewerSavedState: String 593 | } 594 | 595 | type Story implements FeedUnit & Node { 596 | attachments: [StoryAttachment] 597 | 598 | # FeedUnit 599 | canViewerDelete: Boolean 600 | seenState: String 601 | 602 | # Node 603 | actor: Actor 604 | actors: [Actor] 605 | actorCount: Int 606 | address: StreetAddress 607 | allPhones: [Phone] 608 | author: User 609 | backgroundImage: Image 610 | birthdate: Date 611 | body: Text 612 | canViewerComment: Boolean 613 | canViewerLike: Boolean 614 | comments(after: ID, before: ID, first: Int, last: Int, orderby: String): CommentsConnection 615 | doesViewerLike: Boolean 616 | emailAddresses: [String] 617 | feedback: Feedback 618 | firstName(if: Boolean, unless: Boolean): String 619 | friends(after: ID, before: ID, first: Int, last: Int, orderby: [String], find: String, isViewerFriend: Boolean, if: Boolean, unless: Boolean, traits: [PersonalityTraits]): FriendsConnection 620 | hometown: Page 621 | id: ID! 622 | lastName: String 623 | likers(first: Int): LikersOfContentConnection 624 | likeSentence: Text 625 | message: Text 626 | name: String 627 | profilePicture(size: [Int], preset: PhotoSize): Image 628 | segments(first: Int): Segments 629 | screennames: [Screenname] 630 | subscribeStatus: String 631 | subscribers(first: Int): SubscribersConnection 632 | topLevelComments(first: Int): TopLevelCommentsConnection 633 | tracking: String 634 | url(relative: Boolean, site: String): String 635 | websites: [String] 636 | username: String 637 | viewerSavedState: String 638 | } 639 | 640 | type StoryAttachment { 641 | cache_id: ID! 642 | target: Story 643 | styleList: [String] 644 | } 645 | 646 | type StreetAddress { 647 | city: String 648 | country: String 649 | postal_code: String 650 | street: String 651 | } 652 | 653 | 654 | type SubscribersConnection { 655 | count: Int 656 | edges: [FriendsEdge] 657 | pageInfo: PageInfo 658 | } 659 | 660 | type SubscribersEdge { 661 | cursor: String 662 | node: User 663 | source: User 664 | } 665 | 666 | type Text { 667 | text: String 668 | ranges: [String] 669 | } 670 | 671 | type TimezoneInfo { 672 | timezone: String 673 | } 674 | 675 | type TopLevelCommentsConnection { 676 | count: Int 677 | edges: [CommentsEdge] 678 | pageInfo: PageInfo 679 | totalCount: Int 680 | } 681 | 682 | input UnfriendInput { 683 | clientMutationId: String 684 | friendId: ID 685 | } 686 | 687 | type UnfriendResponsePayload { 688 | actor: Actor 689 | clientMutationId: String 690 | formerFriend: User 691 | } 692 | 693 | scalar JSDependency 694 | 695 | interface HasJsField { 696 | js(module: String!, id: String): JSDependency 697 | } 698 | 699 | type PlainUserNameRenderer implements HasJsField & UserNameRenderable { 700 | plaintext: String 701 | data: PlainUserNameData 702 | user: User 703 | js(module: String!, id: String): JSDependency 704 | name: String 705 | } 706 | 707 | type PlainUserNameData { 708 | id: ID 709 | text: String 710 | } 711 | 712 | type MarkdownUserNameRenderer implements HasJsField & UserNameRenderable { 713 | markdown: String 714 | data: MarkdownUserNameData 715 | user: User 716 | js(module: String!, id: String): JSDependency 717 | name: String 718 | } 719 | 720 | type MarkdownUserNameData { 721 | id: ID 722 | markup: String 723 | } 724 | 725 | type CustomNameRenderer { 726 | customField: String 727 | } 728 | 729 | union UserNameRenderer = PlainUserNameRenderer | MarkdownUserNameRenderer | CustomNameRenderer 730 | 731 | interface UserNameRenderable { 732 | name: String 733 | } 734 | 735 | type User implements Named & Node & Actor & HasJsField { 736 | actor: Actor 737 | actors: [Actor] 738 | actorCount: Int 739 | address: StreetAddress 740 | allPhones: [Phone] 741 | author: User 742 | backgroundImage: Image 743 | birthdate: Date 744 | body: Text 745 | canViewerComment: Boolean 746 | canViewerLike: Boolean 747 | checkins(environments: [Environment!]!): CheckinSearchResult 748 | comments(after: ID, before: ID, first: Int, last: Int, orderby: String): CommentsConnection 749 | doesViewerLike: Boolean 750 | emailAddresses: [String] 751 | environment: Environment 752 | feedback: Feedback 753 | firstName(if: Boolean, unless: Boolean): String 754 | friends( 755 | after: ID 756 | before: ID 757 | first: Int 758 | last: Int 759 | orderby: [String] 760 | named: String 761 | scale: Float 762 | find: String 763 | isViewerFriend: Boolean 764 | if: Boolean 765 | unless: Boolean 766 | traits: [PersonalityTraits] 767 | ): FriendsConnection 768 | hometown: Page 769 | id: ID! 770 | js(module: String!, id: String): JSDependency 771 | lastName: String 772 | likers(first: Int): LikersOfContentConnection 773 | likeSentence: Text 774 | message: Text 775 | name: String 776 | nameRenderable(supported: [String!]): UserNameRenderable 777 | nameRenderer(supported: [String!]): UserNameRenderer 778 | nameRendererForContext(context: NameRendererContext, supported: [String!]!): UserNameRenderer 779 | nameRenderers(supported: [String!]): [UserNameRenderer] 780 | storySearch(query: StorySearchInput): [Story] 781 | storyCommentSearch(query: StoryCommentSearchInput): [Comment] 782 | profilePicture( 783 | size: [Int], 784 | preset: PhotoSize 785 | ): Image 786 | profile_picture(scale: Float): Image 787 | segments(first: Int): Segments 788 | screennames: [Screenname] 789 | subscribeStatus: String 790 | subscribers(first: Int): SubscribersConnection 791 | topLevelComments(first: Int): TopLevelCommentsConnection 792 | tracking: String 793 | traits: [PersonalityTraits] 794 | url(relative: Boolean, site: String): String 795 | websites: [String] 796 | username: String 797 | viewerSavedState: String 798 | } 799 | 800 | enum NameRendererContext { 801 | HEADER 802 | OTHER 803 | } 804 | 805 | input StorySearchInput { 806 | text: String 807 | limit: Int 808 | offset: Int 809 | type: StoryType 810 | } 811 | 812 | input StoryCommentSearchInput { 813 | text: String 814 | limit: Int 815 | offset: Int 816 | } 817 | 818 | type ItemFilterResult { 819 | date: String 820 | } 821 | 822 | input ItemFilterInput { 823 | date: String 824 | } 825 | 826 | type Viewer { 827 | configs(named: [String]): ConfigsConnection 828 | actor: Actor 829 | allTimezones: [TimezoneInfo] 830 | isFbEmployee: Boolean 831 | newsFeed(after: ID, first: Int, find: ID): NewsFeedConnection 832 | notificationStories(after: ID, first: Int): NewsFeedConnection 833 | pendingPosts(first: Int): PendingPostsConnection 834 | primaryEmail: String 835 | timezoneEstimate: TimezoneInfo 836 | marketplace_explore( 837 | marketplace_browse_context: MarketplaceBrowseContext, 838 | with_price_between: [Float], 839 | ): MarketplaceExploreConnection 840 | marketplace_settings: MarketPlaceSettings 841 | } 842 | 843 | type MarketPlaceSettings { 844 | location: MarketPlaceSellLocation 845 | categories: [String], 846 | } 847 | 848 | type MarketPlaceSellLocation { 849 | longitude: Float, 850 | latitude: Float, 851 | } 852 | 853 | type MarketplaceExploreConnection { 854 | count: Int 855 | } 856 | 857 | type ViewerNotificationsUpdateAllSeenStateResponsePayload { 858 | stories: [Story] 859 | } 860 | 861 | enum Environment { 862 | WEB 863 | MOBILE 864 | } 865 | 866 | enum MarketplaceBrowseContext { 867 | BROWSE_FEED 868 | CATEGORY_FEED 869 | } 870 | 871 | enum PhotoSize { 872 | SMALL 873 | LARGE 874 | } 875 | 876 | enum PersonalityTraits { 877 | CHEERFUL 878 | DERISIVE 879 | HELPFUL 880 | SNARKY 881 | } 882 | 883 | enum StoryType { 884 | DIRECTED 885 | UNDIRECTED 886 | } 887 | 888 | enum TopLevelCommentsOrdering { 889 | chronological 890 | ranked_threaded 891 | recent_activity 892 | toplevel 893 | } 894 | 895 | # This is added in RelayTestSchema 896 | # enum CropPosition { 897 | # TOP 898 | # CENTER 899 | # BOTTOM 900 | # LEFT 901 | # RIGHT 902 | # } 903 | # 904 | # enum FileExtension { 905 | # JPG 906 | # PNG 907 | # } 908 | 909 | type Settings { 910 | cache_id: ID 911 | notificationSounds: Boolean 912 | notifications(environment: Environment): Boolean 913 | } 914 | -------------------------------------------------------------------------------- /src/__testfixtures__/simple.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | checkinSearchQuery(query: CheckinSearchInput): CheckinSearchResult 3 | defaultSettings: Settings, 4 | route(waypoints: [WayPoint!]!): Route 5 | items(filter: ItemFilterInput): ItemFilterResult 6 | maybeNode: MaybeNode 7 | neverNode: NeverNode 8 | named: Named 9 | me: User 10 | node(id: ID): Node 11 | node_id_required(id: ID!): Node 12 | nodes(ids: [ID!]): [Node] 13 | settings(environment: Environment): Settings 14 | story: Story 15 | task(number: Int): Task 16 | username(name: String!): Actor 17 | usernames(names: [String!]!): [Actor] 18 | viewer: Viewer 19 | _mutation: Mutation 20 | } 21 | -------------------------------------------------------------------------------- /src/__testfixtures__/simple.ts: -------------------------------------------------------------------------------- 1 | const Query = new GraphQLObjectType({ 2 | name: "Query", 3 | fields: () => ({ 4 | checkinSearchQuery: { 5 | type: CheckinSearchResult, 6 | args: { 7 | query: { 8 | type: CheckinSearchInput 9 | } 10 | } 11 | }, 12 | defaultSettings: { 13 | type: Settings 14 | }, 15 | route: { 16 | type: Route, 17 | args: { 18 | waypoints: { 19 | type: GraphQLNonNull(GraphQLList(GraphQLNonNull(WayPoint))) 20 | } 21 | } 22 | }, 23 | items: { 24 | type: ItemFilterResult, 25 | args: { 26 | filter: { 27 | type: ItemFilterInput 28 | } 29 | } 30 | }, 31 | maybeNode: { 32 | type: MaybeNode 33 | }, 34 | neverNode: { 35 | type: NeverNode 36 | }, 37 | named: { 38 | type: Named 39 | }, 40 | me: { 41 | type: User 42 | }, 43 | node: { 44 | type: Node, 45 | args: { 46 | id: { 47 | type: ID 48 | } 49 | } 50 | }, 51 | node_id_required: { 52 | type: Node, 53 | args: { 54 | id: { 55 | type: GraphQLNonNull(ID) 56 | } 57 | } 58 | }, 59 | nodes: { 60 | type: GraphQLList(Node), 61 | args: { 62 | ids: { 63 | type: GraphQLList(GraphQLNonNull(ID)) 64 | } 65 | } 66 | }, 67 | settings: { 68 | type: Settings, 69 | args: { 70 | environment: { 71 | type: Environment 72 | } 73 | } 74 | }, 75 | story: { 76 | type: Story 77 | }, 78 | task: { 79 | type: Task, 80 | args: { 81 | number: { 82 | type: Int 83 | } 84 | } 85 | }, 86 | username: { 87 | type: Actor, 88 | args: { 89 | name: { 90 | type: GraphQLNonNull(String) 91 | } 92 | } 93 | }, 94 | usernames: { 95 | type: GraphQLList(Actor), 96 | args: { 97 | names: { 98 | type: GraphQLNonNull(GraphQLList(GraphQLNonNull(String))) 99 | } 100 | } 101 | }, 102 | viewer: { 103 | type: Viewer 104 | }, 105 | _mutation: { 106 | type: Mutation 107 | } 108 | }) 109 | }); 110 | -------------------------------------------------------------------------------- /src/__tests__/graphql2ts.spec.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import fs from "fs"; 3 | 4 | import { graphql2ts } from '../graphql2ts'; 5 | 6 | const defineTest = (dirName: string, testFilePrefix: string, only: boolean = false) => { 7 | const testName = `transforms correctly ${testFilePrefix}`; 8 | 9 | const myIt = only ? it.only : it; 10 | 11 | myIt(testName, () => { 12 | const fixtureDir = path.join(dirName, '..', '__testfixtures__'); 13 | const inputPath = path.join(fixtureDir, testFilePrefix + '.graphql'); 14 | const expectedOutput = fs.readFileSync( 15 | path.join(fixtureDir, testFilePrefix + '.ts'), 16 | 'utf8' 17 | ); 18 | 19 | const source = fs.readFileSync(inputPath, { encoding: 'utf8' }); 20 | 21 | const result = graphql2ts(source); 22 | 23 | expect(result.trim()).toEqual(expectedOutput.trim()); 24 | }); 25 | }; 26 | 27 | describe('graphql2ts', () => { 28 | defineTest(__dirname, 'simple'); 29 | }); 30 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | import yargs from 'yargs'; 2 | 3 | import fs from "fs"; 4 | import { graphql2ts } from './graphql2ts'; 5 | import path from "path"; 6 | import { promisify } from 'util'; 7 | 8 | const cwd = process.cwd(); 9 | 10 | const writeFileAsync = promisify(fs.writeFile); 11 | 12 | type Argv = { 13 | src: string, 14 | keyMaxLength: number, 15 | } 16 | 17 | export const run = async (argv: Argv) => { 18 | argv = yargs(argv || process.argv.slice(2)) 19 | .usage( 20 | 'Transform .graphql file to .graphql-js .ts file' 21 | ) 22 | .default('graphql', 'schema.graphql') 23 | .describe( 24 | 'graphql', 25 | 'The .graphql file' 26 | ) 27 | .default('output', 'schema.ts') 28 | .describe( 29 | 'output', 30 | 'the schema.ts output file', 31 | ) 32 | .argv; 33 | 34 | const filename = path.join(cwd, argv.graphql); 35 | 36 | const source = fs.readFileSync(filename, { encoding: 'utf8' }); 37 | 38 | const result = await graphql2ts(source); 39 | 40 | console.log('result: ', result); 41 | 42 | await writeFileAsync(path.join(cwd, argv.output), result); 43 | }; 44 | -------------------------------------------------------------------------------- /src/graphql2ts.ts: -------------------------------------------------------------------------------- 1 | import { 2 | parse, 3 | DocumentNode, 4 | ObjectTypeDefinitionNode, 5 | InputObjectTypeDefinitionNode, 6 | EnumTypeDefinitionNode, 7 | UnionTypeDefinitionNode, 8 | InterfaceTypeDefinitionNode, 9 | FieldDefinitionNode, 10 | TypeNode, 11 | StringValueNode, InputValueDefinitionNode 12 | } from 'graphql'; 13 | import {parse as babelParser } from '@babel/parser'; 14 | import generate from '@babel/generator'; 15 | import prettier from "prettier"; 16 | 17 | export const typedNode = (type: TypeNode) => { 18 | switch (type.kind) { 19 | case 'NamedType': 20 | return type.name.value; 21 | case 'ListType': 22 | return `GraphQLList(${typedNode(type.type)})`; 23 | case 'NonNullType': 24 | return `GraphQLNonNull(${typedNode(type.type)})`; 25 | default: 26 | return ''; 27 | } 28 | }; 29 | 30 | export const descriptionDefinition = (description?: StringValueNode) => { 31 | return description ? `description: '${description.value}',` : ''; 32 | }; 33 | 34 | export const inputValueDefinitionNode = (input: InputValueDefinitionNode) => { 35 | const { name, description, type } = input; 36 | 37 | const code = ` 38 | ${name.value}: { 39 | type: ${typedNode(type)}, 40 | ${descriptionDefinition(description)} 41 | }, 42 | `; 43 | 44 | return code; 45 | }; 46 | 47 | export const fieldDefinition = (field: FieldDefinitionNode) => { 48 | const { name, description, type } = field; 49 | 50 | const argCode = field.arguments.map(arg => inputValueDefinitionNode(arg)); 51 | 52 | const args = argCode.length > 0 53 | ? ` 54 | args: { 55 | ${argCode.join('\n')} 56 | } 57 | ` : ''; 58 | 59 | const code = ` 60 | ${name.value}: { 61 | type: ${typedNode(type)}, 62 | ${descriptionDefinition(description)} 63 | ${args} 64 | }, 65 | `; 66 | 67 | return code; 68 | }; 69 | 70 | export const objectTypeDefinition = (definition: ObjectTypeDefinitionNode) => { 71 | const { name, description, fields } = definition; 72 | 73 | const fieldsCode = fields.map(field => fieldDefinition(field)).join('\n'); 74 | 75 | const code = ` 76 | const ${name.value} = new GraphQLObjectType({ 77 | name: '${name.value}', 78 | ${descriptionDefinition(description)} 79 | fields: () => ({ 80 | ${fieldsCode} 81 | }) 82 | }); 83 | `; 84 | const ast = babelParser(code); 85 | 86 | const output = generate(ast); 87 | 88 | return output.code; 89 | }; 90 | 91 | export const inputObjectTypeDefinition = (definition: InputObjectTypeDefinitionNode) => { 92 | return ''; 93 | }; 94 | 95 | export const enumTypeDefinition = (definition: EnumTypeDefinitionNode) => { 96 | return ''; 97 | }; 98 | 99 | export const unionTypeDefinition = (definition: UnionTypeDefinitionNode) => { 100 | return ''; 101 | }; 102 | 103 | export const interfaceTypeDefinition = (definition: InterfaceTypeDefinitionNode) => { 104 | return ''; 105 | }; 106 | 107 | const DEFINITION_TO_TS = { 108 | ObjectTypeDefinition: objectTypeDefinition, 109 | InputObjectTypeDefinition: inputObjectTypeDefinition, 110 | EnumTypeDefinition: enumTypeDefinition, 111 | UnionTypeDefinition: unionTypeDefinition, 112 | InterfaceTypeDefinition: interfaceTypeDefinition, 113 | }; 114 | 115 | export const graphql2ts = (source: string) => { 116 | const document: DocumentNode = parse(source); 117 | 118 | const definitionsTs = document.definitions.slice(0,5).map(definition => { 119 | if (definition.kind in DEFINITION_TO_TS) { 120 | return DEFINITION_TO_TS[definition.kind](definition); 121 | } 122 | 123 | console.log('not implemented for: ', definition.kind); 124 | return '' 125 | }); 126 | 127 | return prettier.format(definitionsTs.join('\n')); 128 | }; 129 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import fs from "fs"; 3 | import { promisify } from 'util'; 4 | import { graphql2ts } from './graphql2ts'; 5 | 6 | const writeFileAsync = promisify(fs.writeFile); 7 | 8 | const cwd = process.cwd(); 9 | 10 | (async () => { 11 | // if (process.argv.length !== 4) { 12 | // console.log(`usage: ${process.argv[0]} ${process.argv[1]} schema.graphql`); 13 | // return; 14 | // } 15 | 16 | const filename = path.join(cwd, process.argv[3] || 'schema.graphql'); 17 | 18 | const source = fs.readFileSync(filename, { encoding: 'utf8' }); 19 | 20 | const result = await graphql2ts(source); 21 | 22 | console.log('result: ', result); 23 | 24 | await writeFileAsync(path.join(cwd, 'schema.ts'), result); 25 | })(); 26 | -------------------------------------------------------------------------------- /test/testUtils.ts: -------------------------------------------------------------------------------- 1 | // based on jscodeshift code base 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | 5 | function runInlineTest(module, options, input, expectedOutput) { 6 | // Handle ES6 modules using default export for the transform 7 | const transform = module.default ? module.default : module; 8 | 9 | // Jest resets the module registry after each test, so we need to always get 10 | // a fresh copy of jscodeshift on every test run. 11 | let jscodeshift = require('jscodeshift'); 12 | if (module.parser) { 13 | jscodeshift = jscodeshift.withParser(module.parser); 14 | } 15 | 16 | const output = transform( 17 | input, 18 | { 19 | jscodeshift, 20 | stats: () => {}, 21 | }, 22 | options || {}, 23 | ); 24 | expect((output || '').trim()).toEqual(expectedOutput.trim()); 25 | } 26 | exports.runInlineTest = runInlineTest; 27 | 28 | /** 29 | * Utility function to run a jscodeshift script within a unit test. This makes 30 | * several assumptions about the environment: 31 | * 32 | * - `dirName` contains the name of the directory the test is located in. This 33 | * should normally be passed via __dirname. 34 | * - The test should be located in a subdirectory next to the transform itself. 35 | * Commonly tests are located in a directory called __tests__. 36 | * - `transformName` contains the filename of the transform being tested, 37 | * excluding the .js extension. 38 | * - `testFilePrefix` optionally contains the name of the file with the test 39 | * data. If not specified, it defaults to the same value as `transformName`. 40 | * This will be suffixed with ".input.js" for the input file and ".output.js" 41 | * for the expected output. For example, if set to "foo", we will read the 42 | * "foo.input.js" file, pass this to the transform, and expect its output to 43 | * be equal to the contents of "foo.output.js". 44 | * - Test data should be located in a directory called __testfixtures__ 45 | * alongside the transform and __tests__ directory. 46 | */ 47 | function runTest(dirName, transformName, options, testFilePrefix) { 48 | if (!testFilePrefix) { 49 | testFilePrefix = transformName; 50 | } 51 | 52 | const fixtureDir = path.join(dirName, '..', '__testfixtures__'); 53 | const inputPath = path.join(fixtureDir, testFilePrefix + '.graphql'); 54 | const source = fs.readFileSync(inputPath, 'utf8'); 55 | const expectedOutput = fs.readFileSync(path.join(fixtureDir, testFilePrefix + '.ts'), 'utf8'); 56 | // Assumes transform is one level up from __tests__ directory 57 | const module = require(path.join(dirName, '..', transformName)); 58 | runInlineTest( 59 | module, 60 | options, 61 | { 62 | path: inputPath, 63 | source, 64 | }, 65 | expectedOutput, 66 | ); 67 | } 68 | exports.runTest = runTest; 69 | 70 | /** 71 | * Handles some boilerplate around defining a simple jest/Jasmine test for a 72 | * jscodeshift transform. 73 | */ 74 | function defineTest(dirName, transformName, options, testFilePrefix, only: boolean = false) { 75 | const testName = testFilePrefix ? `transforms correctly using "${testFilePrefix}" data` : 'transforms correctly'; 76 | describe(transformName, () => { 77 | const myIt = only ? it.only : it; 78 | 79 | myIt(testName, () => { 80 | runTest(dirName, transformName, options, testFilePrefix); 81 | }); 82 | }); 83 | } 84 | exports.defineTest = defineTest; 85 | 86 | function defineInlineTest(module, options, input, expectedOutput, testName) { 87 | it(testName || 'transforms correctly', () => { 88 | runInlineTest( 89 | module, 90 | options, 91 | { 92 | source: input, 93 | }, 94 | expectedOutput, 95 | ); 96 | }); 97 | } 98 | exports.defineInlineTest = defineInlineTest; 99 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | "moduleResolution": "node", 7 | "lib": [ /* Specify library files to be included in the compilation. */ 8 | "esnext", 9 | "dom", 10 | "dom.iterable" 11 | ], 12 | // "allowJs": true, /* Allow javascript files to be compiled. */ 13 | // "checkJs": true, /* Report errors in .js files. */ 14 | "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 15 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 16 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 17 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 18 | // "outFile": "./", /* Concatenate and emit output to single file. */ 19 | "outDir": "./distTs", /* Redirect output structure to the directory. */ 20 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 21 | // "composite": true, /* Enable project compilation */ 22 | // "removeComments": true, /* Do not emit comments to output. */ 23 | "noEmit": true, /* Do not emit outputs. */ 24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 27 | 28 | /* Strict Type-Checking Options */ 29 | "strict": true, /* Enable all strict type-checking options. */ 30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 31 | // "strictNullChecks": true, /* Enable strict null checks. */ 32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "resolveJsonModule": true, 52 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | "skipLibCheck": true 65 | } 66 | } 67 | --------------------------------------------------------------------------------