@khanacademy/graphql-flow 3.1.0 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @khanacademy/graphql-flow
2
2
 
3
+ ## 3.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 09f72b5: Fix handling of input objects with required attributes that have a default value
8
+
9
+ ## 3.1.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 45f09ef: Fix up some more alias resolution in graphql-flow.
14
+
3
15
  ## 3.1.0
4
16
 
5
17
  ### Minor Changes
package/dist/cli/run.js CHANGED
@@ -9,6 +9,8 @@ var _parse = require("../parser/parse");
9
9
 
10
10
  var _resolve = require("../parser/resolve");
11
11
 
12
+ var _utils = require("../parser/utils");
13
+
12
14
  var _config = require("./config");
13
15
 
14
16
  var _apolloUtilities = require("apollo-utilities");
@@ -69,32 +71,16 @@ const inputFiles = cliFiles.length ? cliFiles : findGraphqlTagReferences(makeAbs
69
71
  /** Step (2) */
70
72
 
71
73
  const files = (0, _parse.processFiles)(inputFiles, config, f => {
72
- if ((0, _fs.existsSync)(f)) {
73
- return (0, _fs.readFileSync)(f, "utf8");
74
- }
75
-
76
- if ((0, _fs.existsSync)(f + ".js")) {
77
- return {
78
- text: (0, _fs.readFileSync)(f + ".js", "utf8"),
79
- resolvedPath: f + ".js"
80
- };
81
- }
82
-
83
- if ((0, _fs.existsSync)(f + ".ts")) {
84
- return {
85
- text: (0, _fs.readFileSync)(f + ".ts", "utf8"),
86
- resolvedPath: f + ".ts"
87
- };
88
- }
74
+ const resolvedPath = (0, _utils.getPathWithExtension)(f, config);
89
75
 
90
- if ((0, _fs.existsSync)(f + ".tsx")) {
91
- return {
92
- text: (0, _fs.readFileSync)(f + ".tsx", "utf8"),
93
- resolvedPath: f + ".tsx"
94
- };
76
+ if (!resolvedPath) {
77
+ throw new Error(`Unable to find ${f}`);
95
78
  }
96
79
 
97
- throw new Error(`Unable to find ${f}`);
80
+ return {
81
+ text: (0, _fs.readFileSync)(resolvedPath, "utf8"),
82
+ resolvedPath
83
+ };
98
84
  });
99
85
  let filesHadErrors = false;
100
86
  Object.keys(files).forEach(key => {
@@ -27,7 +27,7 @@ const inputObjectToFlow = (ctx, name) => {
27
27
  return babelTypes.tsLiteralType(babelTypes.stringLiteral(`Unknown input object ${name}`));
28
28
  }
29
29
 
30
- return (0, _utils.maybeAddDescriptionComment)(inputObject.description, (0, _utils.objectTypeFromProperties)(inputObject.inputFields.map(vbl => (0, _utils.maybeAddDescriptionComment)(vbl.description, maybeOptionalObjectTypeProperty(vbl.name, inputRefToFlow(ctx, vbl.type))))));
30
+ return (0, _utils.maybeAddDescriptionComment)(inputObject.description, (0, _utils.objectTypeFromProperties)(inputObject.inputFields.map(vbl => (0, _utils.maybeAddDescriptionComment)(vbl.description, maybeOptionalObjectTypeProperty(vbl.name, inputRefToFlow(ctx, vbl.type, vbl.defaultValue != null))))));
31
31
  };
32
32
 
33
33
  exports.inputObjectToFlow = inputObjectToFlow;
@@ -44,9 +44,11 @@ const maybeOptionalObjectTypeProperty = (name, type) => {
44
44
 
45
45
  exports.maybeOptionalObjectTypeProperty = maybeOptionalObjectTypeProperty;
46
46
 
47
- const inputRefToFlow = (ctx, inputRef) => {
47
+ const inputRefToFlow = (ctx, inputRef, hasDefaultValue = false) => {
48
48
  if (inputRef.kind === "NON_NULL") {
49
- return _inputRefToFlow(ctx, inputRef.ofType);
49
+ const result = _inputRefToFlow(ctx, inputRef.ofType);
50
+
51
+ return hasDefaultValue ? (0, _utils.nullableType)(result) : result;
50
52
  }
51
53
 
52
54
  const result = _inputRefToFlow(ctx, inputRef);
@@ -52,7 +52,7 @@ const listExternalReferences = (file, config) => {
52
52
  return Object.keys(paths);
53
53
  };
54
54
 
55
- const processFile = (filePath, contents) => {
55
+ const processFile = (filePath, contents, config) => {
56
56
  const dir = _path.default.dirname(filePath);
57
57
 
58
58
  const result = {
@@ -75,7 +75,7 @@ const processFile = (filePath, contents) => {
75
75
  var _toplevel$declaration;
76
76
 
77
77
  if (toplevel.type === "ImportDeclaration") {
78
- const newLocals = getLocals(dir, toplevel, filePath);
78
+ const newLocals = getLocals(dir, toplevel, filePath, config);
79
79
 
80
80
  if (newLocals) {
81
81
  Object.keys(newLocals).forEach(k => {
@@ -297,12 +297,13 @@ const processTemplate = (tpl, result, getTemplate) => {
297
297
  };
298
298
  };
299
299
 
300
- const getLocals = (dir, toplevel, myPath) => {
300
+ const getLocals = (dir, toplevel, myPath, config) => {
301
301
  if (toplevel.importKind === "type") {
302
302
  return null;
303
303
  }
304
304
 
305
- const importPath = toplevel.source.value.startsWith(".") ? _path.default.resolve(_path.default.join(dir, toplevel.source.value)) : toplevel.source.value;
305
+ const fixedPath = (0, _utils.fixPathResolution)(toplevel.source.value, config);
306
+ const importPath = fixedPath.startsWith(".") ? _path.default.resolve(_path.default.join(dir, fixedPath)) : fixedPath;
306
307
  const locals = {};
307
308
  toplevel.specifiers.forEach(spec => {
308
309
  if (spec.type === "ImportDefaultSpecifier") {
@@ -343,7 +344,7 @@ const processFiles = (filePaths, config, getFileSource) => {
343
344
  continue;
344
345
  }
345
346
 
346
- const result = processFile(next, getFileSource(next));
347
+ const result = processFile(next, getFileSource(next), config);
347
348
  files[next] = result;
348
349
  listExternalReferences(result, config).forEach(path => {
349
350
  if (!files[path] && !toProcess.includes(path)) {
@@ -38,6 +38,10 @@ exports.resolveDocuments = resolveDocuments;
38
38
  const resolveImport = (expr, files, errors, seen, config) => {
39
39
  const absPath = (0, _utils.getPathWithExtension)(expr.path, config);
40
40
 
41
+ if (!absPath) {
42
+ return null;
43
+ }
44
+
41
45
  if (seen[absPath]) {
42
46
  errors.push({
43
47
  loc: expr.loc,
@@ -3,23 +3,30 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.getPathWithExtension = void 0;
6
+ exports.getPathWithExtension = exports.fixPathResolution = void 0;
7
7
 
8
8
  var _fs = _interopRequireDefault(require("fs"));
9
9
 
10
10
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
11
 
12
- const getPathWithExtension = (pathWithoutExtension, config) => {
13
- // Process the path so that we can handle aliases.
12
+ const fixPathResolution = (path, config) => {
14
13
  if (config.alias) {
15
14
  for (const {
16
15
  find,
17
16
  replacement
18
17
  } of config.alias) {
19
- pathWithoutExtension = pathWithoutExtension.replace(find, replacement);
18
+ path = path.replace(find, replacement);
20
19
  }
21
20
  }
22
21
 
22
+ return path;
23
+ };
24
+
25
+ exports.fixPathResolution = fixPathResolution;
26
+
27
+ const getPathWithExtension = (pathWithoutExtension, config) => {
28
+ pathWithoutExtension = fixPathResolution(pathWithoutExtension, config);
29
+
23
30
  if (/\.(less|css|png|gif|jpg|jpeg|js|jsx|ts|tsx|mjs)$/.test(pathWithoutExtension)) {
24
31
  return pathWithoutExtension;
25
32
  }
@@ -38,13 +45,9 @@ const getPathWithExtension = (pathWithoutExtension, config) => {
38
45
 
39
46
  if (_fs.default.existsSync(pathWithoutExtension + ".ts")) {
40
47
  return pathWithoutExtension + ".ts";
41
- } // NOTE(john): This is a bit of a hack, but it's necessary for when we
42
- // have a file that doesn't exist. This will happen when we delete all of
43
- // the type files before re-running graphql-flow again. We want to ensure
44
- // that we don't error out in this case.
45
-
48
+ }
46
49
 
47
- return "";
50
+ return null;
48
51
  };
49
52
 
50
53
  exports.getPathWithExtension = getPathWithExtension;
package/package.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "@khanacademy/graphql-flow",
3
- "version": "3.1.0",
3
+ "version": "3.1.2",
4
4
  "bin": {
5
5
  "graphql-flow": "./dist/cli/run.js"
6
6
  },
7
+ "jest": {
8
+ "testPathIgnorePatterns": [
9
+ "dist"
10
+ ]
11
+ },
7
12
  "scripts": {
8
13
  "test": "jest",
9
14
  "publish:ci": "yarn run build && changeset publish",
package/schema.json CHANGED
@@ -91,7 +91,23 @@
91
91
  "generate": {"oneOf": [
92
92
  {"$ref": "#/definitions/GenerateConfig"},
93
93
  {"type": "array", "items": {"$ref": "#/definitions/GenerateConfig"}}
94
- ]}
94
+ ]},
95
+ "alias": {
96
+ "type": "array",
97
+ "items": {
98
+ "type": "object",
99
+ "additionalProperties": false,
100
+ "properties": {
101
+ "find": {
102
+ "type": ["string", "object"]
103
+ },
104
+ "replacement": {
105
+ "type": "string"
106
+ }
107
+ },
108
+ "required": [ "find", "replacement" ]
109
+ }
110
+ }
95
111
  },
96
112
  "required": [ "crawl", "generate" ]
97
- }
113
+ }
@@ -60,6 +60,7 @@ input CharacterInput {
60
60
  friends: [ID!]
61
61
  appearsIn: [Episode!]
62
62
  candies: PositiveNumber!
63
+ friendly: Boolean! = true
63
64
  }
64
65
 
65
66
  type Mutation {
@@ -621,6 +621,7 @@ describe("graphql-flow generation", () => {
621
621
  - JEDI*/
622
622
  "NEW_HOPE" | "EMPIRE" | "JEDI"> | null | undefined;
623
623
  candies: number;
624
+ friendly?: boolean | null | undefined;
624
625
  };
625
626
  },
626
627
  response: {
package/src/cli/run.ts CHANGED
@@ -6,6 +6,7 @@ import type {GraphQLSchema} from "graphql/type/schema";
6
6
  import {generateTypeFiles, processPragmas} from "../generateTypeFiles";
7
7
  import {processFiles} from "../parser/parse";
8
8
  import {resolveDocuments} from "../parser/resolve";
9
+ import {getPathWithExtension} from "../parser/utils";
9
10
  import {findApplicableConfig, getSchemas, loadConfigFile} from "./config";
10
11
 
11
12
  import {addTypenameToDocument} from "apollo-utilities";
@@ -81,22 +82,11 @@ const inputFiles = cliFiles.length
81
82
  /** Step (2) */
82
83
 
83
84
  const files = processFiles(inputFiles, config, (f) => {
84
- if (existsSync(f)) {
85
- return readFileSync(f, "utf8");
85
+ const resolvedPath = getPathWithExtension(f, config);
86
+ if (!resolvedPath) {
87
+ throw new Error(`Unable to find ${f}`);
86
88
  }
87
- if (existsSync(f + ".js")) {
88
- return {text: readFileSync(f + ".js", "utf8"), resolvedPath: f + ".js"};
89
- }
90
- if (existsSync(f + ".ts")) {
91
- return {text: readFileSync(f + ".ts", "utf8"), resolvedPath: f + ".ts"};
92
- }
93
- if (existsSync(f + ".tsx")) {
94
- return {
95
- text: readFileSync(f + ".tsx", "utf8"),
96
- resolvedPath: f + ".tsx",
97
- };
98
- }
99
- throw new Error(`Unable to find ${f}`);
89
+ return {text: readFileSync(resolvedPath, "utf8"), resolvedPath};
100
90
  });
101
91
 
102
92
  let filesHadErrors = false;
@@ -31,7 +31,7 @@ export const inputObjectToFlow = (
31
31
  vbl.description,
32
32
  maybeOptionalObjectTypeProperty(
33
33
  vbl.name,
34
- inputRefToFlow(ctx, vbl.type),
34
+ inputRefToFlow(ctx, vbl.type, vbl.defaultValue != null),
35
35
  ),
36
36
  ),
37
37
  ),
@@ -58,9 +58,11 @@ export const maybeOptionalObjectTypeProperty = (
58
58
  export const inputRefToFlow = (
59
59
  ctx: Context,
60
60
  inputRef: IntrospectionInputTypeRef,
61
+ hasDefaultValue = false,
61
62
  ): babelTypes.TSType => {
62
63
  if (inputRef.kind === "NON_NULL") {
63
- return _inputRefToFlow(ctx, inputRef.ofType);
64
+ const result = _inputRefToFlow(ctx, inputRef.ofType);
65
+ return hasDefaultValue ? nullableType(result) : result;
64
66
  }
65
67
  const result = _inputRefToFlow(ctx, inputRef);
66
68
  return transferLeadingComments(result, nullableType(result));
@@ -50,7 +50,7 @@ describe("getPathWithExtension", () => {
50
50
  expect(result).toBe("/path/to/file.js");
51
51
  });
52
52
 
53
- it("returns an empty string if no file is found", () => {
53
+ it("returns null if no file is found", () => {
54
54
  // Arrange
55
55
  jest.spyOn(fs, "existsSync").mockImplementation((path) => false);
56
56
 
@@ -58,7 +58,7 @@ describe("getPathWithExtension", () => {
58
58
  const result = getPathWithExtension("/path/to/file", config);
59
59
 
60
60
  // Assert
61
- expect(result).toBe("");
61
+ expect(result).toBe(null);
62
62
  });
63
63
 
64
64
  it("maps aliases to their correct value", () => {
@@ -11,7 +11,7 @@ import traverse from "@babel/traverse";
11
11
 
12
12
  import path from "path";
13
13
 
14
- import {getPathWithExtension} from "./utils";
14
+ import {fixPathResolution, getPathWithExtension} from "./utils";
15
15
  import {Config} from "../types";
16
16
 
17
17
  /**
@@ -152,6 +152,7 @@ export const processFile = (
152
152
  text: string;
153
153
  resolvedPath: string;
154
154
  },
155
+ config: Config,
155
156
  ): FileResult => {
156
157
  const dir = path.dirname(filePath);
157
158
  const result: FileResult = {
@@ -177,7 +178,7 @@ export const processFile = (
177
178
 
178
179
  ast.program.body.forEach((toplevel) => {
179
180
  if (toplevel.type === "ImportDeclaration") {
180
- const newLocals = getLocals(dir, toplevel, filePath);
181
+ const newLocals = getLocals(dir, toplevel, filePath, config);
181
182
  if (newLocals) {
182
183
  Object.keys(newLocals).forEach((k) => {
183
184
  const local = newLocals[k];
@@ -386,6 +387,7 @@ const getLocals = (
386
387
  dir: string,
387
388
  toplevel: ImportDeclaration,
388
389
  myPath: string,
390
+ config: Config,
389
391
  ):
390
392
  | {
391
393
  [key: string]: Import;
@@ -395,9 +397,10 @@ const getLocals = (
395
397
  if (toplevel.importKind === "type") {
396
398
  return null;
397
399
  }
398
- const importPath = toplevel.source.value.startsWith(".")
399
- ? path.resolve(path.join(dir, toplevel.source.value))
400
- : toplevel.source.value;
400
+ const fixedPath = fixPathResolution(toplevel.source.value, config);
401
+ const importPath = fixedPath.startsWith(".")
402
+ ? path.resolve(path.join(dir, fixedPath))
403
+ : fixedPath;
401
404
  const locals: Record<string, any> = {};
402
405
  toplevel.specifiers.forEach((spec) => {
403
406
  if (spec.type === "ImportDefaultSpecifier") {
@@ -439,7 +442,7 @@ export const processFiles = (
439
442
  if (!next || files[next]) {
440
443
  continue;
441
444
  }
442
- const result = processFile(next, getFileSource(next));
445
+ const result = processFile(next, getFileSource(next), config);
443
446
  files[next] = result;
444
447
  listExternalReferences(result, config).forEach((path) => {
445
448
  if (!files[path] && !toProcess.includes(path)) {
@@ -51,7 +51,10 @@ const resolveImport = (
51
51
  },
52
52
  config: Config,
53
53
  ): Document | null | undefined => {
54
- const absPath: string = getPathWithExtension(expr.path, config);
54
+ const absPath = getPathWithExtension(expr.path, config);
55
+ if (!absPath) {
56
+ return null;
57
+ }
55
58
  if (seen[absPath]) {
56
59
  errors.push({
57
60
  loc: expr.loc,
@@ -1,19 +1,20 @@
1
1
  import fs from "fs";
2
2
  import {Config} from "../types";
3
3
 
4
- export const getPathWithExtension = (
5
- pathWithoutExtension: string,
6
- config: Config,
7
- ): string => {
8
- // Process the path so that we can handle aliases.
4
+ export const fixPathResolution = (path: string, config: Config) => {
9
5
  if (config.alias) {
10
6
  for (const {find, replacement} of config.alias) {
11
- pathWithoutExtension = pathWithoutExtension.replace(
12
- find,
13
- replacement,
14
- );
7
+ path = path.replace(find, replacement);
15
8
  }
16
9
  }
10
+ return path;
11
+ };
12
+
13
+ export const getPathWithExtension = (
14
+ pathWithoutExtension: string,
15
+ config: Config,
16
+ ) => {
17
+ pathWithoutExtension = fixPathResolution(pathWithoutExtension, config);
17
18
  if (
18
19
  /\.(less|css|png|gif|jpg|jpeg|js|jsx|ts|tsx|mjs)$/.test(
19
20
  pathWithoutExtension,
@@ -33,9 +34,5 @@ export const getPathWithExtension = (
33
34
  if (fs.existsSync(pathWithoutExtension + ".ts")) {
34
35
  return pathWithoutExtension + ".ts";
35
36
  }
36
- // NOTE(john): This is a bit of a hack, but it's necessary for when we
37
- // have a file that doesn't exist. This will happen when we delete all of
38
- // the type files before re-running graphql-flow again. We want to ensure
39
- // that we don't error out in this case.
40
- return "";
37
+ return null;
41
38
  };