@stonecrop/schema 0.11.7 → 0.11.8

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.
@@ -1 +1 @@
1
- {"version":"5.9.3"}
1
+ {"version":"6.0.3"}
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Stonecrop Schema CLI
4
+ *
5
+ * Converts GraphQL introspection results to Stonecrop doctype JSON schemas.
6
+ *
7
+ * Usage:
8
+ * stonecrop-schema generate --endpoint <url> --output <dir>
9
+ * stonecrop-schema generate --introspection <file.json> --output <dir>
10
+ * stonecrop-schema generate --sdl <file.graphql> --output <dir>
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
15
+ import { resolve, join } from 'node:path';
16
+ import { parseArgs } from 'node:util';
17
+ import { getIntrospectionQuery } from 'graphql';
18
+ import { convertGraphQLSchema } from './converter/index';
19
+ import { validateDoctype } from './validation';
20
+ /**
21
+ * Fetch an introspection result from a live GraphQL endpoint.
22
+ *
23
+ * @param endpoint - The GraphQL endpoint URL
24
+ * @param headers - Optional HTTP headers
25
+ * @returns The introspection query result
26
+ */
27
+ async function fetchIntrospection(endpoint, headers) {
28
+ const response = await fetch(endpoint, {
29
+ method: 'POST',
30
+ headers: {
31
+ 'Content-Type': 'application/json',
32
+ ...headers,
33
+ },
34
+ body: JSON.stringify({
35
+ query: getIntrospectionQuery(),
36
+ }),
37
+ });
38
+ if (!response.ok) {
39
+ throw new Error(`Failed to fetch introspection: ${response.status} ${response.statusText}`);
40
+ }
41
+ const json = (await response.json());
42
+ if (json.errors?.length) {
43
+ throw new Error(`GraphQL errors: ${json.errors.map(e => e.message).join(', ')}`);
44
+ }
45
+ if (!json.data) {
46
+ throw new Error('No data in introspection response');
47
+ }
48
+ return json.data;
49
+ }
50
+ async function main() {
51
+ const { values, positionals } = parseArgs({
52
+ allowPositionals: true,
53
+ options: {
54
+ endpoint: { type: 'string', short: 'e' },
55
+ introspection: { type: 'string', short: 'i' },
56
+ sdl: { type: 'string', short: 's' },
57
+ output: { type: 'string', short: 'o' },
58
+ include: { type: 'string' },
59
+ exclude: { type: 'string' },
60
+ overrides: { type: 'string' },
61
+ 'custom-scalars': { type: 'string' },
62
+ 'include-unmapped': { type: 'boolean', default: false },
63
+ help: { type: 'boolean', short: 'h' },
64
+ },
65
+ });
66
+ const command = positionals[0];
67
+ if (values.help || !command) {
68
+ printHelp();
69
+ process.exit(command ? 0 : 1);
70
+ }
71
+ if (command !== 'generate') {
72
+ console.error(`Unknown command: ${command}`);
73
+ console.error('Available commands: generate');
74
+ process.exit(1);
75
+ }
76
+ // Determine source
77
+ const sourceCount = [values.endpoint, values.introspection, values.sdl].filter(Boolean).length;
78
+ if (sourceCount !== 1) {
79
+ console.error('Exactly one of --endpoint, --introspection, or --sdl must be provided');
80
+ process.exit(1);
81
+ }
82
+ if (!values.output) {
83
+ console.error('--output <dir> is required');
84
+ process.exit(1);
85
+ }
86
+ const outputDir = resolve(values.output);
87
+ // Build conversion options
88
+ const options = {
89
+ includeUnmappedMeta: values['include-unmapped'],
90
+ };
91
+ if (values.include) {
92
+ options.include = values.include.split(',').map(s => s.trim());
93
+ }
94
+ if (values.exclude) {
95
+ options.exclude = values.exclude.split(',').map(s => s.trim());
96
+ }
97
+ if (values.overrides) {
98
+ const overridesPath = resolve(values.overrides);
99
+ const overridesContent = readFileSync(overridesPath, 'utf-8');
100
+ options.typeOverrides = JSON.parse(overridesContent);
101
+ }
102
+ if (values['custom-scalars']) {
103
+ const scalarsPath = resolve(values['custom-scalars']);
104
+ const scalarsContent = readFileSync(scalarsPath, 'utf-8');
105
+ options.customScalars = JSON.parse(scalarsContent);
106
+ }
107
+ // Resolve source
108
+ let source;
109
+ if (values.endpoint) {
110
+ console.log(`Fetching introspection from ${values.endpoint}...`);
111
+ source = await fetchIntrospection(values.endpoint);
112
+ }
113
+ else if (values.introspection) {
114
+ const filePath = resolve(values.introspection);
115
+ const content = readFileSync(filePath, 'utf-8');
116
+ const parsed = JSON.parse(content);
117
+ // Handle both { data: { __schema: ... } } and { __schema: ... } formats
118
+ source = parsed.data ?? parsed;
119
+ }
120
+ else {
121
+ const filePath = resolve(values.sdl);
122
+ source = readFileSync(filePath, 'utf-8');
123
+ }
124
+ // Convert
125
+ const doctypes = convertGraphQLSchema(source, options);
126
+ if (doctypes.length === 0) {
127
+ console.warn('No entity types found in the schema. Check your include/exclude filters.');
128
+ process.exit(0);
129
+ }
130
+ // Write output
131
+ if (!existsSync(outputDir)) {
132
+ mkdirSync(outputDir, { recursive: true });
133
+ }
134
+ let warnings = 0;
135
+ let errors = 0;
136
+ for (const doctype of doctypes) {
137
+ const fileName = `${doctype.slug}.json`;
138
+ const filePath = join(outputDir, fileName);
139
+ const json = JSON.stringify(doctype, null, '\t');
140
+ writeFileSync(filePath, json + '\n', 'utf-8');
141
+ // Validate the output
142
+ const validation = validateDoctype(doctype);
143
+ if (!validation.success) {
144
+ errors++;
145
+ console.error(` ERROR: ${fileName} failed validation:`);
146
+ for (const err of validation.errors) {
147
+ console.error(` ${err.path.join('.')}: ${err.message}`);
148
+ }
149
+ }
150
+ else {
151
+ // Check for unmapped fields
152
+ const unmappedFields = doctype.fields.filter((f) => f._unmapped);
153
+ if (unmappedFields.length > 0) {
154
+ warnings++;
155
+ console.warn(` WARN: ${fileName} has ${unmappedFields.length} unmapped field(s): ${unmappedFields
156
+ .map((f) => f.fieldname)
157
+ .join(', ')}`);
158
+ }
159
+ }
160
+ }
161
+ console.log(`\nGenerated ${doctypes.length} doctype(s) in ${outputDir}` +
162
+ (warnings ? ` (${warnings} with warnings)` : '') +
163
+ (errors ? ` (${errors} with errors)` : ''));
164
+ if (errors > 0) {
165
+ process.exit(1);
166
+ }
167
+ }
168
+ function printHelp() {
169
+ console.log(`
170
+ stonecrop-schema - Convert GraphQL schemas to Stonecrop doctypes
171
+
172
+ USAGE:
173
+ stonecrop-schema generate [options]
174
+
175
+ SOURCE (exactly one required):
176
+ --endpoint, -e <url> Fetch introspection from a live GraphQL endpoint
177
+ --introspection, -i <file> Read from a saved introspection JSON file
178
+ --sdl, -s <file> Read from a GraphQL SDL (.graphql) file
179
+
180
+ OUTPUT:
181
+ --output, -o <dir> Directory to write doctype JSON files (required)
182
+
183
+ OPTIONS:
184
+ --include <types> Comma-separated list of type names to include
185
+ --exclude <types> Comma-separated list of type names to exclude
186
+ --overrides <file> JSON file with per-type field overrides
187
+ --custom-scalars <file> JSON file mapping custom scalar names to field templates
188
+ --include-unmapped Include _graphqlType metadata on unmapped fields
189
+ --help, -h Show this help message
190
+
191
+ EXAMPLES:
192
+ # From a live PostGraphile server
193
+ stonecrop-schema generate -e http://localhost:5000/graphql -o ./schemas
194
+
195
+ # From a saved introspection result
196
+ stonecrop-schema generate -i introspection.json -o ./schemas
197
+
198
+ # From an SDL file with custom scalars
199
+ stonecrop-schema generate -s schema.graphql -o ./schemas \\
200
+ --custom-scalars custom-scalars.json
201
+
202
+ # Only convert specific types
203
+ stonecrop-schema generate -e http://localhost:5000/graphql -o ./schemas \\
204
+ --include "User,Post,Comment"
205
+ `);
206
+ }
207
+ main().catch(err => {
208
+ console.error('Error:', err.message);
209
+ process.exit(1);
210
+ });
@@ -0,0 +1,8 @@
1
+ // Field types
2
+ export { StonecropFieldType, BUILTIN_FIELD_TYPES, TYPE_MAP, getDefaultComponent, resolveComponent, isBuiltinFieldType, } from './fieldtype';
3
+ // Validation helpers
4
+ export { parseDoctype, parseField, validateDoctype, validateField, } from './validation';
5
+ // GraphQL to Doctype conversion
6
+ export { buildScalarMap, classifyFieldType, convertGraphQLSchema, defaultIsEntityField, defaultIsEntityType, GQL_SCALAR_MAP, INTERNAL_SCALARS, WELL_KNOWN_SCALARS, } from './converter';
7
+ // Naming utilities
8
+ export { toSlug, toPascalCase, pascalToSnake, snakeToCamel, camelToSnake, snakeToLabel, camelToLabel } from './naming';
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.57.0"
8
+ "packageVersion": "7.58.7"
9
9
  }
10
10
  ]
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonecrop/schema",
3
- "version": "0.11.7",
3
+ "version": "0.11.8",
4
4
  "type": "module",
5
5
  "types": "./dist/schema.d.ts",
6
6
  "exports": {
@@ -18,18 +18,18 @@
18
18
  "description": "Stonecrop schema definitions and validation tooling",
19
19
  "sideEffects": false,
20
20
  "dependencies": {
21
- "graphql": "^16.12.0",
21
+ "graphql": "^16.13.2",
22
22
  "zod": "^4.3.6"
23
23
  },
24
24
  "devDependencies": {
25
- "@microsoft/api-documenter": "^7.28.2",
26
- "@rushstack/heft": "^1.2.0",
27
- "@types/node": "^22.19.5",
28
- "@vitest/coverage-istanbul": "^4.0.18",
29
- "jsdom": "^28.1.0",
30
- "typescript": "^5.9.3",
31
- "vite": "^7.3.1",
32
- "vitest": "^4.0.18",
25
+ "@microsoft/api-documenter": "^7.30.5",
26
+ "@rushstack/heft": "^1.2.17",
27
+ "@types/node": "^22.19.17",
28
+ "@vitest/coverage-istanbul": "^4.1.5",
29
+ "jsdom": "^29.1.1",
30
+ "typescript": "^6.0.3",
31
+ "vite": "^7.3.2",
32
+ "vitest": "^4.1.5",
33
33
  "stonecrop-rig": "0.7.0"
34
34
  },
35
35
  "scripts": {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes