graphql-jerky 1.0.8 → 1.0.10

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/README.md CHANGED
@@ -31,9 +31,33 @@ By default, the working directory is searched recursively for `*.graphql` files.
31
31
  All of which are combined into a single schema and rendered to `schema_gen.ts`
32
32
  in the same directory.
33
33
 
34
+ Jerky reads the schema files and the resolver files in the order of their
35
+ paths. Thus, the output is the same on all platforms and file systems.
36
+
34
37
  A path to a directory or file can be passed as the first parameter to limit to
35
38
  scope of the search.
36
39
 
40
+ ### Type extensions
41
+
42
+ Type extensions (`extend type`, `extend interface`, `extend union`,
43
+ `extend enum`, `extend input` and `extend scalar`) are merged into the type
44
+ that they extend. The extension and the definition can be in different files,
45
+ in any order.
46
+
47
+ ```gql
48
+ # user.graphql
49
+ type Query {
50
+ me: User
51
+ }
52
+
53
+ # post.graphql
54
+ extend type Query {
55
+ posts: [Post!]!
56
+ }
57
+ ```
58
+
59
+ An extension of a type that is not defined causes an error.
60
+
37
61
  ### Options
38
62
 
39
63
  #### --graphql
package/build/lib.cjs CHANGED
@@ -277,6 +277,30 @@ class RequiredPackages {
277
277
  }
278
278
  }
279
279
 
280
+ /**
281
+ * Glob options that make Windows paths work as patterns. On Windows, a
282
+ * backslash is a path separator and not an escape character.
283
+ */
284
+ const globOptions = {
285
+ windowsPathsNoEscape: path.sep === "\\",
286
+ };
287
+ /**
288
+ * Converts native path separators to forward slashes.
289
+ *
290
+ * Glob patterns and module specifiers must use forward slashes, even on
291
+ * Windows.
292
+ */
293
+ function toPosixPath(p) {
294
+ return path.sep === "\\" ? p.replaceAll("\\", "/") : p;
295
+ }
296
+ /**
297
+ * Compares paths by code unit. The result does not change with the locale
298
+ * or with the order in which the file system returns entries.
299
+ */
300
+ function comparePaths(a, b) {
301
+ return a < b ? -1 : a > b ? 1 : 0;
302
+ }
303
+
280
304
  function renderSymbolReference(pkgs, ref, alias) {
281
305
  const ns = ref.module
282
306
  ? jen.jen.id(pkgs.addRequires(ref.module, ref.symbol, alias ?? ref.alias))
@@ -285,10 +309,10 @@ function renderSymbolReference(pkgs, ref, alias) {
285
309
  }
286
310
 
287
311
  function compareEntryKey([a], [b]) {
288
- return a.localeCompare(b);
312
+ return a.localeCompare(b, "en");
289
313
  }
290
314
  function compareName({ name: a }, { name: b }) {
291
- return a.localeCompare(b);
315
+ return a.localeCompare(b, "en");
292
316
  }
293
317
  const PRIMITIVE_TYPES = new Set(["ID", "Int", "Float", "String", "Boolean"]);
294
318
  function isPrimitiveType(type) {
@@ -307,8 +331,9 @@ function renderImports(base, imports, aliases, extMode = "omit") {
307
331
  if (mod.startsWith("file:///")) {
308
332
  mod = node_url.fileURLToPath(mod);
309
333
  }
310
- if (mod.startsWith("/")) {
311
- mod = path.relative(base, mod);
334
+ if (path.isAbsolute(mod)) {
335
+ // NOTE: module specifiers always use forward slashes, even on Windows.
336
+ mod = toPosixPath(path.relative(base, mod));
312
337
  if (!mod.startsWith(".")) {
313
338
  mod = `./${mod}`;
314
339
  }
@@ -324,8 +349,8 @@ function renderImports(base, imports, aliases, extMode = "omit") {
324
349
  }
325
350
  return jen.import
326
351
  .obj(...Array.from(symbols)
327
- .sort((a, b) => a.localeCompare(b) ||
328
- importAlias(a).localeCompare(importAlias(b)))
352
+ .sort((a, b) => a.localeCompare(b, "en") ||
353
+ importAlias(a).localeCompare(importAlias(b), "en"))
329
354
  .map((symbol) => symbol in aliases
330
355
  ? jen.id(aliases[symbol]).as.id(symbol)
331
356
  : jen.id(symbol)))
@@ -340,14 +365,8 @@ function renderAstNode(astNode) {
340
365
  }
341
366
  function isBuiltInDirective(name) {
342
367
  // https://spec.graphql.org/draft/#sec-Type-System.Directives.Built-in-Directives
343
- switch (name) {
344
- case "deprecated":
345
- case "skip":
346
- case "include":
347
- case "specifiedBy":
348
- return true;
349
- }
350
- return false;
368
+ // Use the list from graphql, because new versions add directives (for example, @oneOf).
369
+ return graphql.specifiedDirectives.some((d) => d.name === name);
351
370
  }
352
371
  function truthify(arr) {
353
372
  return arr.filter(Boolean);
@@ -1098,9 +1117,9 @@ class GeneratorContext {
1098
1117
  ];
1099
1118
  }
1100
1119
  renderFile(outfile, renderStatements) {
1101
- const base = outfile.startsWith("/") ? outfile : path.resolve(outfile);
1120
+ const base = path.resolve(outfile);
1102
1121
  const statements = renderStatements.call(this);
1103
- const [localImports, externalImports] = partition(Object.entries(this._pkgs.requires), ([pkg]) => pkg.startsWith("/"));
1122
+ const [localImports, externalImports] = partition(Object.entries(this._pkgs.requires), ([pkg]) => path.isAbsolute(pkg));
1104
1123
  return format(jen
1105
1124
  .statements(renderImports(path.dirname(base), externalImports, this._pkgs.aliases, this._extMode), renderImports(path.dirname(base), localImports, this._pkgs.aliases, this._extMode), ...statements)
1106
1125
  .toString());
@@ -1281,23 +1300,25 @@ async function parseResolverFile(path) {
1281
1300
  async function parseTypeResolvers(typeName, glob$1, ignore) {
1282
1301
  const shouldIgnore = createMatcher(ignore);
1283
1302
  const result = {};
1284
- for await (const entry of glob.globIterate(glob$1, { withFileTypes: true })) {
1285
- const fullpath = entry.fullpath();
1286
- if (!shouldIgnore(fullpath) &&
1287
- entry.isFile() &&
1288
- entry.name.endsWith(".ts")) {
1289
- for (const [name, reference] of Object.entries(await parseResolverFile(fullpath))) {
1290
- let realName = name;
1291
- if (realName === "default" &&
1292
- typeof glob$1 === "string" &&
1293
- glob$1.endsWith("/*.ts")) {
1294
- realName = path.basename(fullpath, path.extname(fullpath));
1295
- }
1296
- if (realName in result) {
1297
- console.warn(`WARNING: Duplicate resolver name: ${typeName ? `${typeName}.` : ""}${name} in ${fullpath} and ${result[name].module}`);
1298
- }
1299
- result[realName] = reference;
1300
- }
1303
+ // NOTE: sort the files so that the result (and the warnings) do not change
1304
+ // with the order in which the file system returns entries.
1305
+ const fullpaths = (await glob.glob(glob$1, { ...globOptions, withFileTypes: true }))
1306
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".ts"))
1307
+ .map((entry) => toPosixPath(entry.fullpath()))
1308
+ .filter((fullpath) => !shouldIgnore(fullpath))
1309
+ .sort(comparePaths);
1310
+ for (const fullpath of fullpaths) {
1311
+ for (const [name, reference] of Object.entries(await parseResolverFile(fullpath))) {
1312
+ let realName = name;
1313
+ if (realName === "default" &&
1314
+ typeof glob$1 === "string" &&
1315
+ toPosixPath(glob$1).endsWith("/*.ts")) {
1316
+ realName = path.basename(fullpath, path.extname(fullpath));
1317
+ }
1318
+ if (realName in result) {
1319
+ console.warn(`WARNING: Duplicate resolver name: ${typeName ? `${typeName}.` : ""}${realName} in ${fullpath} and ${result[realName].module}`);
1320
+ }
1321
+ result[realName] = reference;
1301
1322
  }
1302
1323
  }
1303
1324
  return result;
@@ -1309,8 +1330,9 @@ async function parse$2(dirpath, ignore) {
1309
1330
  }
1310
1331
  const out = {};
1311
1332
  const shouldIgnore = createMatcher(ignore);
1312
- for await (const name of await fs.readdir(dirpath)) {
1313
- const file = path.join(dirpath, name);
1333
+ const names = (await fs.readdir(dirpath)).sort(comparePaths);
1334
+ for (const name of names) {
1335
+ const file = path.posix.join(toPosixPath(dirpath), name);
1314
1336
  const entry = await fs.stat(file);
1315
1337
  if (entry.isDirectory()) {
1316
1338
  out[name] = await parseTypeResolvers(name, `${file}/*.ts`, shouldIgnore);
@@ -1613,11 +1635,14 @@ function setTraceToSchema(err, name, loc) {
1613
1635
  }
1614
1636
  return err;
1615
1637
  }
1638
+ function isMergableDefinitionNode(node) {
1639
+ return graphql.isTypeDefinitionNode(node) || node.kind === graphql.Kind.DIRECTIVE_DEFINITION;
1640
+ }
1616
1641
  function namedNodesDict(nodes = []) {
1617
1642
  return Object.fromEntries(nodes.map((node) => [node.name.value, node]));
1618
1643
  }
1619
1644
  function sortNamedNodes(nodes) {
1620
- return Array.from(nodes).sort(({ name: { value: a } }, { name: { value: b } }) => a.localeCompare(b));
1645
+ return Array.from(nodes).sort(({ name: { value: a } }, { name: { value: b } }) => a.localeCompare(b, "en"));
1621
1646
  }
1622
1647
  function isIntValueNodeEqual(a, b) {
1623
1648
  return a.value === b.value;
@@ -1726,7 +1751,7 @@ function mergeFieldDefinitionNodes(name, a, b) {
1726
1751
  console.warn(`WARNING: there is more than one declaration for ${name}.${f.name.value}${traceLine(f.loc)}${traceLine(prev.loc, "previously ")}\n`);
1727
1752
  fields[f.name.value] = {
1728
1753
  ...prev,
1729
- directives: mergeDirectiveNodes(`${name}.${f.name.value}`, prev.directives, f.directives),
1754
+ directives: mergeDirectiveNodes(prev.directives, f.directives),
1730
1755
  };
1731
1756
  }
1732
1757
  else {
@@ -1751,7 +1776,7 @@ function mergeInputValueDefinitionNodes(name, a, b) {
1751
1776
  console.warn(`WARNING: there is more than one declaration for ${name}.${f.name.value}${traceLine(f.loc)}${traceLine(prev.loc, "previously ")}\n`);
1752
1777
  fields[f.name.value] = {
1753
1778
  ...prev,
1754
- directives: mergeDirectiveNodes(`${name}.${f.name.value}`, prev.directives, f.directives),
1779
+ directives: mergeDirectiveNodes(prev.directives, f.directives),
1755
1780
  };
1756
1781
  }
1757
1782
  else {
@@ -1770,18 +1795,19 @@ function mergeNamedNodes(a, b) {
1770
1795
  }
1771
1796
  return Object.values(nodes);
1772
1797
  }
1773
- function mergeDirectiveNodes(name, a, b) {
1774
- const directives = namedNodesDict(a);
1775
- if (b) {
1776
- for (const d of b) {
1777
- if (d.name.value in directives &&
1778
- !isArgumentNodesEqual(directives[d.name.value].arguments, d.arguments)) {
1779
- throw setTraceToSchema(new graphql.GraphQLError(`Cannot merge duplicate directive with different arguments: ${name} @${d.name.value}`), `${name} @${d.name.value}`, d.loc);
1780
- }
1781
- directives[d.name.value] = d;
1782
- }
1783
- }
1784
- return Object.values(directives);
1798
+ function mergeDirectiveNodes(a, b) {
1799
+ // Keep each occurrence, because a repeatable directive can occur more than
1800
+ // once. Skip only an occurrence that is identical to one that is already
1801
+ // applied. buildASTSchema rejects a non-repeatable directive that occurs
1802
+ // more than once.
1803
+ const directives = Array.from(a ?? []);
1804
+ for (const d of b ?? []) {
1805
+ if (!directives.some((prev) => prev.name.value === d.name.value &&
1806
+ isArgumentNodesEqual(prev.arguments, d.arguments))) {
1807
+ directives.push(d);
1808
+ }
1809
+ }
1810
+ return directives;
1785
1811
  }
1786
1812
  function mergeScalarTypeDefinitionNodes(a, b) {
1787
1813
  if (a.name.value !== b.name.value) {
@@ -1789,8 +1815,10 @@ function mergeScalarTypeDefinitionNodes(a, b) {
1789
1815
  }
1790
1816
  return {
1791
1817
  kind: graphql.Kind.SCALAR_TYPE_DEFINITION,
1818
+ loc: a.loc,
1792
1819
  name: a.name,
1793
- directives: mergeDirectiveNodes(a.name.value, a.directives, b.directives),
1820
+ description: a.description || b.description,
1821
+ directives: mergeDirectiveNodes(a.directives, b.directives),
1794
1822
  };
1795
1823
  }
1796
1824
  function mergeObjectTypeDefinitionNodes(a, b) {
@@ -1800,7 +1828,7 @@ function mergeObjectTypeDefinitionNodes(a, b) {
1800
1828
  name: a.name,
1801
1829
  description: a.description || b.description,
1802
1830
  interfaces: mergeNamedNodes(a.interfaces, b.interfaces),
1803
- directives: mergeDirectiveNodes(a.name.value, a.directives, b.directives),
1831
+ directives: mergeDirectiveNodes(a.directives, b.directives),
1804
1832
  fields: mergeFieldDefinitionNodes(a.name.value, a.fields, b.fields),
1805
1833
  };
1806
1834
  }
@@ -1811,7 +1839,7 @@ function mergeInterfaceTypeDefinitionNodes(a, b) {
1811
1839
  name: a.name,
1812
1840
  description: a.description || b.description,
1813
1841
  interfaces: mergeNamedNodes(a.interfaces, b.interfaces),
1814
- directives: mergeDirectiveNodes(a.name.value, a.directives, b.directives),
1842
+ directives: mergeDirectiveNodes(a.directives, b.directives),
1815
1843
  fields: mergeFieldDefinitionNodes(a.name.value, a.fields, b.fields),
1816
1844
  };
1817
1845
  }
@@ -1821,7 +1849,7 @@ function mergeUnionTypeDefinitionNodes(a, b) {
1821
1849
  loc: a.loc,
1822
1850
  name: a.name,
1823
1851
  description: a.description || b.description,
1824
- directives: mergeDirectiveNodes(a.name.value, a.directives, b.directives),
1852
+ directives: mergeDirectiveNodes(a.directives, b.directives),
1825
1853
  types: mergeNamedNodes(a.types, b.types),
1826
1854
  };
1827
1855
  }
@@ -1830,7 +1858,7 @@ function mergeEnumTypeDefinitionNodes(a, b) {
1830
1858
  if (b) {
1831
1859
  for (const v of b.values || []) {
1832
1860
  if (v.name.value in values) {
1833
- throw setTraceToSchema(new graphql.GraphQLError(`Cannot merge duplicate enum value: ${v.name.value}.${v.name.value}`), `${v.name.value}.${v.name.value}`, v.loc);
1861
+ throw setTraceToSchema(new graphql.GraphQLError(`Cannot merge duplicate enum value: ${a.name.value}.${v.name.value}`), `${a.name.value}.${v.name.value}`, v.loc);
1834
1862
  }
1835
1863
  values[v.name.value] = v;
1836
1864
  }
@@ -1840,7 +1868,7 @@ function mergeEnumTypeDefinitionNodes(a, b) {
1840
1868
  loc: a.loc,
1841
1869
  name: a.name,
1842
1870
  description: a.description || b.description,
1843
- directives: mergeDirectiveNodes(a.name.value, a.directives, b.directives),
1871
+ directives: mergeDirectiveNodes(a.directives, b.directives),
1844
1872
  values: Object.values(values),
1845
1873
  };
1846
1874
  }
@@ -1850,26 +1878,74 @@ function mergeInputObjectTypeDefinitionNodes(a, b) {
1850
1878
  loc: a.loc,
1851
1879
  name: a.name,
1852
1880
  description: a.description || b.description,
1853
- directives: mergeDirectiveNodes(a.name.value, a.directives, b.directives),
1881
+ directives: mergeDirectiveNodes(a.directives, b.directives),
1854
1882
  fields: mergeInputValueDefinitionNodes(a.name.value, a.fields, b.fields),
1855
1883
  };
1856
1884
  }
1885
+ const EXTENSION_DEFINITION_KINDS = {
1886
+ [graphql.Kind.SCALAR_TYPE_EXTENSION]: graphql.Kind.SCALAR_TYPE_DEFINITION,
1887
+ [graphql.Kind.OBJECT_TYPE_EXTENSION]: graphql.Kind.OBJECT_TYPE_DEFINITION,
1888
+ [graphql.Kind.INTERFACE_TYPE_EXTENSION]: graphql.Kind.INTERFACE_TYPE_DEFINITION,
1889
+ [graphql.Kind.UNION_TYPE_EXTENSION]: graphql.Kind.UNION_TYPE_DEFINITION,
1890
+ [graphql.Kind.ENUM_TYPE_EXTENSION]: graphql.Kind.ENUM_TYPE_DEFINITION,
1891
+ [graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION]: graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
1892
+ };
1893
+ function mergeTypeDefinitionNodes(a, b) {
1894
+ switch (b.kind) {
1895
+ case graphql.Kind.SCALAR_TYPE_DEFINITION:
1896
+ return mergeScalarTypeDefinitionNodes(a, b);
1897
+ case graphql.Kind.OBJECT_TYPE_DEFINITION:
1898
+ return mergeObjectTypeDefinitionNodes(a, b);
1899
+ case graphql.Kind.INTERFACE_TYPE_DEFINITION:
1900
+ return mergeInterfaceTypeDefinitionNodes(a, b);
1901
+ case graphql.Kind.UNION_TYPE_DEFINITION:
1902
+ return mergeUnionTypeDefinitionNodes(a, b);
1903
+ case graphql.Kind.ENUM_TYPE_DEFINITION:
1904
+ return mergeEnumTypeDefinitionNodes(a, b);
1905
+ case graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION:
1906
+ return mergeInputObjectTypeDefinitionNodes(a, b);
1907
+ }
1908
+ }
1909
+ function applyTypeExtensionNode(definitions, extension) {
1910
+ const name = extension.name.value;
1911
+ const definition = definitions[name];
1912
+ if (!definition) {
1913
+ throw setTraceToSchema(new graphql.GraphQLError(`Cannot extend type that is not defined: ${name}`), name, extension.loc);
1914
+ }
1915
+ const kind = EXTENSION_DEFINITION_KINDS[extension.kind];
1916
+ if (definition.kind !== kind) {
1917
+ throw setTraceToSchema(new graphql.GraphQLError(`Cannot extend ${definition.kind} with ${extension.kind}: ${name}`), name, extension.loc);
1918
+ }
1919
+ // An extension has the same shape as its definition, without a description.
1920
+ const merged = mergeTypeDefinitionNodes(definition, {
1921
+ ...extension,
1922
+ kind,
1923
+ directives: undefined,
1924
+ });
1925
+ // The directives of an extension are added to the directives of the type.
1926
+ // buildASTSchema rejects a non-repeatable directive that occurs more than
1927
+ // once.
1928
+ definitions[name] = {
1929
+ ...merged,
1930
+ directives: [
1931
+ ...(definition.directives ?? []),
1932
+ ...(extension.directives ?? []),
1933
+ ],
1934
+ };
1935
+ }
1857
1936
  function mergeDocumentNodes(documentNodes) {
1858
1937
  const definitions = {};
1938
+ const extensions = [];
1859
1939
  for (const documentNode of documentNodes) {
1860
1940
  for (const definition of documentNode.definitions) {
1861
- switch (definition.kind) {
1862
- case graphql.Kind.SCHEMA_DEFINITION:
1863
- case graphql.Kind.SCHEMA_EXTENSION:
1864
- case graphql.Kind.OPERATION_DEFINITION:
1865
- case graphql.Kind.FRAGMENT_DEFINITION:
1866
- case graphql.Kind.SCALAR_TYPE_EXTENSION:
1867
- case graphql.Kind.OBJECT_TYPE_EXTENSION:
1868
- case graphql.Kind.INTERFACE_TYPE_EXTENSION:
1869
- case graphql.Kind.UNION_TYPE_EXTENSION:
1870
- case graphql.Kind.ENUM_TYPE_EXTENSION:
1871
- case graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION:
1872
- throw new Error(`unexpected definition kind: ${definition.kind}`);
1941
+ if (graphql.isTypeExtensionNode(definition)) {
1942
+ // Apply extensions after all definitions are known, because an
1943
+ // extension can occur before the definition that it extends.
1944
+ extensions.push(definition);
1945
+ continue;
1946
+ }
1947
+ if (!isMergableDefinitionNode(definition)) {
1948
+ throw new Error(`unexpected definition kind: ${definition.kind}`);
1873
1949
  }
1874
1950
  const prevDefinition = definitions[definition.name.value];
1875
1951
  if (!prevDefinition) {
@@ -1887,30 +1963,12 @@ function mergeDocumentNodes(documentNodes) {
1887
1963
  prevDefinition.description.value !== definition.description.value) {
1888
1964
  console.warn(`WARNING: different descriptions for type ${prevDefinition.name.value}: ${JSON.stringify(prevDefinition.description.value)} and ${JSON.stringify(definition.description.value)}${traceLine(definition.description.loc)}${traceLine(prevDefinition.description.loc, "previously ")}\n`);
1889
1965
  }
1890
- switch (definition.kind) {
1891
- case graphql.Kind.SCALAR_TYPE_DEFINITION:
1892
- definitions[definition.name.value] = mergeScalarTypeDefinitionNodes(prevDefinition, definition);
1893
- break;
1894
- case graphql.Kind.OBJECT_TYPE_DEFINITION:
1895
- definitions[definition.name.value] = mergeObjectTypeDefinitionNodes(prevDefinition, definition);
1896
- break;
1897
- case graphql.Kind.INTERFACE_TYPE_DEFINITION:
1898
- definitions[definition.name.value] =
1899
- mergeInterfaceTypeDefinitionNodes(prevDefinition, definition);
1900
- break;
1901
- case graphql.Kind.UNION_TYPE_DEFINITION:
1902
- definitions[definition.name.value] = mergeUnionTypeDefinitionNodes(prevDefinition, definition);
1903
- break;
1904
- case graphql.Kind.ENUM_TYPE_DEFINITION:
1905
- definitions[definition.name.value] = mergeEnumTypeDefinitionNodes(prevDefinition, definition);
1906
- break;
1907
- case graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION:
1908
- definitions[definition.name.value] =
1909
- mergeInputObjectTypeDefinitionNodes(prevDefinition, definition);
1910
- break;
1911
- }
1966
+ definitions[definition.name.value] = mergeTypeDefinitionNodes(prevDefinition, definition);
1912
1967
  }
1913
1968
  }
1969
+ for (const extension of extensions) {
1970
+ applyTypeExtensionNode(definitions, extension);
1971
+ }
1914
1972
  return {
1915
1973
  kind: graphql.Kind.DOCUMENT,
1916
1974
  loc: documentNodes[0].loc,
@@ -1918,21 +1976,31 @@ function mergeDocumentNodes(documentNodes) {
1918
1976
  };
1919
1977
  }
1920
1978
 
1921
- async function* findSchemaFiles(glob$1) {
1922
- for await (const path$1 of glob.globIterate(glob$1)) {
1979
+ async function collectSchemaFiles(glob$1, files) {
1980
+ for (const path$1 of await glob.glob(glob$1, globOptions)) {
1923
1981
  const entry = await fs.stat(path$1);
1924
1982
  if (entry.isFile()) {
1925
- yield path$1;
1983
+ files.add(toPosixPath(path$1));
1926
1984
  }
1927
1985
  else if (entry.isDirectory()) {
1928
- yield* findSchemaFiles(path.join(path$1, "**/*.graphql"));
1986
+ await collectSchemaFiles(path.posix.join(toPosixPath(path$1), "**/*.graphql"), files);
1929
1987
  }
1930
1988
  }
1931
1989
  }
1990
+ /**
1991
+ * Finds the schema files and sorts them by path. The order of the files sets
1992
+ * the order of the merged definitions and fields, so it must not change with
1993
+ * the file system or the platform.
1994
+ */
1995
+ async function findSchemaFiles(glob) {
1996
+ const files = new Set();
1997
+ await collectSchemaFiles(glob, files);
1998
+ return Array.from(files).sort(comparePaths);
1999
+ }
1932
2000
  async function parse(glob, ignore) {
1933
2001
  const shouldIgnore = createMatcher(ignore);
1934
2002
  const documentNodes = [];
1935
- for await (const entry of findSchemaFiles(glob)) {
2003
+ for (const entry of await findSchemaFiles(glob)) {
1936
2004
  if (shouldIgnore(entry)) {
1937
2005
  continue;
1938
2006
  }
package/build/lib.mjs CHANGED
@@ -1,13 +1,13 @@
1
- import path, { resolve, basename, extname, join } from 'node:path';
1
+ import path, { resolve, basename, extname, posix } from 'node:path';
2
2
  import { pathToFileURL, fileURLToPath } from 'node:url';
3
- import { GraphQLNonNull, GraphQLList, GraphQLScalarType, GraphQLEnumType, GraphQLDirective, Kind, GraphQLObjectType, getNamedType, getNullableType, GraphQLInterfaceType, GraphQLUnionType, GraphQLInputObjectType, DirectiveLocation, GraphQLError, parse as parse$3, Source, buildASTSchema, validateSchema } from 'graphql';
3
+ import { GraphQLNonNull, GraphQLList, GraphQLScalarType, GraphQLEnumType, GraphQLDirective, Kind, GraphQLObjectType, getNamedType, getNullableType, GraphQLInterfaceType, GraphQLUnionType, GraphQLInputObjectType, DirectiveLocation, specifiedDirectives, isTypeExtensionNode, GraphQLError, isTypeDefinitionNode, parse as parse$3, Source, buildASTSchema, validateSchema } from 'graphql';
4
4
  import jen$1, { jen } from 'jennifer-js';
5
5
  import format from 'jennifer-js/format';
6
6
  import partition from 'just-partition';
7
7
  import pascalCase from 'just-pascal-case';
8
8
  import fs from 'node:fs/promises';
9
9
  import ts, { getLineAndCharacterOfPosition } from 'typescript';
10
- import { globIterate } from 'glob';
10
+ import { glob } from 'glob';
11
11
  import pm from 'picomatch';
12
12
  import zip from 'just-zip-it';
13
13
 
@@ -275,6 +275,30 @@ class RequiredPackages {
275
275
  }
276
276
  }
277
277
 
278
+ /**
279
+ * Glob options that make Windows paths work as patterns. On Windows, a
280
+ * backslash is a path separator and not an escape character.
281
+ */
282
+ const globOptions = {
283
+ windowsPathsNoEscape: path.sep === "\\",
284
+ };
285
+ /**
286
+ * Converts native path separators to forward slashes.
287
+ *
288
+ * Glob patterns and module specifiers must use forward slashes, even on
289
+ * Windows.
290
+ */
291
+ function toPosixPath(p) {
292
+ return path.sep === "\\" ? p.replaceAll("\\", "/") : p;
293
+ }
294
+ /**
295
+ * Compares paths by code unit. The result does not change with the locale
296
+ * or with the order in which the file system returns entries.
297
+ */
298
+ function comparePaths(a, b) {
299
+ return a < b ? -1 : a > b ? 1 : 0;
300
+ }
301
+
278
302
  function renderSymbolReference(pkgs, ref, alias) {
279
303
  const ns = ref.module
280
304
  ? jen.id(pkgs.addRequires(ref.module, ref.symbol, alias ?? ref.alias))
@@ -283,10 +307,10 @@ function renderSymbolReference(pkgs, ref, alias) {
283
307
  }
284
308
 
285
309
  function compareEntryKey([a], [b]) {
286
- return a.localeCompare(b);
310
+ return a.localeCompare(b, "en");
287
311
  }
288
312
  function compareName({ name: a }, { name: b }) {
289
- return a.localeCompare(b);
313
+ return a.localeCompare(b, "en");
290
314
  }
291
315
  const PRIMITIVE_TYPES = new Set(["ID", "Int", "Float", "String", "Boolean"]);
292
316
  function isPrimitiveType(type) {
@@ -305,8 +329,9 @@ function renderImports(base, imports, aliases, extMode = "omit") {
305
329
  if (mod.startsWith("file:///")) {
306
330
  mod = fileURLToPath(mod);
307
331
  }
308
- if (mod.startsWith("/")) {
309
- mod = path.relative(base, mod);
332
+ if (path.isAbsolute(mod)) {
333
+ // NOTE: module specifiers always use forward slashes, even on Windows.
334
+ mod = toPosixPath(path.relative(base, mod));
310
335
  if (!mod.startsWith(".")) {
311
336
  mod = `./${mod}`;
312
337
  }
@@ -322,8 +347,8 @@ function renderImports(base, imports, aliases, extMode = "omit") {
322
347
  }
323
348
  return jen$1.import
324
349
  .obj(...Array.from(symbols)
325
- .sort((a, b) => a.localeCompare(b) ||
326
- importAlias(a).localeCompare(importAlias(b)))
350
+ .sort((a, b) => a.localeCompare(b, "en") ||
351
+ importAlias(a).localeCompare(importAlias(b), "en"))
327
352
  .map((symbol) => symbol in aliases
328
353
  ? jen$1.id(aliases[symbol]).as.id(symbol)
329
354
  : jen$1.id(symbol)))
@@ -338,14 +363,8 @@ function renderAstNode(astNode) {
338
363
  }
339
364
  function isBuiltInDirective(name) {
340
365
  // https://spec.graphql.org/draft/#sec-Type-System.Directives.Built-in-Directives
341
- switch (name) {
342
- case "deprecated":
343
- case "skip":
344
- case "include":
345
- case "specifiedBy":
346
- return true;
347
- }
348
- return false;
366
+ // Use the list from graphql, because new versions add directives (for example, @oneOf).
367
+ return specifiedDirectives.some((d) => d.name === name);
349
368
  }
350
369
  function truthify(arr) {
351
370
  return arr.filter(Boolean);
@@ -1096,9 +1115,9 @@ class GeneratorContext {
1096
1115
  ];
1097
1116
  }
1098
1117
  renderFile(outfile, renderStatements) {
1099
- const base = outfile.startsWith("/") ? outfile : path.resolve(outfile);
1118
+ const base = path.resolve(outfile);
1100
1119
  const statements = renderStatements.call(this);
1101
- const [localImports, externalImports] = partition(Object.entries(this._pkgs.requires), ([pkg]) => pkg.startsWith("/"));
1120
+ const [localImports, externalImports] = partition(Object.entries(this._pkgs.requires), ([pkg]) => path.isAbsolute(pkg));
1102
1121
  return format(jen$1
1103
1122
  .statements(renderImports(path.dirname(base), externalImports, this._pkgs.aliases, this._extMode), renderImports(path.dirname(base), localImports, this._pkgs.aliases, this._extMode), ...statements)
1104
1123
  .toString());
@@ -1276,26 +1295,28 @@ async function parseResolverFile(path) {
1276
1295
  return [];
1277
1296
  })));
1278
1297
  }
1279
- async function parseTypeResolvers(typeName, glob, ignore) {
1298
+ async function parseTypeResolvers(typeName, glob$1, ignore) {
1280
1299
  const shouldIgnore = createMatcher(ignore);
1281
1300
  const result = {};
1282
- for await (const entry of globIterate(glob, { withFileTypes: true })) {
1283
- const fullpath = entry.fullpath();
1284
- if (!shouldIgnore(fullpath) &&
1285
- entry.isFile() &&
1286
- entry.name.endsWith(".ts")) {
1287
- for (const [name, reference] of Object.entries(await parseResolverFile(fullpath))) {
1288
- let realName = name;
1289
- if (realName === "default" &&
1290
- typeof glob === "string" &&
1291
- glob.endsWith("/*.ts")) {
1292
- realName = basename(fullpath, extname(fullpath));
1293
- }
1294
- if (realName in result) {
1295
- console.warn(`WARNING: Duplicate resolver name: ${typeName ? `${typeName}.` : ""}${name} in ${fullpath} and ${result[name].module}`);
1296
- }
1297
- result[realName] = reference;
1298
- }
1301
+ // NOTE: sort the files so that the result (and the warnings) do not change
1302
+ // with the order in which the file system returns entries.
1303
+ const fullpaths = (await glob(glob$1, { ...globOptions, withFileTypes: true }))
1304
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".ts"))
1305
+ .map((entry) => toPosixPath(entry.fullpath()))
1306
+ .filter((fullpath) => !shouldIgnore(fullpath))
1307
+ .sort(comparePaths);
1308
+ for (const fullpath of fullpaths) {
1309
+ for (const [name, reference] of Object.entries(await parseResolverFile(fullpath))) {
1310
+ let realName = name;
1311
+ if (realName === "default" &&
1312
+ typeof glob$1 === "string" &&
1313
+ toPosixPath(glob$1).endsWith("/*.ts")) {
1314
+ realName = basename(fullpath, extname(fullpath));
1315
+ }
1316
+ if (realName in result) {
1317
+ console.warn(`WARNING: Duplicate resolver name: ${typeName ? `${typeName}.` : ""}${realName} in ${fullpath} and ${result[realName].module}`);
1318
+ }
1319
+ result[realName] = reference;
1299
1320
  }
1300
1321
  }
1301
1322
  return result;
@@ -1307,8 +1328,9 @@ async function parse$2(dirpath, ignore) {
1307
1328
  }
1308
1329
  const out = {};
1309
1330
  const shouldIgnore = createMatcher(ignore);
1310
- for await (const name of await fs.readdir(dirpath)) {
1311
- const file = join(dirpath, name);
1331
+ const names = (await fs.readdir(dirpath)).sort(comparePaths);
1332
+ for (const name of names) {
1333
+ const file = posix.join(toPosixPath(dirpath), name);
1312
1334
  const entry = await fs.stat(file);
1313
1335
  if (entry.isDirectory()) {
1314
1336
  out[name] = await parseTypeResolvers(name, `${file}/*.ts`, shouldIgnore);
@@ -1611,11 +1633,14 @@ function setTraceToSchema(err, name, loc) {
1611
1633
  }
1612
1634
  return err;
1613
1635
  }
1636
+ function isMergableDefinitionNode(node) {
1637
+ return isTypeDefinitionNode(node) || node.kind === Kind.DIRECTIVE_DEFINITION;
1638
+ }
1614
1639
  function namedNodesDict(nodes = []) {
1615
1640
  return Object.fromEntries(nodes.map((node) => [node.name.value, node]));
1616
1641
  }
1617
1642
  function sortNamedNodes(nodes) {
1618
- return Array.from(nodes).sort(({ name: { value: a } }, { name: { value: b } }) => a.localeCompare(b));
1643
+ return Array.from(nodes).sort(({ name: { value: a } }, { name: { value: b } }) => a.localeCompare(b, "en"));
1619
1644
  }
1620
1645
  function isIntValueNodeEqual(a, b) {
1621
1646
  return a.value === b.value;
@@ -1724,7 +1749,7 @@ function mergeFieldDefinitionNodes(name, a, b) {
1724
1749
  console.warn(`WARNING: there is more than one declaration for ${name}.${f.name.value}${traceLine(f.loc)}${traceLine(prev.loc, "previously ")}\n`);
1725
1750
  fields[f.name.value] = {
1726
1751
  ...prev,
1727
- directives: mergeDirectiveNodes(`${name}.${f.name.value}`, prev.directives, f.directives),
1752
+ directives: mergeDirectiveNodes(prev.directives, f.directives),
1728
1753
  };
1729
1754
  }
1730
1755
  else {
@@ -1749,7 +1774,7 @@ function mergeInputValueDefinitionNodes(name, a, b) {
1749
1774
  console.warn(`WARNING: there is more than one declaration for ${name}.${f.name.value}${traceLine(f.loc)}${traceLine(prev.loc, "previously ")}\n`);
1750
1775
  fields[f.name.value] = {
1751
1776
  ...prev,
1752
- directives: mergeDirectiveNodes(`${name}.${f.name.value}`, prev.directives, f.directives),
1777
+ directives: mergeDirectiveNodes(prev.directives, f.directives),
1753
1778
  };
1754
1779
  }
1755
1780
  else {
@@ -1768,18 +1793,19 @@ function mergeNamedNodes(a, b) {
1768
1793
  }
1769
1794
  return Object.values(nodes);
1770
1795
  }
1771
- function mergeDirectiveNodes(name, a, b) {
1772
- const directives = namedNodesDict(a);
1773
- if (b) {
1774
- for (const d of b) {
1775
- if (d.name.value in directives &&
1776
- !isArgumentNodesEqual(directives[d.name.value].arguments, d.arguments)) {
1777
- throw setTraceToSchema(new GraphQLError(`Cannot merge duplicate directive with different arguments: ${name} @${d.name.value}`), `${name} @${d.name.value}`, d.loc);
1778
- }
1779
- directives[d.name.value] = d;
1780
- }
1781
- }
1782
- return Object.values(directives);
1796
+ function mergeDirectiveNodes(a, b) {
1797
+ // Keep each occurrence, because a repeatable directive can occur more than
1798
+ // once. Skip only an occurrence that is identical to one that is already
1799
+ // applied. buildASTSchema rejects a non-repeatable directive that occurs
1800
+ // more than once.
1801
+ const directives = Array.from(a ?? []);
1802
+ for (const d of b ?? []) {
1803
+ if (!directives.some((prev) => prev.name.value === d.name.value &&
1804
+ isArgumentNodesEqual(prev.arguments, d.arguments))) {
1805
+ directives.push(d);
1806
+ }
1807
+ }
1808
+ return directives;
1783
1809
  }
1784
1810
  function mergeScalarTypeDefinitionNodes(a, b) {
1785
1811
  if (a.name.value !== b.name.value) {
@@ -1787,8 +1813,10 @@ function mergeScalarTypeDefinitionNodes(a, b) {
1787
1813
  }
1788
1814
  return {
1789
1815
  kind: Kind.SCALAR_TYPE_DEFINITION,
1816
+ loc: a.loc,
1790
1817
  name: a.name,
1791
- directives: mergeDirectiveNodes(a.name.value, a.directives, b.directives),
1818
+ description: a.description || b.description,
1819
+ directives: mergeDirectiveNodes(a.directives, b.directives),
1792
1820
  };
1793
1821
  }
1794
1822
  function mergeObjectTypeDefinitionNodes(a, b) {
@@ -1798,7 +1826,7 @@ function mergeObjectTypeDefinitionNodes(a, b) {
1798
1826
  name: a.name,
1799
1827
  description: a.description || b.description,
1800
1828
  interfaces: mergeNamedNodes(a.interfaces, b.interfaces),
1801
- directives: mergeDirectiveNodes(a.name.value, a.directives, b.directives),
1829
+ directives: mergeDirectiveNodes(a.directives, b.directives),
1802
1830
  fields: mergeFieldDefinitionNodes(a.name.value, a.fields, b.fields),
1803
1831
  };
1804
1832
  }
@@ -1809,7 +1837,7 @@ function mergeInterfaceTypeDefinitionNodes(a, b) {
1809
1837
  name: a.name,
1810
1838
  description: a.description || b.description,
1811
1839
  interfaces: mergeNamedNodes(a.interfaces, b.interfaces),
1812
- directives: mergeDirectiveNodes(a.name.value, a.directives, b.directives),
1840
+ directives: mergeDirectiveNodes(a.directives, b.directives),
1813
1841
  fields: mergeFieldDefinitionNodes(a.name.value, a.fields, b.fields),
1814
1842
  };
1815
1843
  }
@@ -1819,7 +1847,7 @@ function mergeUnionTypeDefinitionNodes(a, b) {
1819
1847
  loc: a.loc,
1820
1848
  name: a.name,
1821
1849
  description: a.description || b.description,
1822
- directives: mergeDirectiveNodes(a.name.value, a.directives, b.directives),
1850
+ directives: mergeDirectiveNodes(a.directives, b.directives),
1823
1851
  types: mergeNamedNodes(a.types, b.types),
1824
1852
  };
1825
1853
  }
@@ -1828,7 +1856,7 @@ function mergeEnumTypeDefinitionNodes(a, b) {
1828
1856
  if (b) {
1829
1857
  for (const v of b.values || []) {
1830
1858
  if (v.name.value in values) {
1831
- throw setTraceToSchema(new GraphQLError(`Cannot merge duplicate enum value: ${v.name.value}.${v.name.value}`), `${v.name.value}.${v.name.value}`, v.loc);
1859
+ throw setTraceToSchema(new GraphQLError(`Cannot merge duplicate enum value: ${a.name.value}.${v.name.value}`), `${a.name.value}.${v.name.value}`, v.loc);
1832
1860
  }
1833
1861
  values[v.name.value] = v;
1834
1862
  }
@@ -1838,7 +1866,7 @@ function mergeEnumTypeDefinitionNodes(a, b) {
1838
1866
  loc: a.loc,
1839
1867
  name: a.name,
1840
1868
  description: a.description || b.description,
1841
- directives: mergeDirectiveNodes(a.name.value, a.directives, b.directives),
1869
+ directives: mergeDirectiveNodes(a.directives, b.directives),
1842
1870
  values: Object.values(values),
1843
1871
  };
1844
1872
  }
@@ -1848,26 +1876,74 @@ function mergeInputObjectTypeDefinitionNodes(a, b) {
1848
1876
  loc: a.loc,
1849
1877
  name: a.name,
1850
1878
  description: a.description || b.description,
1851
- directives: mergeDirectiveNodes(a.name.value, a.directives, b.directives),
1879
+ directives: mergeDirectiveNodes(a.directives, b.directives),
1852
1880
  fields: mergeInputValueDefinitionNodes(a.name.value, a.fields, b.fields),
1853
1881
  };
1854
1882
  }
1883
+ const EXTENSION_DEFINITION_KINDS = {
1884
+ [Kind.SCALAR_TYPE_EXTENSION]: Kind.SCALAR_TYPE_DEFINITION,
1885
+ [Kind.OBJECT_TYPE_EXTENSION]: Kind.OBJECT_TYPE_DEFINITION,
1886
+ [Kind.INTERFACE_TYPE_EXTENSION]: Kind.INTERFACE_TYPE_DEFINITION,
1887
+ [Kind.UNION_TYPE_EXTENSION]: Kind.UNION_TYPE_DEFINITION,
1888
+ [Kind.ENUM_TYPE_EXTENSION]: Kind.ENUM_TYPE_DEFINITION,
1889
+ [Kind.INPUT_OBJECT_TYPE_EXTENSION]: Kind.INPUT_OBJECT_TYPE_DEFINITION,
1890
+ };
1891
+ function mergeTypeDefinitionNodes(a, b) {
1892
+ switch (b.kind) {
1893
+ case Kind.SCALAR_TYPE_DEFINITION:
1894
+ return mergeScalarTypeDefinitionNodes(a, b);
1895
+ case Kind.OBJECT_TYPE_DEFINITION:
1896
+ return mergeObjectTypeDefinitionNodes(a, b);
1897
+ case Kind.INTERFACE_TYPE_DEFINITION:
1898
+ return mergeInterfaceTypeDefinitionNodes(a, b);
1899
+ case Kind.UNION_TYPE_DEFINITION:
1900
+ return mergeUnionTypeDefinitionNodes(a, b);
1901
+ case Kind.ENUM_TYPE_DEFINITION:
1902
+ return mergeEnumTypeDefinitionNodes(a, b);
1903
+ case Kind.INPUT_OBJECT_TYPE_DEFINITION:
1904
+ return mergeInputObjectTypeDefinitionNodes(a, b);
1905
+ }
1906
+ }
1907
+ function applyTypeExtensionNode(definitions, extension) {
1908
+ const name = extension.name.value;
1909
+ const definition = definitions[name];
1910
+ if (!definition) {
1911
+ throw setTraceToSchema(new GraphQLError(`Cannot extend type that is not defined: ${name}`), name, extension.loc);
1912
+ }
1913
+ const kind = EXTENSION_DEFINITION_KINDS[extension.kind];
1914
+ if (definition.kind !== kind) {
1915
+ throw setTraceToSchema(new GraphQLError(`Cannot extend ${definition.kind} with ${extension.kind}: ${name}`), name, extension.loc);
1916
+ }
1917
+ // An extension has the same shape as its definition, without a description.
1918
+ const merged = mergeTypeDefinitionNodes(definition, {
1919
+ ...extension,
1920
+ kind,
1921
+ directives: undefined,
1922
+ });
1923
+ // The directives of an extension are added to the directives of the type.
1924
+ // buildASTSchema rejects a non-repeatable directive that occurs more than
1925
+ // once.
1926
+ definitions[name] = {
1927
+ ...merged,
1928
+ directives: [
1929
+ ...(definition.directives ?? []),
1930
+ ...(extension.directives ?? []),
1931
+ ],
1932
+ };
1933
+ }
1855
1934
  function mergeDocumentNodes(documentNodes) {
1856
1935
  const definitions = {};
1936
+ const extensions = [];
1857
1937
  for (const documentNode of documentNodes) {
1858
1938
  for (const definition of documentNode.definitions) {
1859
- switch (definition.kind) {
1860
- case Kind.SCHEMA_DEFINITION:
1861
- case Kind.SCHEMA_EXTENSION:
1862
- case Kind.OPERATION_DEFINITION:
1863
- case Kind.FRAGMENT_DEFINITION:
1864
- case Kind.SCALAR_TYPE_EXTENSION:
1865
- case Kind.OBJECT_TYPE_EXTENSION:
1866
- case Kind.INTERFACE_TYPE_EXTENSION:
1867
- case Kind.UNION_TYPE_EXTENSION:
1868
- case Kind.ENUM_TYPE_EXTENSION:
1869
- case Kind.INPUT_OBJECT_TYPE_EXTENSION:
1870
- throw new Error(`unexpected definition kind: ${definition.kind}`);
1939
+ if (isTypeExtensionNode(definition)) {
1940
+ // Apply extensions after all definitions are known, because an
1941
+ // extension can occur before the definition that it extends.
1942
+ extensions.push(definition);
1943
+ continue;
1944
+ }
1945
+ if (!isMergableDefinitionNode(definition)) {
1946
+ throw new Error(`unexpected definition kind: ${definition.kind}`);
1871
1947
  }
1872
1948
  const prevDefinition = definitions[definition.name.value];
1873
1949
  if (!prevDefinition) {
@@ -1885,30 +1961,12 @@ function mergeDocumentNodes(documentNodes) {
1885
1961
  prevDefinition.description.value !== definition.description.value) {
1886
1962
  console.warn(`WARNING: different descriptions for type ${prevDefinition.name.value}: ${JSON.stringify(prevDefinition.description.value)} and ${JSON.stringify(definition.description.value)}${traceLine(definition.description.loc)}${traceLine(prevDefinition.description.loc, "previously ")}\n`);
1887
1963
  }
1888
- switch (definition.kind) {
1889
- case Kind.SCALAR_TYPE_DEFINITION:
1890
- definitions[definition.name.value] = mergeScalarTypeDefinitionNodes(prevDefinition, definition);
1891
- break;
1892
- case Kind.OBJECT_TYPE_DEFINITION:
1893
- definitions[definition.name.value] = mergeObjectTypeDefinitionNodes(prevDefinition, definition);
1894
- break;
1895
- case Kind.INTERFACE_TYPE_DEFINITION:
1896
- definitions[definition.name.value] =
1897
- mergeInterfaceTypeDefinitionNodes(prevDefinition, definition);
1898
- break;
1899
- case Kind.UNION_TYPE_DEFINITION:
1900
- definitions[definition.name.value] = mergeUnionTypeDefinitionNodes(prevDefinition, definition);
1901
- break;
1902
- case Kind.ENUM_TYPE_DEFINITION:
1903
- definitions[definition.name.value] = mergeEnumTypeDefinitionNodes(prevDefinition, definition);
1904
- break;
1905
- case Kind.INPUT_OBJECT_TYPE_DEFINITION:
1906
- definitions[definition.name.value] =
1907
- mergeInputObjectTypeDefinitionNodes(prevDefinition, definition);
1908
- break;
1909
- }
1964
+ definitions[definition.name.value] = mergeTypeDefinitionNodes(prevDefinition, definition);
1910
1965
  }
1911
1966
  }
1967
+ for (const extension of extensions) {
1968
+ applyTypeExtensionNode(definitions, extension);
1969
+ }
1912
1970
  return {
1913
1971
  kind: Kind.DOCUMENT,
1914
1972
  loc: documentNodes[0].loc,
@@ -1916,21 +1974,31 @@ function mergeDocumentNodes(documentNodes) {
1916
1974
  };
1917
1975
  }
1918
1976
 
1919
- async function* findSchemaFiles(glob) {
1920
- for await (const path of globIterate(glob)) {
1977
+ async function collectSchemaFiles(glob$1, files) {
1978
+ for (const path of await glob(glob$1, globOptions)) {
1921
1979
  const entry = await fs.stat(path);
1922
1980
  if (entry.isFile()) {
1923
- yield path;
1981
+ files.add(toPosixPath(path));
1924
1982
  }
1925
1983
  else if (entry.isDirectory()) {
1926
- yield* findSchemaFiles(join(path, "**/*.graphql"));
1984
+ await collectSchemaFiles(posix.join(toPosixPath(path), "**/*.graphql"), files);
1927
1985
  }
1928
1986
  }
1929
1987
  }
1988
+ /**
1989
+ * Finds the schema files and sorts them by path. The order of the files sets
1990
+ * the order of the merged definitions and fields, so it must not change with
1991
+ * the file system or the platform.
1992
+ */
1993
+ async function findSchemaFiles(glob) {
1994
+ const files = new Set();
1995
+ await collectSchemaFiles(glob, files);
1996
+ return Array.from(files).sort(comparePaths);
1997
+ }
1930
1998
  async function parse(glob, ignore) {
1931
1999
  const shouldIgnore = createMatcher(ignore);
1932
2000
  const documentNodes = [];
1933
- for await (const entry of findSchemaFiles(glob)) {
2001
+ for (const entry of await findSchemaFiles(glob)) {
1934
2002
  if (shouldIgnore(entry)) {
1935
2003
  continue;
1936
2004
  }
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "graphql-jerky",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Jerky is a schema-first static graphql schema lightweight code generator.",
5
5
  "type": "module",
6
- "bin": "./build/bin/jerky.cjs",
6
+ "bin": {
7
+ "graphql-jerky": "build/bin/jerky.cjs"
8
+ },
7
9
  "types": "./build/lib.d.ts",
8
10
  "module": "./build/lib.mjs",
9
11
  "main": "./build/lib.cjs",