Skip to content

Commit e742785

Browse files
committed
feat: initial version
1 parent b8e28f7 commit e742785

19 files changed

+1026
-0
lines changed

cli.js

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
#!/usr/bin/env node
2+
3+
import { inspect } from "util";
4+
import { mkdir, writeFile } from "fs/promises";
5+
6+
import inquirer from "inquirer";
7+
import { Octokit } from "@octokit/core";
8+
import { createOAuthDeviceAuth } from "@octokit/auth-oauth-device";
9+
import clipboardy from "clipboardy";
10+
11+
import command from "./lib/command.js";
12+
import createBranchProtection from "./lib/create-branch-protection.js";
13+
import createCoc from "./lib/create-coc.js";
14+
import createContributing from "./lib/create-contributing.js";
15+
import createIssueTemplates from "./lib/create-issue-templates.js";
16+
import createLicense from "./lib/create-license.js";
17+
import createPackageJson from "./lib/create-package-json.js";
18+
import createPullRequest from "./lib/create-pull-request.js";
19+
import createReadme from "./lib/create-readme.js";
20+
import createReleaseAction from "./lib/create-release-action.js";
21+
import createTestAction from "./lib/create-test-action.js";
22+
import createRepository from "./lib/create-repository.js";
23+
import createScript from "./lib/create-script.js";
24+
import prompts from "./lib/prompts.js";
25+
import writePrettyFile from "./lib/write-pretty-file.js";
26+
27+
main();
28+
29+
async function main() {
30+
const { repoIsPrivate } = await inquirer.prompt({
31+
name: "repoIsPrivate",
32+
type: "list",
33+
message: "Do you want to create a public or private repository?",
34+
choices: ["public", "private"],
35+
filter: (input) => input === "private",
36+
});
37+
38+
const auth = createOAuthDeviceAuth({
39+
clientId: "e93735961b3b72ca5c02", // Create Octoherd Script OAuth app by @octoherd
40+
scopes: repoIsPrivate ? ["repo"] : ["public_repo"],
41+
async onVerification({ verification_uri, user_code }) {
42+
console.log("Open %s", verification_uri);
43+
44+
await clipboardy.write(user_code);
45+
console.log("Paste code: %s (copied to your clipboard)", user_code);
46+
47+
await inquirer.prompt({
48+
name: "grant_access",
49+
type: "confirm",
50+
message: "Press <enter> when ready",
51+
});
52+
},
53+
});
54+
55+
const { token } = await auth({ type: "oauth" });
56+
const octokit = new Octokit({ auth: token });
57+
octokit.hook.before("request", async (options) => {
58+
const { method, url, ...parameters } = octokit.request.endpoint.parse(
59+
options
60+
);
61+
console.log(`> ${method} ${url.replace("https://api.github.com", "")}`);
62+
for (const [name, value] of Object.entries(parameters.headers)) {
63+
console.log(` ${name}: ${value}`);
64+
}
65+
if (parameters.body) {
66+
console.log(``);
67+
for (const [name, value] of Object.entries(parameters.body)) {
68+
console.log(` ${name}: ${inspect(value)}`);
69+
}
70+
}
71+
});
72+
73+
// get user information
74+
const {
75+
data: { id: userId, login, name, email, blog: website, twitter_username },
76+
} = await octokit.request("GET /user");
77+
78+
try {
79+
const answers = await prompts({
80+
login,
81+
name,
82+
email,
83+
website,
84+
twitter_username,
85+
});
86+
87+
const [owner, repo] = answers.repository.split("/");
88+
const isUserRepo = answers.repository.startsWith(login);
89+
90+
// create project folder and chdir into it
91+
console.log(`Creating ${answers.path}`);
92+
await mkdir(answers.path, { recursive: true });
93+
process.chdir(answers.path);
94+
95+
await command("git init");
96+
await command("git checkout -b main");
97+
98+
createLicense(answers.licenseName);
99+
console.log(`LICENSE created`);
100+
101+
createCoc(answers.cocEmail);
102+
console.log(`CODE_OF_CONDUCT.md created`);
103+
104+
createContributing({ owner, repo, packageName: answers.packageName });
105+
console.log(`CONTRIBUTING.md created`);
106+
107+
await createReadme({
108+
addWip: true,
109+
repository: answers.repository,
110+
repo,
111+
description: answers.description,
112+
});
113+
console.log(`README.md created`);
114+
115+
await command("git add LICENSE");
116+
await command("git commit -m 'docs(LICENSE): ISC'");
117+
118+
await command("git add CODE_OF_CONDUCT.md");
119+
await command(
120+
"git commit -m 'docs(CODE_OF_CONDUCT): Contributor Covenant'"
121+
);
122+
123+
await command("git add CONTRIBUTING.md");
124+
await command("git commit -m 'docs(CONTRIBUTING): initial version'");
125+
126+
await command("git add README.md");
127+
await command("git commit -m 'docs(README): initial version'");
128+
129+
await createIssueTemplates(answers.packageName);
130+
await command("git add .github/ISSUE_TEMPLATE");
131+
await command("git commit -m 'docs(ISSUE_TEMPLATES): initial version'");
132+
133+
const {
134+
data: { id: repositoryId },
135+
} = await createRepository(octokit, {
136+
isUserRepo,
137+
owner,
138+
repo,
139+
description: answers.description,
140+
repoIsPrivate,
141+
});
142+
143+
await command(
144+
`git remote add origin [email protected]:${answers.repository}.git`
145+
);
146+
await command(`git push -u origin HEAD`);
147+
await command(`git checkout -b initial-version`);
148+
149+
createReadme({
150+
repo,
151+
description: answers.description,
152+
});
153+
154+
await command(`git commit README.md -m 'docs(README): remove WIP note'`);
155+
await command(`git push -u origin HEAD`);
156+
157+
let ownerId = userId;
158+
if (!isUserRepo) {
159+
const { data } = await octokit.request("GET /orgs/{org}", {
160+
org: owner,
161+
});
162+
ownerId = data.id;
163+
}
164+
165+
await createPullRequest(octokit, {
166+
owner,
167+
repo,
168+
ownerId,
169+
repositoryId,
170+
});
171+
172+
await createPackageJson(owner, repo, answers);
173+
console.log("Install dependencies");
174+
const dependencies = ["@octoherd/cli"];
175+
176+
await command(`npm install ${dependencies.join(" ")}`);
177+
178+
await command(`git add package.json`);
179+
await command(`git commit -m 'build(package): initial version'`);
180+
await command(`git add package-lock.json`);
181+
await command(`git commit -m 'build(package): lock file'`);
182+
183+
console.log("Create branch protection for main");
184+
try {
185+
await createBranchProtection(octokit, { owner, repo });
186+
} catch (error) {
187+
if (error.status !== 403) throw error;
188+
189+
console.log(
190+
"Branch protection could not be enabled, because the repository is private and belongs to an organization using the free plan"
191+
);
192+
}
193+
194+
const ignorePaths = ["node_modules"];
195+
await writePrettyFile(".gitignore", ignorePaths.join("\n"));
196+
await command(`git add .gitignore`);
197+
await command(
198+
`git commit -m 'build(gitignore): ${ignorePaths.join(", ")}'`
199+
);
200+
201+
console.log("create script.js and cli.js");
202+
await createScript(answers);
203+
await writeFile(
204+
// we cannot use writePrettyFile, it blows because of the #! in the first line
205+
"cli.js",
206+
`#!/usr/bin/env node
207+
208+
import { script } from "./script.js";
209+
import { run } from "@octoherd/cli/run";
210+
211+
run(script);
212+
`
213+
);
214+
215+
await command(`git add script.js cli.js`);
216+
await command(`git commit -m 'feat: initial version'`);
217+
218+
await createReadme({
219+
addBadges: true,
220+
repo,
221+
description: answers.description,
222+
packageName: answers.packageName,
223+
repository: answers.repository,
224+
});
225+
await command(`git commit README.md -m 'docs(README): badges'`);
226+
227+
await createReadme({
228+
addBadges: true,
229+
addUsage: true,
230+
owner,
231+
repo,
232+
description: answers.description,
233+
packageName: answers.packageName,
234+
repository: answers.repository,
235+
scriptOptions: answers.scriptOptions,
236+
});
237+
await command(`git commit README.md -m 'docs(README): usage'`);
238+
239+
console.log("Create actions");
240+
await createReleaseAction({ owner });
241+
await command(`git add .github/workflows/release.yml`);
242+
await command(`git commit -m 'ci(release): initial version'`);
243+
244+
await createTestAction();
245+
await command(`git add .github/workflows/test.yml`);
246+
await command(`git commit -m 'ci(test): initial version'`);
247+
248+
await command(`git push`);
249+
250+
console.log(`Your new repository is here:
251+
https://github.com/${answers.repository}
252+
253+
To change into the new directory, do
254+
$ cd ${answers.path}`);
255+
} catch (error) {
256+
console.log(error);
257+
}
258+
259+
console.log("All done.");
260+
}

lib/command.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import execa from "execa";
2+
3+
export default async function command(cmd) {
4+
console.log(`$ ${cmd}`);
5+
const { stdout, stderr } = await execa(cmd, { shell: true });
6+
return [stdout, stderr].filter(Boolean).join("\n");
7+
}

lib/create-branch-protection.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export default async function createBranchProtection(octokit, { owner, repo }) {
2+
await octokit.request(
3+
"PUT /repos/{owner}/{repo}/branches/{branch}/protection",
4+
{
5+
owner,
6+
repo,
7+
branch: "main",
8+
enforce_admins: null,
9+
required_pull_request_reviews: {
10+
dismiss_stale_reviews: true,
11+
},
12+
required_status_checks: {
13+
strict: false,
14+
contexts: owner === "octoherd" ? ["WIP", "test"] : ["test"],
15+
},
16+
restrictions: null,
17+
}
18+
);
19+
}

lib/create-coc.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import getTemplateFileContent from "./get-template-file-content.js";
2+
import writePrettyFile from "./write-pretty-file.js";
3+
4+
export default async function createCoc(email) {
5+
const contributorCovenantText = getTemplateFileContent(
6+
"CODE_OF_CONDUCT.md"
7+
).replace("{{EMAIL}}", email);
8+
await writePrettyFile("CODE_OF_CONDUCT.md", contributorCovenantText);
9+
}

lib/create-contributing.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import writePrettyFile from "./write-pretty-file.js";
2+
3+
export default async function createContributing({ owner, repo, packageName }) {
4+
await writePrettyFile(
5+
"CONTRIBUTING.md",
6+
`
7+
# How to contribute
8+
9+
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md).
10+
By participating in this project you agree to abide by its terms.
11+
12+
We appreciate you taking the time to contribute to \`${packageName}\`. Especially as a new contributor, you have a valuable perspective that we lost a long time ago: you will find things confusing and run into problems that no longer occur to us. Please share them with us, so we can make the experience for future contributors the best it could be.
13+
14+
Thank you 💖
15+
16+
## Creating an Issue
17+
18+
Before you create a new Issue:
19+
20+
1. Please make sure there is no [open issue](https://github.com/${owner}/${repo}/issues?utf8=%E2%9C%93&q=is%3Aissue) yet.
21+
2. If it is a bug report, include the steps to reproduce the issue
22+
3. If it is a feature request, please share the motivation for the new feature, what alternatives you tried, and how you would implement it.
23+
24+
## Setup the repository locally
25+
26+
First, fork the repository.
27+
28+
Setup the repository locally. Replace \`<your account name>\` with the name of the account you forked to.
29+
30+
\`\`\`shell
31+
git clone https://github.com/<your account name>/${repo}.git
32+
cd ${repo}
33+
npm install
34+
\`\`\`
35+
36+
Run the tests before making changes to make sure the local setup is working as expected
37+
38+
\`\`\`shell
39+
npm test
40+
\`\`\`
41+
42+
## Submitting the Pull Request
43+
44+
- Create a new branch locally.
45+
- Make your changes in that branch and push them to your fork
46+
- Submit a pull request from your topic branch to the main branch on the \`${owner}/${repo}\` repository.
47+
- Be sure to tag any issues your pull request is taking care of / contributing to. Adding "Closes #123" to a pull request description will automatically close the issue once the pull request is merged in.
48+
49+
## Maintainers only
50+
51+
### Merging the Pull Request & releasing a new version
52+
53+
Releases are automated using [semantic-release](https://github.com/semantic-release/semantic-release).
54+
The following commit message conventions determine which version is released:
55+
56+
1. \`fix: ...\` or \`fix(scope name): ...\` prefix in subject: bumps fix version, e.g. \`1.2.3\` → \`1.2.4\`
57+
2. \`feat: ...\` or \`feat(scope name): ...\` prefix in subject: bumps feature version, e.g. \`1.2.3\` → \`1.3.0\`
58+
3. \`BREAKING CHANGE:\` in body (**Important**: commit body, not subject!): bumps breaking version, e.g. \`1.2.3\` → \`2.0.0\`
59+
60+
Only one version number is bumped at a time, the highest version change trumps the others.
61+
Besides publishing a new version to npm, semantic-release also creates a git tag and release
62+
on GitHub, generates changelogs from the commit messages and puts them into the release notes.
63+
64+
If the pull request looks good but does not follow the commit conventions, use the <kbd>Squash & merge</kbd> button.
65+
`
66+
);
67+
}

0 commit comments

Comments
 (0)