@dillonkearns/elm-graphql
Version:
<img src="https://cdn.jsdelivr.net/gh/martimatix/logo-graphqelm/logo.svg" alt="dillonearns/elm-graphql logo" width="40%" align="right">
223 lines (201 loc) • 6.52 kB
text/typescript
const { Elm } = require("./Main.elm");
import * as fs from "fs-extra";
import { GraphQLClient } from "graphql-request";
import * as http from "http";
import * as request from "request";
import { applyElmFormat } from "./formatted-write";
import { introspectionQuery } from "./introspection-query";
import * as glob from "glob";
import * as path from "path";
import * as childProcess from "child_process";
import {
removeGenerated,
isGenerated,
warnAndExitIfContainsNonGenerated
} from "./cli/generated-code-handler";
const npmPackageVersion = require("../../package.json").version;
const elmPackageVersion = require("../../elm.json").version;
const targetComment = `-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql
-- https://github.com/dillonkearns/elm-graphql
`;
const versionMessage = `npm version ${npmPackageVersion}\nTargeting elm package dillonkearns/elm-graphql@${elmPackageVersion}`;
function prependBasePath(
suffixPath: string,
baseModule: string[],
outputPath: string
): string {
return path.join(outputPath, baseModule.join("/"), suffixPath);
}
let app = Elm.Main.init({ flags: { argv: process.argv, versionMessage } });
// app.ports.print.subscribe(console.log);
app.ports.printAndExitFailure.subscribe((message: string) => {
console.log(message);
process.exit(1);
});
app.ports.printAndExitSuccess.subscribe((message: string) => {
console.log(message);
process.exit(0);
});
app.ports.introspectSchemaFromFile.subscribe(
({
introspectionFilePath,
outputPath,
baseModule,
customDecodersModule
}: {
introspectionFilePath: string;
outputPath: string;
baseModule: string[];
customDecodersModule: string | null;
}) => {
warnAndExitIfContainsNonGenerated({ baseModule, outputPath });
const introspectionFileJson = JSON.parse(
fs.readFileSync(introspectionFilePath).toString()
);
onDataAvailable(
introspectionFileJson.data || introspectionFileJson,
outputPath,
baseModule,
customDecodersModule
);
}
);
app.ports.introspectSchemaFromUrl.subscribe(
({
graphqlUrl,
excludeDeprecated,
outputPath,
baseModule,
headers,
customDecodersModule
}: {
graphqlUrl: string;
excludeDeprecated: boolean;
outputPath: string;
baseModule: string[];
headers: {};
customDecodersModule: string | null;
}) => {
warnAndExitIfContainsNonGenerated({ baseModule, outputPath });
console.log("Fetching GraphQL schema...");
new GraphQLClient(graphqlUrl, {
mode: "cors",
headers: headers
})
.request(introspectionQuery, { includeDeprecated: !excludeDeprecated })
.then(data => {
onDataAvailable(data, outputPath, baseModule, customDecodersModule);
})
.catch(err => {
console.log(err.response || err);
process.exit(1);
});
}
);
function makeEmptyDirectories(
baseModule: string[],
outputPath: string,
directoryNames: string[]
): void {
directoryNames.forEach(dir => {
fs.mkdirpSync(prependBasePath(dir, baseModule, outputPath));
});
}
function onDataAvailable(
data: {},
outputPath: string,
baseModule: string[],
customDecodersModule: string | null
) {
console.log("Generating files...");
app.ports.generatedFiles.subscribe(async function(generatedFile: {
[s: string]: string;
}) {
removeGenerated(prependBasePath("/", baseModule, outputPath));
makeEmptyDirectories(baseModule, outputPath, [
"InputObject",
"Object",
"Interface",
"Union",
"Enum"
]);
await Promise.all(writeGeneratedFiles(outputPath, generatedFile)).catch(
err => {
console.error("Error writing files", err);
}
);
writeIntrospectionFile(baseModule, outputPath);
applyElmFormat(prependBasePath("/", baseModule, outputPath));
if (customDecodersModule) {
verifyCustomCodecsFileIsValid(
outputPath,
baseModule,
customDecodersModule
);
}
console.log("Success!");
});
app.ports.generateFiles.send(data);
}
function verifyCustomCodecsFileIsValid(
outputPath: string,
baseModule: string[],
customDecodersModule: string
) {
const verifyDecodersFile = path.join(
outputPath,
...baseModule,
"VerifyScalarCodecs.elm"
);
try {
childProcess.execSync(`elm make ${verifyDecodersFile} --output=/dev/null`, {
stdio: "pipe"
});
} catch (error) {
console.error(error.message);
console.error(`--------------------------------------------
INVALID SCALAR DECODERS FILE
--------------------------------------------
Your custom scalar decoders module, \`${customDecodersModule}\`, is invalid.
This is because either:
1) This is the first time you've run this CLI with the \`--scalar-codecs\` option.
In this case, get a valid file, you can start by copy-pasting \`${baseModule.join(
"."
)}.ScalarCodecs\`. Then change the module name to \`${customDecodersModule}\`
and you have a valid starting point!
2) You added or renamed a Custom Scalar in your GraphQL schema.
To handle the new Custom Scalar, you can copy the relevant entries from \`${customDecodersModule}\`.
Check the following:
* You have a module called \`${customDecodersModule}\`
* The module is somewhere in your elm path (check the \`source-directories\` in your \`elm.json\`)
You must:
* Have a type for every custom scalar
* Expose each of these types
* Expose a \`decoders\` value
Above the dashes (----) there are some details that might help you debug the issue. Remember, you can always
copy-paste the \`${baseModule.join(
"."
)}.ScalarCodecs\` module to get a valid file.
After you've copy pasted the template file, or tried fixing the file,
re-run this CLI command to make sure it is valid.
`);
process.exit(1);
}
}
function writeGeneratedFiles(
outputPath: string,
generatedFile: {
[s: string]: string;
}
): Promise<void>[] {
return Object.entries(generatedFile).map(([fileName, fileContents]) => {
const filePath = path.join(outputPath, fileName);
return fs.writeFile(filePath, targetComment + fileContents);
});
}
function writeIntrospectionFile(baseModule: string[], outputPath: string) {
fs.writeFileSync(
prependBasePath("elm-graphql-metadata.json", baseModule, outputPath),
`{"targetElmPackageVersion": "${elmPackageVersion}", "generatedByNpmPackageVersion": "${npmPackageVersion}"}`
);
}