Skip to content

Instantly share code, notes, and snippets.

@karlrwjohnson
Created July 24, 2023 17:38
Show Gist options
  • Save karlrwjohnson/cbd2e24abe1326e4a7e8eb537f77b799 to your computer and use it in GitHub Desktop.
Save karlrwjohnson/cbd2e24abe1326e4a7e8eb537f77b799 to your computer and use it in GitHub Desktop.
Split GraphQL Schema files
/*
GraphQL schema file splitter
Not guaranteed to work 100% of the time -- I just needed it to work once, for me, in my project
But I'm saving it in case I need it again
Basic idea is that I started a project with a rather loose policy toward file organization.
Every file had multiple "things" in it.
But I wanted to split it up along the lines of "one file per thing".
-- when one thing exists per file, you no longer argue what order things should appear in!
At time of writing, IntelliJ's "Move to another file" refactoring doesn't work on GraphQLS files
and moving them manually was getting annoying.
*/
import { readdir, readFile, writeFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const sourceDir = resolve(__dirname, 'graphql/_mixed');
const destDirs = {
directive: resolve(__dirname, 'graphql/directives'),
enum: resolve(__dirname, 'graphql/enums'),
input: resolve(__dirname, 'graphql/inputs'),
interface: resolve(__dirname, 'graphql/interfaces'),
scalar: resolve(__dirname, 'graphql/scalars'),
type: resolve(__dirname, 'graphql/types'),
union: resolve(__dirname, 'graphql/unions'),
}
/** @type{({ sourceFilename: string, sourceLineNumber: number, kind: string, name: string, sourceLines: string[] })[]} **/
const identifiedSections = [];
for (const filename of await readdir(sourceDir)) {
const lines = (await readFile(resolve(sourceDir, filename), { encoding: 'utf-8' })).split(/\r?\n/);
let lineNumber = 0;
let sourceLines = [];
while (lineNumber < lines.length) {
const line = lines[lineNumber++];
sourceLines.push(line);
const sectionHeaderMatch = line.match(/^\s*(directive|enum|input|interface|scalar|type|union)\s+((?:\w|@)+)/);
if (sectionHeaderMatch) {
const [, kind, name] = sectionHeaderMatch;
console.log(`${filename}:${lineNumber}: ${kind} ${name}`);
const sectionStartLineNumber = lineNumber;
gatherSectionLines: switch (kind) {
case 'directive': {
if (line.match(/^\s*(directive|enum|input|interface|scalar|type|union)\s+((?:\w|@))\s+\(.+\)/)) {
break;
} else {
while (lineNumber < lines.length) {
const line = lines[lineNumber++];
sourceLines.push(line);
const endSectionMatch = line.match(/^\s*\)/);
if (endSectionMatch) {
break gatherSectionLines;
}
}
throw new Error(`Ran off the end of ${filename} while parsing ${kind} ${name}`);
}
}
case 'scalar': {
break; // these are all one-liners
}
case 'union': {
while (lineNumber < lines.length) {
const line = lines[lineNumber++];
sourceLines.push(line);
const continueSectionMatch = line.match(/^\s+[|A-Za-z]/);
if (!continueSectionMatch) {
break gatherSectionLines;
}
}
break gatherSectionLines;
}
default: {
if (line.match(/^\s*(directive|enum|input|interface|scalar|type|union)\s+((?:\w|@))\s+\{.+\}/)) {
break;
} else {
while (lineNumber < lines.length) {
const line = lines[lineNumber++];
sourceLines.push(line);
const endSectionMatch = line.match(/^\s*\}/);
if (endSectionMatch) {
break gatherSectionLines;
}
}
throw new Error(`Ran off the end of ${filename} while parsing ${kind} ${name}`);
}
}
}
identifiedSections.push({
sourceFilename: filename,
sourceLineNumber: sectionStartLineNumber,
kind,
name,
sourceLines,
});
sourceLines = [];
}
}
}
for (const section of identifiedSections) {
await writeFile(resolve(destDirs[section.kind], section.name + '.graphqls'),
section.sourceLines.join('\n').trim() + '\n',
{ encoding: 'utf-8' });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment