-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpreset.js
More file actions
136 lines (123 loc) · 4.61 KB
/
Copy pathpreset.js
File metadata and controls
136 lines (123 loc) · 4.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Custom graphql-codegen preset: one output file per source .graphql file.
//
// app/notes.graphql -> app/notes.graphql.ts (typed operation wrapper)
// note/card/note.graphql -> note/card/note.graphql.ts (fragment type + unmask helper)
// plus one shared graphql/schema.graphql.ts (scalars, enums, input types).
//
// Each output is wrapped in `namespace $` - the $mol builder compiles it as an
// ordinary module .ts, no builder changes ("two builders through a file seam").
const path = require('path')
const addPlugin = require('@graphql-codegen/add')
const molPlugin = require('./molplugin.js')
const HEADER = '// AUTO-GENERATED by `npm run codegen` - do not edit.\n'
// $-symbol for a file: note/card/note.graphql + package `demo` -> $demo_note_card_note
// (the repo is one mam PACKAGE; symbols are workspace-rooted, hence the prefix)
function fileSymbol(location, pack) {
const rel = path.relative(process.cwd(), location).replace(/\.graphql$/, '')
const segs = [...(pack ? pack.split('/') : []), ...rel.split(path.sep)]
return '$' + segs.join('_').replace(/[^a-zA-Z0-9_]/g, '_')
}
module.exports = {
buildGeneratesSection: options => {
const config = options.config || {}
const pack = config.molPackage || ''
const runtime = config.molRuntime || '$graphql'
// molSchemaTypes: false skips the shared types file (a secondary output
// reusing the one another output already generates, e.g. test fixtures)
const schemaTypesFile = config.molSchemaTypes === false ? null : config.molSchemaTypes || 'graphql/schema.graphql.ts'
const pluginMap = {
add: addPlugin,
mol: molPlugin,
}
// Registry of all named fragments across every .graphql file.
// Fragments are global: any operation may spread any fragment by its
// (globally unique) name - no tie to the component tree.
const fragments = {}
for (const doc of options.documents) {
for (const def of doc.document.definitions) {
if (def.kind !== 'FragmentDefinition') continue
const name = def.name.value
if (fragments[name]) {
throw new Error(
`Duplicate fragment name "${name}" in ${doc.location} and ${fragments[name].location}. ` +
`Fragment names must be globally unique - name each after its file path ` +
`(a/b/c.graphql -> a_b_c), which cannot collide.`,
)
}
fragments[name] = {
location: doc.location,
symbol: fileSymbol(doc.location, pack),
node: def,
source: (doc.rawSDL || '').trim(),
spreads: collectSpreads(def),
}
}
}
const outputs = []
for (const doc of options.documents) {
const defs = doc.document.definitions.filter(
def => def.kind === 'OperationDefinition' || def.kind === 'FragmentDefinition',
)
if (defs.length !== 1) {
throw new Error(
`${doc.location}: expected exactly one operation or fragment per .graphql file, found ${defs.length}`,
)
}
// fragments defined in OTHER files: typescript-operations must know
// them to type spreads, but must not re-emit their types here
const external = Object.values(fragments)
.filter(frag => frag.location !== doc.location)
.map(frag => ({
name: frag.node.name.value,
onType: frag.node.typeCondition.name.value,
node: frag.node,
isExternal: true,
importFrom: null,
}))
outputs.push({
filename: doc.location + '.ts',
schema: options.schema,
schemaAst: options.schemaAst,
documents: [doc],
pluginMap,
plugins: [
{ add: { placement: 'prepend', content: HEADER + 'namespace $ {\n' } },
{ mol: {} },
{ add: { placement: 'append', content: '\n}\n' } },
],
config: {
...config,
externalFragments: external,
molSymbol: fileSymbol(doc.location, pack),
molRuntime: runtime,
molFragments: fragments,
},
})
}
// shared schema-level types (Scalars, Maybe, Exact, enums, inputs) -
// one file, same `namespace $`, referenced by all generated files
if (schemaTypesFile) outputs.push({
filename: schemaTypesFile,
schema: options.schema,
schemaAst: options.schemaAst,
documents: [],
pluginMap,
plugins: [
{ add: { placement: 'prepend', content: HEADER + 'namespace $ {\n' } },
{ mol: {} },
{ add: { placement: 'append', content: '\n}\n' } },
],
config: { ...config, molMode: 'schema' },
})
return outputs
},
}
function collectSpreads(node, acc = new Set()) {
if (node.kind === 'FragmentSpread') acc.add(node.name.value)
for (const key of ['selectionSet', 'selections']) {
const sub = node[key]
if (Array.isArray(sub)) sub.forEach(child => collectSpreads(child, acc))
else if (sub && typeof sub === 'object') collectSpreads(sub, acc)
}
return acc
}