@gesslar/licensed 1.0.1 → 1.3.0

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/LICENSE.txt ADDED
@@ -0,0 +1,12 @@
1
+ Copyright (C) 2026 by Brian M. Workman bmw@gesslar.dev
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted.
5
+
6
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
7
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
8
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
9
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
10
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
11
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
12
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # @gesslar/licensed
2
2
 
3
- A simple CLI tool that generates license documentation for Node.js projects. It
4
- reads your `package.json`, finds your license file, and outputs a formatted
5
- markdown section listing your project license and all dependency licenses.
3
+ A simple CLI and API for generating license documentation for Node.js projects.
4
+ It reads your `package.json`, finds your license file, and outputs formatted
5
+ markdown or structured JSON listing your project license and all dependency
6
+ licenses.
6
7
 
7
8
  ## Installation
8
9
 
@@ -16,7 +17,7 @@ Or as a dev dependency:
16
17
  npm install --save-dev @gesslar/licensed
17
18
  ```
18
19
 
19
- ## Usage
20
+ ## CLI Usage
20
21
 
21
22
  Run from your project directory:
22
23
 
@@ -30,18 +31,73 @@ includes:
30
31
  - Your project's license with a link to the license file
31
32
  - A table of all dependencies with their licenses and repository links
32
33
 
34
+ ## API Usage
35
+
36
+ You can also use `licensed` programmatically. Pass a
37
+ [`FileObject`](https://github.com/gesslar/toolkit) pointing to a project's
38
+ `package.json` and get back structured JSON.
39
+
40
+ ```js
41
+ import {licensed} from "@gesslar/licensed"
42
+ import {DirectoryObject} from "@gesslar/toolkit"
43
+
44
+ const cwd = new DirectoryObject("/path/to/project")
45
+ const jsonFile = cwd.getFile("package.json")
46
+ const data = await licensed(jsonFile)
47
+ ```
48
+
49
+ The returned object has the following shape:
50
+
51
+ ```js
52
+ {
53
+ name: "my-package", // package name, or null
54
+ license: "MIT", // SPDX license identifier, or null
55
+ licenseFile: "LICENSE.txt", // detected license filename, or null
56
+ publicDomain: false, // true for Unlicense, 0BSD, CC0-1.0, MIT-0
57
+ dependencies: [
58
+ { name: "lodash", license: "MIT", repo: "https://github.com/lodash/lodash" },
59
+ { name: "private-pkg", license: "Unknown", repo: null },
60
+ ]
61
+ }
62
+ ```
63
+
64
+ ### Generating Markdown from the API
65
+
66
+ Use `buildLicenseSection` to turn the data into the same markdown the CLI
67
+ produces:
68
+
69
+ ```js
70
+ import {licensed, buildLicenseSection} from "@gesslar/licensed"
71
+
72
+ const data = await licensed(jsonFile)
73
+ const markdown = buildLicenseSection({
74
+ name: data.name,
75
+ license: data.license,
76
+ licenseFile: data.licenseFile,
77
+ depResults: data.dependencies,
78
+ })
79
+ ```
80
+
81
+ ### Other Exports
82
+
83
+ - `cleanUrl(raw)` - Normalizes repository URLs (strips `git+`, `.git`,
84
+ converts SSH/git protocols to HTTPS)
85
+ - `npmView(dep)` - Queries the npm registry for a package's license and
86
+ repository URL
87
+ - `buildLicenseSection(options)` - Generates a markdown license section from
88
+ structured data
89
+
33
90
  ## Requirements
34
91
 
35
92
  - Node.js v24.13.0 or higher
36
93
 
37
94
  ## License
38
95
 
39
- licensed is released into the public domain under the
40
- [Unlicense](UNLICENSE.txt).
96
+ `@gesslar/licensed` is released into the public domain under the [0BSD](LICENSE.txt).
41
97
 
42
98
  This package includes or depends on third-party components under their own
43
99
  licenses:
44
100
 
45
101
  | Dependency | License |
46
102
  | --- | --- |
47
- | [@gesslar/toolkit](https://github.com/gesslar/toolkit) | Unlicense |
103
+ | [@gesslar/toolkit](https://github.com/gesslar/toolkit) | 0BSD |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gesslar/licensed",
3
- "version": "1.0.1",
4
- "description": "Simple CLI to produce license copy for README.md",
3
+ "version": "1.3.0",
4
+ "description": "CLI and library for generating license sections in README.md",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/gesslar/licensed.git"
@@ -11,14 +11,15 @@
11
11
  "name": "gesslar",
12
12
  "url": "https://gesslar.dev"
13
13
  },
14
- "license": "Unlicense",
14
+ "license": "0BSD",
15
15
  "type": "module",
16
16
  "homepage": "https://github.com/gesslar/licensed#readme",
17
17
  "scripts": {
18
18
  "clean": "rm -rfv ./dist",
19
19
  "build": "mkdir -pv ./dist && npm pack --pack-destination ./dist/",
20
- "types": "node -e \"require('fs').rmSync('types',{recursive:true,force:true});\" && tsc -p tsconfig.types.json",
21
- "exec": "node ./src/cli.js",
20
+ "types": "node -e \"require('fs').rmSync('types',{recursive:true,force:true});\" && tsc -p tsconfig.types.json && sed -i '1{/^#!.*/d}' types/index.d.ts",
21
+ "exec": "node ./src/index.js",
22
+ "test": "node --test tests/**/*.test.js",
22
23
  "lint": "eslint src/",
23
24
  "submit": "npm publish --access public --//registry.npmjs.org/:_authToken=\"${NPM_ACCESS_TOKEN}\"",
24
25
  "update": "npx npm-check-updates -u && npm install",
@@ -28,18 +29,27 @@
28
29
  "major": "npm version major"
29
30
  },
30
31
  "dependencies": {
31
- "@gesslar/toolkit": "^3.42.0"
32
+ "@gesslar/toolkit": "^5.0.1"
32
33
  },
33
34
  "devDependencies": {
34
- "@gesslar/uglier": "^2.0.0",
35
- "eslint": "^10.0.2"
35
+ "@gesslar/uglier": "^2.4.0",
36
+ "eslint": "^10.2.0",
37
+ "typescript": "^6.0.2"
36
38
  },
37
39
  "engines": {
38
40
  "node": ">=v24.13.0"
39
41
  },
40
42
  "files": [
41
- "UNLICENSE.txt",
42
- "src/**/*.js"
43
+ "LICENSE.txt",
44
+ "src/**/*.js",
45
+ "types/**/*.d.ts",
46
+ "types/**/*.d.ts.map"
43
47
  ],
44
- "bin": "./src/cli.js"
48
+ "exports": {
49
+ ".": {
50
+ "types": "./types/index.d.ts",
51
+ "default": "./src/index.js"
52
+ }
53
+ },
54
+ "bin": "./src/index.js"
45
55
  }
package/src/index.js ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as TK from "@gesslar/toolkit"
4
+ import {buildLicenseSection, npmView, PUBLIC_DOMAIN} from "./lib.js"
5
+ import {fileURLToPath} from "node:url"
6
+
7
+ /**
8
+ * Generates structured license data for a project.
9
+ *
10
+ * @param {import("@gesslar/toolkit").FileObject} jsonFile - A FileObject
11
+ * pointing to the project's package.json.
12
+ * @returns {Promise<{name: string|null, license: string|null,
13
+ * licenseFile: string|null, publicDomain: boolean,
14
+ * dependencies: Array<{name: string, license: string,
15
+ * repo: string|null}>}>} Structured license data.
16
+ */
17
+ export async function licensed(jsonFile) {
18
+ const pkg = await jsonFile.loadData()
19
+ const cwd = jsonFile.parent
20
+
21
+ const licenseFileObj =
22
+ (await cwd.glob("{LICEN[CS]E,UNLICEN[CS]E}{,.txt,.md}"))?.files[0]
23
+
24
+ const deps = Object.keys(pkg.dependencies ?? {}).sort()
25
+ const depResults = deps.length
26
+ ? await Promise.all(deps.map(dep => npmView(dep)))
27
+ : []
28
+
29
+ return {
30
+ name: pkg.name ?? null,
31
+ license: pkg.license ?? null,
32
+ licenseFile: licenseFileObj?.name ?? null,
33
+ publicDomain: PUBLIC_DOMAIN.includes(pkg.license),
34
+ dependencies: depResults,
35
+ }
36
+ }
37
+
38
+ export {buildLicenseSection, cleanUrl, npmView} from "./lib.js"
39
+
40
+ // CLI mode
41
+ if(process.argv[1] === fileURLToPath(import.meta.url)) {
42
+ const cwd = new TK.DirectoryObject()
43
+ const jsonFile = cwd.getFile("package.json")
44
+ const data = await licensed(jsonFile)
45
+
46
+ const output = buildLicenseSection({
47
+ name: data.name,
48
+ license: data.license,
49
+ licenseFile: data.licenseFile,
50
+ depResults: data.dependencies,
51
+ })
52
+
53
+ TK.Glog(output)
54
+ }
package/src/lib.js ADDED
@@ -0,0 +1,91 @@
1
+ import {execFile} from "node:child_process"
2
+ import {promisify} from "node:util"
3
+
4
+ const exec = promisify(execFile)
5
+
6
+ export const PUBLIC_DOMAIN = ["Unlicense", "0BSD", "CC0-1.0", "MIT-0"]
7
+
8
+ /**
9
+ * Cleans a repository URL to a normalized HTTPS form.
10
+ *
11
+ * @param {string|null|undefined} raw - The raw repository URL.
12
+ * @returns {string|null} The cleaned URL or null.
13
+ */
14
+ export function cleanUrl(raw) {
15
+ if(!raw)
16
+ return null
17
+
18
+ return raw
19
+ .replace(/^git\+/, "")
20
+ .replace(/\.git$/, "")
21
+ .replace(/^ssh:\/\/git@github\.com/, "https://github.com")
22
+ .replace(/^git:\/\//, "https://")
23
+ }
24
+
25
+ /**
26
+ * Queries npm registry for a dependency's license and repository URL.
27
+ *
28
+ * @param {string} dep - The dependency name.
29
+ * @returns {Promise<{name: string, license: string, repo: string|null}>}
30
+ * The dependency info.
31
+ */
32
+ export async function npmView(dep) {
33
+ try {
34
+ const {stdout} = await exec(
35
+ "npm", ["view", dep, "repository.url", "license", "--json"]
36
+ )
37
+ const data = JSON.parse(stdout)
38
+
39
+ return {
40
+ name: dep,
41
+ license: data.license ?? "Unknown",
42
+ repo: cleanUrl(data["repository.url"]),
43
+ }
44
+ } catch {
45
+ return {name: dep, license: "Unknown", repo: null}
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Builds the markdown license section lines.
51
+ *
52
+ * @param {object} options - The options.
53
+ * @param {string|undefined} options.name - Package name.
54
+ * @param {string|undefined} options.license - Package license identifier.
55
+ * @param {string|undefined} options.licenseFile - License file name.
56
+ * @param {Array<{name: string, license: string, repo: string|null}>}
57
+ * options.depResults - Dependency info results.
58
+ * @returns {string} The formatted markdown string.
59
+ */
60
+ export function buildLicenseSection({name, license, licenseFile, depResults}) {
61
+ const projName = name ?? "this project"
62
+ const projLicense = license ?? "Unknown"
63
+
64
+ const phrase = PUBLIC_DOMAIN.includes(projLicense)
65
+ ? `\`${projName}\` is released into the public domain under the`
66
+ : `\`${projName}\` is released under the`
67
+
68
+ const lines = [
69
+ "## License",
70
+ "",
71
+ `${phrase} [${projLicense}](${licenseFile ?? "LICENSE"}).`,
72
+ ]
73
+
74
+ if(depResults?.length) {
75
+ lines.push(
76
+ "",
77
+ "This package includes or depends on third-party components under their own",
78
+ "licenses:",
79
+ "",
80
+ "| Dependency | License |",
81
+ "| --- | --- |",
82
+ )
83
+
84
+ for(const {name: dep, license: depLicense, repo} of depResults) {
85
+ const link = repo ? `[${dep}](${repo})` : dep
86
+ lines.push(`| ${link} | ${depLicense} |`)
87
+ }
88
+ }
89
+
90
+ return lines.join("\n")
91
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Generates structured license data for a project.
3
+ *
4
+ * @param {import("@gesslar/toolkit").FileObject} jsonFile - A FileObject
5
+ * pointing to the project's package.json.
6
+ * @returns {Promise<{name: string|null, license: string|null,
7
+ * licenseFile: string|null, publicDomain: boolean,
8
+ * dependencies: Array<{name: string, license: string,
9
+ * repo: string|null}>}>} Structured license data.
10
+ */
11
+ export function licensed(jsonFile: import("@gesslar/toolkit").FileObject): Promise<{
12
+ name: string | null;
13
+ license: string | null;
14
+ licenseFile: string | null;
15
+ publicDomain: boolean;
16
+ dependencies: Array<{
17
+ name: string;
18
+ license: string;
19
+ repo: string | null;
20
+ }>;
21
+ }>;
22
+ export { buildLicenseSection, cleanUrl, npmView } from "./lib.js";
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":";AAMA;;;;;;;;;GASG;AACH,mCAPW,OAAO,kBAAkB,EAAE,UAAU,GAEnC,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,GAAC,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,GAAC,IAAI,CAAC;IACzD,WAAW,EAAE,MAAM,GAAC,IAAI,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC;IAChD,YAAY,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QACnD,IAAI,EAAE,MAAM,GAAC,IAAI,CAAA;KAAC,CAAC,CAAA;CAAC,CAAC,CAqBzB"}
package/types/lib.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Cleans a repository URL to a normalized HTTPS form.
3
+ *
4
+ * @param {string|null|undefined} raw - The raw repository URL.
5
+ * @returns {string|null} The cleaned URL or null.
6
+ */
7
+ export function cleanUrl(raw: string | null | undefined): string | null;
8
+ /**
9
+ * Queries npm registry for a dependency's license and repository URL.
10
+ *
11
+ * @param {string} dep - The dependency name.
12
+ * @returns {Promise<{name: string, license: string, repo: string|null}>}
13
+ * The dependency info.
14
+ */
15
+ export function npmView(dep: string): Promise<{
16
+ name: string;
17
+ license: string;
18
+ repo: string | null;
19
+ }>;
20
+ /**
21
+ * Builds the markdown license section lines.
22
+ *
23
+ * @param {object} options - The options.
24
+ * @param {string|undefined} options.name - Package name.
25
+ * @param {string|undefined} options.license - Package license identifier.
26
+ * @param {string|undefined} options.licenseFile - License file name.
27
+ * @param {Array<{name: string, license: string, repo: string|null}>}
28
+ * options.depResults - Dependency info results.
29
+ * @returns {string} The formatted markdown string.
30
+ */
31
+ export function buildLicenseSection({ name, license, licenseFile, depResults }: {
32
+ name: string | undefined;
33
+ license: string | undefined;
34
+ licenseFile: string | undefined;
35
+ depResults: Array<{
36
+ name: string;
37
+ license: string;
38
+ repo: string | null;
39
+ }>;
40
+ }): string;
41
+ export const PUBLIC_DOMAIN: string[];
42
+ //# sourceMappingURL=lib.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.js"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,8BAHW,MAAM,GAAC,IAAI,GAAC,SAAS,GACnB,MAAM,GAAC,IAAI,CAWvB;AAED;;;;;;GAMG;AACH,6BAJW,MAAM,GACJ,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAC,IAAI,CAAA;CAAC,CAAC,CAkBvE;AAED;;;;;;;;;;GAUG;AACH,gFAPG;IAAkC,IAAI,EAA9B,MAAM,GAAC,SAAS;IACU,OAAO,EAAjC,MAAM,GAAC,SAAS;IACU,WAAW,EAArC,MAAM,GAAC,SAAS;IAEd,UAAU,EADZ,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAC,IAAI,CAAA;KAAC,CAAC;CAEjE,GAAU,MAAM,CAiClB;AArFD,qCAAsE"}
package/UNLICENSE.txt DELETED
@@ -1,24 +0,0 @@
1
- This is free and unencumbered software released into the public domain.
2
-
3
- Anyone is free to copy, modify, publish, use, compile, sell, or
4
- distribute this software, either in source code form or as a compiled
5
- binary, for any purpose, commercial or non-commercial, and by any
6
- means.
7
-
8
- In jurisdictions that recognize copyright laws, the author or authors
9
- of this software dedicate any and all copyright interest in the
10
- software to the public domain. We make this dedication for the benefit
11
- of the public at large and to the detriment of our heirs and
12
- successors. We intend this dedication to be an overt act of
13
- relinquishment in perpetuity of all present and future rights to this
14
- software under copyright law.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
- IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
- OTHER DEALINGS IN THE SOFTWARE.
23
-
24
- For more information, please refer to <https://unlicense.org>
package/src/cli.js DELETED
@@ -1,83 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import * as TK from "@gesslar/toolkit"
4
-
5
- import {execFile} from "node:child_process"
6
- import {promisify} from "node:util"
7
-
8
- ;(async() => {
9
- const exec = promisify(execFile)
10
-
11
- const cwd = new TK.DirectoryObject()
12
- const jsonFile = cwd.getFile("package.json")
13
- const pkg = await jsonFile.loadData()
14
-
15
- // Detect license file in project root
16
- const licenseFile = (await cwd.glob("{LICEN[CS]E,UNLICEN[CS]E}{,.txt,.md}"))?.files[0]
17
-
18
- // Project name without scope
19
- const name = pkg.name?.replace(/^@[^/]+\//, "") ?? "this project"
20
- const license = pkg.license ?? "Unknown"
21
-
22
- const publicDomain = ["Unlicense", "0BSD", "CC0-1.0"]
23
- const phrase = publicDomain.includes(license)
24
- ? `${name} is released into the public domain under the`
25
- : `${name} is released under the`
26
-
27
- const lines = [
28
- "## License",
29
- "",
30
- phrase,
31
- `[${license}](${licenseFile?.name ?? "LICENSE"}).`,
32
- ]
33
-
34
- const deps = Object.keys(pkg.dependencies ?? {}).sort()
35
-
36
- if(deps.length) {
37
- lines.push(
38
- "",
39
- "This package includes or depends on third-party components under their own",
40
- "licenses:",
41
- "",
42
- "| Dependency | License |",
43
- "| --- | --- |",
44
- )
45
-
46
- const results = await Promise.all(deps.map(dep => npmView(dep)))
47
-
48
- for(const {name: dep, license: depLicense, repo} of results) {
49
- const link = repo ? `[${dep}](${repo})` : dep
50
- lines.push(`| ${link} | ${depLicense} |`)
51
- }
52
- }
53
-
54
- TK.Glog(lines.join("\n"))
55
-
56
- async function npmView(dep) {
57
- try {
58
- const {stdout} = await exec(
59
- "npm", ["view", dep, "repository.url", "license", "--json"]
60
- )
61
- const data = JSON.parse(stdout)
62
-
63
- return {
64
- name: dep,
65
- license: data.license ?? "Unknown",
66
- repo: cleanUrl(data["repository.url"]),
67
- }
68
- } catch {
69
- return {name: dep, license: "Unknown", repo: null}
70
- }
71
- }
72
-
73
- function cleanUrl(raw) {
74
- if(!raw)
75
- return null
76
-
77
- return raw
78
- .replace(/^git\+/, "")
79
- .replace(/\.git$/, "")
80
- .replace(/^ssh:\/\/git@github\.com/, "https://github.com")
81
- .replace(/^git:\/\//, "https://")
82
- }
83
- })()