New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@tobiastengler/create-relay-app

Package Overview
Dependencies
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tobiastengler/create-relay-app - npm Package Compare versions

Comparing version 0.0.0-experimental-e6e505b7a to 0.0.0-experimental-ea7ea2081

dist/ast.js

134

dist/bin.js
#!/usr/bin/env node
import path, { dirname } from "path";
import { TaskRunner } from "./TaskRunner.js";
import { AddGraphQlSchemaFileTask } from "./tasks/AddGraphQlSchemaFileTask.js";
import { GenerateGraphQlSchemaFileTask } from "./tasks/GenerateGraphQlSchemaFileTask.js";
import chalk from "chalk";
import { AddRelayConfigurationTask } from "./tasks/AddRelayConfigurationTask.js";
import { InstallNpmPackagesTask } from "./tasks/InstallNpmPackagesTask.js";
import { AddRelayPluginConfigurationTask } from "./tasks/AddRelayPluginConfigurationTask.js";
import { AddRelayEnvironmentTask } from "./tasks/AddRelayEnvironmentTask.js";
import { traverseUpToFindFile, hasUnsavedGitChanges, printError, } from "./helpers.js";
import { ConfigureRelayGraphqlTransformTask } from "./tasks/ConfigureRelayGraphqlTransformTask.js";
import { AddRelayEnvironmentProviderTask } from "./tasks/AddRelayEnvironmentProviderTask.js";
import { traverseUpToFindFile, printError, getRelayDevDependencies, getRelayCompilerLanguage, highlight, printInvalidArg, getToolchainSettings, prettifyRelativePath, } from "./helpers.js";
import { exit } from "process";
import { BABEL_RELAY_PACKAGE, PACKAGE_FILE, VITE_RELAY_PACKAGE, } from "./consts.js";
import { ARTIFACT_DIR_ARG, BABEL_RELAY_PACKAGE, PACKAGE_FILE, REACT_RELAY_PACKAGE, SCHEMA_FILE_ARG, SRC_DIR_ARG, } from "./consts.js";
import { fileURLToPath } from "url";
import { getCliArguments } from "./cli.js";
import { getCliArguments, promptForMissingCliArguments } from "./cli.js";
import { hasUnsavedGitChanges, isValidArtifactDirectory, isValidSchemaPath, isValidSrcDirectory, } from "./validation.js";
import { GenerateRelayEnvironmentTask } from "./tasks/GenerateRelayEnvironmentTask.js";
import { GenerateArtifactDirectoryTask } from "./tasks/GenerateArtifactDirectoryTask.js";
import { getProjectRelayEnvFilepath } from "./defaults.js";
// INIT ENVIRONMENT
const distDirectory = dirname(fileURLToPath(import.meta.url));

@@ -20,3 +25,3 @@ const ownPackageDirectory = path.join(distDirectory, "..");

if (!packageJsonFile) {
printError(`Could not find a ${chalk.cyan.bold(PACKAGE_FILE)} in the ${chalk.cyan.bold(workingDirectory)} directory.`);
printError(`Could not find a ${highlight(PACKAGE_FILE)} in the ${highlight(workingDirectory)} directory.`);
exit(1);

@@ -31,20 +36,53 @@ }

};
// todo: handle errors
const cliArguments = await getCliArguments(envArguments);
const settings = Object.assign(Object.assign(Object.assign({}, envArguments), cliArguments), {
// todo: determine based on toolchain
srcDirectory: "./src" });
if (!settings.ignoreGitChanges) {
const hasUnsavedChanges = await hasUnsavedGitChanges(envArguments.projectRootDirectory);
if (hasUnsavedChanges) {
printError(`Please commit or discard all changes in the ${chalk.cyan.bold(envArguments.projectRootDirectory)} directory before continuing.`);
exit(1);
// GET ARGUMENTS
let cliArgs;
try {
const partialCliArguments = await getCliArguments(envArguments);
if (!partialCliArguments.ignoreGitChanges) {
const hasUnsavedChanges = await hasUnsavedGitChanges(envArguments.projectRootDirectory);
if (hasUnsavedChanges) {
printError(`Please commit or discard all changes in the ${highlight(envArguments.projectRootDirectory)} directory before continuing.`);
exit(1);
}
}
cliArgs = await promptForMissingCliArguments(partialCliArguments, envArguments);
}
const dependencies = ["react-relay"];
const devDependencies = getRelayDevDependencies(settings.toolChain, settings.useTypescript);
catch (error) {
if (error instanceof Error) {
printError(error.message);
}
else {
printError("Unexpected error while parsing CLI arguments");
}
exit(1);
}
// VALIDATE ARGUMENTS
const schemaPathValid = isValidSchemaPath(cliArgs.schemaFile, envArguments.projectRootDirectory);
if (schemaPathValid !== true) {
printInvalidArg(SCHEMA_FILE_ARG, schemaPathValid, cliArgs.schemaFile);
exit(1);
}
const srcDirValid = isValidSrcDirectory(cliArgs.src, envArguments.projectRootDirectory);
if (srcDirValid !== true) {
printInvalidArg(SRC_DIR_ARG, srcDirValid, cliArgs.src);
exit(1);
}
const artifactDirValid = isValidArtifactDirectory(cliArgs.artifactDirectory, cliArgs.toolchain, envArguments.projectRootDirectory);
if (artifactDirValid !== true) {
printInvalidArg(ARTIFACT_DIR_ARG, artifactDirValid, cliArgs.artifactDirectory);
exit(1);
}
const toolchainSettings = await getToolchainSettings(envArguments, cliArgs);
const settings = Object.assign(Object.assign(Object.assign(Object.assign({}, envArguments), cliArgs), { compilerLanguage: getRelayCompilerLanguage(cliArgs.typescript), relayEnvFilepath: getProjectRelayEnvFilepath(envArguments, cliArgs) }), toolchainSettings);
// EXECUTE TASKS
const dependencies = [REACT_RELAY_PACKAGE];
const devDependencies = getRelayDevDependencies(settings.toolchain, settings.typescript);
const relRelayEnvPath = prettifyRelativePath(settings.projectRootDirectory, settings.relayEnvFilepath);
const relMainPath = prettifyRelativePath(settings.projectRootDirectory, settings.mainFilepath);
const relConfigPath = prettifyRelativePath(settings.projectRootDirectory, settings.configFilepath);
const relSchemaPath = prettifyRelativePath(settings.projectRootDirectory, settings.schemaFile);
const runner = new TaskRunner([
{
title: `Add Relay dependencies: ${dependencies
.map((d) => chalk.cyan.bold(d))
.map((d) => highlight(d))
.join(" ")}`,

@@ -55,3 +93,3 @@ task: new InstallNpmPackagesTask(dependencies, false, settings),

title: `Add Relay devDependencies: ${devDependencies
.map((d) => chalk.cyan.bold(d))
.map((d) => highlight(d))
.join(" ")}`,

@@ -61,17 +99,28 @@ task: new InstallNpmPackagesTask(devDependencies, true, settings),

{
title: "Add Relay configuration to package.json",
title: `Add Relay configuration to ${highlight(PACKAGE_FILE)}`,
task: new AddRelayConfigurationTask(settings),
},
{
title: "Add Relay plugin configuration",
task: new AddRelayPluginConfigurationTask(settings),
title: `Configure Relay transform in ${highlight(relConfigPath)}`,
task: new ConfigureRelayGraphqlTransformTask(settings),
when: !!settings.configFilepath, // todo: remove once cra is fully supported
},
{
title: "Add Relay environment",
task: new AddRelayEnvironmentTask(settings),
title: `Generate Relay environment ${highlight(relRelayEnvPath)}`,
task: new GenerateRelayEnvironmentTask(settings),
},
{
title: `Generate GraphQL schema file (${chalk.cyan.bold(settings.schemaFilePath)})`,
task: new AddGraphQlSchemaFileTask(settings),
title: `Add RelayEnvironmentProvider to ${highlight(relMainPath)}`,
task: new AddRelayEnvironmentProviderTask(settings),
when: !!settings.configFilepath, // todo: remove once cra is fully supported
},
{
title: `Generate GraphQL schema file ${highlight(relSchemaPath)}`,
task: new GenerateGraphQlSchemaFileTask(settings),
},
{
title: `Generate artifact directory ${highlight(settings.artifactDirectory)}`,
task: new GenerateArtifactDirectoryTask(settings),
when: !!settings.artifactDirectory,
},
]);

@@ -83,24 +132,17 @@ try {

console.log();
printError("Some of the tasks unexpectedly failed.");
printError("Some of the tasks failed unexpectedly.");
exit(1);
// todo: if tasks fail, display ways to resovle the tasks manually
}
// DISPLAY RESULT
console.log();
console.log(chalk.yellow.bold("Next steps:"));
console.log(`1. Replace ${chalk.cyan.bold(settings.schemaFilePath)} with your own GraphQL schema file.`);
// todo: get correct path to file
console.log(`2. Replace the value of the ${chalk.cyan.bold("HOST")} variable in the RelayEnvironment.ts file.`);
console.log();
function getRelayDevDependencies(toolChain, useTypescript) {
const relayDevDep = ["relay-compiler"];
if (useTypescript) {
relayDevDep.push("@types/react-relay");
relayDevDep.push("@types/relay-runtime");
}
if (toolChain === "cra" || toolChain === "vite") {
relayDevDep.push(BABEL_RELAY_PACKAGE);
}
if (toolChain === "vite") {
relayDevDep.push(VITE_RELAY_PACKAGE);
}
return relayDevDep;
console.log(chalk.cyan.bold.underline("Next steps"));
console.log();
console.log(`1. Replace ${highlight(relSchemaPath)} with your own GraphQL schema file.`);
console.log(`2. Replace the value of the ${highlight("HOST")} variable in the ${highlight(relRelayEnvPath)} file.`);
// todo: remove once cra is fully supported
if (settings.toolchain === "cra") {
console.log(`3. Setup ${highlight(BABEL_RELAY_PACKAGE)} to conform to your project. ${chalk.dim("(This will soon be automated!)")}`);
}
console.log();

@@ -1,9 +0,8 @@

import path from "path";
import { PACKAGE_FILE } from "./consts.js";
import { PackageManagerOptions, ToolChainOptions, } from "./types.js";
import fs from "fs/promises";
import { PackageManagerOptions, ToolchainOptions, } from "./types.js";
import { program } from "commander";
import chalk from "chalk";
import inquirer from "inquirer";
import { getProjectToolChain, doesProjectUseTypescript, getProjectPackageManager, } from "./helpers.js";
import { getPackageDetails, prettifyPath, } from "./helpers.js";
import { getDefaultCliArguments, getProjectSchemaFilepath, } from "./defaults.js";
import { isValidArtifactDirectory, isValidSchemaPath, isValidSrcDirectory, } from "./validation.js";
import { ARTIFACT_DIR_ARG, IGNORE_GIT_CHANGES_ARG, PACKAGE_MANAGER_ARG, SCHEMA_FILE_ARG, SRC_DIR_ARG, TOOLCHAIN_ARG, TYPESCRIPT_ARG, VERSION_ARG, YES_ARG, } from "./consts.js";
export async function getCliArguments(env) {

@@ -14,55 +13,23 @@ const { name: PACKAGE_NAME, version: PACKAGE_VERSION, description: PACKAGE_DESCRIPTION, } = await getPackageDetails(env);

.description(PACKAGE_DESCRIPTION)
.version(PACKAGE_VERSION, "-v, --version");
// If you change argumemts, make to match up RawCliArguments as well.
.version(PACKAGE_VERSION, `-v, ${VERSION_ARG}`);
// If you change argumemts, make sure to match up RawCliArguments as well.
program
.option("-t, --toolchain <toolchain>", "the toolchain used to bundle / serve the project")
.option("--typescript", "use Typescript")
.option("-s, --schema-file <path>", "path to a GraphQL schema file, relative to the root directory")
.option("-p, --package-manager <manager>")
.option("--ignore-git-changes", "do not exit if the current directory has un-commited Git changes")
.option("-y, --yes", `answer \"yes\" to any prompts`);
.option(`-t, ${TOOLCHAIN_ARG} <toolchain>`, "the toolchain used to bundle / serve the project", parseToolChain)
.option(TYPESCRIPT_ARG, "use Typescript")
.option(`-f, ${SCHEMA_FILE_ARG} <path>`, "path to a GraphQL schema file, relative to the root directory")
.option(`-s, ${SRC_DIR_ARG} <path>`, "path to the directory, where the Relay compiler will be run on")
.option(`-a, ${ARTIFACT_DIR_ARG} <path>`, "path to the directory, where the Relay compiler will be run on")
.option(`-p, ${PACKAGE_MANAGER_ARG} <manager>`, "the package manager to use for installing packages", parsePackageManager)
.option(IGNORE_GIT_CHANGES_ARG, "do not exit if the current directory has un-commited Git changes")
.option(`-y, ${YES_ARG}`, `answer \"yes\" to any prompts`);
program.parse();
const rawCliArguments = program.opts();
const partialCliArguments = parseCliArguments(rawCliArguments);
return await promptForMissingCliArguments(partialCliArguments, env);
return program.opts();
}
function parseCliArguments(args) {
return {
toolChain: tryParseToolChain(args.toolchain),
packageManager: tryParsePackageManager(args.packageManager),
schemaFilePath: args.schemaFile,
useTypescript: args.typescript,
skipPrompts: args.yes,
ignoreGitChanges: args.ignoreGitChanges,
};
}
async function getDefaultCliArguments(existingArgs, env) {
const packageManager = existingArgs.packageManager ||
(await getProjectPackageManager(env.projectRootDirectory));
const toolChain = existingArgs.toolChain ||
(await getProjectToolChain(env.projectRootDirectory));
const useTypescript = existingArgs.useTypescript ||
(await doesProjectUseTypescript(env.projectRootDirectory, packageManager));
// todo: use the src directory as base once configurable
const schemaFilePath = existingArgs.schemaFilePath || "./schema.graphql";
const ignoreGitChanges = existingArgs.ignoreGitChanges || false;
const skipPrompts = existingArgs.skipPrompts || false;
return {
packageManager,
toolChain,
useTypescript,
schemaFilePath,
ignoreGitChanges,
skipPrompts,
};
}
async function promptForMissingCliArguments(existingArgs, env) {
export async function promptForMissingCliArguments(existingArgs, env) {
const defaults = await getDefaultCliArguments(existingArgs, env);
const definedExistingArgs = Object.assign({}, existingArgs);
Object.keys(definedExistingArgs).forEach((k) => definedExistingArgs[k] === undefined && delete definedExistingArgs[k]);
// todo: find better way to skip prompts, but still show inquirer results
if (existingArgs.skipPrompts) {
return Object.assign(Object.assign({}, defaults), definedExistingArgs);
// todo: find way to auto submit inquirer form instead,
// so the default values are still outputted to the console.
if (existingArgs.yes) {
return buildFinalArgs(defaults, {});
}
// todo: handle artifact directory
// todo: maybe handle subscription or @defer / @stream setup

@@ -72,34 +39,49 @@ // todo: handle error

{
name: "toolChain",
message: "Select the toolchain your project is using",
name: "toolchain",
message: "Select the toolchain your project was setup with",
type: "list",
default: defaults.toolChain,
choices: ToolChainOptions,
when: !existingArgs.toolChain,
default: defaults.toolchain,
choices: ToolchainOptions,
when: !existingArgs.toolchain,
},
{
name: "useTypescript",
name: "typescript",
message: "Does your project use Typescript",
type: "confirm",
default: defaults.useTypescript,
when: !existingArgs.useTypescript,
default: defaults.typescript,
when: !existingArgs.typescript,
},
{
name: "schemaFilePath",
name: "src",
message: "Select the root directory of your application code",
type: "input",
default: defaults.src,
validate: (input) => isValidSrcDirectory(input, env.projectRootDirectory),
when: !existingArgs.src,
},
{
name: "schemaFile",
message: "Select the path to your GraphQL schema file",
type: "input",
default: defaults.schemaFilePath,
// todo: also have this validation for cli args
// todo: validate that it's inside project dir
validate: (input) => {
if (!input.endsWith(".graphql")) {
return `File needs to end in ${chalk.green(".graphql")}`;
}
return true;
default: (answers) => {
var _a, _b;
return getProjectSchemaFilepath((_a = answers.toolchain) !== null && _a !== void 0 ? _a : defaults.toolchain, (_b = answers.src) !== null && _b !== void 0 ? _b : defaults.src);
},
when: !existingArgs.schemaFilePath,
validate: (input) => isValidSchemaPath(input, env.projectRootDirectory),
when: !existingArgs.schemaFile,
},
{
name: "artifactDirectory",
message: "Select, if needed, a directory to place all Relay artifacts in",
type: "input",
default: defaults.artifactDirectory || undefined,
validate: (input, answers) => {
var _a;
return isValidArtifactDirectory(input, (_a = answers === null || answers === void 0 ? void 0 : answers.toolchain) !== null && _a !== void 0 ? _a : defaults.toolchain, env.projectRootDirectory);
},
when: !existingArgs.artifactDirectory,
},
{
name: "packageManager",
message: "Select the package manager you wish to use to install packages",
message: "Select the package manager to install packages with",
type: "list",

@@ -112,25 +94,13 @@ default: defaults.packageManager,

console.log();
return Object.assign(Object.assign({}, answers), definedExistingArgs);
return buildFinalArgs(defaults, answers);
}
async function getPackageDetails(env) {
const ownPackageJsonFile = path.join(env.ownPackageDirectory, PACKAGE_FILE);
const packageJsonContent = await fs.readFile(ownPackageJsonFile, "utf8");
const packageJson = JSON.parse(packageJsonContent);
const name = packageJson === null || packageJson === void 0 ? void 0 : packageJson.name;
if (!name) {
throw new Error(`Could not determine name in ${ownPackageJsonFile}`);
}
const version = packageJson === null || packageJson === void 0 ? void 0 : packageJson.version;
if (!version) {
throw new Error(`Could not determine version in ${ownPackageJsonFile}`);
}
const description = packageJson === null || packageJson === void 0 ? void 0 : packageJson.description;
if (!description) {
throw new Error(`Could not determine description in ${ownPackageJsonFile}`);
}
return { name, version, description };
function buildFinalArgs(defaults, answers) {
const combined = Object.assign(Object.assign({}, defaults), answers);
return Object.assign(Object.assign({}, combined), { src: prettifyPath(combined.src), schemaFile: prettifyPath(combined.schemaFile), artifactDirectory: combined.artifactDirectory
? prettifyPath(combined.artifactDirectory)
: "" });
}
function tryParsePackageManager(rawInput) {
function parsePackageManager(rawInput) {
if (!rawInput) {
return undefined;
return null;
}

@@ -147,7 +117,7 @@ const input = getNormalizedCliString(rawInput);

}
throw invalidArgError("--package-manager", input, PackageManagerOptions);
throw invalidArgError(PACKAGE_MANAGER_ARG, input, PackageManagerOptions);
}
function tryParseToolChain(rawInput) {
function parseToolChain(rawInput) {
if (!rawInput) {
return undefined;
return null;
}

@@ -164,3 +134,3 @@ const input = getNormalizedCliString(rawInput);

}
throw invalidArgError("--toolchain", input, ToolChainOptions);
throw invalidArgError(TOOLCHAIN_ARG, input, ToolchainOptions);
}

@@ -167,0 +137,0 @@ function invalidArgError(arg, value, validValues) {

export const TS_CONFIG_FILE = "tsconfig.json";
export const PACKAGE_FILE = "package.json";
export const NEXTJS_CONFIG_FILE = "next.config.js";
export const VITE_CONFIG_FILE_NO_EXT = "vite.config";
export const VITE_SRC_DIR = "src";
export const VITE_MAIN_FILE_NO_EXT = "./src/main";
export const VITE_RELAY_ENV_FILE_NO_EXT = "./src/RelayEnvironment";
export const TYPESCRIPT_PACKAGE = "typescript";
export const BABEL_RELAY_PACKAGE = "babel-plugin-relay";
export const REACT_RELAY_PACKAGE = "react-relay";
// todo: remove branch, once released
export const VITE_RELAY_PACKAGE = "vite-plugin-relay@https://github.com/tobias-tengler/vite-plugin-relay#tte/fix-plugin";
// CLI ARGUMENTS
export const TOOLCHAIN_ARG = "--toolchain";
export const TYPESCRIPT_ARG = "--typescript";
export const SCHEMA_FILE_ARG = "--schema-file";
export const SRC_DIR_ARG = "--src";
export const ARTIFACT_DIR_ARG = "--artifact-directory";
export const PACKAGE_MANAGER_ARG = "--package-manager";
export const IGNORE_GIT_CHANGES_ARG = "--ignore-git-changes";
export const YES_ARG = "--yes";
export const VERSION_ARG = "--version";

@@ -1,148 +0,15 @@

import { exec, execSync, spawn } from "child_process";
import path from "path";
import fs from "fs/promises";
import { NEXTJS_CONFIG_FILE, TS_CONFIG_FILE, TYPESCRIPT_PACKAGE, } from "./consts.js";
import { BABEL_RELAY_PACKAGE, PACKAGE_FILE, VITE_RELAY_PACKAGE, } from "./consts.js";
import glob from "glob";
import t from "@babel/types";
import { parse } from "@babel/parser";
import generate from "@babel/generator";
import { format } from "prettier";
import chalk from "chalk";
export function printInvalidArg(arg, validationMsg, value) {
printError(`Invalid ${arg} specified: ${value} ${chalk.dim(validationMsg)}`);
}
export function printError(message) {
console.log(chalk.red("✖") + " " + message);
}
export function insertNamedImport(path, importName, packageName) {
const importIdentifier = t.identifier(importName);
const program = path.findParent((p) => p.isProgram());
const existingImport = program.node.body.find((s) => t.isImportDeclaration(s) &&
s.source.value === packageName &&
s.specifiers.some((sp) => t.isImportSpecifier(sp) && sp.local.name === importName));
if (!!existingImport) {
return importIdentifier;
}
const importDeclaration = t.importDeclaration([t.importSpecifier(t.cloneNode(importIdentifier), importIdentifier)], t.stringLiteral(packageName));
// Insert import at start of file.
program.node.body.unshift(importDeclaration);
return importIdentifier;
export function highlight(message) {
return chalk.cyan.bold(message);
}
export function parseAst(code) {
return parse(code, {
sourceType: "module",
plugins: ["typescript", "jsx"],
});
}
export function printAst(ast, oldCode) {
const newCode = generate.default(ast, { retainLines: true }, oldCode).code;
return format(newCode, {
bracketSameLine: false,
parser: "babel-ts",
});
}
export function getRelayCompilerLanguage(useTypescript) {
if (useTypescript) {
return "typescript";
}
else {
return "javascript";
}
}
export async function hasUnsavedGitChanges(dir) {
const isPartOfGitRepo = await new Promise((resolve) => {
exec("git rev-parse --is-inside-work-tree", { cwd: dir }, (error) => {
resolve(!error);
});
});
if (!isPartOfGitRepo) {
return false;
}
const hasUnsavedChanges = await new Promise((resolve) => {
exec("git status --porcelain", { cwd: dir }, (error, stdout) => {
resolve(!!error || !!stdout);
});
});
return hasUnsavedChanges;
}
export async function getProjectToolChain(projectRootDirectory) {
const nextjsConfigFile = await findFileInDirectory(projectRootDirectory, NEXTJS_CONFIG_FILE);
if (!!nextjsConfigFile) {
return "next";
}
const viteConfigFiles = await searchFilesInDirectory(projectRootDirectory, "vite.config.*");
if (viteConfigFiles.some((f) => !!f)) {
return "vite";
}
return "cra";
}
export async function doesProjectUseTypescript(projectRootDirectory, manager) {
const tsconfigFile = await findFileInDirectory(projectRootDirectory, TS_CONFIG_FILE);
if (!!tsconfigFile) {
return true;
}
const typescriptInstalled = await isNpmPackageInstalled(manager, projectRootDirectory, TYPESCRIPT_PACKAGE);
if (typescriptInstalled) {
return true;
}
return false;
}
export async function isNpmPackageInstalled(manager, projectRootDirectory, packageName) {
const command = manager;
const useYarn = manager === "yarn";
let args = [];
if (useYarn) {
args = ["list", "--depth=0", "--pattern", packageName];
}
else {
args = ["ls", "--depth=0", packageName];
}
return new Promise((resolve) => {
const child = spawn(command, args, {
// stdio: "inherit",
cwd: projectRootDirectory,
env: process.env,
shell: true,
});
child.stdout.on("data", (data) => {
const stringData = data.toString();
if (stringData.includes(packageName)) {
resolve(true);
}
});
child.on("close", () => {
resolve(false);
});
});
}
export async function getProjectPackageManager(projectRootDirectory) {
try {
const userAgent = process.env.npm_config_user_agent;
// If this script is being run by a specific manager,
// we use this mananger.
if (userAgent) {
if (userAgent.startsWith("yarn")) {
return "yarn";
}
else if (userAgent.startsWith("pnpm")) {
return "pnpm";
}
}
try {
execSync("yarn --version", { stdio: "ignore" });
const hasLockfile = await findFileInDirectory(projectRootDirectory, "yarn.lock");
if (hasLockfile) {
// Yarn is installed and the project contains a yarn.lock file.
return "yarn";
}
}
catch (_a) {
execSync("pnpm --version", { stdio: "ignore" });
const hasLockfile = await findFileInDirectory(projectRootDirectory, "pnpm-lock.yaml");
if (hasLockfile) {
// pnpm is installed and the project contains a pnpm-lock.yml file.
return "pnpm";
}
}
}
catch (_b) { }
return "npm";
}
export async function traverseUpToFindFile(directory, filename) {

@@ -196,1 +63,121 @@ let currentDirectory = directory;

}
export async function getPackageDetails(env) {
const ownPackageJsonFile = path.join(env.ownPackageDirectory, PACKAGE_FILE);
const packageJsonContent = await fs.readFile(ownPackageJsonFile, "utf8");
const packageJson = JSON.parse(packageJsonContent);
const name = packageJson === null || packageJson === void 0 ? void 0 : packageJson.name;
if (!name) {
throw new Error(`Could not determine name in ${ownPackageJsonFile}`);
}
const version = packageJson === null || packageJson === void 0 ? void 0 : packageJson.version;
if (!version) {
throw new Error(`Could not determine version in ${ownPackageJsonFile}`);
}
const description = packageJson === null || packageJson === void 0 ? void 0 : packageJson.description;
if (!description) {
throw new Error(`Could not determine description in ${ownPackageJsonFile}`);
}
return { name, version, description };
}
export function getRelayDevDependencies(toolchain, useTypescript) {
const relayDevDep = ["relay-compiler"];
if (useTypescript) {
relayDevDep.push("@types/react-relay");
relayDevDep.push("@types/relay-runtime");
}
if (toolchain === "cra" || toolchain === "vite") {
relayDevDep.push(BABEL_RELAY_PACKAGE);
}
if (toolchain === "vite") {
relayDevDep.push(VITE_RELAY_PACKAGE);
}
return relayDevDep;
}
export function getSpecifiedProperties(obj) {
const keys = Object.keys(obj);
const newObj = {};
for (const key of keys) {
if (obj[key] === null) {
continue;
}
newObj[key] = obj[key];
}
return newObj;
}
export function isSubDirectory(parent, dir) {
const relative = path.relative(parent, dir);
return !relative.startsWith("..") && !path.isAbsolute(relative);
}
export function prettifyRelativePath(parentPath, childPath) {
const relativePath = path.relative(parentPath, childPath);
return prettifyPath(relativePath);
}
export function prettifyPath(input) {
let normalizedPath = normalizePath(input);
if (!normalizedPath.startsWith("..") && !normalizedPath.startsWith("./")) {
normalizedPath = "./" + normalizedPath;
}
return normalizedPath;
}
export function normalizePath(input) {
return input.split(path.sep).join("/");
}
export function removeExtension(filename) {
return filename.substring(0, filename.lastIndexOf(".")) || filename;
}
export function getRelayCompilerLanguage(useTypescript) {
if (useTypescript) {
return "typescript";
}
else {
return "javascript";
}
}
export async function getToolchainSettings(env, args) {
if (args.toolchain === "vite") {
const configFilename = "vite.config" + (args.typescript ? ".ts" : ".js");
const configFilepath = await findFileInDirectory(env.projectRootDirectory, configFilename);
if (!configFilepath) {
throw new Error(`${configFilename} not found`);
}
const mainFilename = "main" + (args.typescript ? ".tsx" : ".jsx");
const searchDirectory = path.join(env.projectRootDirectory, "src");
const mainFilepath = await findFileInDirectory(searchDirectory, mainFilename);
if (!mainFilepath) {
throw new Error(`${mainFilename} not found`);
}
return {
configFilepath,
mainFilepath,
};
}
else if (args.toolchain === "next") {
const configFilename = "next.config.js";
const configFilepath = await findFileInDirectory(env.projectRootDirectory, configFilename);
if (!configFilepath) {
throw new Error(`${configFilename} not found`);
}
const mainFilename = "_app" + (args.typescript ? ".tsx" : ".jsx");
const searchDirectory = path.join(env.projectRootDirectory, "pages");
const mainFilepath = await findFileInDirectory(searchDirectory, mainFilename);
if (!mainFilepath) {
throw new Error(`${mainFilename} not found`);
}
return {
configFilepath,
mainFilepath,
};
}
else {
const mainFilename = "index" + (args.typescript ? ".tsx" : ".jsx");
const searchDirectory = path.join(env.projectRootDirectory, "src");
const mainFilepath = await findFileInDirectory(searchDirectory, mainFilename);
if (!mainFilepath) {
throw new Error(`${mainFilename} not found`);
}
return {
configFilepath: "",
mainFilepath,
};
}
}
import chalk from "chalk";
export class TaskBase {
setSpinner(spinner) {
init(spinner) {
this.spinner = spinner;

@@ -8,3 +8,4 @@ }

var _a;
(_a = this.spinner) === null || _a === void 0 ? void 0 : _a.warn(this.spinner.text + " " + chalk.dim("[Skipped: " + message + "]"));
const reason = message ? ": " + message : undefined;
(_a = this.spinner) === null || _a === void 0 ? void 0 : _a.warn(this.spinner.text + " " + chalk.dim(`[Skipped${reason}]`));
throw new TaskSkippedError();

@@ -11,0 +12,0 @@ }

@@ -8,5 +8,8 @@ import ora from "ora";

async run() {
for (const { title, task } of this.taskDefs) {
for (const { title, task, when } of this.taskDefs) {
if (when === false) {
continue;
}
const spinner = ora(title);
task.setSpinner(spinner);
task.init(spinner);
try {

@@ -13,0 +16,0 @@ spinner.start();

import { TaskBase } from "../TaskBase.js";
import fs from "fs/promises";
import { getRelayCompilerLanguage } from "../helpers.js";
export class AddRelayConfigurationTask extends TaskBase {

@@ -24,6 +23,6 @@ constructor(settings) {

// Add "relay" configuration section
packageJson["relay"] = {
src: this.settings.srcDirectory,
language: getRelayCompilerLanguage(this.settings.useTypescript),
schema: this.settings.schemaFilePath,
const relayConfig = {
src: this.settings.src,
language: this.settings.compilerLanguage,
schema: this.settings.schemaFile,
exclude: [

@@ -35,2 +34,6 @@ "**/node_modules/**",

};
if (this.settings.artifactDirectory) {
relayConfig.artifactDirectory = this.settings.artifactDirectory;
}
packageJson["relay"] = relayConfig;
}

@@ -37,0 +40,0 @@ const serializedPackageJson = JSON.stringify(packageJson, null, 2);

@@ -1,2 +0,2 @@

export const ToolChainOptions = ["cra", "next", "vite"];
export const ToolchainOptions = ["cra", "next", "vite"];
export const PackageManagerOptions = ["npm", "yarn", "pnpm"];
{
"name": "@tobiastengler/create-relay-app",
"version": "0.0.0-experimental-e6e505b7a",
"version": "0.0.0-experimental-ea7ea2081",
"description": "Easy configuration of Relay for existing projects",

@@ -40,3 +40,2 @@ "homepage": "https://github.com/tobias-tengler/create-relay-app#readme",

"@types/prettier": "^2.7.0",
"fs-extra": "^10.1.0",
"tslib": "^2.4.0",

@@ -51,2 +50,3 @@ "typescript": "^4.7.4"

"commander": "^9.4.0",
"fs-extra": "^10.1.0",
"glob": "^8.0.3",

@@ -53,0 +53,0 @@ "inquirer": "^9.1.0",

@@ -12,2 +12,6 @@ <h1 align="center" style="font-size: 30px;">create-relay-app</h1>

<p align="center">
<img src="./showcase.gif" alt="Showcase" />
</p>
## Motivation

@@ -30,3 +34,3 @@

```bash
npx @tobiastengler/create-relay-app
npx @tobiastengler/create-relay-app@experimental
```

@@ -68,3 +72,3 @@

### -s, --schema-file &lt;path&gt;
### -f, --schema-file &lt;path&gt;

@@ -83,2 +87,30 @@ Specifies the location of the GraphQL schema file inside of your project directory.

### -s, --src &lt;path&gt;
Specifies the source directory of your project, where the Relay compiler will be run on.
_Possible values_
A relative path to a directory inside the root directory of your project.
_Example_
```bash
--src ./src
```
### -a, --artifact-directory &lt;path&gt;
Specifies a directory, where all artifacts generated by the Relay compiler will be placed.
_Possible values_
A relative path to a directory inside the root directory of your project.
_Example_
```bash
--artifact-directory ./src/__generated__
```
### -p, --package-manager &lt;manager&gt;

@@ -118,7 +150,7 @@

If there is a `next.config.js` file in the root directory of your project, we assume it's a _Next.js_ project, unless specified otherwise.
If the `next` package is installed, we assume it's a _Next.js_ project, unless specified otherwise.
If there is a `vite.config.js` or `vite.config.ts` file in the root directory of your project, we assume it's a _Vite.js_ project, unless specified otherwise.
If the `vite` package is installed, we assume it's a _Vite.js_ project, unless specified otherwise.
If none of the files above are matched, we assume it's a _Create React App_ project, unless specified otherwise.
Otherwise, we assume it's a _Create React App_ project, unless specified otherwise.

@@ -135,10 +167,18 @@ ### --typescript

- `yarn`, if `yarn` is installed and a `yarn.lock` file exists at the root of the project.
- `pnpm`, if `pnpm` is installed and a `pnpm-lock.yml` file exists at the root of the project.
- `yarn`, if `yarn` is installed **and** a `yarn.lock` file exists at the root of the project.
- `pnpm`, if `pnpm` is installed **and** a `pnpm-lock.yml` file exists at the root of the project.
- `npm` in all other cases.
### --src
If the [toolchain](#t---toolchain-lttoolchaingt) is `next`, `./pages`, otherwise `./src`, unless specified otherwise.
### --schema-file
`./schema.graphql`, unless specified otherwise.
If the [toolchain](#t---toolchain-lttoolchaingt) is `next`, `./src/schema.graphql`, otherwise the value of [--src](#s---src-ltpathgt) joined with `schema.graphql`, unless specified otherwise.
### --artifact-directory
If the [toolchain](#t---toolchain-lttoolchaingt) is `next`, `./__generated__`, otherwise it's not specified, unless specified otherwise.
### --yes

@@ -145,0 +185,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc