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-c4ec8b793 to 0.0.0-experimental-dfc800e6e

assets/env

203

dist/bin.js
#!/usr/bin/env node
import path from "path";
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 inquirer from "inquirer";
import { ToolChainOptions, LanguageOptions, PackageManagerOptions, } from "./types.js";
import { InstallNpmPackagesTask } from "./tasks/InstallNpmPackagesTask.js";
import { AddRelayPluginConfigurationTask } from "./tasks/AddRelayPluginConfigurationTask.js";
import { AddRelayEnvironmentTask } from "./tasks/AddRelayEnvironmentTask.js";
import { traverseUpToFindFile, getPackageManagerToUse } 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 { 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, 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));
const ownPackageDirectory = path.join(distDirectory, "..");
const workingDirectory = process.cwd();
// FIND package.json FILE
const packageJsonFile = await traverseUpToFindFile(workingDirectory, "package.json");
const packageJsonFile = await traverseUpToFindFile(workingDirectory, PACKAGE_FILE);
if (!packageJsonFile) {
// package.json file is missing.
throw new Error("package.json file is missing");
printError(`Could not find a ${highlight(PACKAGE_FILE)} in the ${highlight(workingDirectory)} directory.`);
exit(1);
}
const projectRootDirectory = path.dirname(packageJsonFile);
// CHECK REPO FOR UNSAVED CHANGES
// const hasUnsavedChanges = await hasUnsavedGitChanges(projectDir);
// if (hasUnsavedChanges) {
// throw new Error("Project has unsaved changes");
// }
const settings = await readProjectSettings();
console.log();
const dependencies = ["react-relay"];
const devDependencies = getRelayDevDependencies(settings.toolchain, settings.language);
const envArguments = {
workingDirectory,
ownPackageDirectory,
packageJsonFile,
projectRootDirectory,
};
// 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);
}
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(" ")}`,
task: new InstallNpmPackagesTask(dependencies, settings.packageManager, projectRootDirectory),
task: new InstallNpmPackagesTask(dependencies, false, settings),
},
{
title: `Add Relay devDependencies: ${devDependencies
.map((d) => chalk.cyan.bold(d))
.map((d) => highlight(d))
.join(" ")}`,
task: new InstallNpmPackagesTask(devDependencies, settings.packageManager, projectRootDirectory, true),
task: new InstallNpmPackagesTask(devDependencies, true, settings),
},
{
title: "Add Relay configuration to package.json",
task: new AddRelayConfigurationTask(packageJsonFile, settings.schemaFilePath, settings.language),
title: `Add Relay configuration to ${highlight(PACKAGE_FILE)}`,
task: new AddRelayConfigurationTask(settings),
},
{
title: "Add Relay plugin configuration",
task: new AddRelayPluginConfigurationTask(projectRootDirectory, settings.toolchain, settings.language),
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(),
title: `Generate Relay environment ${highlight(relRelayEnvPath)}`,
task: new GenerateRelayEnvironmentTask(settings),
},
{
title: `Generate GraphQL schema file (${chalk.cyan.bold(settings.schemaFilePath)})`,
task: new AddGraphQlSchemaFileTask(settings.schemaFilePath),
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,
},
]);
await runner.run();
try {
await runner.run();
}
catch (_a) {
console.log();
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.italic.bold("### NEXT STEPS ###"));
console.log(`1. Replace ${chalk.cyan.bold(settings.schemaFilePath)} with your own GraphQL schema file.`);
console.log(`2. Replace the HOST variable in the RelayEnvironment.ts file.`);
console.log();
// todo: add integration tests
async function readProjectSettings() {
const defaultPackageManager = getPackageManagerToUse();
// todo: handle artifact directory
// todo: handle error
return await inquirer.prompt([
{
name: "toolchain",
message: "Select the toolchain your project is using",
type: "list",
default: 0,
choices: ToolChainOptions,
},
{
name: "language",
message: "Select the language of your project",
type: "list",
default: 0,
choices: LanguageOptions,
},
{
// todo: validate that it's inside project dir and ends in .graphql
name: "schemaFilePath",
message: "Select the path to your GraphQL schema file",
type: "input",
default: "./src/schema.graphql",
validate: (input) => {
if (!input.endsWith(".graphql")) {
return `File needs to end in ${chalk.green(".graphql")}`;
}
return true;
},
},
{
name: "packageManager",
message: "Select the package manager you wish to use to install packages",
type: "list",
default: defaultPackageManager,
choices: PackageManagerOptions,
},
]);
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!)")}`);
}
export function getRelayDevDependencies(toolChain, language) {
let relayDevDep = ["relay-compiler"];
if (toolChain === "Create-React-App") {
relayDevDep = relayDevDep.concat(["babel-plugin-relay", "graphql"]);
}
else if (toolChain === "Vite") {
relayDevDep.push("vite-plugin-relay");
}
if (language === "Typescript") {
relayDevDep = relayDevDep.concat(["@types/react-relay"]);
}
return relayDevDep;
}
console.log();

@@ -1,44 +0,15 @@

import { exec, execSync } from "child_process";
import path from "path";
import { promises as fs } from "fs";
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;
import fs from "fs/promises";
import { BABEL_RELAY_PACKAGE, PACKAGE_FILE, VITE_RELAY_PACKAGE, } from "./consts.js";
import glob from "glob";
import chalk from "chalk";
export function printInvalidArg(arg, validationMsg, value) {
printError(`Invalid ${arg} specified: ${value} ${chalk.dim(validationMsg)}`);
}
export function getPackageManagerToUse() {
try {
const userAgent = process.env.npm_config_user_agent;
if (userAgent) {
if (userAgent.startsWith("yarn")) {
return "yarn";
}
else if (userAgent.startsWith("pnpm")) {
return "pnpm";
}
}
try {
execSync("yarn --version", { stdio: "ignore" });
return "yarn";
}
catch (_a) {
execSync("pnpm --version", { stdio: "ignore" });
return "pnpm";
}
}
catch (_b) {
return "npm";
}
export function printError(message) {
console.log(chalk.red("✖") + " " + message);
}
export function highlight(message) {
return chalk.cyan.bold(message);
}
export async function traverseUpToFindFile(directory, filename) {

@@ -75,1 +46,138 @@ let currentDirectory = directory;

}
export async function searchFilesInDirectory(directory, pattern) {
return new Promise((resolve) => {
try {
glob(pattern, { cwd: directory }, (error, matches) => {
if (error || !matches || !matches.some((m) => !!m)) {
resolve([]);
}
else {
resolve(matches);
}
});
}
catch (_a) {
resolve([]);
}
});
}
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 {

@@ -22,9 +25,12 @@ spinner.start();

let errorMsg;
if (!!error && typeof error === "string") {
errorMsg = error;
if (!!error) {
if (typeof error === "string") {
errorMsg = error;
}
else if (error instanceof Error) {
errorMsg = error.message;
}
}
errorMsg !== null && errorMsg !== void 0 ? errorMsg : (errorMsg = "Unexpected error");
task.error(errorMsg);
// throw error;
break;
}

@@ -31,0 +37,0 @@ }

import { TaskBase } from "../TaskBase.js";
import { promises as fs } from "fs";
import fs from "fs/promises";
export class AddRelayConfigurationTask extends TaskBase {
constructor(packageJsonFile, schemaFilePath, language) {
constructor(settings) {
super();
this.packageJsonFile = packageJsonFile;
this.schemaFilePath = schemaFilePath;
this.language = language;
this.settings = settings;
}
async run() {
var _a;
const compilerLanguage = getCompilerLanguage(this.language);
// todo: handle error
const packageJsonContent = await fs.readFile(this.packageJsonFile, {
const packageJsonContent = await fs.readFile(this.settings.packageJsonFile, {
encoding: "utf-8",

@@ -26,7 +23,6 @@ });

// Add "relay" configuration section
packageJson["relay"] = {
// todo: this should probably be different for the Next.js project
src: "./src",
language: compilerLanguage,
schema: this.schemaFilePath,
const relayConfig = {
src: this.settings.src,
language: this.settings.compilerLanguage,
schema: this.settings.schemaFile,
exclude: [

@@ -38,17 +34,11 @@ "**/node_modules/**",

};
if (this.settings.artifactDirectory) {
relayConfig.artifactDirectory = this.settings.artifactDirectory;
}
packageJson["relay"] = relayConfig;
}
const serializedPackageJson = JSON.stringify(packageJson, null, 2);
// todo: handle error
await fs.writeFile(this.packageJsonFile, serializedPackageJson, "utf-8");
await fs.writeFile(this.settings.packageJsonFile, serializedPackageJson, "utf-8");
}
}
function getCompilerLanguage(language) {
switch (language) {
case "Typescript":
return "typescript";
case "Flow":
return "flow";
default:
return "javascript";
}
}

@@ -0,16 +1,16 @@

import { spawn } from "child_process";
import { TaskBase } from "../TaskBase.js";
export class InstallNpmPackagesTask extends TaskBase {
constructor(packages, manager, workingDirectory, isDevDependency = false) {
constructor(packages, isDevDependency, settings) {
super();
this.packages = packages;
this.manager = manager;
this.workingDirectory = workingDirectory;
this.isDevDependency = isDevDependency;
this.settings = settings;
}
async run() {
const command = this.manager;
const useYarn = this.manager === "yarn";
const command = this.settings.packageManager;
const useYarn = command === "yarn";
let args = [];
if (useYarn) {
args = ["add", "--exact", "--cwd", this.workingDirectory];
args = ["add", "--exact", "--cwd", this.settings.projectRootDirectory];
if (this.isDevDependency) {

@@ -29,19 +29,18 @@ args.push("--dev");

}
return Promise.resolve();
// return new Promise((resolve, reject) => {
// const child = spawn(command, args, {
// // stdio: "inherit",
// cwd: this.workingDirectory,
// env: process.env,
// shell: true,
// });
// child.on("close", (code) => {
// if (code !== 0) {
// reject({ command: `${command} ${args.join(" ")}` });
// return;
// }
// resolve();
// });
// });
return new Promise((resolve, reject) => {
const child = spawn(command, args, {
// stdio: "inherit",
cwd: this.settings.projectRootDirectory,
env: process.env,
shell: true,
});
child.on("close", (code) => {
if (code !== 0) {
reject({ command: `${command} ${args.join(" ")}` });
return;
}
resolve();
});
});
}
}

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

export const ToolChainOptions = [
"Create-React-App",
"Next.js",
"Vite",
];
export const LanguageOptions = ["Typescript", "JavaScript", "Flow"];
export const ToolchainOptions = ["cra", "next", "vite"];
export const PackageManagerOptions = ["npm", "yarn", "pnpm"];
{
"name": "@tobiastengler/create-relay-app",
"version": "0.0.0-experimental-c4ec8b793",
"version": "0.0.0-experimental-dfc800e6e",
"description": "Easy configuration of Relay for existing projects",
"homepage": "https://github.com/tobias-tengler/create-relay-app#readme",
"license": "MIT",
"author": "tobias-tengler",
"author": {
"name": "Tobias Tengler",
"url": "https://github.com/tobias-tengler",
"email": "contact.tobiastengler@gmail.com"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tobias-tengler/create-relay-app.git"
},
"bugs": {
"url": "https://github.com/tobias-tengler/create-relay-app/issues"
},
"keywords": [],
"files": [
"dist"
"./dist",
"./assets"
],

@@ -17,5 +32,10 @@ "bin": "./dist/bin.js",

"devDependencies": {
"@types/babel__generator": "^7.6.4",
"@types/babel__traverse": "^7.18.0",
"@types/fs-extra": "^9.0.13",
"@types/glob": "^7.2.0",
"@types/inquirer": "^9.0.0",
"@types/node": "^18.6.5",
"@types/ora": "^3.2.0",
"@types/prettier": "^2.7.0",
"tslib": "^2.4.0",

@@ -25,6 +45,13 @@ "typescript": "^4.7.4"

"dependencies": {
"@babel/generator": "^7.18.12",
"@babel/parser": "^7.18.11",
"@babel/traverse": "^7.18.11",
"chalk": "^5.0.1",
"commander": "^9.4.0",
"fs-extra": "^10.1.0",
"glob": "^8.0.3",
"inquirer": "^9.1.0",
"ora": "^6.1.2"
"ora": "^6.1.2",
"prettier": "^2.7.1"
}
}
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