Skip to content

Commit

Permalink
feat: license plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone committed Sep 15, 2021
1 parent 3f93c32 commit b8f4515
Show file tree
Hide file tree
Showing 12 changed files with 356 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .releaseconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"plugins": ["lerna"],
"plugins": ["lerna", "license"],
"exec": {
"before_commit": "yarn run build"
}
Expand Down
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
Placeholder for the next version (at the beginning of the line):
## **WORK IN PROGRESS**
-->
## **WORK IN PROGRESS**
* New plugin `license` to check for outdated license years

## 3.0.0 (2021-09-15)
* Split into plugins
* Interactive version chooser
Expand Down Expand Up @@ -53,4 +56,4 @@
* Added support for monorepos that are managed with [lerna](https://github.com/lerna/lerna)

## v1.5.1 (2020-06-06)
* Added support for splitting the changelog into README.md and CHANGELOG_OLD.md
* Added support for splitting the changelog into README.md and CHANGELOG_OLD.md
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,11 @@ Since version 3, the release script is separated into several plugins, making it
If you need some of these plugins, you have to install and them separately:
| Plugin name | Description |
| ----------- | ----------------------------------- |
| `iobroker` | Update ioBroker's `io-package.json` |
| `lerna` | Monorepo support with `lerna` |
| Plugin name | Description |
| ----------- | ----------------------------------------- |
| `iobroker` | Update ioBroker's `io-package.json` |
| `lerna` | Monorepo support with `lerna` |
| `license` | Check for outdated years in license files |
To do so, add them as a `devDependency`:
Expand Down Expand Up @@ -333,6 +334,16 @@ npm run release patch -- --no-workflow-check
By default the most recent 7 news entries are kept in `io-package.json`. Using this option, you can change the limit.
### `license` plugin options
#### Change where to look for license files to check (`--license`)
By default, the files `LICENSE`, `README` with and without `.md` extension are checked (glob `"{LICENSE,README}{,.md}"`). Using this option, you can define an array of glob patterns to change the search paths.
```bash
npm run release patch -- --license "**/LICENSE" "packages/*/README.md"
```
## Workflow file for automatic release
When using Github Actions, you can enable automatic release on `npm` and `Github Releases` after a tagged build was successful. To do so, include the following job in your workflow definition file (e.g. `.github/workflows/test-and-release.yml`) and configure it to depend on the build jobs (here, they are called `unit-tests`).
Expand Down
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
"<rootDir>/packages/plugin-git/src",
"<rootDir>/packages/plugin-iobroker/src",
"<rootDir>/packages/plugin-lerna/src",
"<rootDir>/packages/plugin-license/src",
"<rootDir>/packages/plugin-package/src",
"<rootDir>/packages/plugin-version/src",
// Add others as necessary
Expand All @@ -23,6 +24,7 @@ module.exports = {
"^@alcalzone/release-script-plugin-iobroker(.*)":
"<rootDir>/packages/plugin-iobroker/src$1",
"^@alcalzone/release-script-plugin-lerna(.*)": "<rootDir>/packages/plugin-lerna/src$1",
"^@alcalzone/release-script-plugin-license(.*)": "<rootDir>/packages/plugin-license/src$1",
"^@alcalzone/release-script-plugin-package(.*)": "<rootDir>/packages/plugin-package/src$1",
"^@alcalzone/release-script-plugin-version(.*)": "<rootDir>/packages/plugin-version/src$1",
"^@alcalzone/release-script-testing(.*)": "<rootDir>/packages/testing/src$1",
Expand Down
21 changes: 21 additions & 0 deletions mkplugin.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash

#
# Small helper to create a new plugin skeleton
#

name="$1"
Name="${name^}"

cp -r packages/plugin-template "packages/plugin-$name"
cd "packages/plugin-$name"
rm -rf build
rm *.tsbuildinfo

sed -i "s/template/$name/g" package.json
sed -i '/ "private": true,/d' package.json
sed -i "s/template/$name/g" src/index.ts
sed -i "s/Template/$Name/g" src/index.ts
sed -i "s/Template/$Name/g" src/index.test.ts

cd ../..
63 changes: 63 additions & 0 deletions packages/plugin-license/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"name": "@alcalzone/release-script-plugin-license",
"publishConfig": {
"access": "public"
},
"version": "3.0.0",
"description": "Plugin for Al Calzone's release script: license",
"keywords": [],
"license": "MIT",
"author": {
"name": "AlCalzone",
"email": "[email protected]"
},
"main": "build/index.js",
"exports": {
".": "./build/index.js",
"./package.json": "./package.json",
"./*.map": "./build/*.js.map",
"./*": "./build/*.js"
},
"types": "build/index.d.ts",
"typesVersions": {
"*": {
"build/index.d.ts": [
"build/index.d.ts"
],
"*": [
"build/*"
]
}
},
"files": [
"build/",
"LICENSE"
],
"engines": {
"node": ">=12.20"
},
"dependencies": {
"@alcalzone/release-script-core": "3.0.0",
"fs-extra": "^10.0.0",
"tiny-glob": "^0.2.9"
},
"devDependencies": {
"@alcalzone/release-script-testing": "3.0.0",
"jest-extended": "^0.11.5",
"typescript": "*"
},
"scripts": {
"clean": "tsc -b tsconfig.build.json --clean",
"build": "tsc -b tsconfig.build.json",
"watch": "tsc -b tsconfig.build.json --watch"
},
"homepage": "https://github.com/AlCalzone/release-script/tree/main/packages/plugin-license#readme",
"repository": {
"type": "git",
"url": "[email protected]:AlCalzone/release-script.git"
},
"bugs": {
"url": "https://github.com/AlCalzone/release-script/issues"
},
"readmeFilename": "README.md"
}
101 changes: 101 additions & 0 deletions packages/plugin-license/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { DefaultStages } from "@alcalzone/release-script-core";
import { createMockContext, TestFS } from "@alcalzone/release-script-testing";
import LicensePlugin from ".";

describe("License plugin", () => {
describe("check stage", () => {
let testFS: TestFS;
let testFSRoot: string;
beforeEach(async () => {
testFS = new TestFS();
testFSRoot = await testFS.getRoot();
});
afterEach(async () => {
await testFS.remove();
});

it("errors when the copyright year is outdated (Test 1)", async () => {
const licPlugin = new LicensePlugin();
const context = createMockContext({
plugins: [licPlugin],
cwd: testFSRoot,
argv: {
license: ["{LICENSE,README}{,.md}"],
},
});

await testFS.create({
"README.md": `## License
Apache 2.0 Copyright 2018-2020`,
});

await licPlugin.executeStage(context, DefaultStages.check);
expect(context.errors).toContainEqual(expect.stringMatching("outdated copyright year"));
expect(context.errors).toContainEqual(expect.stringMatching("2018-2020"));
});

it("errors when the copyright year is outdated (Test 2)", async () => {
const licPlugin = new LicensePlugin();
const context = createMockContext({
plugins: [licPlugin],
cwd: testFSRoot,
argv: {
license: ["{LICENSE,README}{,.md}"],
},
});

await testFS.create({
"LICENSE.md": `Copyright 2018 [email protected]`,
});

await licPlugin.executeStage(context, DefaultStages.check);
expect(context.errors).toContainEqual(expect.stringMatching("outdated copyright year"));
expect(context.errors).toContainEqual(expect.stringMatching("2018"));
});

it("errors when the copyright year is outdated (Test 3)", async () => {
const licPlugin = new LicensePlugin();
const context = createMockContext({
plugins: [licPlugin],
cwd: testFSRoot,
argv: {
license: ["{LICENSE,README}{,.md}"],
},
});

await testFS.create({
LICENSE: `Copyright (C) 2018 - 2019 [email protected]`,
"README.md": `Copyright 2017 [email protected]`,
});

await licPlugin.executeStage(context, DefaultStages.check);
expect(context.errors).toContainEqual(expect.stringMatching("outdated copyright year"));
expect(context.errors).toContainEqual(expect.stringMatching("2018 - 2019"));
expect(context.errors).toContainEqual(expect.stringMatching("2017"));
});

it("errors when the copyright year is outdated (Test 3)", async () => {
const licPlugin = new LicensePlugin();
const context = createMockContext({
plugins: [licPlugin],
cwd: testFSRoot,
argv: {
license: ["packages/**/{LICENSE,README}{,.md}"],
},
});

await testFS.create({
"packages/p1/LICENSE": `Copyright ${new Date().getFullYear()} is ok`,
"packages/p2/README.md": `Copyright 2017 [email protected]`,
});

await licPlugin.executeStage(context, DefaultStages.check);
expect(context.errors).toContainEqual(
expect.stringMatching(/packages[\\/]p2[\\/]README.md/i),
);
expect(context.errors).not.toContainEqual(
expect.stringMatching(/packages[\\/]p1[\\/]LICENSE/i),
);
});
});
});
68 changes: 68 additions & 0 deletions packages/plugin-license/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { DefaultStages } from "@alcalzone/release-script-core";
import type { Context, Plugin, Stage } from "@alcalzone/release-script-core/types";
import fs from "fs-extra";
import path from "path";
import glob from "tiny-glob";
import type { Argv } from "yargs";

class LicensePlugin implements Plugin {
public readonly id = "license";
public readonly stages = [DefaultStages.check];

public defineCLIOptions(yargs: Argv<any>): Argv<any> {
return yargs.options({
license: {
string: true,
array: true,
description: `Globs matching the license files to check`,
default: ["{LICENSE,README}{,.md}"],
},
});
}

async executeStage(context: Context, stage: Stage): Promise<void> {
if (stage.id === "check") {
const globs = context.argv.license as string[];
const files: string[] = [];
for (const pattern of globs) {
files.push(
...(await glob(pattern, {
cwd: context.cwd,
dot: true,
})),
);
}

for (const file of files) {
const filePath = path.join(context.cwd, file);
if (!(await fs.pathExists(filePath))) continue;

const fileContent = await fs.readFile(filePath, "utf8");
const regex =
/copyright\s*(\(c\))?\s*(?<range>(?:\d{4}\s*-\s*)?(?<current>\d{4}))/gi;
let match: RegExpExecArray | null;
let latest: RegExpExecArray | undefined;
while ((match = regex.exec(fileContent))) {
if (
!latest ||
parseInt(match.groups!.current) > parseInt(latest.groups!.current)
) {
latest = match;
}
}

if (!latest) continue;
const latestYear = parseInt(latest.groups!.current);
if (latestYear < new Date().getFullYear()) {
context.cli.error(
`File "${file}" contains an outdated copyright year: ${
latest.groups!.range
}`,
);
}
}
}
}
}

export default LicensePlugin;
20 changes: 20 additions & 0 deletions packages/plugin-license/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// tsconfig for building - only applies to the src directory
{
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "build"
},
"references": [
// Add references to other projects here
{
"path": "../core/tsconfig.build.json"
},
],
"include": [
"src/**/*.ts"
],
"exclude": [
"src/**/*.test.ts"
]
}
21 changes: 21 additions & 0 deletions packages/plugin-license/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// tsconfig for IntelliSense - active in all files in the current package
{
"extends": "../../tsconfig.json",
"compilerOptions": {},
"references": [
// Add references to other projects here
{
"path": "../core/tsconfig.build.json"
},
{
"path": "../testing/tsconfig.build.json"
},
],
"include": [
"src/**/*.ts"
],
"exclude": [
"build/**",
"node_modules/**"
]
}
3 changes: 3 additions & 0 deletions tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
{
"path": "packages/plugin-lerna/tsconfig.build.json"
},
{
"path": "packages/plugin-license/tsconfig.build.json"
},
{
"path": "packages/plugin-package/tsconfig.build.json"
},
Expand Down
Loading

0 comments on commit b8f4515

Please sign in to comment.