Merged in chore/delete-unused-files (pull request #3346)
chore: Delete unused files * Delete unused files Ignore design-system for now Approved-by: Joakim Jäderberg
This commit is contained in:
@@ -1,150 +0,0 @@
|
||||
# GraphQL to TypeScript Converter
|
||||
|
||||
This script converts GraphQL files (`.graphql`) to TypeScript files (`.graphql.ts`) that use the `gql` template literal from `graphql-tag`.
|
||||
|
||||
## Features
|
||||
|
||||
- Converts individual fragments and queries into separate TypeScript exports
|
||||
- Handles GraphQL imports (`#import`) and converts them to TypeScript imports
|
||||
- Preserves fragment references and generates proper import statements
|
||||
- Groups multiple imports from the same file
|
||||
- Supports fragments, queries, mutations, and subscriptions
|
||||
- Handles files with multiple operations
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Conversion
|
||||
|
||||
Convert all GraphQL files in the project:
|
||||
|
||||
```bash
|
||||
yarn graphql:convert
|
||||
```
|
||||
|
||||
Convert files matching a specific pattern:
|
||||
|
||||
```bash
|
||||
yarn graphql:convert "packages/trpc/**/*.graphql"
|
||||
```
|
||||
|
||||
Convert a single file:
|
||||
|
||||
```bash
|
||||
yarn graphql:convert "path/to/file.graphql"
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `--dry-run`: Preview what files would be converted without actually converting them
|
||||
- `--delete-originals`: Delete original `.graphql` files after successful conversion
|
||||
- `--help`, `-h`: Show help message
|
||||
|
||||
### Examples
|
||||
|
||||
Preview conversion:
|
||||
|
||||
```bash
|
||||
yarn graphql:convert --dry-run
|
||||
```
|
||||
|
||||
Convert and delete originals:
|
||||
|
||||
```bash
|
||||
yarn graphql:convert --delete-originals
|
||||
```
|
||||
|
||||
Convert specific directory:
|
||||
|
||||
```bash
|
||||
yarn graphql:convert "packages/trpc/lib/graphql/Fragments/**/*.graphql"
|
||||
```
|
||||
|
||||
## Input Format
|
||||
|
||||
### GraphQL Fragment
|
||||
|
||||
```graphql
|
||||
fragment Contact on ContactBlock {
|
||||
sections {
|
||||
__typename
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GraphQL Query with Imports
|
||||
|
||||
```graphql
|
||||
#import "../Fragments/System.graphql"
|
||||
#import "../Fragments/Metadata.graphql"
|
||||
|
||||
query GetData($locale: String!) {
|
||||
data(locale: $locale) {
|
||||
...System
|
||||
...Metadata
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
### TypeScript Fragment
|
||||
|
||||
```typescript
|
||||
import { gql } from "graphql-tag";
|
||||
|
||||
export const Contact = gql`
|
||||
fragment Contact on ContactBlock {
|
||||
sections {
|
||||
__typename
|
||||
}
|
||||
}
|
||||
`;
|
||||
```
|
||||
|
||||
### TypeScript Query with Imports
|
||||
|
||||
```typescript
|
||||
import { gql } from "graphql-tag";
|
||||
|
||||
import { System } from "../Fragments/System.graphql";
|
||||
import { Metadata } from "../Fragments/Metadata.graphql";
|
||||
|
||||
export const GetData = gql`
|
||||
query GetData($locale: String!) {
|
||||
data(locale: $locale) {
|
||||
...System
|
||||
...Metadata
|
||||
}
|
||||
}
|
||||
${System}
|
||||
${Metadata}
|
||||
`;
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Parse GraphQL Files**: Reads `.graphql` files and extracts imports and operations
|
||||
2. **Handle Imports**: Converts `#import` statements to TypeScript imports by:
|
||||
- Reading the imported file to determine export names
|
||||
- Converting paths from `.graphql` to `.graphql.ts`
|
||||
- Grouping multiple imports from the same file
|
||||
3. **Extract Operations**: Identifies fragments, queries, mutations, and subscriptions
|
||||
4. **Generate TypeScript**: Creates TypeScript files with:
|
||||
- `gql` template literals
|
||||
- Proper import statements
|
||||
- Named exports for each operation
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `glob`: For file pattern matching
|
||||
- `tsx`: For TypeScript execution
|
||||
- `@types/node`: For Node.js types
|
||||
- `graphql-tag`: For the `gql` template literal (runtime dependency)
|
||||
|
||||
## Notes
|
||||
|
||||
- The script preserves the original file structure and naming
|
||||
- Fragment references (`...FragmentName`) are preserved in the GraphQL content
|
||||
- Multiple operations in a single file are split into separate exports
|
||||
- Import conflicts are avoided by using the exact export names from referenced files
|
||||
- Generated files maintain the same directory structure with `.graphql.ts` extension
|
||||
@@ -1,373 +0,0 @@
|
||||
#!/usr/bin/env tsx
|
||||
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { glob } from "glob";
|
||||
|
||||
interface ImportInfo {
|
||||
fragmentName: string;
|
||||
importPath: string;
|
||||
variableName: string;
|
||||
}
|
||||
|
||||
interface GraphQLFile {
|
||||
content: string;
|
||||
imports: ImportInfo[];
|
||||
operations: Array<{ name: string; content: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts individual fragments/operations from GraphQL content
|
||||
*/
|
||||
function extractOperations(
|
||||
content: string
|
||||
): Array<{ name: string; content: string }> {
|
||||
const operations: Array<{ name: string; content: string }> = [];
|
||||
|
||||
// Split content into lines for processing
|
||||
const lines = content.split("\n");
|
||||
let currentOperation: { name: string; content: string[] } | null = null;
|
||||
let braceCount = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
|
||||
// Check if this line starts a new operation
|
||||
const fragmentMatch = trimmedLine.match(/^fragment\s+(\w+)\s+on/);
|
||||
const queryMatch = trimmedLine.match(/^query\s+(\w+)\s*[({]/);
|
||||
const mutationMatch = trimmedLine.match(/^mutation\s+(\w+)\s*[({]/);
|
||||
const subscriptionMatch = trimmedLine.match(
|
||||
/^subscription\s+(\w+)\s*[({]/
|
||||
);
|
||||
|
||||
if (fragmentMatch || queryMatch || mutationMatch || subscriptionMatch) {
|
||||
// Finish previous operation if exists
|
||||
if (currentOperation && currentOperation.content.length > 0) {
|
||||
operations.push({
|
||||
name: currentOperation.name,
|
||||
content: currentOperation.content.join("\n").trim(),
|
||||
});
|
||||
}
|
||||
|
||||
// Start new operation
|
||||
const operationName = (fragmentMatch ||
|
||||
queryMatch ||
|
||||
mutationMatch ||
|
||||
subscriptionMatch)![1];
|
||||
currentOperation = {
|
||||
name: operationName,
|
||||
content: [line],
|
||||
};
|
||||
|
||||
// Count braces in the current line
|
||||
braceCount =
|
||||
(line.match(/{/g) || []).length -
|
||||
(line.match(/}/g) || []).length;
|
||||
} else if (currentOperation) {
|
||||
// Add line to current operation
|
||||
currentOperation.content.push(line);
|
||||
|
||||
// Update brace count
|
||||
braceCount +=
|
||||
(line.match(/{/g) || []).length -
|
||||
(line.match(/}/g) || []).length;
|
||||
|
||||
// If we've closed all braces, this operation is complete
|
||||
if (braceCount === 0 && trimmedLine.includes("}")) {
|
||||
operations.push({
|
||||
name: currentOperation.name,
|
||||
content: currentOperation.content.join("\n").trim(),
|
||||
});
|
||||
currentOperation = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle case where file ends without closing brace
|
||||
if (currentOperation && currentOperation.content.length > 0) {
|
||||
operations.push({
|
||||
name: currentOperation.name,
|
||||
content: currentOperation.content.join("\n").trim(),
|
||||
});
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates TypeScript content from parsed GraphQL
|
||||
*/
|
||||
function generateTypeScriptContent(parsedFile: GraphQLFile): string {
|
||||
const { imports, operations } = parsedFile;
|
||||
|
||||
let output = 'import { gql } from "graphql-tag"\n';
|
||||
|
||||
// Add imports for fragments - group by import path to avoid duplicates
|
||||
if (imports.length > 0) {
|
||||
output += "\n";
|
||||
|
||||
const importsByPath = new Map<string, string[]>();
|
||||
for (const imp of imports) {
|
||||
if (!importsByPath.has(imp.importPath)) {
|
||||
importsByPath.set(imp.importPath, []);
|
||||
}
|
||||
if (
|
||||
!importsByPath.get(imp.importPath)!.includes(imp.variableName)
|
||||
) {
|
||||
importsByPath.get(imp.importPath)!.push(imp.variableName);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [importPath, variableNames] of importsByPath) {
|
||||
output += `import { ${variableNames.join(", ")} } from "${importPath}"\n`;
|
||||
}
|
||||
}
|
||||
|
||||
output += "\n";
|
||||
|
||||
// Generate exports for each operation
|
||||
if (operations.length === 0) {
|
||||
// If no operation names found, use a default export
|
||||
const defaultName = "GraphQLDocument";
|
||||
const fragmentSubstitutions =
|
||||
imports.length > 0
|
||||
? "\n" +
|
||||
imports.map((imp) => `\${${imp.variableName}}`).join("\n")
|
||||
: "";
|
||||
output += `export const ${defaultName} = gql\`\n${parsedFile.content}${fragmentSubstitutions}\n\`\n`;
|
||||
} else {
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
const operation = operations[i];
|
||||
const fragmentSubstitutions =
|
||||
imports.length > 0
|
||||
? "\n" +
|
||||
imports.map((imp) => `\${${imp.variableName}}`).join("\n")
|
||||
: "";
|
||||
output += `export const ${operation.name} = gql\`\n${operation.content}${fragmentSubstitutions}\n\`\n`;
|
||||
|
||||
if (i < operations.length - 1) {
|
||||
output += "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a GraphQL import path to a TypeScript import path
|
||||
*/
|
||||
function convertImportPath(graphqlPath: string): string {
|
||||
// Remove the .graphql extension and add .graphql
|
||||
const withoutExt = graphqlPath.replace(/\.graphql$/, "");
|
||||
return `${withoutExt}.graphql`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the export names from a GraphQL file by analyzing the fragments it contains
|
||||
*/
|
||||
function getExportNamesFromGraphQLFile(filePath: string): string[] {
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, "utf-8");
|
||||
const operations = extractOperations(content);
|
||||
return operations.map((op) => op.name);
|
||||
} catch {
|
||||
// If file doesn't exist or can't be read, try to infer from path
|
||||
console.warn(
|
||||
`Warning: Could not read ${filePath}, inferring export name from path`
|
||||
);
|
||||
return [getVariableNameFromPath(filePath)];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the expected variable name from a file path
|
||||
*/
|
||||
function getVariableNameFromPath(filePath: string): string {
|
||||
const basename = path.basename(filePath, ".graphql");
|
||||
|
||||
// Convert kebab-case or snake_case to PascalCase
|
||||
return basename
|
||||
.split(/[-_]/)
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a GraphQL file and extracts imports and content
|
||||
*/
|
||||
function parseGraphQLFile(filePath: string): GraphQLFile {
|
||||
const content = fs.readFileSync(filePath, "utf-8");
|
||||
const lines = content.split("\n");
|
||||
const imports: ImportInfo[] = [];
|
||||
const contentLines: string[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
|
||||
if (trimmedLine.startsWith("#import")) {
|
||||
// Extract import path from #import "path"
|
||||
const match = trimmedLine.match(/#import\s+"([^"]+)"/);
|
||||
if (match) {
|
||||
const importPath = match[1];
|
||||
const fullImportPath = path.resolve(
|
||||
path.dirname(filePath),
|
||||
importPath
|
||||
);
|
||||
const exportNames =
|
||||
getExportNamesFromGraphQLFile(fullImportPath);
|
||||
const tsImportPath = convertImportPath(importPath);
|
||||
|
||||
// Add all exports from the imported file
|
||||
for (const exportName of exportNames) {
|
||||
imports.push({
|
||||
fragmentName: exportName,
|
||||
importPath: tsImportPath,
|
||||
variableName: exportName,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (trimmedLine && !trimmedLine.startsWith("#")) {
|
||||
contentLines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
const cleanContent = contentLines.join("\n").trim();
|
||||
const operations = extractOperations(cleanContent);
|
||||
|
||||
return {
|
||||
content: cleanContent,
|
||||
imports,
|
||||
operations,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single GraphQL file to TypeScript
|
||||
*/
|
||||
function convertFile(graphqlPath: string): void {
|
||||
try {
|
||||
console.log(`Converting: ${graphqlPath}`);
|
||||
|
||||
const parsed = parseGraphQLFile(graphqlPath);
|
||||
const tsContent = generateTypeScriptContent(parsed);
|
||||
const tsPath = graphqlPath.replace(/\.graphql$/, ".graphql.ts");
|
||||
|
||||
fs.writeFileSync(tsPath, tsContent, "utf-8");
|
||||
console.log(`✓ Created: ${tsPath}`);
|
||||
|
||||
// Optionally remove the original .graphql file
|
||||
// Uncomment the next line if you want to delete the original files
|
||||
// fs.unlinkSync(graphqlPath)
|
||||
} catch (error) {
|
||||
console.error(`Error converting ${graphqlPath}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to convert all GraphQL files
|
||||
*/
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.includes("--help") || args.includes("-h")) {
|
||||
console.log(`
|
||||
GraphQL to TypeScript Converter
|
||||
|
||||
Converts GraphQL files (.graphql) to TypeScript files (.graphql.ts) using gql template literals.
|
||||
|
||||
Usage: tsx convert-graphql-to-ts.ts [options] [pattern]
|
||||
|
||||
Options:
|
||||
--help, -h Show this help message
|
||||
--dry-run Show what files would be converted without converting them
|
||||
--delete-originals Delete original .graphql files after conversion
|
||||
|
||||
Examples:
|
||||
tsx convert-graphql-to-ts.ts # Convert all .graphql files
|
||||
tsx convert-graphql-to-ts.ts "packages/trpc/**/*.graphql" # Convert specific pattern
|
||||
tsx convert-graphql-to-ts.ts --dry-run # Preview conversion
|
||||
tsx convert-graphql-to-ts.ts --delete-originals # Convert and delete originals
|
||||
|
||||
Features:
|
||||
• Converts fragments, queries, mutations, and subscriptions
|
||||
• Handles GraphQL imports (#import) and converts to TypeScript imports
|
||||
• Preserves fragment references and generates proper import statements
|
||||
• Groups multiple imports from the same file
|
||||
• Splits multiple operations into separate exports
|
||||
`);
|
||||
return;
|
||||
}
|
||||
|
||||
const dryRun = args.includes("--dry-run");
|
||||
const deleteOriginals = args.includes("--delete-originals");
|
||||
|
||||
// Get the pattern from args or use default
|
||||
const pattern = args.find((arg) => !arg.startsWith("--")) || "**/*.graphql";
|
||||
|
||||
console.log(`🔍 Searching for GraphQL files with pattern: ${pattern}`);
|
||||
|
||||
try {
|
||||
const files = await glob(pattern, {
|
||||
ignore: ["**/*.graphql.ts", "**/node_modules/**"],
|
||||
});
|
||||
|
||||
if (files.length === 0) {
|
||||
console.log("❌ No GraphQL files found.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📁 Found ${files.length} GraphQL files`);
|
||||
|
||||
if (dryRun) {
|
||||
console.log("\n📋 Files that would be converted:");
|
||||
files.forEach((file, index) => {
|
||||
console.log(
|
||||
` ${index + 1}. ${file} → ${file.replace(/\.graphql$/, ".graphql.ts")}`
|
||||
);
|
||||
});
|
||||
console.log("\n🔍 --dry-run mode: No files were converted.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("\n🔄 Converting files...");
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
try {
|
||||
console.log(
|
||||
`📝 [${i + 1}/${files.length}] Converting: ${file}`
|
||||
);
|
||||
convertFile(file);
|
||||
successCount++;
|
||||
|
||||
if (deleteOriginals) {
|
||||
fs.unlinkSync(file);
|
||||
console.log(`🗑️ Deleted: ${file}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error converting ${file}:`, error);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n✅ Conversion complete!`);
|
||||
console.log(` 📈 Successfully converted: ${successCount} files`);
|
||||
if (errorCount > 0) {
|
||||
console.log(` ❌ Errors: ${errorCount} files`);
|
||||
}
|
||||
if (deleteOriginals && successCount > 0) {
|
||||
console.log(` 🗑️ Deleted: ${successCount} original files`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Error:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the script
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main().catch(console.error);
|
||||
}
|
||||
Reference in New Issue
Block a user