@myooken/license-output 0.2.0 → 0.2.1
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 +10 -3
- package/package.json +1 -1
- package/src/cli.js +10 -3
- package/src/constants.js +4 -1
- package/src/core.js +20 -5
- package/src/existing.js +7 -1
- package/src/render.js +16 -8
- package/src/scan.js +4 -2
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Third-Party License Output for node_modules
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@myooken/license-output)
|
|
4
|
+
[](https://www.npmjs.com/package/@myooken/license-output)
|
|
5
|
+
[](https://www.npmjs.com/package/@myooken/license-output)
|
|
6
|
+
|
|
7
|
+
https://www.npmjs.com/package/@myooken/license-output
|
|
8
|
+
|
|
3
9
|
### What is this?
|
|
4
10
|
|
|
5
11
|
A tool to scan `node_modules` and **output third-party licenses in Markdown**.
|
|
@@ -8,7 +14,7 @@ It generates two files: `THIRD-PARTY-LICENSE.md` (main content) and `THIRD-PARTY
|
|
|
8
14
|
### Highlights
|
|
9
15
|
|
|
10
16
|
- **ESM / Node.js 18+**, zero dependencies
|
|
11
|
-
- **Outputs full license texts** from LICENSE/NOTICE/COPYING files
|
|
17
|
+
- **Outputs full license texts** from LICENSE/NOTICE/THIRD-PARTY-NOTICES/COPYING files
|
|
12
18
|
- **Review file** flags missing Source / license / license files
|
|
13
19
|
- `--fail-on-missing` supports CI enforcement
|
|
14
20
|
|
|
@@ -44,7 +50,7 @@ third-party-license
|
|
|
44
50
|
| `--license [file]` | Write main file only; optional filename | `THIRD-PARTY-LICENSE.md` |
|
|
45
51
|
| `--recreate` | Regenerate files from current `node_modules` only (drops removed packages) | `true` (default) |
|
|
46
52
|
| `--update` | Merge with existing outputs, keep removed packages, and mark their presence | `false` |
|
|
47
|
-
| `--fail-on-missing` | Exit with code 1 if LICENSE/NOTICE/COPYING are missing
|
|
53
|
+
| `--fail-on-missing` | Exit with code 1 if LICENSE/NOTICE/THIRD-PARTY-NOTICES/COPYING are missing | `false` |
|
|
48
54
|
| `-h`, `--help` | Show help | - |
|
|
49
55
|
|
|
50
56
|
> If neither `--review` nor `--license` is specified, **both files are generated**.
|
|
@@ -98,7 +104,7 @@ Outputs are sorted by package key. Use `mode: "update"` to merge with existing f
|
|
|
98
104
|
- **THIRD-PARTY-LICENSE.md**
|
|
99
105
|
- List of packages
|
|
100
106
|
- Source / License info
|
|
101
|
-
- Full LICENSE/NOTICE/COPYING texts
|
|
107
|
+
- Full LICENSE/NOTICE/THIRD-PARTY-NOTICES/COPYING texts
|
|
102
108
|
- Usage line shows whether the package is present in the current `node_modules`
|
|
103
109
|
- **THIRD-PARTY-LICENSE-REVIEW.md**
|
|
104
110
|
- Review-oriented checklist
|
|
@@ -123,3 +129,4 @@ Outputs are sorted by package key. Use `mode: "update"` to merge with existing f
|
|
|
123
129
|
- Exit code 1: missing license files when `--fail-on-missing` is set, or `node_modules` not found.
|
|
124
130
|
- Throws an error if `node_modules` does not exist.
|
|
125
131
|
- Missing `license` or `repository` fields are flagged in the review file.
|
|
132
|
+
- Paths printed in outputs/logs are shown relative to the current working directory.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import fsp from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { collectThirdPartyLicenses, DEFAULT_OPTIONS } from "./core.js";
|
|
7
|
+
import { LICENSE_FILES_LABEL } from "./constants.js";
|
|
7
8
|
|
|
8
9
|
// 引数パース: --review / --license は最後に指定されたものを優先し、直後の値があれば出力ファイル名として扱う
|
|
9
10
|
function parseArgs(argv) {
|
|
@@ -107,12 +108,18 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
107
108
|
await Promise.all(writeTasks);
|
|
108
109
|
|
|
109
110
|
if (result.options.writeMain)
|
|
110
|
-
console.log(
|
|
111
|
+
console.log(
|
|
112
|
+
`Generated: ${result.options.outFileDisplay ?? result.options.outFile}`
|
|
113
|
+
);
|
|
111
114
|
if (result.options.writeReview)
|
|
112
|
-
console.log(
|
|
115
|
+
console.log(
|
|
116
|
+
`Review: ${
|
|
117
|
+
result.options.reviewFileDisplay ?? result.options.reviewFile
|
|
118
|
+
}`
|
|
119
|
+
);
|
|
113
120
|
console.log(`Packages: ${result.stats.packages}`);
|
|
114
121
|
console.log(
|
|
115
|
-
`Missing
|
|
122
|
+
`Missing ${LICENSE_FILES_LABEL}: ${result.stats.missingFiles.length}`
|
|
116
123
|
);
|
|
117
124
|
|
|
118
125
|
if (result.stats.missingFiles.length > 0 && result.options.failOnMissing) {
|
package/src/constants.js
CHANGED
|
@@ -9,4 +9,7 @@ export const DEFAULT_OPTIONS = {
|
|
|
9
9
|
|
|
10
10
|
// ライセンスらしいファイル名を検出する正規表現
|
|
11
11
|
export const LICENSE_LIKE_RE =
|
|
12
|
-
/^(LICEN[CS]E|COPYING|NOTICE)(\..*)?$|^(LICEN[CS]E|COPYING|NOTICE)-/i;
|
|
12
|
+
/^(LICEN[CS]E|COPYING|NOTICE|THIRD[-_. ]?PARTY[-_. ]?NOTICES?)(\..*)?$|^(LICEN[CS]E|COPYING|NOTICE|THIRD[-_. ]?PARTY[-_. ]?NOTICES?)-/i;
|
|
13
|
+
|
|
14
|
+
export const LICENSE_FILES_LABEL =
|
|
15
|
+
"LICENSE/NOTICE/THIRD-PARTY-NOTICES/COPYING";
|
package/src/core.js
CHANGED
|
@@ -54,12 +54,22 @@ export async function collectThirdPartyLicenses(options = {}) {
|
|
|
54
54
|
export { DEFAULT_OPTIONS } from "./constants.js";
|
|
55
55
|
|
|
56
56
|
function normalizeOptions(options) {
|
|
57
|
+
const cwd = process.cwd();
|
|
58
|
+
const nodeModules = path.resolve(
|
|
59
|
+
options.nodeModules ?? DEFAULT_OPTIONS.nodeModules
|
|
60
|
+
);
|
|
61
|
+
const outFile = path.resolve(options.outFile ?? DEFAULT_OPTIONS.outFile);
|
|
62
|
+
const reviewFile = path.resolve(
|
|
63
|
+
options.reviewFile ?? DEFAULT_OPTIONS.reviewFile
|
|
64
|
+
);
|
|
65
|
+
|
|
57
66
|
return {
|
|
58
|
-
nodeModules
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
nodeModules,
|
|
68
|
+
outFile,
|
|
69
|
+
reviewFile,
|
|
70
|
+
nodeModulesDisplay: makeDisplayPath(nodeModules, cwd),
|
|
71
|
+
outFileDisplay: makeDisplayPath(outFile, cwd),
|
|
72
|
+
reviewFileDisplay: makeDisplayPath(reviewFile, cwd),
|
|
63
73
|
failOnMissing: Boolean(
|
|
64
74
|
options.failOnMissing ?? DEFAULT_OPTIONS.failOnMissing
|
|
65
75
|
),
|
|
@@ -75,6 +85,11 @@ function normalizeMode(mode) {
|
|
|
75
85
|
return m === "update" ? "update" : DEFAULT_OPTIONS.mode;
|
|
76
86
|
}
|
|
77
87
|
|
|
88
|
+
function makeDisplayPath(targetPath, cwd) {
|
|
89
|
+
const rel = path.relative(cwd, targetPath);
|
|
90
|
+
return rel || ".";
|
|
91
|
+
}
|
|
92
|
+
|
|
78
93
|
async function assertNodeModulesExists(dir) {
|
|
79
94
|
const stat = await fsp.stat(dir).catch(() => null);
|
|
80
95
|
if (!stat || !stat.isDirectory()) {
|
package/src/existing.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fsp from "node:fs/promises";
|
|
2
2
|
import { makeAnchorId, uniqSorted } from "./fs-utils.js";
|
|
3
|
+
import { LICENSE_FILES_LABEL } from "./constants.js";
|
|
3
4
|
|
|
4
5
|
export async function parseExistingMainFile(filePath) {
|
|
5
6
|
const content = await readFileSafe(filePath);
|
|
@@ -82,7 +83,12 @@ function parseMainBlock(key, body) {
|
|
|
82
83
|
) {
|
|
83
84
|
continue;
|
|
84
85
|
}
|
|
85
|
-
if (
|
|
86
|
+
if (
|
|
87
|
+
val.startsWith("(no LICENSE/NOTICE/COPYING files)") ||
|
|
88
|
+
val.startsWith(`(no ${LICENSE_FILES_LABEL} files)`)
|
|
89
|
+
) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
86
92
|
fileNames.push(val);
|
|
87
93
|
}
|
|
88
94
|
|
package/src/render.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { mdSafeText, uniqSorted } from "./fs-utils.js";
|
|
4
|
+
import { LICENSE_FILES_LABEL } from "./constants.js";
|
|
4
5
|
|
|
5
6
|
// メインのTHIRD-PARTY-LICENSE.mdを描画する
|
|
6
7
|
export function renderMain(packages, opts) {
|
|
@@ -9,7 +10,7 @@ export function renderMain(packages, opts) {
|
|
|
9
10
|
|
|
10
11
|
push("# Third-Party Licenses");
|
|
11
12
|
push("");
|
|
12
|
-
push(`Generated from: ${opts.nodeModules}`);
|
|
13
|
+
push(`Generated from: ${opts.nodeModulesDisplay ?? opts.nodeModules}`);
|
|
13
14
|
push("");
|
|
14
15
|
|
|
15
16
|
for (const pkg of packages) {
|
|
@@ -21,9 +22,9 @@ export function renderMain(packages, opts) {
|
|
|
21
22
|
|
|
22
23
|
const fileNames = pkg.fileNames ?? [];
|
|
23
24
|
if (fileNames.length === 0) {
|
|
24
|
-
push(
|
|
25
|
+
push(`- (no ${LICENSE_FILES_LABEL} files)`);
|
|
25
26
|
push("");
|
|
26
|
-
push(
|
|
27
|
+
push(`_No ${LICENSE_FILES_LABEL} file found in package directory._`);
|
|
27
28
|
push("");
|
|
28
29
|
continue;
|
|
29
30
|
}
|
|
@@ -53,17 +54,17 @@ export function renderReview(
|
|
|
53
54
|
) {
|
|
54
55
|
const lines = [];
|
|
55
56
|
const push = (s = "") => lines.push(s);
|
|
56
|
-
const
|
|
57
|
+
const mainPath = makeMainLinkPath(opts);
|
|
57
58
|
|
|
58
59
|
push("# THIRD-PARTY-LICENSE-REVIEW");
|
|
59
60
|
push("");
|
|
60
|
-
push(`Generated from: ${opts.nodeModules}`);
|
|
61
|
-
push(`Main file: ${
|
|
61
|
+
push(`Generated from: ${opts.nodeModulesDisplay ?? opts.nodeModules}`);
|
|
62
|
+
push(`Main file: ${mainPath}`);
|
|
62
63
|
push("");
|
|
63
64
|
|
|
64
65
|
for (const it of [...packages].sort((a, b) => a.key.localeCompare(b.key))) {
|
|
65
66
|
push(`## ${it.key}`);
|
|
66
|
-
push(`- Main: ${
|
|
67
|
+
push(`- Main: ${mainPath}#${it.anchor}`);
|
|
67
68
|
push(`- Source: ${it.source ?? "(missing)"}`);
|
|
68
69
|
push(`- License: ${it.license ?? "(missing)"}`);
|
|
69
70
|
|
|
@@ -107,11 +108,18 @@ export function renderReview(
|
|
|
107
108
|
|
|
108
109
|
writeList("Missing Source", missingSource);
|
|
109
110
|
writeList("Missing package.json license field", missingLicenseField);
|
|
110
|
-
writeList(
|
|
111
|
+
writeList(`Missing ${LICENSE_FILES_LABEL} files`, missingFiles);
|
|
111
112
|
|
|
112
113
|
return lines.join(os.EOL) + os.EOL;
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
function makeMainLinkPath(opts) {
|
|
117
|
+
const baseDir = path.dirname(opts.reviewFile);
|
|
118
|
+
const rel = path.relative(baseDir, opts.outFile);
|
|
119
|
+
const normalized = rel.replace(/\\/g, "/");
|
|
120
|
+
return normalized || path.basename(opts.outFile);
|
|
121
|
+
}
|
|
122
|
+
|
|
115
123
|
function describeUsage(usage) {
|
|
116
124
|
if (usage === "missing") {
|
|
117
125
|
return "Not found in node_modules (kept from previous output)";
|
package/src/scan.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
walkForPackageJson,
|
|
9
9
|
} from "./fs-utils.js";
|
|
10
10
|
import { getRepositoryUrl } from "./url.js";
|
|
11
|
+
import { LICENSE_FILES_LABEL } from "./constants.js";
|
|
11
12
|
|
|
12
13
|
// node_modules を走査してパッケージ情報を集約する
|
|
13
14
|
export async function gatherPackages(opts) {
|
|
@@ -57,8 +58,9 @@ export async function gatherPackages(opts) {
|
|
|
57
58
|
|
|
58
59
|
if (licFiles.length === 0) {
|
|
59
60
|
missingFiles.push(key);
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
const missingMsg = `Missing ${LICENSE_FILES_LABEL} files`;
|
|
62
|
+
flags.push(missingMsg);
|
|
63
|
+
opts.warn(`Missing ${LICENSE_FILES_LABEL} in ${pkgDir} (${key})`);
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
const licenseTexts =
|