@hellpig/anarchy-legal 1.11.1 → 1.12.4
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/CHANGELOG.md +1 -0
- package/README.md +1 -1
- package/dist/Constants/LegalDocumentType.js +15 -0
- package/dist/Constants/LegalDocumentType.js.map +1 -0
- package/dist/Services/LegalFilesService.js +66 -0
- package/dist/Services/LegalFilesService.js.map +1 -0
- package/dist/Services/LegalFilesUtilsService.js +246 -0
- package/dist/Services/LegalFilesUtilsService.js.map +1 -0
- package/dist/Services/NoticeService.js +132 -0
- package/dist/Services/NoticeService.js.map +1 -0
- package/dist/Services/NoticeUtilsService.js +125 -0
- package/dist/Services/NoticeUtilsService.js.map +1 -0
- package/dist/Services/RepoUtilsService.js +450 -0
- package/dist/Services/RepoUtilsService.js.map +1 -0
- package/dist/Services/ThirdPartyLicensesService.js +129 -0
- package/dist/Services/ThirdPartyLicensesService.js.map +1 -0
- package/dist/index.js +7 -1111
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -31,4 +31,4 @@ SOFTWARE.
|
|
|
31
31
|
See local files `LICENSE`, `CHANGELOG`, `NOTICE` (pointer), and files in `./legal`:
|
|
32
32
|
`DISCLAIMER`, `EULA`, `PRIVACY`, `SECURITY`, `NOTICE` (full), `THIRD_PARTY_LICENSES`.
|
|
33
33
|
|
|
34
|
-
Contacts — Privacy:
|
|
34
|
+
Contacts — Privacy: pnf036+anarchy@gmail.com, Security: pnf036+anarchy_security@gmail.com.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const LegalDocumentType = {
|
|
2
|
+
DISCLAIMER: "DISCLAIMER",
|
|
3
|
+
EULA: "EULA",
|
|
4
|
+
EU_DECLARATION_OF_CONFORMITY: "EU_DECLARATION_OF_CONFORMITY",
|
|
5
|
+
INSTRUCTIONS: "INSTRUCTIONS",
|
|
6
|
+
PRIVACY: "PRIVACY",
|
|
7
|
+
SECURITY: "SECURITY",
|
|
8
|
+
SUPPORT: "SUPPORT",
|
|
9
|
+
TECHNICAL_DOCUMENTATION: "TECHNICAL_DOCUMENTATION",
|
|
10
|
+
VULN_HANDLING: "VULN_HANDLING"
|
|
11
|
+
};
|
|
12
|
+
export {
|
|
13
|
+
LegalDocumentType
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=LegalDocumentType.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LegalDocumentType.js","sources":["../../src/Constants/LegalDocumentType.ts"],"sourcesContent":["import type { TLegalDocumentType } from '@Anarchy/Legal/Models';\n\nexport const LegalDocumentType: Readonly<Record<TLegalDocumentType, TLegalDocumentType>> = {\n DISCLAIMER: 'DISCLAIMER',\n EULA: 'EULA',\n EU_DECLARATION_OF_CONFORMITY: 'EU_DECLARATION_OF_CONFORMITY',\n INSTRUCTIONS: 'INSTRUCTIONS',\n PRIVACY: 'PRIVACY',\n SECURITY: 'SECURITY',\n SUPPORT: 'SUPPORT',\n TECHNICAL_DOCUMENTATION: 'TECHNICAL_DOCUMENTATION',\n VULN_HANDLING: 'VULN_HANDLING'\n};\n"],"names":[],"mappings":"AAEO,MAAM,oBAA8E;AAAA,EACzF,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,8BAA8B;AAAA,EAC9B,cAAc;AAAA,EACd,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,yBAAyB;AAAA,EACzB,eAAe;AACjB;"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import yargs from "yargs";
|
|
4
|
+
import { hideBin } from "yargs/helpers";
|
|
5
|
+
import { LegalDocumentType } from "../Constants/LegalDocumentType.js";
|
|
6
|
+
import { LegalFilesUtilsService } from "./LegalFilesUtilsService.js";
|
|
7
|
+
import { RepoUtilsService } from "./RepoUtilsService.js";
|
|
8
|
+
function LegalFilesService() {
|
|
9
|
+
let isDebug = false;
|
|
10
|
+
const repoUtilsService = RepoUtilsService();
|
|
11
|
+
const legalFilesUtilsService = LegalFilesUtilsService(repoUtilsService);
|
|
12
|
+
const { debugLog, findMonorepoRoot, resolveWorkspaceFromArg, loadWorkspaces } = repoUtilsService;
|
|
13
|
+
const { assertTemplatesPresent, getConfiguredDocKeys, generateAll, readConfig } = legalFilesUtilsService;
|
|
14
|
+
const options = {
|
|
15
|
+
templateExtension: ".md",
|
|
16
|
+
defaultTemplateBaseName: (docType) => `${docType}_TEMPLATE`
|
|
17
|
+
};
|
|
18
|
+
async function generate() {
|
|
19
|
+
const argv = await yargs(hideBin(process.argv)).usage("$0 --workspace <name|path> --out <dir> [--templates <dir>] [--types DISCLAIMER,EULA,...] [--debug]").option("workspace", { type: "string", demandOption: true, describe: "Target workspace (name or path relative to monorepo root)" }).option("out", { type: "string", demandOption: true, describe: "Output directory for generated files (relative to current working dir allowed)" }).option("templates", { type: "string", describe: "Templates directory. Default: packages/anarchy-legal/templates" }).option("types", { type: "string", describe: `Comma-separated list of doc types. Default: ${Object.values(LegalDocumentType).join(",")}` }).option("debug", { type: "boolean", default: false }).help().parseAsync();
|
|
20
|
+
isDebug = Boolean(argv.debug);
|
|
21
|
+
repoUtilsService.setDebugMode(isDebug);
|
|
22
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const startCandidates = [process.env.INIT_CWD, process.cwd(), scriptDir].filter(Boolean);
|
|
24
|
+
const rootDir = await startCandidates.reduce(async (accP, c) => {
|
|
25
|
+
const acc = await accP;
|
|
26
|
+
if (acc) return acc;
|
|
27
|
+
try {
|
|
28
|
+
return await findMonorepoRoot(c);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
debugLog(isDebug, "no root from", c, ":", e.message);
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
}, Promise.resolve(void 0));
|
|
34
|
+
if (!rootDir) throw new Error(`Failed to find monorepo root from: ${startCandidates.join(", ")}`);
|
|
35
|
+
const workspaces = await loadWorkspaces(rootDir);
|
|
36
|
+
const ws = await resolveWorkspaceFromArg(String(argv.workspace), workspaces, rootDir);
|
|
37
|
+
debugLog(isDebug, "target workspace:", ws.name, ws.dir);
|
|
38
|
+
const templatesDir = argv.templates ? path.isAbsolute(argv.templates) ? argv.templates : path.resolve(process.cwd(), argv.templates) : path.resolve(scriptDir, "../../src/Templates");
|
|
39
|
+
debugLog(isDebug, "templates dir:", templatesDir);
|
|
40
|
+
const outDir = path.isAbsolute(argv.out) ? argv.out : path.resolve(process.cwd(), String(argv.out));
|
|
41
|
+
debugLog(isDebug, "out dir:", outDir);
|
|
42
|
+
const config = await readConfig(ws.dir);
|
|
43
|
+
const configKeys = Array.from(getConfiguredDocKeys(config));
|
|
44
|
+
if (!configKeys.length) {
|
|
45
|
+
console.log("Nothing to generate: no doc types configured (or filtered out by --types).");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const cliKeys = (() => {
|
|
49
|
+
if (!argv.types) return new Set(configKeys);
|
|
50
|
+
const parts = String(argv.types).split(",").map((s) => s.trim()).filter(Boolean);
|
|
51
|
+
const ok = new Set(configKeys);
|
|
52
|
+
return new Set(parts.filter((p) => ok.has(p)));
|
|
53
|
+
})();
|
|
54
|
+
if (!cliKeys.size) {
|
|
55
|
+
console.log("Nothing to generate: all requested sections were filtered out or missing in config.");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
assertTemplatesPresent(config, cliKeys);
|
|
59
|
+
await generateAll({ ws, outDir, templatesDir, keys: cliKeys, config }, options);
|
|
60
|
+
}
|
|
61
|
+
return { generate };
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
LegalFilesService
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=LegalFilesService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LegalFilesService.js","sources":["../../src/Services/LegalFilesService.ts"],"sourcesContent":["import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { TAnarchyLegalConfig, TLegalDocumentType, TLegalFilesService, TLegalFilesUtilsService, TRepoUtilsService, TTemplateGeneratorOptions, TWorkspaceInfo } from '@Anarchy/Legal'; // eslint-disable-next-line spellcheck/spell-checker\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\n\nimport { LegalDocumentType } from '../Constants/LegalDocumentType.ts';\nimport { LegalFilesUtilsService } from './LegalFilesUtilsService.ts';\nimport { RepoUtilsService } from './RepoUtilsService.ts';\n\nexport function LegalFilesService(): TLegalFilesService {\n let isDebug: boolean = false;\n const repoUtilsService: TRepoUtilsService = RepoUtilsService();\n const legalFilesUtilsService: TLegalFilesUtilsService = LegalFilesUtilsService(repoUtilsService);\n const { debugLog, findMonorepoRoot, resolveWorkspaceFromArg, loadWorkspaces } = repoUtilsService;\n const { assertTemplatesPresent, getConfiguredDocKeys, generateAll, readConfig } = legalFilesUtilsService;\n\n const options: TTemplateGeneratorOptions = {\n templateExtension: '.md',\n defaultTemplateBaseName: (docType: TLegalDocumentType): string => `${docType}_TEMPLATE`\n };\n\n async function generate(): Promise<void> {\n // eslint-disable-next-line spellcheck/spell-checker\n const argv = await yargs(hideBin(process.argv))\n // .scriptName('anarchy-legal:files')\n .usage('$0 --workspace <name|path> --out <dir> [--templates <dir>] [--types DISCLAIMER,EULA,...] [--debug]')\n .option('workspace', { type: 'string', demandOption: true, describe: 'Target workspace (name or path relative to monorepo root)' })\n .option('out', { type: 'string', demandOption: true, describe: 'Output directory for generated files (relative to current working dir allowed)' })\n .option('templates', { type: 'string', describe: 'Templates directory. Default: packages/anarchy-legal/templates' })\n .option('types', { type: 'string', describe: `Comma-separated list of doc types. Default: ${Object.values(LegalDocumentType).join(',')}` })\n .option('debug', { type: 'boolean', default: false })\n .help()\n .parseAsync();\n\n isDebug = Boolean(argv.debug);\n repoUtilsService.setDebugMode(isDebug);\n\n const scriptDir: string = path.dirname(fileURLToPath(import.meta.url));\n const startCandidates: string[] = [process.env.INIT_CWD, process.cwd(), scriptDir].filter(Boolean) as string[];\n\n // Find monorepo root\n const rootDir: string | undefined = await startCandidates.reduce<Promise<string | undefined>>(async (accP, c) => {\n const acc: string | undefined = await accP;\n if (acc) return acc;\n try {\n return await findMonorepoRoot(c);\n } catch (e) {\n debugLog(isDebug, 'no root from', c, ':', (e as Error).message);\n return undefined;\n }\n }, Promise.resolve(undefined));\n if (!rootDir) throw new Error(`Failed to find monorepo root from: ${startCandidates.join(', ')}`);\n\n // Load workspaces and resolve target\n const workspaces: ReadonlyMap<string, TWorkspaceInfo> = await loadWorkspaces(rootDir);\n const ws = await resolveWorkspaceFromArg(String(argv.workspace), workspaces, rootDir);\n debugLog(isDebug, 'target workspace:', ws.name, ws.dir);\n\n // Resolve templates dir\n const templatesDir: string = argv.templates ? (path.isAbsolute(argv.templates) ? argv.templates : path.resolve(process.cwd(), argv.templates)) : path.resolve(scriptDir, '../../src/Templates');\n debugLog(isDebug, 'templates dir:', templatesDir);\n\n // Resolve out dir\n const outDir: string = path.isAbsolute(argv.out as string) ? (argv.out as string) : path.resolve(process.cwd(), String(argv.out));\n debugLog(isDebug, 'out dir:', outDir);\n\n const config: TAnarchyLegalConfig = await readConfig(ws.dir);\n const configKeys: ReadonlyArray<string> = Array.from(getConfiguredDocKeys(config));\n\n if (!configKeys.length) {\n console.log('Nothing to generate: no doc types configured (or filtered out by --types).');\n return;\n }\n\n const cliKeys: ReadonlySet<string> = ((): Set<string> => {\n if (!argv.types) return new Set(configKeys);\n const parts: ReadonlyArray<string> = String(argv.types)\n .split(',')\n .map((s: string): string => s.trim())\n .filter(Boolean);\n const ok = new Set(configKeys);\n return new Set(parts.filter((p: string): boolean => ok.has(p)));\n })();\n\n if (!cliKeys.size) {\n console.log('Nothing to generate: all requested sections were filtered out or missing in config.');\n return;\n }\n\n assertTemplatesPresent(config, cliKeys);\n\n // Go\n await generateAll({ ws, outDir, templatesDir, keys: cliKeys, config }, options);\n }\n\n return { generate };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { UTCDate } from "@date-fns/utc";
|
|
5
|
+
import { parseISO, isValid } from "date-fns";
|
|
6
|
+
import { format } from "date-fns/format";
|
|
7
|
+
import { globby } from "globby";
|
|
8
|
+
import { LegalDocumentType } from "../Constants/LegalDocumentType.js";
|
|
9
|
+
function LegalFilesUtilsService(repoUtilsService) {
|
|
10
|
+
const { debugLog, isExist, isDebug } = repoUtilsService;
|
|
11
|
+
async function readConfig(wsDir) {
|
|
12
|
+
const candidates = [path.join(wsDir, "anarchy-legal.config.js"), path.join(wsDir, "anarchy-legal.config.mjs"), path.join(wsDir, "anarchy-legal.config.cjs")];
|
|
13
|
+
const found = await candidates.reduce(async (prevPromise, p) => {
|
|
14
|
+
const prev = await prevPromise;
|
|
15
|
+
if (prev) return prev;
|
|
16
|
+
return await isExist(p) ? p : void 0;
|
|
17
|
+
}, Promise.resolve(void 0));
|
|
18
|
+
if (!found) {
|
|
19
|
+
debugLog(isDebug(), "config: <none> (no JS config found)");
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const mod = await import(pathToFileURL(found).href);
|
|
24
|
+
const exported = mod && "default" in mod ? mod.default : mod;
|
|
25
|
+
if (!exported || typeof exported !== "object" || Array.isArray(exported)) throw new Error("Invalid config export. Expected an object like { GENERIC: {...}, EULA: {...}, ... }.");
|
|
26
|
+
const obj = exported;
|
|
27
|
+
const processConfigSection = (k) => {
|
|
28
|
+
const v = obj[k];
|
|
29
|
+
if (v === void 0) return void 0;
|
|
30
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) {
|
|
31
|
+
console.warn(`[warn] anarchy-legal.config: section "${k}" must be an object; got ${typeof v}. Skipped.`);
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
const { template, messages, relativeOutput, outputName } = v;
|
|
35
|
+
return [
|
|
36
|
+
k,
|
|
37
|
+
{
|
|
38
|
+
...template ? { template } : {},
|
|
39
|
+
...messages ? { messages } : {},
|
|
40
|
+
...relativeOutput ? { relativeOutput } : {},
|
|
41
|
+
...outputName ? { outputName } : {}
|
|
42
|
+
}
|
|
43
|
+
];
|
|
44
|
+
};
|
|
45
|
+
const processedEntries = Object.keys(obj).map(processConfigSection).filter((entry) => entry !== void 0);
|
|
46
|
+
const config = Object.fromEntries(processedEntries);
|
|
47
|
+
debugLog(isDebug(), "config file:", found, "keys:", Object.keys(config));
|
|
48
|
+
return config;
|
|
49
|
+
} catch (e) {
|
|
50
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
51
|
+
throw new Error(`Failed to load config ${found}: ${msg}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function findTemplateFile(templatesDir, docType, { templateExtension, defaultTemplateBaseName }, desiredBase) {
|
|
55
|
+
if (desiredBase) {
|
|
56
|
+
const exact = path.join(templatesDir, `${desiredBase}${templateExtension}`);
|
|
57
|
+
if (await isExist(exact)) return exact;
|
|
58
|
+
}
|
|
59
|
+
const def = path.join(templatesDir, `${defaultTemplateBaseName(docType)}${templateExtension}`);
|
|
60
|
+
if (await isExist(def)) return def;
|
|
61
|
+
const pattern = path.join(templatesDir, `${docType}_*${templateExtension}`);
|
|
62
|
+
const found = await globby([pattern], { absolute: true });
|
|
63
|
+
const [first] = found.toSorted();
|
|
64
|
+
return first;
|
|
65
|
+
}
|
|
66
|
+
const PLACEHOLDER_RE = /{{\s*([A-Z0-9_]+)\s*}}/g;
|
|
67
|
+
const formatWithDateFns = (dateStr, format$1) => {
|
|
68
|
+
const d = dateStr.toLowerCase() === "now" ? new UTCDate() : (() => {
|
|
69
|
+
const iso = parseISO(dateStr);
|
|
70
|
+
return isValid(iso) ? iso : new Date(dateStr);
|
|
71
|
+
})();
|
|
72
|
+
if (!isValid(d)) return "";
|
|
73
|
+
try {
|
|
74
|
+
return format(d, format$1);
|
|
75
|
+
} catch {
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
function materializeMessage(v) {
|
|
80
|
+
if (v === null || v === void 0) return "";
|
|
81
|
+
if (typeof v === "string") return v;
|
|
82
|
+
if (typeof v === "number") return Number.isFinite(v) ? String(v) : "";
|
|
83
|
+
if (typeof v === "boolean") return v ? "true" : "false";
|
|
84
|
+
if (typeof v === "object" && "date" in v && "format" in v) {
|
|
85
|
+
const { date, format: format2 } = v;
|
|
86
|
+
if (typeof date === "string" && typeof format2 === "string") return formatWithDateFns(date, format2);
|
|
87
|
+
}
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
function collectPlaceholders(tpl) {
|
|
91
|
+
return new Set([...tpl.matchAll(PLACEHOLDER_RE)].map((m) => m[1]));
|
|
92
|
+
}
|
|
93
|
+
function packagePlaceholder(key, pkg) {
|
|
94
|
+
const k = key.toUpperCase();
|
|
95
|
+
const str = (v) => typeof v === "string" ? v : void 0;
|
|
96
|
+
const arrStr = (v) => Array.isArray(v) ? v.map((x) => typeof x === "string" ? x : JSON.stringify(x)).join(", ") : void 0;
|
|
97
|
+
function authorToString(a) {
|
|
98
|
+
if (!a) return void 0;
|
|
99
|
+
if (typeof a === "string") return a;
|
|
100
|
+
if (typeof a === "object") {
|
|
101
|
+
const n = str(a.name) ?? "";
|
|
102
|
+
const e = str(a.email);
|
|
103
|
+
const w = str(a.url);
|
|
104
|
+
return [n, e ? `<${e}>` : "", w ? `(${w})` : ""].filter(Boolean).join(" ").trim();
|
|
105
|
+
}
|
|
106
|
+
return void 0;
|
|
107
|
+
}
|
|
108
|
+
switch (k) {
|
|
109
|
+
case "NAME":
|
|
110
|
+
return str(pkg.name);
|
|
111
|
+
case "VERSION":
|
|
112
|
+
return str(pkg.version);
|
|
113
|
+
case "DESCRIPTION":
|
|
114
|
+
return str(pkg.description);
|
|
115
|
+
case "HOMEPAGE":
|
|
116
|
+
return str(pkg.homepage);
|
|
117
|
+
case "LICENSE":
|
|
118
|
+
return str(pkg.license);
|
|
119
|
+
case "REPOSITORY": {
|
|
120
|
+
const repo = pkg.repository;
|
|
121
|
+
if (typeof repo === "string") return repo;
|
|
122
|
+
const url = str(repo?.url);
|
|
123
|
+
return url ?? void 0;
|
|
124
|
+
}
|
|
125
|
+
case "BUGS_URL": {
|
|
126
|
+
const bugs = pkg.bugs;
|
|
127
|
+
if (typeof bugs === "string") return bugs;
|
|
128
|
+
const url = str(bugs?.url);
|
|
129
|
+
return url ?? void 0;
|
|
130
|
+
}
|
|
131
|
+
case "AUTHOR":
|
|
132
|
+
return authorToString(pkg.author);
|
|
133
|
+
case "AUTHORS":
|
|
134
|
+
return arrStr(pkg.authors);
|
|
135
|
+
case "KEYWORDS":
|
|
136
|
+
return arrStr(pkg.keywords);
|
|
137
|
+
default: {
|
|
138
|
+
const direct = pkg[key.toLowerCase()];
|
|
139
|
+
if (typeof direct === "string") return direct;
|
|
140
|
+
return void 0;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function buildPlaceholderValues(_docType, tplText, pkg, generic, specific) {
|
|
145
|
+
const names = collectPlaceholders(tplText);
|
|
146
|
+
const pkgValues = Array.from(names).reduce((acc, name) => {
|
|
147
|
+
if (!name.startsWith("PACKAGE_")) return acc;
|
|
148
|
+
const suffix = name.slice("PACKAGE_".length);
|
|
149
|
+
const v = packagePlaceholder(suffix, pkg);
|
|
150
|
+
return v !== void 0 ? { ...acc, [name]: v } : acc;
|
|
151
|
+
}, {});
|
|
152
|
+
const genericValues = generic ? Object.fromEntries(Object.entries(generic).map(([k, v]) => [k, materializeMessage(v)])) : {};
|
|
153
|
+
const specificValues = specific ? Object.fromEntries(Object.entries(specific).map(([k, v]) => [k, materializeMessage(v)])) : {};
|
|
154
|
+
return { ...pkgValues, ...genericValues, ...specificValues };
|
|
155
|
+
}
|
|
156
|
+
function buildContext(docType, tplText, pkg, generic, specific) {
|
|
157
|
+
const values = buildPlaceholderValues(docType, tplText, pkg, generic, specific);
|
|
158
|
+
const raw = {
|
|
159
|
+
...Object.fromEntries(Object.entries(values)),
|
|
160
|
+
...generic || {},
|
|
161
|
+
...specific || {}
|
|
162
|
+
};
|
|
163
|
+
return { values, raw };
|
|
164
|
+
}
|
|
165
|
+
function renderVariables(tpl, values, onMissing) {
|
|
166
|
+
const VAR_RE = /{{\s*([A-Z0-9_]+)\s*}}/g;
|
|
167
|
+
return tpl.replace(VAR_RE, (_m, g1) => {
|
|
168
|
+
const v = values[g1];
|
|
169
|
+
if (v === void 0) {
|
|
170
|
+
return "";
|
|
171
|
+
}
|
|
172
|
+
return v;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function renderSections(input, truthyMap) {
|
|
176
|
+
const SECTION_RE = /{{\s*([#^])\s*([A-Z0-9_]+)\s*}}([\s\S]*?){{\s*\/\s*\2\s*}}/g;
|
|
177
|
+
const processUntilConverged = (current) => {
|
|
178
|
+
const next = current.replace(SECTION_RE, (_m, sigil, name, body) => {
|
|
179
|
+
const condition = Boolean(truthyMap[name]);
|
|
180
|
+
const pass = sigil === "#" ? condition : !condition;
|
|
181
|
+
return pass ? renderSections(body, truthyMap) : "";
|
|
182
|
+
});
|
|
183
|
+
return next === current ? current : processUntilConverged(next);
|
|
184
|
+
};
|
|
185
|
+
return processUntilConverged(input);
|
|
186
|
+
}
|
|
187
|
+
async function generateForType(input, key, options) {
|
|
188
|
+
const genericConfig = input.config["GENERIC"];
|
|
189
|
+
const specificConfig = input.config[key];
|
|
190
|
+
if (!specificConfig?.template || specificConfig.template.trim() === "") throw new Error(`[${key}] missing "template" in anarchy-legal.config.js`);
|
|
191
|
+
const desiredBase = specificConfig.template;
|
|
192
|
+
const tplPath = await findTemplateFile(input.templatesDir, key, options, desiredBase);
|
|
193
|
+
if (!tplPath) throw new Error(`[${key}] template "${desiredBase}" not found under templates dir: ${input.templatesDir}`);
|
|
194
|
+
const tplText = await fs.readFile(tplPath, "utf8");
|
|
195
|
+
const { values, raw } = buildContext(key, tplText, input.ws.pkg, genericConfig?.messages, specificConfig?.messages);
|
|
196
|
+
const afterSections = renderSections(tplText, raw);
|
|
197
|
+
const namesAfter = collectPlaceholders(afterSections);
|
|
198
|
+
const missing = Array.from(namesAfter).filter((name) => values[name] === void 0);
|
|
199
|
+
if (missing.length) console.warn(`[warn] ${key}: ${missing.length} placeholders had no value: ${missing.slice(0, 10).join(", ")}${missing.length > 10 ? "…" : ""}`);
|
|
200
|
+
const rendered = renderVariables(afterSections, values);
|
|
201
|
+
const relOut = specificConfig.relativeOutput?.trim();
|
|
202
|
+
if (relOut && path.isAbsolute(relOut)) console.warn(`[warn] ${key}: relativeOutput is absolute ("${relOut}"); it will be used as-is.`);
|
|
203
|
+
const targetDir = relOut ? path.resolve(input.outDir, relOut) : input.outDir;
|
|
204
|
+
const baseName = (specificConfig.outputName?.trim() || key).replace(/\s+$/, "");
|
|
205
|
+
const outName = `${baseName}.md`;
|
|
206
|
+
const outPath = path.join(targetDir, outName);
|
|
207
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
208
|
+
await fs.writeFile(outPath, rendered, "utf8");
|
|
209
|
+
console.log(`${baseName}.md written -> ${outPath}`);
|
|
210
|
+
}
|
|
211
|
+
async function generateAll(renderInput, options) {
|
|
212
|
+
for (const k of renderInput.keys) {
|
|
213
|
+
await generateForType(renderInput, k, options);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function getConfiguredDocTypes(config) {
|
|
217
|
+
const set = /* @__PURE__ */ new Set();
|
|
218
|
+
Object.values(LegalDocumentType).forEach((docType) => {
|
|
219
|
+
if (config[docType]) set.add(docType);
|
|
220
|
+
});
|
|
221
|
+
return set;
|
|
222
|
+
}
|
|
223
|
+
function assertTemplatesPresent(config, keys) {
|
|
224
|
+
const missing = Array.from(keys).filter((k) => {
|
|
225
|
+
const tpl = config[k]?.template;
|
|
226
|
+
return typeof tpl !== "string" || tpl.trim() === "";
|
|
227
|
+
});
|
|
228
|
+
if (missing.length) {
|
|
229
|
+
throw new Error(`anarchy-legal.config.js: "template" is required for sections: ${missing.join(", ")}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function getConfiguredDocKeys(config) {
|
|
233
|
+
return new Set(Object.keys(config || {}).filter((k) => k !== "GENERIC"));
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
assertTemplatesPresent,
|
|
237
|
+
generateAll,
|
|
238
|
+
getConfiguredDocKeys,
|
|
239
|
+
getConfiguredDocTypes,
|
|
240
|
+
readConfig
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
export {
|
|
244
|
+
LegalFilesUtilsService
|
|
245
|
+
};
|
|
246
|
+
//# sourceMappingURL=LegalFilesUtilsService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LegalFilesUtilsService.js","sources":["../../src/Services/LegalFilesUtilsService.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport type {\n TAnarchyLegalConfig,\n TAnarchyLegalConfigEntry,\n TLegalDocumentType,\n TLegalFilesUtilsService,\n TRenderInput,\n TRepoUtilsService,\n TTemplateGeneratorOptions,\n TTemplateMessages\n} from '@Anarchy/Legal/Models';\nimport { UTCDate } from '@date-fns/utc';\nimport { isValid, parseISO } from 'date-fns';\nimport { format as dfFormat } from 'date-fns/format';\n// eslint-disable-next-line spellcheck/spell-checker\nimport { globby } from 'globby';\n\nimport { LegalDocumentType } from '../Constants/LegalDocumentType.ts';\n\nexport function LegalFilesUtilsService(repoUtilsService: TRepoUtilsService): TLegalFilesUtilsService {\n const { debugLog, isExist, isDebug } = repoUtilsService;\n\n async function readConfig(wsDir: string): Promise<TAnarchyLegalConfig> | never {\n const candidates: ReadonlyArray<string> = [path.join(wsDir, 'anarchy-legal.config.js'), path.join(wsDir, 'anarchy-legal.config.mjs'), path.join(wsDir, 'anarchy-legal.config.cjs')];\n\n const found: string | undefined = await candidates.reduce<Promise<string | undefined>>(async (prevPromise: Promise<string | undefined>, p: string): Promise<string | undefined> => {\n const prev: string | undefined = await prevPromise;\n if (prev) return prev;\n return (await isExist(p)) ? p : undefined;\n }, Promise.resolve(undefined));\n\n if (!found) {\n debugLog(isDebug(), 'config: <none> (no JS config found)');\n return {};\n }\n\n try {\n const mod = await import(pathToFileURL(found).href);\n // Support both ESM default export and CommonJS module.exports\n const exported = (mod && 'default' in mod ? (mod as any).default : mod) as unknown;\n\n if (!exported || typeof exported !== 'object' || Array.isArray(exported)) throw new Error('Invalid config export. Expected an object like { GENERIC: {...}, EULA: {...}, ... }.');\n\n // Very light validation and narrowing\n const obj = exported as Record<string, unknown>;\n\n const processConfigSection = (k: 'GENERIC' | string): [string, any] | undefined => {\n const v = obj[k];\n if (v === undefined) return undefined;\n if (!v || typeof v !== 'object' || Array.isArray(v)) {\n console.warn(`[warn] anarchy-legal.config: section \"${k}\" must be an object; got ${typeof v}. Skipped.`);\n return undefined;\n }\n const { template, messages, relativeOutput, outputName } = v as TAnarchyLegalConfigEntry;\n return [\n k,\n {\n ...(template ? { template } : {}),\n ...(messages ? { messages } : {}),\n ...(relativeOutput ? { relativeOutput } : {}),\n ...(outputName ? { outputName } : {})\n }\n ];\n };\n\n const processedEntries = Object.keys(obj)\n .map(processConfigSection)\n .filter((entry): entry is [string, any] => entry !== undefined);\n const config: TAnarchyLegalConfig = Object.fromEntries(processedEntries);\n\n debugLog(isDebug(), 'config file:', found, 'keys:', Object.keys(config));\n return config;\n } catch (e) {\n const msg: string = e instanceof Error ? e.message : String(e);\n throw new Error(`Failed to load config ${found}: ${msg}`);\n }\n }\n\n async function findTemplateFile(\n templatesDir: string,\n docType: TLegalDocumentType,\n { templateExtension, defaultTemplateBaseName }: TTemplateGeneratorOptions,\n desiredBase?: string\n ): Promise<string | undefined> {\n // 1) exact by desiredBase\n if (desiredBase) {\n const exact: string = path.join(templatesDir, `${desiredBase}${templateExtension}`);\n if (await isExist(exact)) return exact;\n }\n // 2) default <TYPE>_TEMPLATE.md\n const def: string = path.join(templatesDir, `${defaultTemplateBaseName(docType)}${templateExtension}`);\n if (await isExist(def)) return def;\n // 3) first match <TYPE>_*_TEMPLATE.md (alphabetically)\n const pattern: string = path.join(templatesDir, `${docType}_*${templateExtension}`);\n // eslint-disable-next-line spellcheck/spell-checker\n const found: string[] = await globby([pattern], { absolute: true });\n const [first] = found.toSorted();\n return first;\n }\n\n const PLACEHOLDER_RE = /{{\\s*([A-Z0-9_]+)\\s*}}/g;\n\n /** Parse date string with date-fns (supports \"now\", ISO, and Date constructor fallback) */\n /** Parse/format via date-fns.\n * Special case: when dateStr === \"now\", output uses UTC date/time (not local).\n */\n const formatWithDateFns = (dateStr: string, format: string): string => {\n const d: Date =\n dateStr.toLowerCase() === 'now'\n ? new UTCDate()\n : ((): Date => {\n const iso: Date = parseISO(dateStr);\n return isValid(iso) ? iso : new Date(dateStr);\n })();\n if (!isValid(d)) return ''; // invalid date → empty\n try {\n return dfFormat(d, format);\n } catch {\n return '';\n }\n };\n\n // Convert message value → string (for variable substitution)\n function materializeMessage(v: unknown): string {\n if (v === null || v === undefined) return '';\n if (typeof v === 'string') return v;\n if (typeof v === 'number') return Number.isFinite(v) ? String(v) : '';\n if (typeof v === 'boolean') return v ? 'true' : 'false'; // only for {{VAR}} replacement\n // date-object: { date: string; format: string } -> via date-fns\n if (typeof v === 'object' && 'date' in (v as any) && 'format' in (v as any)) {\n const { date, format } = v as { date: string; format: string };\n if (typeof date === 'string' && typeof format === 'string') return formatWithDateFns(date, format);\n }\n return '';\n }\n\n // Extract placeholders present in a template text\n function collectPlaceholders(tpl: string): ReadonlySet<string> {\n return new Set<string>([...tpl.matchAll(PLACEHOLDER_RE)].map((m) => m[1] as string));\n }\n\n // PACKAGE_* resolution from package.json\n function packagePlaceholder(key: string, pkg: Readonly<Record<string, unknown>>): string | undefined {\n // key is suffix after \"PACKAGE_\", e.g. NAME, VERSION, AUTHOR, AUTHORS, LICENSE, REPOSITORY, HOMEPAGE, BUGS_URL, DESCRIPTION\n const k: string = key.toUpperCase();\n\n const str = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined);\n const arrStr = (v: unknown): string | undefined => (Array.isArray(v) ? (v as unknown[]).map((x) => (typeof x === 'string' ? x : JSON.stringify(x))).join(', ') : undefined);\n\n function authorToString(a: unknown): string | undefined {\n if (!a) return undefined;\n if (typeof a === 'string') return a;\n if (typeof a === 'object') {\n const n: string = str((a as any).name) ?? '';\n const e: string | undefined = str((a as any).email);\n const w: string | undefined = str((a as any).url);\n return [n, e ? `<${e}>` : '', w ? `(${w})` : ''].filter(Boolean).join(' ').trim();\n }\n return undefined;\n }\n\n switch (k) {\n case 'NAME':\n return str(pkg.name);\n case 'VERSION':\n return str(pkg.version);\n case 'DESCRIPTION':\n return str(pkg.description);\n case 'HOMEPAGE':\n return str(pkg.homepage);\n case 'LICENSE':\n return str(pkg.license);\n case 'REPOSITORY': {\n const repo = (pkg as any).repository;\n if (typeof repo === 'string') return repo;\n const url: string | undefined = str(repo?.url);\n return url ?? undefined;\n }\n case 'BUGS_URL': {\n const bugs = (pkg as any).bugs;\n if (typeof bugs === 'string') return bugs;\n const url = str(bugs?.url);\n return url ?? undefined;\n }\n case 'AUTHOR':\n return authorToString((pkg as any).author);\n case 'AUTHORS':\n return arrStr((pkg as any).authors);\n case 'KEYWORDS':\n return arrStr((pkg as any).keywords);\n default: {\n // if someone uses {{PACKAGE_FOO_BAR}}, try a naive lower-cased lookup \"foo_bar\" then \"fooBar\"\n const direct: unknown = pkg[key.toLowerCase()];\n if (typeof direct === 'string') return direct;\n return undefined;\n }\n }\n }\n\n // Build final map for a given doc type\n function buildPlaceholderValues(\n _docType: TLegalDocumentType,\n tplText: string,\n pkg: Readonly<Record<string, unknown>>,\n generic: TTemplateMessages | undefined,\n specific: TTemplateMessages | undefined\n ): Readonly<Record<string, string>> {\n const names: ReadonlySet<string> = collectPlaceholders(tplText);\n\n const pkgValues: Record<string, string> = Array.from(names).reduce<Record<string, string>>((acc, name) => {\n if (!name.startsWith('PACKAGE_')) return acc;\n const suffix: string = name.slice('PACKAGE_'.length);\n const v: string | undefined = packagePlaceholder(suffix, pkg);\n return v !== undefined ? { ...acc, [name]: v } : acc;\n }, {});\n\n const genericValues: Record<string, string> = generic ? Object.fromEntries(Object.entries(generic).map(([k, v]) => [k, materializeMessage(v)])) : {};\n\n const specificValues: Record<string, string> = specific ? Object.fromEntries(Object.entries(specific).map(([k, v]) => [k, materializeMessage(v)])) : {};\n\n return { ...pkgValues, ...genericValues, ...specificValues };\n }\n\n // Build both: materialized values for {{VAR}} and raw map for section truthiness\n function buildContext(\n docType: TLegalDocumentType,\n tplText: string,\n pkg: Readonly<Record<string, unknown>>,\n generic: Readonly<Record<string, unknown>> | undefined,\n specific: Readonly<Record<string, unknown>> | undefined\n ): Readonly<{ values: Record<string, string>; raw: Record<string, unknown> }> {\n const values: Readonly<Record<string, string>> = buildPlaceholderValues(docType, tplText, pkg, generic as any, specific as any);\n\n const raw: Record<string, unknown> = {\n ...Object.fromEntries(Object.entries(values)),\n ...(generic || {}),\n ...(specific || {})\n };\n\n return { values, raw };\n }\n\n // Replace variables {{VAR}} with materialized values\n function renderVariables(tpl: string, values: Readonly<Record<string, string>>, onMissing?: (name: string) => void): string {\n const VAR_RE = /{{\\s*([A-Z0-9_]+)\\s*}}/g;\n return tpl.replace(VAR_RE, (_m, g1: string) => {\n const v: string = values[g1];\n if (v === undefined) {\n onMissing?.(g1);\n return '';\n }\n return v;\n });\n }\n\n // Evaluate sections recursively until nothing left\n function renderSections(input: string, truthyMap: Readonly<Record<string, unknown>>): string {\n const SECTION_RE = /{{\\s*([#^])\\s*([A-Z0-9_]+)\\s*}}([\\s\\S]*?){{\\s*\\/\\s*\\2\\s*}}/g;\n\n const processUntilConverged = (current: string): string => {\n const next = current.replace(SECTION_RE, (_m, sigil: '#' | '^', name: string, body: string) => {\n const condition: boolean = Boolean(truthyMap[name]);\n const pass: boolean = sigil === '#' ? condition : !condition;\n return pass ? renderSections(body, truthyMap) : '';\n });\n return next === current ? current : processUntilConverged(next);\n };\n\n return processUntilConverged(input);\n }\n\n async function generateForType(input: TRenderInput, key: string, options: TTemplateGeneratorOptions): Promise<void> {\n const genericConfig = input.config['GENERIC'];\n const specificConfig = input.config[key];\n\n if (!specificConfig?.template || specificConfig.template.trim() === '') throw new Error(`[${key}] missing \"template\" in anarchy-legal.config.js`);\n\n const desiredBase: string = specificConfig.template;\n\n const tplPath: string | undefined = await findTemplateFile(input.templatesDir, key, options, desiredBase);\n if (!tplPath) throw new Error(`[${key}] template \"${desiredBase}\" not found under templates dir: ${input.templatesDir}`);\n\n const tplText: string = await fs.readFile(tplPath, 'utf8');\n\n const { values, raw } = buildContext(key, tplText, input.ws.pkg, genericConfig?.messages, specificConfig?.messages);\n\n const afterSections: string = renderSections(tplText, raw);\n\n const namesAfter: ReadonlySet<string> = collectPlaceholders(afterSections);\n const missing: ReadonlyArray<string> = Array.from(namesAfter).filter((name: string): boolean => values[name] === undefined);\n if (missing.length) console.warn(`[warn] ${key}: ${missing.length} placeholders had no value: ${missing.slice(0, 10).join(', ')}${missing.length > 10 ? '…' : ''}`);\n\n const rendered: string = renderVariables(afterSections, values);\n\n const relOut: string | undefined = specificConfig.relativeOutput?.trim();\n if (relOut && path.isAbsolute(relOut)) console.warn(`[warn] ${key}: relativeOutput is absolute (\"${relOut}\"); it will be used as-is.`);\n const targetDir: string = relOut ? path.resolve(input.outDir, relOut) : input.outDir;\n\n const baseName: string = (specificConfig.outputName?.trim() || key).replace(/\\s+$/, '');\n\n const outName: string = `${baseName}.md`;\n const outPath: string = path.join(targetDir, outName);\n await fs.mkdir(path.dirname(outPath), { recursive: true });\n await fs.writeFile(outPath, rendered, 'utf8');\n console.log(`${baseName}.md written -> ${outPath}`);\n }\n\n async function generateAll(renderInput: TRenderInput & { keys: ReadonlySet<string> }, options: TTemplateGeneratorOptions): Promise<void> {\n // eslint-disable-next-line functional/no-loop-statements\n for (const k of renderInput.keys) {\n await generateForType(renderInput, k, options);\n }\n }\n\n // Return only those doc types that are explicitly present in the config object\n function getConfiguredDocTypes(config: TAnarchyLegalConfig): ReadonlySet<TLegalDocumentType> {\n const set = new Set<TLegalDocumentType>();\n Object.values(LegalDocumentType).forEach((docType: TLegalDocumentType) => {\n if (config[docType]) set.add(docType);\n });\n return set;\n }\n\n // Ensure every configured doc type has a non-empty \"template\" field\n function assertTemplatesPresent(config: TAnarchyLegalConfig, keys: ReadonlySet<string>): void | never {\n const missing = Array.from(keys).filter((k) => {\n const tpl = config[k]?.template;\n return typeof tpl !== 'string' || tpl.trim() === '';\n });\n\n if (missing.length) {\n throw new Error(`anarchy-legal.config.js: \"template\" is required for sections: ${missing.join(', ')}`);\n }\n }\n\n function getConfiguredDocKeys(config: TAnarchyLegalConfig): ReadonlySet<string> {\n return new Set(Object.keys(config || {}).filter((k) => k !== 'GENERIC'));\n }\n\n return {\n assertTemplatesPresent,\n generateAll,\n getConfiguredDocKeys,\n getConfiguredDocTypes,\n readConfig\n };\n}\n"],"names":["format","dfFormat"],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import yargs from "yargs";
|
|
5
|
+
import { hideBin } from "yargs/helpers";
|
|
6
|
+
import { NoticeUtilsService } from "./NoticeUtilsService.js";
|
|
7
|
+
import { RepoUtilsService } from "./RepoUtilsService.js";
|
|
8
|
+
function NoticeService() {
|
|
9
|
+
let isDebug = false;
|
|
10
|
+
const repoUtilsService = RepoUtilsService();
|
|
11
|
+
const noticeUtilsService = NoticeUtilsService();
|
|
12
|
+
const { debugLog, findMonorepoRoot, isExist, loadWorkspaces, resolveWorkspaceFromArg } = repoUtilsService;
|
|
13
|
+
const { collectAllHeadingIds, parseThirdPartyMarkdown, loadUpstreamNotice } = noticeUtilsService;
|
|
14
|
+
function renderNotice(wsName, entries, includeUpstream, sourceName) {
|
|
15
|
+
const header = `# Third-Party Notices
|
|
16
|
+
|
|
17
|
+
## Application: ${wsName}
|
|
18
|
+
|
|
19
|
+
`;
|
|
20
|
+
const subHeader = `This product includes third-party components. Their **licenses and attributions** are listed below.
|
|
21
|
+
For the **full license texts**, see \`${sourceName}\`.
|
|
22
|
+
|
|
23
|
+
Components listed: ${entries.length}
|
|
24
|
+
|
|
25
|
+
## 1) Mandatory Attributions (verbatim)
|
|
26
|
+
|
|
27
|
+
The following notices are reproduced as provided by the respective licensors (e.g., **Apache-2.0 NOTICE**, **CC-BY credits**, **font attributions**):
|
|
28
|
+
`;
|
|
29
|
+
const noRecordsNote = "**Note:** No third-party components included.";
|
|
30
|
+
const blocks = entries.map((v) => {
|
|
31
|
+
const licenses = v.licenses.length ? v.licenses.join(", ") + "\n" : "UNKNOWN";
|
|
32
|
+
const repository = v.repository ? `**Repository:** ${v.repository}
|
|
33
|
+
|
|
34
|
+
` : "";
|
|
35
|
+
const url = v.url ? `**URL:** ${v.url}
|
|
36
|
+
|
|
37
|
+
` : "";
|
|
38
|
+
const inferredCopyright = v.inferredCopyright ? `**Attribution:** ${v.inferredCopyright}
|
|
39
|
+
` : "";
|
|
40
|
+
const base = `
|
|
41
|
+
## ${v.name}@${v.version}
|
|
42
|
+
|
|
43
|
+
**License(s):** ${licenses}
|
|
44
|
+
${repository}${url}${inferredCopyright}
|
|
45
|
+
---
|
|
46
|
+
`;
|
|
47
|
+
const upstream = includeUpstream && v.upstreamNotice ? [`**Upstream NOTICE:**`, ...v.upstreamNotice.split(/\r?\n/).map((ln) => `> ${ln}`), ``] : [];
|
|
48
|
+
return [base, ...upstream];
|
|
49
|
+
});
|
|
50
|
+
const footer = `
|
|
51
|
+
## 2) General OSS Acknowledgment
|
|
52
|
+
|
|
53
|
+
This product incorporates open-source software. **If any term of this file or the EULA conflicts with an OSS license for a specific component, the OSS license controls for that component.**
|
|
54
|
+
`;
|
|
55
|
+
return entries.length !== 0 ? [header, subHeader, ...blocks.flat(), footer].join("") : [header, noRecordsNote].join("");
|
|
56
|
+
}
|
|
57
|
+
async function generate() {
|
|
58
|
+
const argv = await yargs(hideBin(process.argv)).scriptName("anarchy-legal:notice").usage("$0 --workspace <name|path> [--source <path>] [--source-name <file>] [--out <NOTICE.md>] [--include-upstream-notices] [--max-upstream-notice-kb <N>] [--audit] [--strict] [--debug]").option("workspace", { type: "string", demandOption: true, describe: "Target workspace (name or path relative to monorepo root)" }).option("source", { type: "string", describe: "Path to the input attribution file (default is <workspace>/<source-name>)" }).option("source-name", { type: "string", default: "THIRD_PARTY_LICENSES.md", describe: "File name inside the workspace to read from when --source is not provided" }).option("out", { type: "string", describe: "Path to output NOTICE.md. Default: <workspace>/NOTICE.md" }).option("include-upstream-notices", { type: "boolean", default: false, describe: "Also read upstream NOTICE files from dependency install paths (if present in source)" }).option("max-upstream-notice-kb", { type: "number", default: 128, describe: "Max size per upstream NOTICE to read (kilobytes)" }).option("audit", { type: "boolean", default: false, describe: "Print a diff between headings in source and parsed entries" }).option("strict", { type: "boolean", default: false, describe: "With --audit, exit with code 2 if mismatches found" }).option("debug", { type: "boolean", default: false }).help().parseAsync();
|
|
59
|
+
isDebug = Boolean(argv.debug);
|
|
60
|
+
repoUtilsService.setDebugMode(isDebug);
|
|
61
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
62
|
+
const startCandidates = [process.env.INIT_CWD, process.cwd(), scriptDir].filter(Boolean);
|
|
63
|
+
const rootDir = await startCandidates.reduce(async (accP, c) => {
|
|
64
|
+
const acc = await accP;
|
|
65
|
+
if (acc) return acc;
|
|
66
|
+
try {
|
|
67
|
+
return await findMonorepoRoot(c);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
debugLog(isDebug, "no root from", c, ":", e.message);
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
}, Promise.resolve(void 0));
|
|
73
|
+
if (!rootDir) throw new Error(`Failed to find monorepo root from: ${startCandidates.join(", ")}`);
|
|
74
|
+
const workspaces = await loadWorkspaces(rootDir);
|
|
75
|
+
const ws = await resolveWorkspaceFromArg(String(argv.workspace), workspaces, rootDir);
|
|
76
|
+
debugLog(isDebug, "target workspace:", ws.name, ws.dir);
|
|
77
|
+
const sourceName = String(argv["source-name"] || "THIRD_PARTY_LICENSES.md");
|
|
78
|
+
const defaultSource = path.join(ws.dir, sourceName);
|
|
79
|
+
const srcPath = argv.source ? path.isAbsolute(argv.source) ? argv.source : path.resolve(process.cwd(), argv.source) : defaultSource;
|
|
80
|
+
const outPath = argv.out ? path.isAbsolute(argv.out) ? argv.out : path.resolve(process.cwd(), argv.out) : path.join(ws.dir, "NOTICE.md");
|
|
81
|
+
debugLog(isDebug, "source:", srcPath);
|
|
82
|
+
debugLog(isDebug, "out:", outPath);
|
|
83
|
+
if (!await isExist(srcPath)) {
|
|
84
|
+
console.error(`Source file not found: ${srcPath}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
const src = await fs.readFile(srcPath, "utf8");
|
|
88
|
+
const declaredIds = collectAllHeadingIds(src);
|
|
89
|
+
const entries = parseThirdPartyMarkdown(src);
|
|
90
|
+
debugLog(isDebug, "parsed entries:", entries.length);
|
|
91
|
+
const finalEntries = await (async () => {
|
|
92
|
+
if (!argv["include-upstream-notices"]) return entries;
|
|
93
|
+
const maxBytes = Math.max(1, Math.floor(Number(argv["max-upstream-notice-kb"]) || 128)) * 1024;
|
|
94
|
+
const withUpstream = await Promise.all(
|
|
95
|
+
entries.map(async (e) => {
|
|
96
|
+
if (!e.path) return e;
|
|
97
|
+
const u = await loadUpstreamNotice(e.path, maxBytes);
|
|
98
|
+
return u ? { ...e, upstreamNotice: u } : e;
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
const filledCount = withUpstream.filter((e) => Boolean(e.upstreamNotice)).length;
|
|
102
|
+
debugLog(isDebug, "upstream notices loaded:", filledCount);
|
|
103
|
+
return withUpstream;
|
|
104
|
+
})();
|
|
105
|
+
if (argv.audit) {
|
|
106
|
+
const parsedIds = new Set(finalEntries.map((e) => e.id));
|
|
107
|
+
const missing = Array.from(declaredIds).filter((id) => !parsedIds.has(id)).toSorted();
|
|
108
|
+
console.log(`NOTICE audit:
|
|
109
|
+
headings in source: ${declaredIds.size}
|
|
110
|
+
parsed entries: ${parsedIds.size}
|
|
111
|
+
missing in NOTICE: ${missing.length}`);
|
|
112
|
+
if (missing.length) {
|
|
113
|
+
console.log(missing.map((x) => ` - ${x}`).join("\n"));
|
|
114
|
+
if (argv.strict) {
|
|
115
|
+
console.error("Audit failed: some entries were not parsed into NOTICE.");
|
|
116
|
+
process.exit(2);
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
console.log("Audit OK: all entries accounted for.");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const md = renderNotice(ws.name, entries, Boolean(argv["include-upstream-notices"]), sourceName);
|
|
123
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
124
|
+
await fs.writeFile(outPath, md, "utf8");
|
|
125
|
+
console.log(`NOTICE.md written -> ${outPath}`);
|
|
126
|
+
}
|
|
127
|
+
return { generate };
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
NoticeService
|
|
131
|
+
};
|
|
132
|
+
//# sourceMappingURL=NoticeService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NoticeService.js","sources":["../../src/Services/NoticeService.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { TNoticeService, TNoticeUtilsService, TRepoUtilsService, TTemplateParsedEntry, TWorkspaceInfo } from '@Anarchy/Legal/Models';\n// eslint-disable-next-line spellcheck/spell-checker\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\n\nimport { NoticeUtilsService } from './NoticeUtilsService.ts';\nimport { RepoUtilsService } from './RepoUtilsService.ts';\n\nexport function NoticeService(): TNoticeService {\n let isDebug: boolean = false;\n const repoUtilsService: TRepoUtilsService = RepoUtilsService();\n const noticeUtilsService: TNoticeUtilsService = NoticeUtilsService();\n\n const { debugLog, findMonorepoRoot, isExist, loadWorkspaces, resolveWorkspaceFromArg } = repoUtilsService;\n const { collectAllHeadingIds, parseThirdPartyMarkdown, loadUpstreamNotice } = noticeUtilsService;\n\n function renderNotice(wsName: string, entries: ReadonlyArray<TTemplateParsedEntry>, includeUpstream: boolean, sourceName: string): string {\n const header: string = `# Third-Party Notices\n\n## Application: ${wsName}\n\n`;\n\n const subHeader: string = `This product includes third-party components. Their **licenses and attributions** are listed below.\nFor the **full license texts**, see \\`${sourceName}\\`.\n\nComponents listed: ${entries.length}\n\n## 1) Mandatory Attributions (verbatim)\n\nThe following notices are reproduced as provided by the respective licensors (e.g., **Apache-2.0 NOTICE**, **CC-BY credits**, **font attributions**):\n`;\n\n const noRecordsNote: string = '**Note:** No third-party components included.';\n\n const blocks: ReadonlyArray<ReadonlyArray<string>> = entries.map((v: TTemplateParsedEntry): ReadonlyArray<string> => {\n const licenses: string = v.licenses.length ? v.licenses.join(', ') + '\\n' : 'UNKNOWN';\n const repository: string = v.repository ? `**Repository:** ${v.repository}\\n\\n` : '';\n const url: string = v.url ? `**URL:** ${v.url}\\n\\n` : '';\n const inferredCopyright: string = v.inferredCopyright ? `**Attribution:** ${v.inferredCopyright}\\n` : '';\n\n const base: string = `\n## ${v.name}@${v.version}\n\n**License(s):** ${licenses}\n${repository}${url}${inferredCopyright}\n---\n`;\n const upstream: ReadonlyArray<string> = includeUpstream && v.upstreamNotice ? [`**Upstream NOTICE:**`, ...v.upstreamNotice.split(/\\r?\\n/).map((ln) => `> ${ln}`), ``] : [];\n return [base, ...upstream];\n });\n\n const footer: string = `\n ## 2) General OSS Acknowledgment\n\nThis product incorporates open-source software. **If any term of this file or the EULA conflicts with an OSS license for a specific component, the OSS license controls for that component.**\n`;\n\n return entries.length !== 0 ? [header, subHeader, ...blocks.flat(), footer].join('') : [header, noRecordsNote].join('');\n }\n\n async function generate(): Promise<void> {\n // eslint-disable-next-line spellcheck/spell-checker\n const argv = await yargs(hideBin(process.argv))\n .scriptName('anarchy-legal:notice')\n .usage('$0 --workspace <name|path> [--source <path>] [--source-name <file>] [--out <NOTICE.md>] [--include-upstream-notices] [--max-upstream-notice-kb <N>] [--audit] [--strict] [--debug]')\n .option('workspace', { type: 'string', demandOption: true, describe: 'Target workspace (name or path relative to monorepo root)' })\n .option('source', { type: 'string', describe: 'Path to the input attribution file (default is <workspace>/<source-name>)' })\n .option('source-name', { type: 'string', default: 'THIRD_PARTY_LICENSES.md', describe: 'File name inside the workspace to read from when --source is not provided' })\n .option('out', { type: 'string', describe: 'Path to output NOTICE.md. Default: <workspace>/NOTICE.md' })\n .option('include-upstream-notices', { type: 'boolean', default: false, describe: 'Also read upstream NOTICE files from dependency install paths (if present in source)' })\n .option('max-upstream-notice-kb', { type: 'number', default: 128, describe: 'Max size per upstream NOTICE to read (kilobytes)' })\n .option('audit', { type: 'boolean', default: false, describe: 'Print a diff between headings in source and parsed entries' })\n .option('strict', { type: 'boolean', default: false, describe: 'With --audit, exit with code 2 if mismatches found' })\n .option('debug', { type: 'boolean', default: false })\n .help()\n .parseAsync();\n\n isDebug = Boolean(argv.debug);\n repoUtilsService.setDebugMode(isDebug);\n\n // Locate monorepo root\n const scriptDir: string = path.dirname(fileURLToPath(import.meta.url));\n const startCandidates = [process.env.INIT_CWD, process.cwd(), scriptDir].filter(Boolean) as string[];\n const rootDir: string | undefined = await startCandidates.reduce<Promise<string | undefined>>(async (accP: Promise<string | undefined>, c: string): Promise<string | undefined> => {\n const acc: string | undefined = await accP;\n if (acc) return acc;\n try {\n return await findMonorepoRoot(c);\n } catch (e) {\n debugLog(isDebug, 'no root from', c, ':', (e as Error).message);\n return undefined;\n }\n }, Promise.resolve(undefined));\n if (!rootDir) throw new Error(`Failed to find monorepo root from: ${startCandidates.join(', ')}`);\n\n // Workspaces\n const workspaces: ReadonlyMap<string, TWorkspaceInfo> = await loadWorkspaces(rootDir);\n const ws: TWorkspaceInfo = await resolveWorkspaceFromArg(String(argv.workspace), workspaces, rootDir);\n debugLog(isDebug, 'target workspace:', ws.name, ws.dir);\n\n // Source & Out paths\n const sourceName: string = String(argv['source-name'] || 'THIRD_PARTY_LICENSES.md');\n const defaultSource: string = path.join(ws.dir, sourceName);\n const srcPath: string = argv.source ? (path.isAbsolute(argv.source) ? argv.source : path.resolve(process.cwd(), argv.source)) : defaultSource;\n const outPath: string = argv.out ? (path.isAbsolute(argv.out) ? argv.out : path.resolve(process.cwd(), argv.out)) : path.join(ws.dir, 'NOTICE.md');\n\n debugLog(isDebug, 'source:', srcPath);\n debugLog(isDebug, 'out:', outPath);\n\n if (!(await isExist(srcPath))) {\n console.error(`Source file not found: ${srcPath}`);\n process.exit(1);\n }\n\n const src: string = await fs.readFile(srcPath, 'utf8');\n\n // collect declared ids from headings for audit\n const declaredIds: ReadonlySet<string> = collectAllHeadingIds(src);\n\n const entries = parseThirdPartyMarkdown(src);\n debugLog(isDebug, 'parsed entries:', entries.length);\n\n // Optional upstream NOTICE load\n const finalEntries: ReadonlyArray<TTemplateParsedEntry> = await (async (): Promise<ReadonlyArray<TTemplateParsedEntry>> => {\n if (!argv['include-upstream-notices']) return entries;\n const maxBytes: number = Math.max(1, Math.floor(Number(argv['max-upstream-notice-kb']) || 128)) * 1024;\n const withUpstream = await Promise.all(\n entries.map(async (e: TTemplateParsedEntry) => {\n if (!e.path) return e;\n const u: string | undefined = await loadUpstreamNotice(e.path, maxBytes);\n return u ? { ...e, upstreamNotice: u } : e;\n })\n );\n const filledCount: number = withUpstream.filter((e): boolean => Boolean(e.upstreamNotice)).length;\n debugLog(isDebug, 'upstream notices loaded:', filledCount);\n return withUpstream;\n })();\n\n // Audit report\n if (argv.audit) {\n const parsedIds = new Set(finalEntries.map((e: TTemplateParsedEntry): string => e.id));\n const missing: ReadonlyArray<string> = Array.from(declaredIds)\n .filter((id: string): boolean => !parsedIds.has(id))\n .toSorted();\n\n console.log(`NOTICE audit:\n headings in source: ${declaredIds.size}\n parsed entries: ${parsedIds.size}\n missing in NOTICE: ${missing.length}`);\n\n if (missing.length) {\n console.log(missing.map((x: string): string => ` - ${x}`).join('\\n'));\n if (argv.strict) {\n console.error('Audit failed: some entries were not parsed into NOTICE.');\n process.exit(2);\n }\n } else {\n console.log('Audit OK: all entries accounted for.');\n }\n }\n\n const md: string = renderNotice(ws.name, entries, Boolean(argv['include-upstream-notices']), sourceName);\n await fs.mkdir(path.dirname(outPath), { recursive: true });\n await fs.writeFile(outPath, md, 'utf8');\n console.log(`NOTICE.md written -> ${outPath}`);\n }\n\n return { generate };\n}\n"],"names":[],"mappings":"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"}
|