Skip to content

Commit a2c3e90

Browse files
committed
Add automated setup script to streamline template initialization
- Replace manual setup steps with automated `setup.js` script - Script handles GitHub configuration, branch protection, and package.json updates - Simplify README quick start to single command with optional --private flag - Automate NPM token configuration and repository settings via GitHub CLI
1 parent 67dc954 commit a2c3e90

File tree

2 files changed

+195
-37
lines changed

2 files changed

+195
-37
lines changed

README.md

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,10 @@ Personal template for creating TypeScript libraries.
44

55
## Quick start
66

7-
1. If it should be published to NPM, add the `NPM_TOKEN` secret (make sure not to leave a trailing newline in there!). Otherwise, add `"private": true` in `package.json`.
8-
2. Update the package name, description and repo URL in `package.json`
9-
3. Enable 'Allow GitHub Actions to create and approve pull requests' in _Settings > Actions (General) > `Workflow permissions_
10-
4. Set protection on the master branch: require a pull request before merging, require reivew from code owners, require status checks to pass (select both ci options)
11-
5. Add the repo to the [file sync automation rules](https://github.com/domdomegg/domdomegg/blob/master/.github/workflows/repo-file-sync.yaml)
12-
6. Update the README, using the template commented out below
7+
*Prequisites: Node.js, Git, and the GitHub CLI (authed). AI agents like [Claude Code](https://www.anthropic.com/claude-code) should be able to complete all these steps.*
138

14-
<!--
15-
16-
# TODO: name of library
17-
18-
TODO: A short description of what the library does, explaining why people might want to use it.
19-
20-
## Usage
21-
22-
TODO: usage instructions
23-
24-
## Contributing
25-
26-
Pull requests are welcomed on GitHub! To get started:
27-
28-
1. Install Git and Node.js
29-
2. Clone the repository
30-
3. Install dependencies with `npm install`
31-
4. Run `npm run test` to run tests
32-
5. Build with `npm run build`
33-
34-
## Releases
35-
36-
Versions follow the [semantic versioning spec](https://semver.org/).
37-
38-
To release:
39-
40-
1. Use `npm version <major | minor | patch>` to bump the version
41-
2. Run `git push --follow-tags` to push with tags
42-
3. Wait for GitHub Actions to publish to the NPM registry.
43-
44-
-->
9+
1. Run the setup script: `node setup.js [--private]`
10+
- `--private` will prevent the package being published to NPM. Usually packages should be public and will be published, but if you're unsure ask the user.
11+
2. Complete the TODOs in `package.json` and `README.md` (don't change anything else!)
12+
3. Run `npm install`, then check everything works with `npm run test && npm run build && npm run lint`
13+
4. Commit the changes with the commit message 'Initialization from template'

setup.js

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
#!/usr/bin/env node
2+
3+
import fs from 'node:fs';
4+
import path from 'node:path';
5+
import {execSync} from 'node:child_process';
6+
import {fileURLToPath} from 'node:url';
7+
8+
const __filename = fileURLToPath(import.meta.url);
9+
const __dirname = path.dirname(__filename);
10+
11+
function exec(command) {
12+
try {
13+
return execSync(command, {encoding: 'utf8', stdio: ['pipe', 'pipe', 'inherit']});
14+
} catch (error) {
15+
console.error(`❌ Failed to run: ${command}`, error);
16+
throw error;
17+
}
18+
}
19+
20+
function safeExec(command, description) {
21+
try {
22+
console.log(`⏳ ${description}...`);
23+
exec(command);
24+
console.log(`✅ ${description} complete`);
25+
return true;
26+
} catch (error) {
27+
console.warn(`⚠️ ${description} failed - you may need to do this manually`, error);
28+
return false;
29+
}
30+
}
31+
32+
function configureNpmToken(isPrivate) {
33+
if (!isPrivate) {
34+
console.log('⏳ Fetching NPM token from secrets repository...');
35+
const npmToken = exec('gh api repos/domdomegg/secrets/contents/npm.txt --jq .content | base64 -d').trim();
36+
exec(`gh secret set NPM_TOKEN --body "${npmToken}"`);
37+
console.log('✅ NPM_TOKEN secret configured');
38+
}
39+
}
40+
41+
function updatePackageJson(packageName, repoUrl, isPrivate) {
42+
const packageJsonPath = path.join(__dirname, 'package.json');
43+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
44+
45+
packageJson.name = packageName;
46+
packageJson.description = 'TODO: A short description of what the library does and why people might want to use it.';
47+
packageJson.repository.url = repoUrl;
48+
49+
if (isPrivate) {
50+
packageJson.private = true;
51+
}
52+
53+
fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
54+
console.log('✅ Updated package.json');
55+
}
56+
57+
function enableGitHubActionsPermissions() {
58+
safeExec('gh repo edit --enable-issues --enable-wiki=false --enable-projects=false', 'Configuring repository settings');
59+
safeExec('gh api repos/:owner/:repo/actions/permissions/fork-pr-contributor-approval -X PUT --input - << \'EOF\'\n{"approval_policy":"first_time_contributors_new_to_github"}\nEOF', 'Enabling first-time contributors to trigger GitHub Actions');
60+
safeExec('gh api repos/:owner/:repo/actions/permissions/workflow -X PUT --input - << \'EOF\'\n{"can_approve_pull_request_reviews":true}\nEOF', 'Allowing GitHub Actions to create and approve pull requests');
61+
}
62+
63+
function setupBranchProtection() {
64+
safeExec(`gh api repos/:owner/:repo/branches/master/protection -X PUT --input - << 'EOF'
65+
{
66+
"required_status_checks": {
67+
"strict": false,
68+
"contexts": ["ci lts/*", "ci (current)"]
69+
},
70+
"enforce_admins": false,
71+
"required_pull_request_reviews": {
72+
"required_approving_review_count": 1
73+
},
74+
"restrictions": null,
75+
"allow_force_pushes": true
76+
}
77+
EOF`, 'Setting up branch protection');
78+
}
79+
80+
function addToFileSyncAutomation(repoUrl) {
81+
console.log('⏳ Adding repository to file sync automation...');
82+
const syncConfigUrl = 'repos/domdomegg/domdomegg/contents/.github/workflows/repo-file-sync.yaml';
83+
const syncConfig = exec(`gh api ${syncConfigUrl} --jq .content | base64 -d`);
84+
85+
const repoMatch = repoUrl.match(/github\.com[/:]([\w-]+)\/([\w-]+)/);
86+
const [, owner, repo] = repoMatch;
87+
const newRepoEntry = ` ${owner}/${repo}`;
88+
89+
if (!syncConfig.includes(newRepoEntry.trim())) {
90+
let updatedConfig = syncConfig;
91+
92+
// Add to Dependabot automation section
93+
updatedConfig = updatedConfig.replace(
94+
/(- name: Dependabot automation[\s\S]*?REPOSITORIES: \|[\s\S]*?)( {12}domdomegg\/typescript-library-template)/,
95+
`$1$2\n${newRepoEntry}`,
96+
);
97+
98+
// Add to Node.js general template section
99+
updatedConfig = updatedConfig.replace(
100+
/(- name: Node\.js general template[\s\S]*?REPOSITORIES: \|[\s\S]*?)( {12}domdomegg\/typescript-library-template)/,
101+
`$1$2\n${newRepoEntry}`,
102+
);
103+
104+
const currentSha = exec(`gh api ${syncConfigUrl} --jq .sha`).trim();
105+
106+
const updatePayload = {
107+
message: `Add ${owner}/${repo} to file sync automation`,
108+
content: Buffer.from(updatedConfig).toString('base64'),
109+
sha: currentSha,
110+
};
111+
112+
exec(`gh api ${syncConfigUrl} -X PUT --input - << 'EOF'
113+
${JSON.stringify(updatePayload)}
114+
EOF`);
115+
console.log('✅ Added to file sync automation');
116+
} else {
117+
console.log('ℹ️ Repository already in file sync automation');
118+
}
119+
}
120+
121+
function updateReadme(packageName, isPrivate) {
122+
const readmeContent = `# ${packageName}
123+
124+
TODO: A short description of what the library does and why people might want to use it.
125+
126+
## Usage
127+
128+
TODO: Add usage instructions
129+
130+
## Contributing
131+
132+
Pull requests are welcomed on GitHub! To get started:
133+
134+
1. Install Git and Node.js
135+
2. Clone the repository
136+
3. Install dependencies with \`npm install\`
137+
4. Run \`npm run test\` to run tests
138+
5. Build with \`npm run build\`${isPrivate
139+
? ''
140+
: `
141+
142+
## Releases
143+
144+
Versions follow the [semantic versioning spec](https://semver.org/).
145+
146+
To release:
147+
148+
1. Use \`npm version <major | minor | patch>\` to bump the version
149+
2. Run \`git push --follow-tags\` to push with tags
150+
3. Wait for GitHub Actions to publish to the NPM registry.
151+
`}`;
152+
153+
fs.writeFileSync(path.join(__dirname, 'README.md'), readmeContent);
154+
console.log('✅ Updated README.md');
155+
}
156+
157+
function deleteSetupJs() {
158+
fs.unlinkSync(__filename);
159+
console.log('✅ Deleted setup.js');
160+
}
161+
162+
async function main() {
163+
console.log('🚀 Setting up your TypeScript library...\n');
164+
165+
// Check git and gh CLI is available
166+
exec('git --version');
167+
exec('gh api user');
168+
169+
// Get current repo info
170+
const remoteUrl = exec('git remote get-url origin').trim();
171+
const repoUrl = remoteUrl;
172+
const match = remoteUrl.match(/([^/]+)\.git$/);
173+
const packageName = match?.[1];
174+
if (!match || !packageName) {
175+
throw new Error('Could not extract package name from git remote');
176+
}
177+
178+
const isPrivate = process.argv.includes('--private');
179+
180+
configureNpmToken(isPrivate);
181+
updatePackageJson(packageName, repoUrl, isPrivate);
182+
enableGitHubActionsPermissions();
183+
setupBranchProtection();
184+
addToFileSyncAutomation(repoUrl);
185+
updateReadme(packageName, isPrivate);
186+
deleteSetupJs();
187+
}
188+
189+
main().catch(console.error);

0 commit comments

Comments
 (0)