Skip to content

Commit 82a39e5

Browse files
committed
feat(init): add configurable license generation
1 parent 200db5c commit 82a39e5

7 files changed

Lines changed: 114 additions & 2 deletions

File tree

‎.github/licenses/MIT.txt‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) CURRENT_YEAR PROJECT_OWNER
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎.github/scripts/init-repo.sh‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ package_manager="${REPO_FOUNDRY_PACKAGE_MANAGER:-auto}"
1313
bootstrap=true
1414
release_type="${REPO_FOUNDRY_RELEASE_TYPE:-auto}"
1515
npm_publish="${REPO_FOUNDRY_NPM_PUBLISH:-false}"
16+
license="${REPO_FOUNDRY_LICENSE:-agpl-3.0-or-later}"
17+
license_file="${REPO_FOUNDRY_LICENSE_FILE:-}"
1618
tool_dir=""
1719

1820
cleanup() {
@@ -35,6 +37,8 @@ Options:
3537
ci,codeql,security,test,draft-pr,release-pr,release,dependabot
3638
--package-manager NAME auto, bun, pnpm, yarn, or npm
3739
--release-type NAME auto, node, python, rust, or simple
40+
--license NAME agpl-3.0-or-later, mit, preserve, or none
41+
--license-file PATH Use an exact custom license file
3842
--npm-publish Enable npm publication in the release workflow
3943
--dry-run Preview changes without writing files
4044
--prune Remove disabled standard workflows (never custom workflows)
@@ -58,6 +62,8 @@ while [ "$#" -gt 0 ]; do
5862
--features) features="${2:?missing feature list}"; shift 2 ;;
5963
--package-manager) package_manager="${2:?missing package manager}"; shift 2 ;;
6064
--release-type) release_type="${2:?missing release type}"; shift 2 ;;
65+
--license) license="${2:?missing license}"; shift 2 ;;
66+
--license-file) license_file="${2:?missing license file}"; shift 2 ;;
6167
--npm-publish) npm_publish=true; shift ;;
6268
--dry-run) dry_run=true; shift ;;
6369
--prune) prune=true; shift ;;
@@ -99,7 +105,9 @@ sync_args=(
99105
--languages "$languages"
100106
--features "$features"
101107
--package-manager "$package_manager"
108+
--license "$license"
102109
)
110+
if [ -n "$license_file" ]; then sync_args+=(--license-file "$license_file"); fi
103111
if [ "$dry_run" = true ]; then sync_args+=(--check); else sync_args+=(--apply); fi
104112
if [ "$prune" = true ]; then sync_args+=(--prune); fi
105113
bash "$sync_script" "${sync_args[@]}"
@@ -117,6 +125,7 @@ features="$(awk -F': ' '/^features:/ {print $2; exit}' .github/template.yml 2>/d
117125
package_manager="$(awk -F': ' '/^package_manager:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
118126
release_type="$(awk -F': ' '/^release_type:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
119127
npm_publish="$(awk -F': ' '/^npm_publish:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
128+
license="$(awk -F': ' '/^license:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
120129
{
121130
printf 'version: 1\n'
122131
if [ -n "$template_ref" ]; then
@@ -128,6 +137,7 @@ npm_publish="$(awk -F': ' '/^npm_publish:/ {print $2; exit}' .github/template.ym
128137
printf 'package_manager: %s\n' "$package_manager"
129138
printf 'release_type: %s\n' "$release_type"
130139
printf 'npm_publish: %s\n' "$npm_publish"
140+
printf 'license: %s\n' "$license"
131141
} > .github/template.yml
132142

133143
if [ "$bootstrap" = false ]; then

‎.github/scripts/sync-template.sh‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Options:
1313
--languages LIST auto or comma-separated: typescript,rust,python,solidity
1414
--features LIST all or comma-separated standard features
1515
--package-manager NAME auto, bun, pnpm, yarn, or npm
16+
--license NAME preserve, agpl-3.0-or-later, mit, or none
17+
--license-file PATH Use an exact custom license file
1618
--check Preview changes (default)
1719
--apply Apply changes
1820
--prune Remove disabled standard workflows
@@ -39,10 +41,14 @@ package_manager_set=false
3941
template_ref=""
4042
release_type="${REPO_FOUNDRY_RELEASE_TYPE:-auto}"
4143
npm_publish="${REPO_FOUNDRY_NPM_PUBLISH:-false}"
44+
license="${REPO_FOUNDRY_LICENSE:-preserve}"
45+
license_file="${REPO_FOUNDRY_LICENSE_FILE:-}"
4246
release_type_set=false
4347
npm_publish_set=false
48+
license_set=false
4449
[ -n "${REPO_FOUNDRY_RELEASE_TYPE:-}" ] && release_type_set=true
4550
[ -n "${REPO_FOUNDRY_NPM_PUBLISH:-}" ] && npm_publish_set=true
51+
[ -n "${REPO_FOUNDRY_LICENSE:-}" ] && license_set=true
4652
node_version="24.18.0"
4753
bun_version="1.3.14"
4854
python_version="3.13"
@@ -96,6 +102,8 @@ while [ "$#" -gt 0 ]; do
96102
--languages) languages="${2:?missing language list}"; languages_set=true; shift 2 ;;
97103
--features) features="${2:?missing feature list}"; features_set=true; shift 2 ;;
98104
--package-manager) package_manager="${2:?missing package manager}"; package_manager_set=true; shift 2 ;;
105+
--license) license="${2:?missing license}"; license_set=true; shift 2 ;;
106+
--license-file) license_file="${2:?missing license file}"; shift 2 ;;
99107
--check) mode="check"; shift ;;
100108
--apply) mode="apply"; shift ;;
101109
--prune) prune=true; shift ;;
@@ -130,6 +138,10 @@ if [ -f .github/template.yml ]; then
130138
configured_npm_publish="$(awk -F': ' '/^npm_publish:/ {print $2; exit}' .github/template.yml)"
131139
[ -n "$configured_npm_publish" ] && npm_publish="$configured_npm_publish"
132140
fi
141+
if [ "$license_set" != true ]; then
142+
configured_license="$(awk -F': ' '/^license:/ {print $2; exit}' .github/template.yml)"
143+
[ -n "$configured_license" ] && license="$configured_license"
144+
fi
133145
fi
134146
[ -n "$package_manager" ] || package_manager=auto
135147
case "$package_manager" in
@@ -146,6 +158,10 @@ contains_word "$valid_release_types" "$release_type" || {
146158
printf 'Unsupported release type: %s\n' "$release_type" >&2
147159
exit 2
148160
}
161+
case "$license" in
162+
preserve|agpl-3.0-or-later|mit|none) ;;
163+
*) printf 'Unsupported license: %s\n' "$license" >&2; exit 2 ;;
164+
esac
149165

150166
if [ -d "$source" ] && [ -f "$source/.github/scripts/sync-template.sh" ]; then
151167
template_root="$source"
@@ -276,6 +292,12 @@ for file in "${files[@]}"; do
276292
echo "Template file missing: $file" >&2
277293
exit 1
278294
fi
295+
if { [ "$file" = LICENSE ] || [ "$file" = NOTICE ]; } && [ "$license" = preserve ] && [ -f "$file" ]; then
296+
continue
297+
fi
298+
if { [ "$file" = LICENSE ] || [ "$file" = NOTICE ]; } && [ "$license" = none ]; then
299+
continue
300+
fi
279301
if [ "$file" = .github/CODEOWNERS ] && [ -f "$file" ]; then
280302
continue
281303
fi
@@ -315,6 +337,37 @@ for file in "${files[@]}"; do
315337
fi
316338
done
317339

340+
write_license() {
341+
local owner license_source
342+
[ "$license" != preserve ] || return 0
343+
[ "$license" != none ] || return 0
344+
if [ -n "$license_file" ]; then
345+
[ -f "$license_file" ] || { printf 'License file not found: %s\n' "$license_file" >&2; exit 1; }
346+
license_source="$license_file"
347+
else
348+
case "$license" in
349+
agpl-3.0-or-later) license_source="$template_root/LICENSE" ;;
350+
mit) license_source="$template_root/.github/licenses/MIT.txt" ;;
351+
esac
352+
[ -f "$license_source" ] || { printf 'License template missing: %s\n' "$license_source" >&2; exit 1; }
353+
fi
354+
changed=$((changed + 1))
355+
if [ "$mode" = check ]; then
356+
printf 'Would generate LICENSE (%s)\n' "$license"
357+
return
358+
fi
359+
owner="${REPO_FOUNDRY_LICENSE_OWNER:-$(git config user.name 2>/dev/null || true)}"
360+
[ -n "$owner" ] || owner="Project contributors"
361+
sed "s/PROJECT_OWNER/$owner/g; s/CURRENT_YEAR/$(date +%Y)/g" "$license_source" > LICENSE
362+
{
363+
printf 'Copyright (C) %s %s\n\n' "$(date +%Y)" "$owner"
364+
printf 'This repository is distributed under %s.\n' "$license"
365+
} > NOTICE
366+
printf 'Generated LICENSE and NOTICE for %s.\n' "$license"
367+
}
368+
369+
write_license
370+
318371
# Release Please owns changelog updates after initialization. Preserve an
319372
# existing project history, but give new repositories the expected file.
320373
if [ ! -f CHANGELOG.md ]; then
@@ -458,6 +511,7 @@ if [ "$mode" = "apply" ]; then
458511
fi
459512
printf 'release_type: %s\n' "$release_type"
460513
printf 'npm_publish: %s\n' "$npm_publish"
514+
printf 'license: %s\n' "$license"
461515
} > .github/template.yml
462516
fi
463517

‎.github/template.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ features: all
66
package_manager: bun
77
release_type: node
88
npm_publish: true
9+
license: agpl-3.0-or-later
910
runner: ubuntu-latest
1011
unit_runner: ubuntu-slim
1112
cache_packages: auto

‎.github/template.yml.example‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ features: all
77
package_manager: auto
88
release_type: auto
99
npm_publish: false
10+
license: preserve
1011
runner: ubuntu-latest
1112
unit_runner: ubuntu-slim
1213
cache_packages: auto

‎README.md‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@ npx code-foundry init \
2828
Use `code-foundry sync` to update an existing repository and
2929
`code-foundry doctor` to validate it. Add `--dry-run` to preview changes.
3030

31+
Choose the license during initialization without editing generated files:
32+
33+
```bash
34+
npx code-foundry init --license mit
35+
npx code-foundry init --license agpl-3.0-or-later
36+
npx code-foundry init --license-file ./legal/LICENSE.txt
37+
```
38+
39+
Initialization defaults to AGPL for new repositories. Synchronization defaults
40+
to `--license preserve`, so an existing repository's license is never replaced
41+
unless you explicitly select a license or provide `--license-file`.
42+
3143
## Setup
3244

3345
1. Install [mise](https://mise.jdx.dev/).
@@ -36,10 +48,13 @@ Use `code-foundry sync` to update an existing repository and
3648

3749
## Applying the baseline to an existing repository
3850

39-
Run the sync script from the repository root. It updates shared workflows, GitHub forms, hooks, policy files, and tool configuration while preserving the repository's README, `.mise.toml` selections, extra workflows, and application code.
51+
Run the package entrypoint from the repository root. It updates shared
52+
workflows, GitHub forms, hooks, policy files, and tool configuration while
53+
preserving the repository's README, `.mise.toml` selections, extra workflows,
54+
licenses, and application code.
4055

4156
```bash
42-
bash .github/scripts/init-repo.sh
57+
npx code-foundry init
4358
```
4459

4560
Use `bash .github/scripts/sync-template.sh --source ... --check` to preview differences. For local template development, pass the template checkout as `--source` and `--ref staging`.

‎src/cli.mjs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Init/sync options:
2323
--features LIST all or ci,codeql,security,test,draft-pr,release-pr,release,dependabot
2424
--package-manager NAME auto, bun, pnpm, yarn, or npm
2525
--release-type NAME auto, node, python, rust, simple, or none
26+
--license NAME agpl-3.0-or-later, mit, preserve, or none
27+
--license-file PATH Use an exact custom license file
2628
--npm-publish Enable npm publication in the release workflow
2729
--dry-run Preview changes without writing files
2830
--prune Remove disabled standard workflows
@@ -47,6 +49,8 @@ function parseArgs(argv) {
4749
features: process.env.REPO_FOUNDRY_FEATURES || 'all',
4850
packageManager: process.env.REPO_FOUNDRY_PACKAGE_MANAGER || 'auto',
4951
releaseType: process.env.REPO_FOUNDRY_RELEASE_TYPE || 'auto',
52+
license: process.env.REPO_FOUNDRY_LICENSE || (command === 'init' ? 'agpl-3.0-or-later' : 'preserve'),
53+
licenseFile: process.env.REPO_FOUNDRY_LICENSE_FILE || '',
5054
npmPublish: process.env.REPO_FOUNDRY_NPM_PUBLISH === 'true',
5155
languagesSet: false,
5256
featuresSet: false,
@@ -64,6 +68,8 @@ function parseArgs(argv) {
6468
['--features', 'features'],
6569
['--package-manager', 'packageManager'],
6670
['--release-type', 'releaseType'],
71+
['--license', 'license'],
72+
['--license-file', 'licenseFile'],
6773
])
6874

6975
while (argv.length) {
@@ -107,6 +113,8 @@ function main() {
107113
const { command, options } = parseArgs(process.argv.slice(2))
108114
const target = resolve(options.target)
109115
const common = ['--source', options.source, '--ref', options.ref]
116+
common.push('--license', options.license)
117+
if (options.licenseFile) common.push('--license-file', options.licenseFile)
110118
if (options.languagesSet) common.push('--languages', options.languages)
111119
if (options.featuresSet) common.push('--features', options.features)
112120
if (options.prune) common.push('--prune')
@@ -122,7 +130,9 @@ function main() {
122130
'--features', options.features,
123131
'--package-manager', options.packageManager,
124132
'--release-type', options.releaseType,
133+
'--license', options.license,
125134
]
135+
if (options.licenseFile) initArgs.push('--license-file', options.licenseFile)
126136
if (options.protection) initArgs.push('--protection')
127137
if (options.dryRun) initArgs.push('--dry-run')
128138
if (options.prune) initArgs.push('--prune')

0 commit comments

Comments
 (0)