@tobiastengler/create-relay-app
Version:
Easy configuration of Relay for existing projects
163 lines (162 loc) • 7.76 kB
JavaScript
import path, { dirname } from "path";
import { exit } from "process";
import { fileURLToPath } from "url";
import { InvalidArgError } from "./arguments/ArgumentBase.js";
import { ArgumentHandler, ArtifactDirectoryArgument, PackageManagerArgument, SchemaFileArgument, SrcArgument, ToolchainArgument, TypeScriptArgument, SubscriptionsArgument, } from "./arguments/index.js";
import { BABEL_RELAY_MACRO, GITHUB_CODE_URL, PACKAGE_FILE } from "./consts.js";
import { Filesystem } from "./misc/Filesystem.js";
import { getPackageManger, getExecutingPackageManager } from "./misc/packageManagers/index.js";
import { GenerateArtifactDirectoryTask, GenerateRelayEnvironmentTask, GenerateGraphQlSchemaFileTask, TaskRunner, ConfigureRelayCompilerTask, Cra_AddBabelMacroTypeDefinitionsTask, InstallNpmDependenciesTask, InstallNpmDevDependenciesTask, Vite_ConfigureVitePluginRelayTask, Next_ConfigureNextCompilerTask, Cra_AddRelayEnvironmentProvider, Vite_AddRelayEnvironmentProvider, Next_AddRelayEnvironmentProvider, ConfigureEolOfArtifactsTask, HTTP_ENDPOINT, WEBSOCKET_ENDPOINT, Next_AddTypeHelpers, } from "./tasks/index.js";
import { headline, bold, importantHeadline, printError } from "./utils/index.js";
import { ProjectContext } from "./misc/ProjectContext.js";
import { Environment, MissingPackageJsonError } from "./misc/Environment.js";
import { Git } from "./misc/Git.js";
import { CommandRunner } from "./misc/CommandRunner.js";
import { AddRelayCompilerScriptsTask } from "./tasks/AddRelayCompilerScriptsTask.js";
const fs = new Filesystem();
const cmdRunner = new CommandRunner();
const distDirectory = dirname(fileURLToPath(import.meta.url));
const ownPackageJsonFilepath = path.join(distDirectory, "..", PACKAGE_FILE);
const cwd = process.cwd();
const pacMan = getExecutingPackageManager();
const env = new Environment(cwd, ownPackageJsonFilepath, fs);
// Determine environment information, such as where the package.json
// of the target project lies.
try {
await env.init();
}
catch (error) {
if (error instanceof MissingPackageJsonError) {
printError(`Could not find a ${bold(PACKAGE_FILE)} in the ${bold(cwd)} directory.`);
console.log();
console.log(headline("Correct usage"));
console.log();
console.log("1. Remember to first scaffold a React project using:");
console.log(" Next.js: " + bold(pacMan + " create next-app --typescript"));
console.log(" Vite.js: " + bold(pacMan + " create vite --template react-ts"));
console.log(" Create React App: " + bold(pacMan + " create react-app <project-name> --template typescript"));
console.log();
console.log("2. Move into the scaffolded directory:");
console.log(" " + bold("cd <project-name>"));
console.log();
console.log(`3. Run the original command again:`);
console.log(" " + bold(pacMan + " create @tobiastengler/relay-app"));
}
else if (error instanceof Error) {
printError("Unexpected error while gathering environment information: " + error.message);
}
else {
printError("Unexpected error while gathering environment information");
}
exit(1);
}
// Define all of the possible CLI arguments.
const argumentHandler = new ArgumentHandler([
new ToolchainArgument(env),
new TypeScriptArgument(fs, env),
new SrcArgument(fs, env),
new SchemaFileArgument(fs, env),
new ArtifactDirectoryArgument(fs, env),
new SubscriptionsArgument(),
new PackageManagerArgument(fs, env),
]);
let isGitRepo = false;
let userArgs;
try {
// Get the arguments provided to the program.
const cliArgs = await argumentHandler.parseArgs(env);
try {
const git = new Git();
isGitRepo = await git.isGitRepository(env.cwd);
if (isGitRepo && !cliArgs.ignoreGitChanges) {
const hasUnsavedChanges = await git.hasUnsavedChanges(env.cwd);
if (hasUnsavedChanges) {
printError(`Please commit or discard all changes in the ${bold(env.cwd)} directory before continuing.`);
exit(1);
}
}
}
catch (_a) {
// We just ignore it if something goes wrong in the git detection.
}
// Prompt for all of the missing arguments, required to execute the program.
userArgs = await argumentHandler.resolveMissingArgs(cliArgs);
console.log();
}
catch (error) {
if (error instanceof InvalidArgError) {
printError(error.message);
}
else if (error instanceof Error) {
printError("Error while parsing CLI arguments: " + error.message);
}
else {
printError("Unexpected error while parsing CLI arguments");
}
exit(1);
}
// Instantiate a package manager, based on the user's choice.
const packageManager = getPackageManger(userArgs.packageManager, cmdRunner, env.cwd);
// Build a context that contains all of the configuration.
const context = new ProjectContext(env, userArgs, packageManager, fs);
// Define tasks that should be executed.
const runner = new TaskRunner([
new InstallNpmDependenciesTask(context),
new InstallNpmDevDependenciesTask(context),
new ConfigureRelayCompilerTask(context),
new AddRelayCompilerScriptsTask(context),
new GenerateRelayEnvironmentTask(context),
new GenerateGraphQlSchemaFileTask(context),
new GenerateArtifactDirectoryTask(context),
new ConfigureEolOfArtifactsTask(context, isGitRepo),
new Cra_AddBabelMacroTypeDefinitionsTask(context),
new Cra_AddRelayEnvironmentProvider(context),
new Vite_ConfigureVitePluginRelayTask(context),
new Vite_AddRelayEnvironmentProvider(context),
new Next_ConfigureNextCompilerTask(context),
new Next_AddTypeHelpers(context),
new Next_AddRelayEnvironmentProvider(context),
]);
// Execute all of the tasks sequentially.
try {
await runner.run();
}
catch (_b) {
// The error should've already been correctly handled by the runner,
// we just exit the program here.
console.log();
printError("Some of the tasks failed unexpectedly.");
exit(1);
}
console.log();
console.log();
// Display a guide to the user on how to continue setting up his project.
console.log(headline("Next steps"));
console.log();
console.log(`1. Replace ${bold(context.schemaPath.rel)} with your own GraphQL schema file.`);
const endpoints = bold(HTTP_ENDPOINT) + (!context.args.subscriptions ? "" : " / " + bold(WEBSOCKET_ENDPOINT));
console.log(`2. Replace the value of the ${endpoints} variable in the ${bold(context.relayEnvFile.rel)} file.`);
const artifactFileExt = "*" + context.artifactExtension;
console.log(`3. Ignore ${bold(artifactFileExt)} files in your linter / formatter configuration.`);
// Create React app comes with some annoyances, so we warn the user about it,
// and provide possible solutions that can be manually implemented.
if (context.is("cra")) {
console.log();
console.log(importantHeadline("Important"));
console.log();
console.log(`Remember you need to import ${bold("graphql")} like the following:`);
console.log(" " + bold(`import graphql from \"${BABEL_RELAY_MACRO}\";`));
console.log();
console.log(`Otherwise the transform of the ${bold("graphql``")} tagged literal will not work!`);
console.log("If you do not want to use the macro, you can check out the following document for guidance:");
console.log(GITHUB_CODE_URL + "/docs/cra-babel-setup.md");
}
if (context.is("next")) {
console.log();
console.log(importantHeadline("Important"));
console.log();
console.log(`Follow this guide, if you want to fetch data on the server instead of on the client:`);
console.log(GITHUB_CODE_URL + "/docs/next-data-fetching.md");
}
console.log();