@wipcomputer/wip-license-guard 1.9.14 → 1.9.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/SKILL.md +23 -3
- package/cli.mjs +35 -38
- package/core.mjs +127 -6
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
###### WIP Computer
|
|
2
|
+
|
|
3
|
+
# License Guard
|
|
4
|
+
|
|
5
|
+
Enforce licensing on every commit. Copyright, dual-license, CLA. Checked automatically.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- Ensures your own repos have correct copyright, license type, and LICENSE files
|
|
10
|
+
- Interactive first-run setup
|
|
11
|
+
- Toolbox-aware: checks every sub-tool
|
|
12
|
+
- Auto-fix mode repairs issues
|
|
13
|
+
- `readme-license` scans all your repos and applies a standard license block to every README in one command
|
|
14
|
+
- Removes duplicate license sections from sub-tool READMEs
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
node tools/wip-license-guard/cli.mjs /path/to/repo
|
|
20
|
+
node tools/wip-license-guard/cli.mjs /path/to/repo --fix
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- node (18+)
|
|
26
|
+
- git
|
|
27
|
+
|
|
28
|
+
## Interfaces
|
|
29
|
+
|
|
30
|
+
- **CLI**: Command-line tool
|
|
31
|
+
|
|
32
|
+
## Part of [AI DevOps Toolbox](https://github.com/wipcomputer/wip-ai-devops-toolbox)
|
package/SKILL.md
CHANGED
|
@@ -29,17 +29,37 @@ metadata:
|
|
|
29
29
|
|
|
30
30
|
# wip-license-guard
|
|
31
31
|
|
|
32
|
-
License compliance for your own repos.
|
|
32
|
+
License compliance for your own repos. Ensures correct copyright, dual-license blocks, LICENSE files, and README license sections.
|
|
33
33
|
|
|
34
34
|
## When to Use This Skill
|
|
35
35
|
|
|
36
36
|
- Before a release, to verify all files have correct license headers
|
|
37
37
|
- After adding new source files to a repo
|
|
38
38
|
- To enforce the MIT/AGPL dual-license pattern
|
|
39
|
+
- To standardize README license sections across all your repos
|
|
39
40
|
|
|
40
41
|
## CLI
|
|
41
42
|
|
|
42
43
|
```bash
|
|
43
|
-
wip-license-guard
|
|
44
|
-
wip-license-guard
|
|
44
|
+
wip-license-guard check [path] # audit repo against config
|
|
45
|
+
wip-license-guard check --fix [path] # auto-fix LICENSE, CLA, copyright issues
|
|
46
|
+
wip-license-guard init [path] # interactive setup
|
|
47
|
+
wip-license-guard init --from-standard # apply WIP Computer defaults (no prompts)
|
|
48
|
+
wip-license-guard readme-license [path] # audit README license sections
|
|
49
|
+
wip-license-guard readme-license --dry-run # preview what would change
|
|
50
|
+
wip-license-guard readme-license --fix # apply standard block to all READMEs
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### readme-license
|
|
54
|
+
|
|
55
|
+
Scans all repos for README license sections. Three modes:
|
|
56
|
+
|
|
57
|
+
- **No flags**: audit only. Reports non-standard, missing, and sub-tool READMEs that shouldn't have license sections.
|
|
58
|
+
- **--dry-run**: preview. Shows what each README has now and what would change. No files touched.
|
|
59
|
+
- **--fix**: apply. Replaces non-standard sections with the standard dual MIT/AGPLv3 block. Removes license sections from sub-tool READMEs.
|
|
60
|
+
|
|
61
|
+
Works on a single repo or a directory of repos:
|
|
62
|
+
```bash
|
|
63
|
+
wip-license-guard readme-license /path/to/one-repo
|
|
64
|
+
wip-license-guard readme-license /path/to/directory-of-repos
|
|
45
65
|
```
|
package/cli.mjs
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
import { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { createInterface } from 'node:readline';
|
|
9
|
-
import { generateLicense, generateReadmeBlock, replaceReadmeLicenseSection, removeReadmeLicenseSection } from './core.mjs';
|
|
9
|
+
import { generateLicense, generateCLA, generateReadmeBlock, replaceReadmeLicenseSection, removeReadmeLicenseSection } from './core.mjs';
|
|
10
10
|
|
|
11
11
|
const args = process.argv.slice(2);
|
|
12
12
|
const HELP_FLAGS = ['--help', '-h', 'help'];
|
|
13
13
|
const command = HELP_FLAGS.some(f => args.includes(f)) ? 'help' : (args.find(a => !a.startsWith('--')) || 'check');
|
|
14
14
|
const target = args.find((a, i) => i > 0 && !a.startsWith('--')) || '.';
|
|
15
15
|
const FIX = args.includes('--fix');
|
|
16
|
+
const DRY_RUN = args.includes('--dry-run');
|
|
16
17
|
const QUIET = args.includes('--quiet');
|
|
17
18
|
const FROM_STANDARD = args.includes('--from-standard');
|
|
18
19
|
|
|
@@ -38,29 +39,6 @@ const WIP_STANDARD = {
|
|
|
38
39
|
attribution: 'Built by Parker Todd Brooks, Lēsa (OpenClaw, Claude Opus 4.6), Claude Code (Claude Opus 4.6).',
|
|
39
40
|
};
|
|
40
41
|
|
|
41
|
-
function generateCLA() {
|
|
42
|
-
return `###### WIP Computer
|
|
43
|
-
|
|
44
|
-
# Contributor License Agreement
|
|
45
|
-
|
|
46
|
-
By submitting a pull request to this repository, you agree to the following:
|
|
47
|
-
|
|
48
|
-
1. **You grant WIP Computer, Inc. a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license** to use, reproduce, modify, distribute, sublicense, and otherwise exploit your contribution under any license, including commercial licenses.
|
|
49
|
-
|
|
50
|
-
2. **You retain copyright** to your contribution. This agreement does not transfer ownership. You can use your own code however you want.
|
|
51
|
-
|
|
52
|
-
3. **You confirm** that your contribution is your original work, or that you have the right to submit it under these terms.
|
|
53
|
-
|
|
54
|
-
4. **You understand** that your contribution may be used in both open source and commercial versions of this software.
|
|
55
|
-
|
|
56
|
-
This is standard open source governance. Apache, Google, Meta, and Anthropic all use similar agreements. The goal is simple: keep the tools free for everyone while allowing WIP Computer, Inc. to offer commercial licenses to companies that need them.
|
|
57
|
-
|
|
58
|
-
Using these tools to build your own software is always free. This agreement only matters if WIP Computer, Inc. needs to relicense the codebase commercially.
|
|
59
|
-
|
|
60
|
-
If you have questions, open an issue or reach out.
|
|
61
|
-
`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
42
|
async function init(repoPath) {
|
|
65
43
|
const configPath = join(repoPath, '.license-guard.json');
|
|
66
44
|
|
|
@@ -74,12 +52,12 @@ async function init(repoPath) {
|
|
|
74
52
|
ok(`Config saved to .license-guard.json`);
|
|
75
53
|
|
|
76
54
|
const licensePath = join(repoPath, 'LICENSE');
|
|
77
|
-
writeFileSync(licensePath, generateLicense(config));
|
|
55
|
+
writeFileSync(licensePath, generateLicense(config, repoPath));
|
|
78
56
|
ok(`LICENSE file generated (dual MIT+AGPLv3)`);
|
|
79
57
|
|
|
80
58
|
const claPath = join(repoPath, 'CLA.md');
|
|
81
59
|
if (!existsSync(claPath)) {
|
|
82
|
-
writeFileSync(claPath, generateCLA());
|
|
60
|
+
writeFileSync(claPath, generateCLA(repoPath));
|
|
83
61
|
ok(`CLA.md generated`);
|
|
84
62
|
} else {
|
|
85
63
|
ok(`CLA.md already exists`);
|
|
@@ -132,14 +110,14 @@ async function init(repoPath) {
|
|
|
132
110
|
ok(`Config saved to .license-guard.json`);
|
|
133
111
|
|
|
134
112
|
const licensePath = join(repoPath, 'LICENSE');
|
|
135
|
-
const licenseText = generateLicense(config);
|
|
113
|
+
const licenseText = generateLicense(config, repoPath);
|
|
136
114
|
writeFileSync(licensePath, licenseText);
|
|
137
115
|
ok(`LICENSE file generated`);
|
|
138
116
|
|
|
139
117
|
// Generate CLA.md if it doesn't exist
|
|
140
118
|
const claPath = join(repoPath, 'CLA.md');
|
|
141
119
|
if (!existsSync(claPath)) {
|
|
142
|
-
writeFileSync(claPath, generateCLA());
|
|
120
|
+
writeFileSync(claPath, generateCLA(repoPath));
|
|
143
121
|
ok(`CLA.md generated`);
|
|
144
122
|
}
|
|
145
123
|
|
|
@@ -173,7 +151,7 @@ async function check(repoPath) {
|
|
|
173
151
|
warn('LICENSE file missing');
|
|
174
152
|
issues++;
|
|
175
153
|
if (FIX) {
|
|
176
|
-
writeFileSync(licensePath, generateLicense(config));
|
|
154
|
+
writeFileSync(licensePath, generateLicense(config, repoPath));
|
|
177
155
|
ok('LICENSE file created (--fix)');
|
|
178
156
|
issues--;
|
|
179
157
|
}
|
|
@@ -184,7 +162,7 @@ async function check(repoPath) {
|
|
|
184
162
|
warn(`LICENSE copyright does not match "${config.copyright}"`);
|
|
185
163
|
issues++;
|
|
186
164
|
if (FIX) {
|
|
187
|
-
writeFileSync(licensePath, generateLicense(config));
|
|
165
|
+
writeFileSync(licensePath, generateLicense(config, repoPath));
|
|
188
166
|
ok('LICENSE file updated (--fix)');
|
|
189
167
|
issues--;
|
|
190
168
|
}
|
|
@@ -197,7 +175,7 @@ async function check(repoPath) {
|
|
|
197
175
|
warn('LICENSE file is MIT-only but config says MIT+AGPL');
|
|
198
176
|
issues++;
|
|
199
177
|
if (FIX) {
|
|
200
|
-
writeFileSync(licensePath, generateLicense(config));
|
|
178
|
+
writeFileSync(licensePath, generateLicense(config, repoPath));
|
|
201
179
|
ok('LICENSE file updated to dual-license (--fix)');
|
|
202
180
|
issues--;
|
|
203
181
|
}
|
|
@@ -213,7 +191,7 @@ async function check(repoPath) {
|
|
|
213
191
|
warn('CLA.md missing');
|
|
214
192
|
issues++;
|
|
215
193
|
if (FIX) {
|
|
216
|
-
writeFileSync(claPath, generateCLA());
|
|
194
|
+
writeFileSync(claPath, generateCLA(repoPath));
|
|
217
195
|
ok('CLA.md created (--fix)');
|
|
218
196
|
issues--;
|
|
219
197
|
}
|
|
@@ -288,7 +266,7 @@ async function check(repoPath) {
|
|
|
288
266
|
warn(`tools/${entry.name}/LICENSE missing`);
|
|
289
267
|
issues++;
|
|
290
268
|
if (FIX) {
|
|
291
|
-
writeFileSync(toolLicense, generateLicense(config));
|
|
269
|
+
writeFileSync(toolLicense, generateLicense(config, repoPath));
|
|
292
270
|
ok(`tools/${entry.name}/LICENSE created (--fix)`);
|
|
293
271
|
issues--;
|
|
294
272
|
}
|
|
@@ -298,7 +276,7 @@ async function check(repoPath) {
|
|
|
298
276
|
warn(`tools/${entry.name}/LICENSE wrong copyright`);
|
|
299
277
|
issues++;
|
|
300
278
|
if (FIX) {
|
|
301
|
-
writeFileSync(toolLicense, generateLicense(config));
|
|
279
|
+
writeFileSync(toolLicense, generateLicense(config, repoPath));
|
|
302
280
|
ok(`tools/${entry.name}/LICENSE updated (--fix)`);
|
|
303
281
|
issues--;
|
|
304
282
|
}
|
|
@@ -321,7 +299,8 @@ async function check(repoPath) {
|
|
|
321
299
|
}
|
|
322
300
|
|
|
323
301
|
async function readmeLicense(targetPath) {
|
|
324
|
-
|
|
302
|
+
const mode = FIX ? '--fix' : DRY_RUN ? '--dry-run' : '';
|
|
303
|
+
log(`\n wip-license-guard readme-license${mode ? ' ' + mode : ''}\n`);
|
|
325
304
|
|
|
326
305
|
// Detect if targetPath is a single repo or a directory of repos
|
|
327
306
|
const repos = [];
|
|
@@ -376,8 +355,16 @@ async function readmeLicense(targetPath) {
|
|
|
376
355
|
} else if (content.includes('## License')) {
|
|
377
356
|
warn(`${repoName}/README.md ... non-standard license section`);
|
|
378
357
|
totalIssues++;
|
|
358
|
+
if (DRY_RUN) {
|
|
359
|
+
// Extract current license section for preview
|
|
360
|
+
const match = content.match(/## License[\s\S]*?(?=\n## [^#]|$)/);
|
|
361
|
+
if (match) {
|
|
362
|
+
log(` current: ${match[0].split('\n').slice(0, 3).join(' | ').substring(0, 80)}...`);
|
|
363
|
+
}
|
|
364
|
+
log(` would replace with: standard dual MIT/AGPLv3 block`);
|
|
365
|
+
}
|
|
379
366
|
if (FIX) {
|
|
380
|
-
const updated = replaceReadmeLicenseSection(content, config);
|
|
367
|
+
const updated = replaceReadmeLicenseSection(content, config, repoPath);
|
|
381
368
|
writeFileSync(readmePath, updated);
|
|
382
369
|
ok(`${repoName}/README.md ... updated to standard (--fix)`);
|
|
383
370
|
totalIssues--;
|
|
@@ -385,8 +372,11 @@ async function readmeLicense(targetPath) {
|
|
|
385
372
|
} else {
|
|
386
373
|
warn(`${repoName}/README.md ... missing ## License`);
|
|
387
374
|
totalIssues++;
|
|
375
|
+
if (DRY_RUN) {
|
|
376
|
+
log(` would add: standard dual MIT/AGPLv3 block at end of README`);
|
|
377
|
+
}
|
|
388
378
|
if (FIX) {
|
|
389
|
-
const updated = replaceReadmeLicenseSection(content, config);
|
|
379
|
+
const updated = replaceReadmeLicenseSection(content, config, repoPath);
|
|
390
380
|
writeFileSync(readmePath, updated);
|
|
391
381
|
ok(`${repoName}/README.md ... added standard block (--fix)`);
|
|
392
382
|
totalIssues--;
|
|
@@ -410,6 +400,9 @@ async function readmeLicense(targetPath) {
|
|
|
410
400
|
if (subContent.includes('## License')) {
|
|
411
401
|
warn(`${repoName}/tools/${tool.name}/README.md ... has license section (should be removed)`);
|
|
412
402
|
totalIssues++;
|
|
403
|
+
if (DRY_RUN) {
|
|
404
|
+
log(` would remove: ## License section from sub-tool README`);
|
|
405
|
+
}
|
|
413
406
|
if (FIX) {
|
|
414
407
|
const cleaned = removeReadmeLicenseSection(subContent);
|
|
415
408
|
writeFileSync(subReadme, cleaned);
|
|
@@ -425,8 +418,11 @@ async function readmeLicense(targetPath) {
|
|
|
425
418
|
log('');
|
|
426
419
|
if (totalIssues === 0) {
|
|
427
420
|
log(' All README license sections are correct.\n');
|
|
421
|
+
} else if (DRY_RUN) {
|
|
422
|
+
log(` ${totalIssues} issue(s) found. Dry run complete. No changes made.`);
|
|
423
|
+
log(` Run with --fix to apply changes.\n`);
|
|
428
424
|
} else {
|
|
429
|
-
log(` ${totalIssues} issue(s) found. Run with --fix to
|
|
425
|
+
log(` ${totalIssues} issue(s) found. Run with --dry-run to preview or --fix to apply.\n`);
|
|
430
426
|
}
|
|
431
427
|
|
|
432
428
|
return totalIssues;
|
|
@@ -453,6 +449,7 @@ if (command === 'init') {
|
|
|
453
449
|
check [path] Audit repo against saved config. Exit 1 if issues found.
|
|
454
450
|
check --fix [path] Auto-fix issues (update LICENSE files, wrong copyright).
|
|
455
451
|
readme-license [path] Scan README license sections. Works on one repo or a directory of repos.
|
|
452
|
+
readme-license --dry-run Preview what would change. Shows current vs standard for each README.
|
|
456
453
|
readme-license --fix Apply standard license block to all READMEs. Remove from sub-tools.
|
|
457
454
|
help Show this help.
|
|
458
455
|
|
package/core.mjs
CHANGED
|
@@ -1,12 +1,81 @@
|
|
|
1
1
|
// wip-license-guard/core.mjs
|
|
2
2
|
// License generation and validation logic.
|
|
3
|
+
// Reads templates from ai/wip-templates/readme/ when available.
|
|
4
|
+
// Falls back to hardcoded defaults for standalone use.
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { join, dirname } from 'node:path';
|
|
8
|
+
|
|
9
|
+
// ── Template Resolution ─────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Find the templates directory. Checks:
|
|
13
|
+
* 1. WIP_TEMPLATES_DIR env var
|
|
14
|
+
* 2. Walk up from repoPath looking for ai/wip-templates/readme/
|
|
15
|
+
* 3. Walk up from this file's location (for toolbox-internal use)
|
|
16
|
+
* Returns null if not found.
|
|
17
|
+
*/
|
|
18
|
+
function findTemplatesDir(repoPath) {
|
|
19
|
+
// 1. Env var
|
|
20
|
+
const envDir = process.env.WIP_TEMPLATES_DIR;
|
|
21
|
+
if (envDir && existsSync(join(envDir, 'LICENSE.md'))) return envDir;
|
|
22
|
+
|
|
23
|
+
// 2. Walk up from repoPath
|
|
24
|
+
if (repoPath) {
|
|
25
|
+
let dir = repoPath;
|
|
26
|
+
for (let i = 0; i < 10; i++) {
|
|
27
|
+
const candidate = join(dir, 'ai', 'wip-templates', 'readme');
|
|
28
|
+
if (existsSync(join(candidate, 'LICENSE.md'))) return candidate;
|
|
29
|
+
const parent = dirname(dir);
|
|
30
|
+
if (parent === dir) break;
|
|
31
|
+
dir = parent;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 3. Walk up from this file (tools/wip-license-guard/ -> repo root)
|
|
36
|
+
const thisDir = dirname(new URL(import.meta.url).pathname);
|
|
37
|
+
let dir = thisDir;
|
|
38
|
+
for (let i = 0; i < 10; i++) {
|
|
39
|
+
const candidate = join(dir, 'ai', 'wip-templates', 'readme');
|
|
40
|
+
if (existsSync(join(candidate, 'LICENSE.md'))) return candidate;
|
|
41
|
+
const parent = dirname(dir);
|
|
42
|
+
if (parent === dir) break;
|
|
43
|
+
dir = parent;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Read a template file. Returns content or null.
|
|
51
|
+
*/
|
|
52
|
+
function readTemplate(templatesDir, filename) {
|
|
53
|
+
if (!templatesDir) return null;
|
|
54
|
+
const path = join(templatesDir, filename);
|
|
55
|
+
if (!existsSync(path)) return null;
|
|
56
|
+
return readFileSync(path, 'utf8');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Extract the markdown format section from wip-lic-footer.md.
|
|
61
|
+
* The file has two sections: // PLAIN TXT and // MD FORMAT.
|
|
62
|
+
* Returns the MD FORMAT section, or the whole file if no marker found.
|
|
63
|
+
*/
|
|
64
|
+
function extractMdFormat(content) {
|
|
65
|
+
const marker = '// MD FORMAT';
|
|
66
|
+
const idx = content.indexOf(marker);
|
|
67
|
+
if (idx === -1) return content;
|
|
68
|
+
return content.slice(idx + marker.length).trim();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── License Generation ──────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
export function generateLicense(config, repoPath) {
|
|
5
74
|
const { copyright, license, year } = config;
|
|
6
75
|
|
|
7
76
|
if (license === 'MIT') return generateMIT(copyright, year);
|
|
8
77
|
if (license === 'AGPL-3.0') return generateAGPL(copyright, year);
|
|
9
|
-
if (license === 'MIT+AGPL') return generateDual(copyright, year);
|
|
78
|
+
if (license === 'MIT+AGPL') return generateDual(copyright, year, repoPath);
|
|
10
79
|
|
|
11
80
|
return generateMIT(copyright, year);
|
|
12
81
|
}
|
|
@@ -56,7 +125,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
56
125
|
`;
|
|
57
126
|
}
|
|
58
127
|
|
|
59
|
-
function generateDual(copyright, year) {
|
|
128
|
+
function generateDual(copyright, year, repoPath) {
|
|
129
|
+
// Try template first
|
|
130
|
+
const templatesDir = findTemplatesDir(repoPath);
|
|
131
|
+
const template = readTemplate(templatesDir, 'LICENSE.md');
|
|
132
|
+
if (template) {
|
|
133
|
+
// Replace copyright year if template has a different one
|
|
134
|
+
return template.replace(/Copyright \(c\) \d{4}/, `Copyright (c) ${year}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Hardcoded fallback
|
|
60
138
|
return `Dual License: MIT + AGPLv3
|
|
61
139
|
|
|
62
140
|
Copyright (c) ${year} ${copyright}
|
|
@@ -112,7 +190,40 @@ AGPLv3 for personal use is free. Commercial licenses available.
|
|
|
112
190
|
`;
|
|
113
191
|
}
|
|
114
192
|
|
|
115
|
-
|
|
193
|
+
// ── CLA Generation ──────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
export function generateCLA(repoPath) {
|
|
196
|
+
// Try template first
|
|
197
|
+
const templatesDir = findTemplatesDir(repoPath);
|
|
198
|
+
const template = readTemplate(templatesDir, 'cla.md');
|
|
199
|
+
if (template) return template;
|
|
200
|
+
|
|
201
|
+
// Hardcoded fallback
|
|
202
|
+
return `###### WIP Computer
|
|
203
|
+
|
|
204
|
+
# Contributor License Agreement
|
|
205
|
+
|
|
206
|
+
By submitting a pull request to this repository, you agree to the following:
|
|
207
|
+
|
|
208
|
+
1. **You grant WIP Computer, Inc. a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license** to use, reproduce, modify, distribute, sublicense, and otherwise exploit your contribution under any license, including commercial licenses.
|
|
209
|
+
|
|
210
|
+
2. **You retain copyright** to your contribution. This agreement does not transfer ownership. You can use your own code however you want.
|
|
211
|
+
|
|
212
|
+
3. **You confirm** that your contribution is your original work, or that you have the right to submit it under these terms.
|
|
213
|
+
|
|
214
|
+
4. **You understand** that your contribution may be used in both open source and commercial versions of this software.
|
|
215
|
+
|
|
216
|
+
This is standard open source governance. Apache, Google, Meta, and Anthropic all use similar agreements. The goal is simple: keep the tools free for everyone while allowing WIP Computer, Inc. to offer commercial licenses to companies that need them.
|
|
217
|
+
|
|
218
|
+
Using these tools to build your own software is always free. This agreement only matters if WIP Computer, Inc. needs to relicense the codebase commercially.
|
|
219
|
+
|
|
220
|
+
If you have questions, open an issue or reach out.
|
|
221
|
+
`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── README License Block ────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
export function generateReadmeBlock(config, repoPath) {
|
|
116
227
|
const { license, attribution } = config;
|
|
117
228
|
|
|
118
229
|
if (license === 'MIT') {
|
|
@@ -129,6 +240,14 @@ AGPLv3. AGPLv3 for personal use is free.${attribution ? '\n\n' + attribution : '
|
|
|
129
240
|
`;
|
|
130
241
|
}
|
|
131
242
|
|
|
243
|
+
// MIT+AGPL: try template first
|
|
244
|
+
const templatesDir = findTemplatesDir(repoPath);
|
|
245
|
+
const footer = readTemplate(templatesDir, 'wip-lic-footer.md');
|
|
246
|
+
if (footer) {
|
|
247
|
+
return extractMdFormat(footer);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Hardcoded fallback
|
|
132
251
|
return `## License
|
|
133
252
|
|
|
134
253
|
Dual-license model designed to keep tools free while preventing commercial resellers.
|
|
@@ -160,13 +279,15 @@ By submitting a PR, you agree to the [Contributor License Agreement](CLA.md).
|
|
|
160
279
|
${attribution ? '\n' + attribution : ''}`;
|
|
161
280
|
}
|
|
162
281
|
|
|
282
|
+
// ── README License Section Replace/Remove ───────────────────────────
|
|
283
|
+
|
|
163
284
|
/**
|
|
164
285
|
* Replace ## License section in readme content.
|
|
165
286
|
* If no ## License exists, appends the block at the end.
|
|
166
287
|
* Returns the updated content.
|
|
167
288
|
*/
|
|
168
|
-
export function replaceReadmeLicenseSection(content, config) {
|
|
169
|
-
const block = generateReadmeBlock(config);
|
|
289
|
+
export function replaceReadmeLicenseSection(content, config, repoPath) {
|
|
290
|
+
const block = generateReadmeBlock(config, repoPath);
|
|
170
291
|
|
|
171
292
|
// Match from "## License" to the next ## heading or end of file
|
|
172
293
|
const licenseRegex = /## License[\s\S]*?(?=\n## [^#]|\n---\s*$|$)/;
|
package/package.json
CHANGED