UNPKG

create-octokit-project

Version:

`npm init` script to create a new Octokit JS module (plugin, authentication strategy, or otherwise)

488 lines (418 loc) 14.9 kB
import { inspect } from "node:util"; import { mkdir } from "node:fs/promises"; import inquirer from "inquirer"; import { Octokit } from "@octokit/core"; import { createOAuthDeviceAuth } from "@octokit/auth-oauth-device"; import clipboardy from "clipboardy"; import { command } from "./lib/command.js"; import { createBranchProtection } from "./lib/create-branch-protection.js"; import { createCoc } from "./lib/create-coc.js"; import { createContributing } from "./lib/create-contributing.js"; import { createEsbuildScript } from "./lib/create-esbuild-script.js"; import { createIssueTemplates } from "./lib/create-issue-templates.js"; import { createLicense } from "./lib/create-license.js"; import { createPackageJson } from "./lib/create-package-json.js"; import { createPullRequest } from "./lib/create-pull-request.js"; import { createReadme } from "./lib/create-readme.js"; import { createReleaseAction } from "./lib/create-release-action.js"; import { createUpdatePrettierAction } from "./lib/create-update-prettier-action.js"; import { createTestAction } from "./lib/create-test-action.js"; import { createRenovateConfig } from "./lib/create-renovate-config.js"; import { createRepository } from "./lib/create-repository.js"; import { inviteCollaborators } from "./lib/invite-collaborators.js"; import { prompts } from "./lib/prompts.js"; import { writePrettyFile } from "./lib/write-pretty-file.js"; export async function main() { const { repositoryType } = await inquirer.prompt([ { name: "repositoryType", type: "list", message: "Do you want to create a public or private repository?", choices: ["public", "private"], }, ]); const { useOctokitOrg } = await inquirer.prompt([ { name: "useOctokitOrg", type: "confirm", message: "If you have access, do you want to create the repository in the @octokit organization?", default: false, }, ]); const scopes = [ repositoryType === "public" ? "public_repo" : "repo", useOctokitOrg ? "admin:org" : "", ].filter(Boolean); const auth = createOAuthDeviceAuth({ clientId: "797fc7c2acb5f7c1bed3", // Create Octokit Project OAuth app by @octokit scopes, async onVerification({ verification_uri, user_code }) { console.log("Open %s", verification_uri); console.log("Paste code: %s (copied to your clipboard)", user_code); await clipboardy.write(user_code); await inquirer.prompt({ name: "grant_access", type: "confirm", message: "Press <enter> when ready", }); }, }); const { token } = await auth({ type: "oauth" }); const octokit = new Octokit({ auth: token }); octokit.hook.before("request", async (options) => { const { method, url, ...parameters } = octokit.request.endpoint.parse(options); console.log(`> ${method} ${url.replace("https://api.github.com", "")}`); for (const [name, value] of Object.entries(parameters.headers)) { console.log(` ${name}: ${value}`); } if (parameters.body) { console.log(``); for (const [name, value] of Object.entries(parameters.body)) { console.log(` ${name}: ${inspect(value)}`); } } }); const { data: { id: userId, login: username, name, email, blog: website }, } = await octokit.request("GET /user"); try { const answers = await prompts({ username, name, email, website, useOctokitOrg, }); const [owner, repo] = answers.repository.split("/"); const isUserRepo = answers.repository.startsWith(username); let ownerId = userId; if (!isUserRepo) { const { data } = await octokit.request("GET /orgs/{org}", { org: owner, }); ownerId = data.id; } // create project folder and chdir into it console.log(`Creating ${answers.path}`); await mkdir(answers.path, { recursive: true }); process.chdir(answers.path); await command("git init"); await command("git checkout -b main"); createLicense(answers.licenseName); console.log(`LICENSE created`); createCoc(answers.cocEmail); console.log(`CODE_OF_CONDUCT.md created`); createContributing({ owner, repo, packageName: answers.packageName }); console.log(`CONTRIBUTING.md created`); await createReadme({ addWip: true, repository: answers.repository, repo, description: answers.description, }); console.log(`README.md created`); await command("git add LICENSE"); await command("git commit -m 'docs(LICENSE): MIT'"); await command("git add CODE_OF_CONDUCT.md"); await command( "git commit -m 'docs(CODE_OF_CONDUCT): Contributor Covenant'", ); await command("git add CONTRIBUTING.md"); await command("git commit -m 'docs(CONTRIBUTING): initial version'"); await command("git add README.md"); await command("git commit -m 'docs(README): initial version'"); await createIssueTemplates(useOctokitOrg); await command("git add .github/ISSUE_TEMPLATE"); await command("git commit -m 'docs(ISSUE_TEMPLATES): initial version'"); const { data: { id: repositoryId }, } = await createRepository(octokit, { isUserRepo, owner, repo, description: answers.description, }); await command( `git remote add origin git@github.com:${answers.repository}.git`, ); await command(`git push -u origin HEAD`); await command(`git checkout -b initial-version`); createReadme({ repo, description: answers.description, }); await command(`git commit README.md -m 'docs(README): remove WIP note'`); await command(`git push -u origin HEAD`); await createPullRequest(octokit, { owner, repo, ownerId, repositoryId, }); await createPackageJson(answers); console.log("Install dependencies"); const dependencies = []; const devDependencies = [ "@octokit/tsconfig", "esbuild", "glob", "@types/jest", "@types/node", "jest", "prettier", "semantic-release", "semantic-release-plugin-update-version-in-files", "ts-jest", "typescript", ]; if (answers.isPlugin || answers.isAuthenticationStrategy) { devDependencies.push("@octokit/core"); } if (answers.isAuthenticationStrategy) { dependencies.push("@octokit/types"); } await command(`npm install --save-dev ${devDependencies.join(" ")}`); if (dependencies.length) { await command(`npm install ${dependencies.join(" ")}`); } await command(`git add package.json`); await command(`git commit -m 'build(package): initial version'`); await command(`git add package-lock.json`); await command(`git commit -m 'build(package): lock file'`); if (owner === "octokit") { console.log("Inviting collaborators..."); await inviteCollaborators(octokit, { owner, repo }); } console.log("Create branch protection for main"); await createBranchProtection(octokit, { owner, repo }); await writePrettyFile( ".gitignore", ["coverage/", "node_modules/", "pkg/"].join("\n"), ); await command(`git add .gitignore`); await command( `git commit -m 'build(gitignore): coverage, node_modules, pkg'`, ); writePrettyFile( "tsconfig.json", JSON.stringify({ extends: "@octokit/tsconfig", include: ["src/**/*"], compilerOptions: { declaration: true, outDir: "pkg/dist-types", emitDeclarationOnly: true, sourceMap: true, }, }), ); await command(`git add tsconfig.json`); await command( `git commit -m 'build(typescript): configuration for esbuild'`, ); console.log("create smoke test"); if (answers.isPlugin) { await writePrettyFile( "test/smoke.test.ts", ` import { Octokit } from "@octokit/core"; import { ${answers.exportName} } from "../src"; describe("Smoke test", () => { it("{ ${answers.exportName} } export is a function", () => { expect(${answers.exportName}).toBeInstanceOf(Function); }); it("${answers.exportName}.VERSION is set", () => { expect(${answers.exportName}.VERSION).toEqual("0.0.0-development"); }); it("Loads plugin", () => { expect(() => { const TestOctokit = Octokit.plugin(${answers.exportName}) new TestOctokit(); }).not.toThrow(); }); }); `, ); } else { await writePrettyFile( "test/smoke.test.ts", ` import { ${answers.exportName} } from "../src"; describe("Smoke test", () => { it("is a function", () => { expect(${answers.exportName}).toBeInstanceOf(Function); }); it("${answers.exportName}.VERSION is set", () => { expect(${answers.exportName}.VERSION).toEqual("0.0.0-development"); }); }); `, ); } await command(`git add test`); await command(`git commit -m 'test: initial version'`); console.log("create src"); await writePrettyFile( "src/version.ts", 'export const VERSION = "0.0.0-development"', ); if (answers.isPlugin) { await writePrettyFile( "src/index.ts", ` import { Octokit } from "@octokit/core"; import { VERSION } from "./version"; type Options = Record<string, unknown>; /** * @param octokit Octokit instance * @param options Options passed to Octokit constructor */ export function ${answers.exportName}(octokit: Octokit, options: Options) {} ${answers.exportName}.VERSION = VERSION; `, ); } else if (answers.isAuthenticationStrategy) { await writePrettyFile( "src/index.ts", ` import { VERSION } from "./version"; import { auth } from "./auth"; import { hook } from "./hook"; import { StrategyOptions, AuthOptions, Authentication } from "./types"; export type Types = { StrategyOptions: any; AuthOptions: any; Authentication: any; }; export const ${answers.exportName}: StrategyInterface = function ${answers.exportName}( options: StrategyOption ) { return Object.assign(auth.bind(null, options), { hook: hook.bind(null, options), }); }; `, ); await writePrettyFile( "src/types.ts", ` export type Types = { StrategyOptions: any; AuthOptions: any; Authentication: any; }; `, ); await writePrettyFile( "src/auth.ts", ` import { AuthOptions, Authentication } from "./types"; export async function auth(options: AuthOptions): Promise<Authentication> { // TODO: add implementation } `, ); await writePrettyFile( "src/hook.ts", ` import { EndpointDefaults, EndpointOptions, OctokitResponse, RequestInterface, RequestParameters, Route, } from "@octokit/types"; import { AuthOptions } from "./types"; type AnyResponse = OctokitResponse<any> export async function hook( options: AuthOptions, request: RequestInterface, route: Route | EndpointOptions, parameters: RequestParameters = {} ): Promise<AnyResponse> { // TODO: add implementation // probably something like setting the authorization header return request(route, parameters); } `, ); } else { const isClass = /^[A-Z]/.test(answers.exportName); if (isClass) { await writePrettyFile( "src/index.ts", ` import { VERSION } from './version' export class ${answers.exportName} { static VERSION = VERSION } `, ); } else { await writePrettyFile( "src/index.ts", ` import { VERSION } from './version' export function ${answers.exportName}() {} ${answers.exportName}.VERSION = VERSION `, ); } } await createEsbuildScript(answers); await command(`git add src`); await command(`git commit -m 'feat: initial version'`); await createReadme({ addBadges: true, repo, description: answers.description, packageName: answers.packageName, repository: answers.repository, }); await command(`git commit README.md -m 'docs(README): badges'`); await createReadme({ useOctokitOrg, addBadges: true, addUsage: true, repo, description: answers.description, packageName: answers.packageName, repository: answers.repository, isPlugin: answers.isPlugin, isAuthenticationStrategy: answers.isAuthenticationStrategy, octokitUsageExample: answers.octokitUsageExample, exportName: answers.exportName, supportsBrowsers: answers.supportsBrowsers, supportsNode: answers.supportsNode, usageExample: answers.usageExample, }); await command(`git commit README.md -m 'docs(README): usage'`); console.log("Create actions"); await createReleaseAction({ owner }); await command(`git add .github/workflows/release.yml`); await command(`git commit -m 'ci(release): initial version'`); await createTestAction(); await command(`git add .github/workflows/test.yml`); await command(`git commit -m 'ci(test): initial version'`); await createUpdatePrettierAction({ owner }); await command(`git add .github/workflows/update-prettier.yml`); await command(`git commit -m 'ci(update-prettier): initial version'`); if (owner === "octokit") { console.log("Create Renovate configuration"); await createRenovateConfig(); await command(`git add .github/renovate.json`); await command(`git commit -m 'ci(renovate): add Renovate configuration'`); } await command(`git push`); console.log(`Your new repository is here: https://github.com/${answers.repository} To change into the new directory, do $ cd ${answers.path}`); } catch (error) { console.log(error); } console.log("All done."); }