From 783e2b453be549a870de011e6eac655c7cfb51ce Mon Sep 17 00:00:00 2001
From: Simon Lecoq <22963968+lowlighter@users.noreply.github.com>
Date: Thu, 11 Feb 2021 21:39:40 +0100
Subject: [PATCH] Add licenses plugin (#118)
---
Dockerfile | 5 +-
.../api/github/graphql/licenses.default.mjs | 278 ++++++++++++++++++
.../github/graphql/licenses.repository.mjs | 13 +
source/plugins/licenses/README.md | 37 +++
source/plugins/licenses/index.mjs | 147 +++++++++
source/plugins/licenses/metadata.yml | 32 ++
.../plugins/licenses/queries/licenses.graphql | 20 ++
.../licenses/queries/repository.graphql | 14 +
source/plugins/licenses/tests.yml | 13 +
source/templates/classic/style.css | 51 ++++
source/templates/repository/partials/_.json | 3 +-
.../repository/partials/licenses.ejs | 120 ++++++++
12 files changed, 730 insertions(+), 3 deletions(-)
create mode 100644 source/app/mocks/api/github/graphql/licenses.default.mjs
create mode 100644 source/app/mocks/api/github/graphql/licenses.repository.mjs
create mode 100644 source/plugins/licenses/README.md
create mode 100644 source/plugins/licenses/index.mjs
create mode 100644 source/plugins/licenses/metadata.yml
create mode 100644 source/plugins/licenses/queries/licenses.graphql
create mode 100644 source/plugins/licenses/queries/repository.graphql
create mode 100644 source/plugins/licenses/tests.yml
create mode 100644 source/templates/repository/partials/licenses.ejs
diff --git a/Dockerfile b/Dockerfile
index 2fe63407..0f99975d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -16,12 +16,13 @@ RUN chmod +x /metrics/source/app/action/index.mjs \
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \
- # Install ruby to support linguist
- # Based on https://github.com/github/linguist
+ # Install ruby to support github gems
+ # Based on https://github.com/github/linguist and https://github.com/github/licensed
&& apt-get update \
&& apt-get install -y ruby-full \
&& apt-get install -y git g++ cmake pkg-config libicu-dev zlib1g-dev libcurl4-openssl-dev libssl-dev ruby-dev \
&& gem install github-linguist \
+ && gem install licensed \
# Install python for node-gyp
&& apt-get update \
&& apt-get install -y python3 \
diff --git a/source/app/mocks/api/github/graphql/licenses.default.mjs b/source/app/mocks/api/github/graphql/licenses.default.mjs
new file mode 100644
index 00000000..5da8a885
--- /dev/null
+++ b/source/app/mocks/api/github/graphql/licenses.default.mjs
@@ -0,0 +1,278 @@
+/**Mocked data */
+ export default function({faker, query, login = faker.internet.userName()}) {
+ console.debug("metrics/compute/mocks > mocking graphql api result > licenses/default")
+ return ({
+ licenses:[
+ {
+ spdxId:"AGPL-3.0",
+ name:"GNU Affero General Public License v3.0",
+ nickname:"GNU AGPLv3",
+ key:"agpl-3.0",
+ limitations:[
+ {key:"liability", label:"Liability"},
+ {key:"warranty", label:"Warranty"},
+ ],
+ conditions:[
+ {key:"include-copyright", label:"License and copyright notice"},
+ {key:"document-changes", label:"State changes"},
+ {key:"disclose-source", label:"Disclose source"},
+ {key:"network-use-disclose", label:"Network use is distribution"},
+ {key:"same-license", label:"Same license"},
+ ],
+ permissions:[
+ {key:"commercial-use", label:"Commercial use"},
+ {key:"modifications", label:"Modification"},
+ {key:"distribution", label:"Distribution"},
+ {key:"patent-use", label:"Patent use"},
+ {key:"private-use", label:"Private use"},
+ ],
+ },
+ {
+ spdxId:"Apache-2.0",
+ name:"Apache License 2.0",
+ nickname:null,
+ key:"apache-2.0",
+ limitations:[
+ {key:"trademark-use", label:"Trademark use"},
+ {key:"liability", label:"Liability"},
+ {key:"warranty", label:"Warranty"},
+ ],
+ conditions:[
+ {key:"include-copyright", label:"License and copyright notice"},
+ {key:"document-changes", label:"State changes"},
+ ],
+ permissions:[
+ {key:"commercial-use", label:"Commercial use"},
+ {key:"modifications", label:"Modification"},
+ {key:"distribution", label:"Distribution"},
+ {key:"patent-use", label:"Patent use"},
+ {key:"private-use", label:"Private use"},
+ ],
+ },
+ {
+ spdxId:"BSD-2-Clause",
+ name:'BSD 2-Clause "Simplified" License',
+ nickname:null,
+ key:"bsd-2-clause",
+ limitations:[
+ {key:"liability", label:"Liability"},
+ {key:"warranty", label:"Warranty"},
+ ],
+ conditions:[
+ {key:"include-copyright", label:"License and copyright notice"},
+ ],
+ permissions:[
+ {key:"commercial-use", label:"Commercial use"},
+ {key:"modifications", label:"Modification"},
+ {key:"distribution", label:"Distribution"},
+ {key:"private-use", label:"Private use"},
+ ],
+ },
+ {
+ spdxId:"BSD-3-Clause",
+ name:'BSD 3-Clause "New" or "Revised" License',
+ nickname:null,
+ key:"bsd-3-clause",
+ limitations:[
+ {key:"liability", label:"Liability"},
+ {key:"warranty", label:"Warranty"},
+ ],
+ conditions:[
+ {key:"include-copyright", label:"License and copyright notice"},
+ ],
+ permissions:[
+ {key:"commercial-use", label:"Commercial use"},
+ {key:"modifications", label:"Modification"},
+ {key:"distribution", label:"Distribution"},
+ {key:"private-use", label:"Private use"},
+ ],
+ },
+ {
+ spdxId:"BSL-1.0",
+ name:"Boost Software License 1.0",
+ nickname:null,
+ key:"bsl-1.0",
+ limitations:[
+ {key:"liability", label:"Liability"},
+ {key:"warranty", label:"Warranty"},
+ ],
+ conditions:[
+ {key:"include-copyright--source", label:"License and copyright notice for source"},
+ ],
+ permissions:[
+ {key:"commercial-use", label:"Commercial use"},
+ {key:"modifications", label:"Modification"},
+ {key:"distribution", label:"Distribution"},
+ {key:"private-use", label:"Private use"},
+ ],
+ },
+ {
+ spdxId:"CC0-1.0",
+ name:"Creative Commons Zero v1.0 Universal",
+ nickname:null,
+ key:"cc0-1.0",
+ limitations:[
+ {key:"liability", label:"Liability"},
+ {key:"trademark-use", label:"Trademark use"},
+ {key:"patent-use", label:"Patent use"},
+ {key:"warranty", label:"Warranty"},
+ ],
+ conditions:[],
+ permissions:[
+ {key:"commercial-use", label:"Commercial use"},
+ {key:"modifications", label:"Modification"},
+ {key:"distribution", label:"Distribution"},
+ {key:"private-use", label:"Private use"},
+ ],
+ },
+ {
+ spdxId:"EPL-2.0",
+ name:"Eclipse Public License 2.0",
+ nickname:null,
+ key:"epl-2.0",
+ limitations:[
+ {key:"liability", label:"Liability"},
+ {key:"warranty", label:"Warranty"},
+ ],
+ conditions:[
+ {key:"disclose-source", label:"Disclose source"},
+ {key:"include-copyright", label:"License and copyright notice"},
+ {key:"same-license", label:"Same license"},
+ ],
+ permissions:[
+ {key:"commercial-use", label:"Commercial use"},
+ {key:"distribution", label:"Distribution"},
+ {key:"modifications", label:"Modification"},
+ {key:"patent-use", label:"Patent use"},
+ {key:"private-use", label:"Private use"},
+ ],
+ },
+ {
+ spdxId:"GPL-2.0",
+ name:"GNU General Public License v2.0",
+ nickname:"GNU GPLv2",
+ key:"gpl-2.0",
+ limitations:[
+ {key:"liability", label:"Liability"},
+ {key:"warranty", label:"Warranty"},
+ ],
+ conditions:[
+ {key:"include-copyright", label:"License and copyright notice"},
+ {key:"document-changes", label:"State changes"},
+ {key:"disclose-source", label:"Disclose source"},
+ {key:"same-license", label:"Same license"},
+ ],
+ permissions:[
+ {key:"commercial-use", label:"Commercial use"},
+ {key:"modifications", label:"Modification"},
+ {key:"distribution", label:"Distribution"},
+ {key:"private-use", label:"Private use"},
+ ],
+ },
+ {
+ spdxId:"GPL-3.0",
+ name:"GNU General Public License v3.0",
+ nickname:"GNU GPLv3",
+ key:"gpl-3.0",
+ limitations:[
+ {key:"liability", label:"Liability"},
+ {key:"warranty", label:"Warranty"},
+ ],
+ conditions:[
+ {key:"include-copyright", label:"License and copyright notice"},
+ {key:"document-changes", label:"State changes"},
+ {key:"disclose-source", label:"Disclose source"},
+ {key:"same-license", label:"Same license"},
+ ],
+ permissions:[
+ {key:"commercial-use", label:"Commercial use"},
+ {key:"modifications", label:"Modification"},
+ {key:"distribution", label:"Distribution"},
+ {key:"patent-use", label:"Patent use"},
+ {key:"private-use", label:"Private use"},
+ ],
+ },
+ {
+ spdxId:"LGPL-2.1",
+ name:"GNU Lesser General Public License v2.1",
+ nickname:"GNU LGPLv2.1",
+ key:"lgpl-2.1",
+ limitations:[
+ {key:"liability", label:"Liability"},
+ {key:"warranty", label:"Warranty"},
+ ],
+ conditions:[
+ {key:"include-copyright", label:"License and copyright notice"},
+ {key:"disclose-source", label:"Disclose source"},
+ {key:"document-changes", label:"State changes"},
+ {key:"same-license--library", label:"Same license (library)"},
+ ],
+ permissions:[
+ {key:"commercial-use", label:"Commercial use"},
+ {key:"modifications", label:"Modification"},
+ {key:"distribution", label:"Distribution"},
+ {key:"private-use", label:"Private use"},
+ ],
+ },
+ {
+ spdxId:"MIT",
+ name:"MIT License",
+ nickname:null,
+ key:"mit",
+ limitations:[
+ {key:"liability", label:"Liability"},
+ {key:"warranty", label:"Warranty"},
+ ],
+ conditions:[
+ {key:"include-copyright", label:"License and copyright notice"},
+ ],
+ permissions:[
+ {key:"commercial-use", label:"Commercial use"},
+ {key:"modifications", label:"Modification"},
+ {key:"distribution", label:"Distribution"},
+ {key:"private-use", label:"Private use"},
+ ],
+ },
+ {
+ spdxId:"MPL-2.0",
+ name:"Mozilla Public License 2.0",
+ nickname:null,
+ key:"mpl-2.0",
+ limitations:[
+ {key:"liability", label:"Liability"},
+ {key:"trademark-use", label:"Trademark use"},
+ {key:"warranty", label:"Warranty"},
+ ],
+ conditions:[
+ {key:"disclose-source", label:"Disclose source"},
+ {key:"include-copyright", label:"License and copyright notice"},
+ {key:"same-license--file", label:"Same license (file)"},
+ ],
+ permissions:[
+ {key:"commercial-use", label:"Commercial use"},
+ {key:"modifications", label:"Modification"},
+ {key:"distribution", label:"Distribution"},
+ {key:"patent-use", label:"Patent use"},
+ {key:"private-use", label:"Private use"},
+ ],
+ },
+ {
+ spdxId:"Unlicense",
+ name:"The Unlicense",
+ nickname:null,
+ key:"unlicense",
+ limitations:[
+ {key:"liability", label:"Liability"},
+ {key:"warranty", label:"Warranty"},
+ ],
+ conditions:[],
+ permissions:[
+ {key:"private-use", label:"Private use"},
+ {key:"commercial-use", label:"Commercial use"},
+ {key:"modifications", label:"Modification"},
+ {key:"distribution", label:"Distribution"},
+ ],
+ },
+ ],
+ })
+ }
diff --git a/source/app/mocks/api/github/graphql/licenses.repository.mjs b/source/app/mocks/api/github/graphql/licenses.repository.mjs
new file mode 100644
index 00000000..2eb533c2
--- /dev/null
+++ b/source/app/mocks/api/github/graphql/licenses.repository.mjs
@@ -0,0 +1,13 @@
+/**Mocked data */
+ export default function({faker, query, login = faker.internet.userName()}) {
+ console.debug("metrics/compute/mocks > mocking graphql api result > licenses/repository")
+ return ({
+ user:{
+ repository:{
+ licenseInfo:{spdxId:"MIT", name:"MIT License", nickname:null, key:"mit"},
+ url:"https://github.com/lowlighter/metrics",
+ databaseId:293860197,
+ },
+ },
+ })
+ }
diff --git a/source/plugins/licenses/README.md b/source/plugins/licenses/README.md
new file mode 100644
index 00000000..ac7b6c6d
--- /dev/null
+++ b/source/plugins/licenses/README.md
@@ -0,0 +1,37 @@
+### đ Licenses
+
+ â ī¸ This is NOT legal advice, use at your own risk
+ đŖ Do NOT enable this plugin on public web instances (plugin allows raw commands injection)
+
+The *licenses* plugin lets you display license informations like permissions, limitations and conditions along with additional metrics about dependencies.
+
+
+
+
+ With licenses ratio
+
+
+
+ |
+
+
+Project must be setup with dependencies using `plugin_licenses_setup` option (for example, `npm ci` for a NodeJS project).
+
+Dependencies will be analyzed with [github/licensed](https://github.com/github/licensed) and compared against GitHub known licenses.
+
+#### âšī¸ Examples workflows
+
+[âĄī¸ Available options for this plugin](metadata.yml)
+
+```yaml
+- uses: lowlighter/metrics@latest
+ with:
+ # ... other options
+ template: repository
+ user: repository-owner
+ query: '{"repo":"repository-name"}'
+ plugin_licenses: yes
+ plugin_licenses_setup: npm ci # Command to setup target repository
+ plugin_licenses_ratio: yes # Display used licenses ratio
+ plugin_licenses_legal: yes # Display permissions, limitations and conditions
+```
\ No newline at end of file
diff --git a/source/plugins/licenses/index.mjs b/source/plugins/licenses/index.mjs
new file mode 100644
index 00000000..0f71db27
--- /dev/null
+++ b/source/plugins/licenses/index.mjs
@@ -0,0 +1,147 @@
+//Setup
+ export default async function({login, q, imports, data, graphql, queries, account}, {enabled = false} = {}) {
+ //Plugin execution
+ try {
+ //Check if plugin is enabled and requirements are met
+ if ((!enabled)||(!q.licenses))
+ return null
+
+ //Load inputs
+ let {setup, ratio, legal} = imports.metadata.plugins.licenses.inputs({data, account, q})
+
+ //Initialization
+ const {user:{repository}} = await graphql(queries.licenses.repository({owner:data.repo.owner.login, name:data.repo.name, account}))
+ const result = {ratio, legal, default:repository.licenseInfo, licensed:{available:false}, text:{}, list:[], used:{}, dependencies:[], known:0, unknown:0}
+ const {used, text} = result
+
+ //Register existing licenses properties
+ const licenses = Object.fromEntries((await graphql(queries.licenses())).licenses.map(license => [license.key, license]))
+ for (const license of Object.values(licenses))
+ [...license.limitations, ...license.conditions, ...license.permissions].flat().map(({key, label}) => text[key] = label)
+ colors(licenses)
+
+ //Check if licensed exists
+ if (await imports.which("licensed")) {
+ //Setup for licensed
+ console.debug(`metrics/compute/${login}/plugins > licenses > searching dependencies licenses using licensed`)
+ const path = imports.paths.join(imports.os.tmpdir(), `${repository.databaseId}`)
+ //Create temporary directory
+ console.debug(`metrics/compute/${login}/plugins > licenses > creating temp dir ${path}`)
+ await imports.fs.rmdir(path, {recursive:true})
+ await imports.fs.mkdir(path, {recursive:true})
+ //Clone repository
+ console.debug(`metrics/compute/${login}/plugins > licenses > cloning temp git repository ${repository.url} to ${path}`)
+ const git = imports.git(path)
+ await git.clone(repository.url, path)
+ //Run setup
+ if (setup) {
+ console.debug(`metrics/compute/${login}/plugins > licenses > running setup [${setup}]`)
+ await imports.run(setup, {cwd:path}, {prefixed:false})
+ }
+ //Create configuration file if needed
+ if (!(await imports.fs.stat(imports.paths.join(path, ".licensed.yml")).then(() => 1).catch(() => 0))) {
+ console.debug(`metrics/compute/${login}/plugins > licenses > building .licensed.yml configuration file`)
+ await imports.fs.writeFile(imports.paths.join(path, ".licensed.yml"), [
+ "cache_path: .licensed",
+ ].join("\n"))
+ }
+ else
+ console.debug(`metrics/compute/${login}/plugins > licenses > a .licensed.yml configuration file already exists`)
+ //Spawn licensed process
+ console.debug(`metrics/compute/${login}/plugins > licenses > running licensed`)
+ JSON.parse(await imports.run("licensed list --format=json --licenses", {cwd:path})).apps
+ .map(({sources}) => sources?.flatMap(source => source.dependencies.map(({dependency, license}) => {
+ used[license] = (used[license] ?? 0) + 1
+ result.dependencies.push(dependency)
+ result.known += (license in licenses)
+ result.unknown += !(license in licenses)
+ })))
+ //Cleaning
+ console.debug(`metrics/compute/${login}/plugins > licensed > cleaning temp dir ${path}`)
+ await imports.fs.rmdir(path, {recursive:true})
+ }
+ else
+ console.debug(`metrics/compute/${login}/plugins > licenses > licensed not available`)
+
+ //List licenses properties
+ console.debug(`metrics/compute/${login}/plugins > licenses > compute licenses properties`)
+ const base = {permissions:new Set(), limitations:new Set(), conditions:new Set()}
+ const combined = {permissions:new Set(), limitations:new Set(), conditions:new Set()}
+ const detected = Object.entries(used).map(([key, _value]) => ({key}))
+ for (const properties of Object.keys(base)) {
+ //Base license
+ if (repository.licenseInfo)
+ licenses[repository.licenseInfo.key]?.[properties]?.map(({key}) => base[properties].add(key))
+ //Combined licenses
+ for (const {key} of detected)
+ licenses[key]?.[properties]?.map(({key}) => combined[properties].add(key))
+ }
+
+ //Merge limitations and conditions
+ for (const properties of ["limitations", "conditions"])
+ result[properties] = [[...base[properties]].map(key => ({key, text:text[key], inherited:false})), [...combined[properties]].filter(key => !base[properties].has(key)).map(key => ({key, text:text[key], inherited:true}))].flat()
+ //Remove base permissions conflicting with inherited limitations
+ result.permissions = [...base.permissions].filter(key => !combined.limitations.has(key)).map(key => ({key, text:text[key]}))
+
+ //Count used licenses
+ console.debug(`metrics/compute/${login}/plugins > licenses > computing ratio`)
+ const total = Object.values(used).reduce((a, b) => a + b, 0)
+ //Format used licenses and compute positions
+ const list = Object.entries(used).map(([key, count]) => ({name:licenses[key]?.spdxId ?? `${key.charAt(0).toLocaleUpperCase()}${key.substring(1)}`, key, count, value:count/total, x:0, color:licenses[key]?.color ?? "#6e7681", order:licenses[key]?.order ?? -1})).sort((a, b) => a.order === b.order ? b.count - a.count : b.order - a.order)
+ for (let i = 0; i < list.length; i++)
+ list[i].x = (list[i-1]?.x ?? 0) + (list[i-1]?.value ?? 0)
+ //Save ratios
+ result.list = list
+
+ //Results
+ return result
+ }
+ //Handle errors
+ catch (error) {
+ throw {error:{message:"An error occured", instance:error}}
+ }
+ }
+
+/**Licenses colorizer (based on categorie) */
+ function colors(licenses) {
+ for (const [license, value] of Object.entries(licenses)) {
+ const [permissions, conditions] = [value.permissions, value.conditions].map(properties => properties.map(({key}) => key))
+ switch (true) {
+ //Other licenses
+ case (license === "other"):{
+ value.color = "#8b949e"
+ value.order = 0
+ break
+ }
+ //Strongly protective licenses and network protective
+ case ((conditions.includes("disclose-source"))&&(conditions.includes("same-license"))&&(conditions.includes("network-use-disclose"))):{
+ value.color = "#388bfd"
+ value.order = 1
+ break
+ }
+ //Strongly protective licenses
+ case ((conditions.includes("disclose-source"))&&(conditions.includes("same-license"))):{
+ value.color = "#79c0ff"
+ value.order = 2
+ break
+ }
+ //Weakly protective licenses
+ case ((conditions.includes("disclose-source"))&&(conditions.includes("same-license--library"))):{
+ value.color = "#7ee787"
+ value.order = 3
+ break
+ }
+ //Permissive license
+ case ((permissions.includes("private-use"))&&(permissions.includes("commercial-use"))&&(permissions.includes("modifications"))&&(permissions.includes("distribution"))):{
+ value.color = "#56d364"
+ value.order = 4
+ break
+ }
+ //Unknown
+ default:{
+ value.color = "#6e7681"
+ value.order = -1
+ }
+ }
+ }
+ }
\ No newline at end of file
diff --git a/source/plugins/licenses/metadata.yml b/source/plugins/licenses/metadata.yml
new file mode 100644
index 00000000..77d43e4a
--- /dev/null
+++ b/source/plugins/licenses/metadata.yml
@@ -0,0 +1,32 @@
+name: "đ Licenses"
+cost: N/A
+supports:
+ - repository
+inputs:
+
+ # Enable or disable plugin
+ plugin_licenses:
+ description: Display licenses informations
+ type: boolean
+ default: no
+
+ # Command to use to setup target repository
+ # It is required to install all dependencies that will be analyzed with github/licensed
+ plugin_licenses_setup:
+ description: Command to setup target repository
+ type: string
+ default: ""
+ example: npm ci
+
+ # Display used licenses from both repository license and dependencies licenses ratio
+ plugin_licenses_ratio:
+ description: Display used licenses ratio
+ type: boolean
+ default: no
+
+ # Display permissions, limitations and conditions from both repository license and dependencies licenses
+ # Note that this is NOT legal advice, use at your own risk
+ plugin_licenses_legal:
+ description: Display legal informations about used licenses
+ type: boolean
+ default: yes
\ No newline at end of file
diff --git a/source/plugins/licenses/queries/licenses.graphql b/source/plugins/licenses/queries/licenses.graphql
new file mode 100644
index 00000000..4a124508
--- /dev/null
+++ b/source/plugins/licenses/queries/licenses.graphql
@@ -0,0 +1,20 @@
+query LicensesDefault {
+ licenses {
+ spdxId
+ name
+ nickname
+ key
+ limitations {
+ key
+ label
+ }
+ conditions {
+ key
+ label
+ }
+ permissions {
+ key
+ label
+ }
+ }
+}
diff --git a/source/plugins/licenses/queries/repository.graphql b/source/plugins/licenses/queries/repository.graphql
new file mode 100644
index 00000000..7c5530a6
--- /dev/null
+++ b/source/plugins/licenses/queries/repository.graphql
@@ -0,0 +1,14 @@
+query LicensesRepository {
+ $account(login: "$owner") {
+ repository(name: "$name") {
+ licenseInfo {
+ spdxId
+ name
+ nickname
+ key
+ }
+ url
+ databaseId
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/plugins/licenses/tests.yml b/source/plugins/licenses/tests.yml
new file mode 100644
index 00000000..180461ba
--- /dev/null
+++ b/source/plugins/licenses/tests.yml
@@ -0,0 +1,13 @@
+- name: Licenses plugin (complete)
+ uses: lowlighter/metrics@latest
+ with:
+ token: MOCKED_TOKEN
+ template: repository
+ query: '{"repo":"metrics"}'
+ plugin_licenses: yes
+ plugin_licenses_setup: npm ci
+ plugin_licenses_ratio: yes
+ plugin_licenses_legal: yes
+ timeout: 1200000
+ modes:
+ - action
\ No newline at end of file
diff --git a/source/templates/classic/style.css b/source/templates/classic/style.css
index 62c9f9d9..b44d6924 100644
--- a/source/templates/classic/style.css
+++ b/source/templates/classic/style.css
@@ -635,6 +635,57 @@
border-radius: 7px;
}
+/* Licenses */
+ .licenses {
+ display: flex;
+ }
+ .licenses .column {
+ align-items: flex-start;
+ font-size: 12px;
+ color: #666666;
+ flex-shrink: 0;
+ }
+ .licenses-details {
+ margin-top: 8px;
+ }
+ .field.license.details {
+ display: flex;
+ justify-content: space-between;
+ }
+ .field.license.details small {
+ display: flex;
+ justify-content: space-between;
+ color: #666666;
+ text-align: right;
+ }
+ .licenses .column:nth-child(1) {
+ margin-left: 13px;
+ width: 25%;
+ }
+ .licenses .column:nth-child(2) {
+ width: 25%;
+ }
+ .licenses .column:nth-child(3) {
+ width: 50%;
+ }
+ .licenses .column svg {
+ height: 12px;
+ width: 12px;
+ }
+ .licenses .column .title {
+ font-weight: 600;
+ margin-left: 15px;
+ }
+ .licenses .column .permission svg {
+ fill: #56d364;
+ }
+ .licenses .column .limitation svg {
+ fill: #f85149;
+ }
+ .licenses .column .condition svg {
+ fill: #58a6ff;
+ }
+
/* Fade animation */
.af {
opacity: 0;
diff --git a/source/templates/repository/partials/_.json b/source/templates/repository/partials/_.json
index d5dc9d61..f35e07f6 100644
--- a/source/templates/repository/partials/_.json
+++ b/source/templates/repository/partials/_.json
@@ -6,5 +6,6 @@
"pagespeed",
"stargazers",
"people",
- "activity"
+ "activity",
+ "licenses"
]
\ No newline at end of file
diff --git a/source/templates/repository/partials/licenses.ejs b/source/templates/repository/partials/licenses.ejs
new file mode 100644
index 00000000..ada1da96
--- /dev/null
+++ b/source/templates/repository/partials/licenses.ejs
@@ -0,0 +1,120 @@
+<% if (plugins.licenses) { %>
+
+
+
+ Licenses
+
+ <% if (plugins.licenses.error) { %>
+
+
+
+ <%= plugins.licenses.error.message %>
+
+
+ <% } else { %>
+
+
+
+
+ <%= plugins.licenses.default?.spdxId ?? "No license provided" %>
+
+
+
+ <%= plugins.licenses.dependencies.length %> dependenc<%= s(plugins.licenses.dependencies.length, "y") %>
+
+
+
+
+
+ <%= plugins.licenses.known %> known license<%= s(plugins.licenses.known) %> used
+
+
+
+ <%= plugins.licenses.unknown %> unknown license<%= s(plugins.licenses.unknown) %> used
+
+
+
+ <% if (plugins.licenses.ratio) { %>
+
+
+
+
+
+ <% for (const row of [0, 1]) { %>
+
+ <% for (const {name, value, color, count} of plugins.licenses.list.filter((_, i) => i%2 === row)) { %>
+
+
+
+ <%= f.ellipsis(name) %>
+
+
<%= count %>
+
+ <% } %>
+
+ <% } %>
+
+
+ <% } %>
+ <% if (plugins.licenses.legal) { %>
+
+
+ <% if (plugins.licenses.permissions?.length) { %>
+
+
Permissions
+ <% for (const {text, disabled} of plugins.licenses.permissions) { %>
+
">
+ <% if (disabled) { %>
+
+ <% } else { %>
+
+ <% } %>
+ <%= text %>
+
+ <% } %>
+
+ <% } %>
+ <% if (plugins.licenses.limitations?.length) { %>
+
+
Limitations
+ <% for (const {text, inherited} of plugins.licenses.limitations) { %>
+
+ <% if (inherited) { %>
+
+ <% } else { %>
+
+ <% } %>
+ <%= text %>
+
+ <% } %>
+
+ <% } %>
+ <% if (plugins.licenses.conditions?.length) { %>
+
+
Conditions
+ <% for (const {text, inherited} of plugins.licenses.conditions) { %>
+
+ <% if (inherited) { %>
+
+ <% } else { %>
+
+ <% } %>
+ <%= text %>
+
+ <% } %>
+
+ <% } %>
+
+
+ <% } %>
+ <% } %>
+
+<% } %>