-
Notifications
You must be signed in to change notification settings - Fork 574
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add create-yoga-app for - feature #1387
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
bin/ | ||
node_modules/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
import chalk from "chalk"; | ||
import { mkdir } from "fs/promises"; | ||
import path from "path"; | ||
import retry from "async-retry"; | ||
import { isWriteAccess } from "./utils/checkFileAccess.js"; | ||
import { | ||
getRepoInfo, | ||
hasRepo, | ||
existsInRepo, | ||
downloadAndExtractRepo, | ||
downloadAndExtractExample, | ||
} from "./utils/example.js"; | ||
import { isFolderEmpty } from "./utils/isFolderEmpty.js"; | ||
import { existsSync, writeFileSync } from "fs"; | ||
import { install } from "./utils/install.js"; | ||
import os from "os"; | ||
|
||
interface YogaProps { | ||
appPath: string; | ||
packageManager: any; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be typed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe as |
||
template?: string; | ||
templatePath: string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess we don't need two flags? what's the reason for having two? |
||
typescript: string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we might not need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, will remove that. |
||
} | ||
|
||
export class DownloadError extends Error {} | ||
|
||
export const createYogaApp = async ({ | ||
appPath, | ||
packageManager, | ||
template, | ||
templatePath, | ||
typescript, | ||
}: YogaProps) => { | ||
const isTypescript = typescript ? "typescript" : "default"; | ||
const example = template; | ||
let repoInfo; | ||
|
||
if (example) { | ||
let repoUrl: URL | undefined; | ||
|
||
try { | ||
repoUrl = new URL(example); | ||
} catch (error: any) { | ||
if (error.code !== "ERR_INVALID_URL") { | ||
console.error(error); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
if (repoUrl) { | ||
if (repoUrl.origin !== "https://github.com") { | ||
console.error( | ||
`Invalid URL: ${chalk.red( | ||
`"${example}"` | ||
)}. Only GitHub repositories are supported. Please use a GitHub URL and try again.` | ||
); | ||
process.exit(1); | ||
} | ||
|
||
repoInfo = await getRepoInfo(repoUrl, templatePath); | ||
|
||
if (!repoInfo) { | ||
console.error( | ||
`Found invalid GitHub URL: ${chalk.red( | ||
`"${example}"` | ||
)}. Please fix the URL and try again.` | ||
); | ||
process.exit(1); | ||
} | ||
|
||
const found = await hasRepo(repoInfo); | ||
|
||
if (!found) { | ||
console.error( | ||
`Could not locate the repository for ${chalk.red( | ||
`"${example}"` | ||
)}. Please check that the repository exists and try again.` | ||
); | ||
process.exit(1); | ||
} | ||
} else if (example !== "test") { | ||
const found = await existsInRepo(example); | ||
|
||
if (!found) { | ||
console.error( | ||
`Could not locate an example named ${chalk.red( | ||
`"${example}"` | ||
)}. It could be due to the following:\n`, | ||
`1. Your spelling of example ${chalk.red( | ||
`"${example}"` | ||
)} might be incorrect.\n`, | ||
`2. You might not be connected to the internet or you are behind a proxy.` | ||
); | ||
process.exit(1); | ||
} | ||
} | ||
} | ||
const root = path.resolve(appPath); | ||
|
||
if (!(await isWriteAccess(path.dirname(root)))) { | ||
console.log( | ||
chalk.red.bold( | ||
"❌ Path provided is not writable, please check the access or change the Path" | ||
) | ||
); | ||
process.exit(1); | ||
} | ||
const appName = path.basename(root); | ||
|
||
if (!existsSync(appName)) { | ||
await mkdir(appName); | ||
} | ||
|
||
if (!isFolderEmpty(root, appName)) { | ||
process.exit(1); | ||
} | ||
|
||
console.log( | ||
chalk.bgGreen.white.italic( | ||
`Creating your Yoga Application in ${chalk.cyan(root)}` | ||
) | ||
); | ||
console.log(); | ||
|
||
process.chdir(root); | ||
|
||
const packageJsonPath = path.join(root, "package.json"); | ||
let hasPackageJson = false; | ||
|
||
if (example) { | ||
try { | ||
if (repoInfo) { | ||
const repoInfo2 = repoInfo; | ||
console.log( | ||
`Downloading files from repo ${chalk.cyan( | ||
example | ||
)}. This might take a moment.` | ||
); | ||
console.log(); | ||
await retry(() => downloadAndExtractRepo(root, repoInfo2), { | ||
retries: 3, | ||
}); | ||
} else { | ||
console.log( | ||
`Downloading files form examples of ${chalk.cyan( | ||
example | ||
)} yoga Project. This might take a few moments.` | ||
); | ||
console.log(); | ||
await retry(() => downloadAndExtractExample(root, example), { | ||
retries: 3, | ||
}); | ||
} | ||
} catch (reason) { | ||
function isErrorLike(err: unknown): err is { message: string } { | ||
return ( | ||
typeof err === "object" && | ||
err !== null && | ||
typeof (err as { message?: unknown }).message === "string" | ||
); | ||
} | ||
throw new DownloadError( | ||
isErrorLike(reason) ? reason.message : reason + "" | ||
); | ||
} | ||
|
||
const gitignore = ` | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
.pnpm-debug.log* | ||
|
||
# local env files | ||
.env*.local | ||
`; | ||
|
||
const ignorePath = path.join(root, ".gitignore"); | ||
if (!existsSync(ignorePath)) { | ||
writeFileSync( | ||
path.join(root, ".gitignore"), | ||
JSON.stringify(gitignore, null, 2) + os.EOL | ||
); | ||
} | ||
|
||
hasPackageJson = existsSync(packageJsonPath); | ||
if (hasPackageJson) { | ||
console.log("Installing packages. This might take a couple of minutes."); | ||
console.log(); | ||
|
||
await install(root, null, { packageManager, isOnline: true }); | ||
console.log(); | ||
} else { | ||
console.error( | ||
"There is no package.json file in the example you've download, skipping Package Installation." | ||
); | ||
} | ||
} | ||
|
||
console.log(chalk.cyanBright("Bye, bye!✌🏻")); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
#!/usr/bin/env node | ||
import { Command } from "commander"; | ||
// import packageJson from "./package.json"; | ||
import chalk from "chalk"; | ||
import prompts from "prompts"; | ||
import { validateNpmName } from "./utils/validatePkgName.js"; | ||
import path from "path"; | ||
import { getPackageManager } from "./utils/getPackageManager.js"; | ||
import { createYogaApp } from "./create-yoga.js"; | ||
|
||
const packageJson = { name: "", version: "1.0" }; | ||
|
||
let projectOutputPath = ""; | ||
|
||
const program = new Command(packageJson.name) | ||
.version(packageJson.version) | ||
.configureOutput({ | ||
writeErr: (error: string) => console.log(chalk.bgRed.red.bold(error)), | ||
}) | ||
.argument("<project-directory>") | ||
.usage("<project-directory> [options]") | ||
.action((name: string) => { | ||
if (!name) { | ||
console.log("Error"); | ||
} | ||
projectOutputPath = name; | ||
}) | ||
.option("--ts, --typescript", "Create Project using Typescript") | ||
.option( | ||
"--use-npm ", | ||
` | ||
|
||
Use NPM instaed of Yarn` | ||
) | ||
.option( | ||
"--use-pnpm", | ||
` | ||
|
||
Use PNPM instead of Yarn/NPM` | ||
) | ||
.option( | ||
"-t, --template [name]|[github-url]", | ||
`An example to bootstrap the app with. You can use an example name | ||
from the official GraphQL-yoga repo or a GitHub URL. The URL can use | ||
any branch and/or subdirectory | ||
` | ||
) | ||
.option( | ||
"-p,--path <path-to-example>", | ||
` | ||
|
||
In a rare case, your GitHub URL might contain a branch name with | ||
a slash (e.g. bug/fix-1) and the path to the example (e.g. foo/bar). | ||
In this case, you must specify the path to the example separately: | ||
--path foo/bar | ||
` | ||
) | ||
.allowUnknownOption() | ||
.parse(process.argv); | ||
|
||
async function start() { | ||
if (!projectOutputPath || projectOutputPath.length < 1) { | ||
const response = await prompts({ | ||
type: "text", | ||
name: "path", | ||
message: "Please enter your project folder name", | ||
validate: (val: string) => val.length > 1, | ||
}); | ||
|
||
if (!response.path) { | ||
console.log(chalk.bgRed.red.bold("Please enter Project path")); | ||
process.exit(1); | ||
} | ||
projectOutputPath = response?.path.trim(); | ||
} | ||
const resolvedProjectPath = path.resolve(projectOutputPath); | ||
const projectName = path.basename(resolvedProjectPath); | ||
|
||
const { valid, problems } = validateNpmName(projectName); | ||
// console.log(valid, problems); | ||
|
||
if (!valid) { | ||
console.log( | ||
chalk.red.bold(`\n❌ Couldn't able to create Project because: \n\n`) | ||
); | ||
|
||
problems?.forEach((p) => console.log(chalk.red.bold(` ${p}\n`))); | ||
process.exit(1); | ||
} | ||
|
||
const template = program.getOptionValue("template"); | ||
if (typeof template === "boolean" || template === true) { | ||
console.log( | ||
chalk.cyan.italic( | ||
`[EXIT]: Please provide the template name or URL or remove the example options` | ||
) | ||
); | ||
process.exit(1); | ||
} | ||
const useNpm = program.getOptionValue("UseNpm"); | ||
const usePnpm = program.getOptionValue("usePnpm"); | ||
const packageManager: any = getPackageManager(useNpm, usePnpm); | ||
|
||
const project = typeof template === "string" && template.trim(); | ||
|
||
const typescript = program.getOptionValue("typescript"); | ||
const templatePath = program.getOptionValue("path"); | ||
try { | ||
await createYogaApp({ | ||
appPath: resolvedProjectPath, | ||
packageManager, | ||
template: project || undefined, | ||
templatePath, | ||
typescript, | ||
}); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
} | ||
|
||
start(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
{ | ||
"name": "create-yoga-app", | ||
"description": "A starter kit Project for creating simple GraphQL Yoga Projects", | ||
"version": "1.0.0", | ||
"exports": "./index.js", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"scripts": { | ||
"build": "ncc build ./index.ts -o bin", | ||
"test": "npm run build && yarn link create-yoga-app && yarn create yoga-app ./example -t aws-lambda-bundle", | ||
"prerelease": "rimraf ./dist/", | ||
"release": "ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register", | ||
"publish": "echo Hi" | ||
}, | ||
"repository": { | ||
"url": "https://github.com/dotansimha/graphql-yoga", | ||
"type": "git" | ||
}, | ||
"bin": { | ||
"create-yoga-app": "bin/index.js" | ||
}, | ||
"keywords": [ | ||
"yoga", | ||
"starter-kit", | ||
"graphql", | ||
"graphql-yoga", | ||
"template" | ||
], | ||
"license": "MIT", | ||
"engines": { | ||
"node": ">=14" | ||
}, | ||
"devDependencies": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are usually pinning all devDepenencies |
||
"@types/async-retry": "^1.4.4", | ||
"@types/node": "^18.0.3", | ||
"@types/prompts": "^2.0.14", | ||
"@types/tar": "^6.1.1", | ||
"@types/validate-npm-package-name": "^4.0.0", | ||
"async-retry": "^1.3.3", | ||
"chalk": "4.1.2", | ||
"commander": "^9.3.0", | ||
"execa": "^6.1.0", | ||
"got": "^12.1.0", | ||
"prompts": "^2.4.2", | ||
"tar": "^6.1.11", | ||
"typescript": "^4.7.4", | ||
"validate-npm-package-name": "^4.0.0" | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add some comments explaining the flags here?