Skip to content

Commit

Permalink
feat(ui5-task-copyright): new task to prepend copyrights to sources (#…
Browse files Browse the repository at this point in the history
…952)

BREAKING CHANGE: new release
  • Loading branch information
petermuessig authored Feb 19, 2024
1 parent c982231 commit d7e8ad6
Show file tree
Hide file tree
Showing 18 changed files with 535 additions and 4 deletions.
2 changes: 2 additions & 0 deletions packages/ui5-task-copyright/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
test/__dist__/*
!test/__dist__/.keepMe
1 change: 1 addition & 0 deletions packages/ui5-task-copyright/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
69 changes: 69 additions & 0 deletions packages/ui5-task-copyright/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# UI5 task for appending copyright headers for TypeScript, JavaScript and XML files

> :wave: This is a **community project** and there is no official support for this package! Feel free to use it, open issues, contribute, and help answering questions.
Task for [ui5-builder](https://github.com/SAP/ui5-builder) for appending copyright headers to every TypeScript (`*.ts`), JavaScript (`*.js`), or XML (`*.xml`) source file.

## Prerequisites

- Requires at least [`@ui5/[email protected]`](https://sap.github.io/ui5-tooling/v3/pages/CLI/) (to support [`specVersion: "3.0"`](https://sap.github.io/ui5-tooling/pages/Configuration/#specification-version-30))

> :warning: **UI5 Tooling Compatibility**
> All releases of this tooling extension using the major version `3` require UI5 Tooling V3. Any previous releases below major version `3` (if available) also support older versions of the UI5 Tooling. But the usage of the latest UI5 Tooling is strongly recommended!
## Install

```bash
npm install ui5-task-copyright --save-dev
```

## Configuration options (in `$yourapp/ui5.yaml`)

- copyright: `String`
the value of the copyright or the path to a file containing the copyright statement - if not given, the task will be skipped

- copyrightPlaceholder: `String` (defaults to: `copyright`)
the name of the copyright placeholder to check for in the copyright comments and to replace with the copyright value (will replace all hits of `${copyright}` or `@copyright@`)

- currentYearPlaceholder: `String` (defaults to: `currentYear`)
the name of the currentYear placeholder in the copyright comments which will be replaced with the currentYear value (will replace all hits of `${currentYear}` or `@currentYear@`)

- excludePatterns: `Array<String>`
array of paths inside `$yourapp/` to exclude from the minification, e.g. 3-rd party libs in `lib/*`. defaults to an empty array `[]`.

## Usage

1. Define the dependency in `$yourapp/package.json`:

```json
"devDependencies": {
// ...
"ui5-task-copyright": "*"
// ...
}
```

2. configure it in `$yourapp/ui5.yaml`:

```yaml
builder:
customTasks:
- name: ui5-task-copyright
beforeTask: replaceCopyright
configuration:
copyright: "Copyright ${currentYear} UI5 Community"
excludePatterns:
- "thirdparty/"
```
## How to obtain support
Please use the GitHub bug tracking system to post questions, bug reports or to create pull requests.
## Contributing
Any type of contribution (code contributions, pull requests, issues) to this showcase will be equally appreciated.
## License
This work is [dual-licensed](../../LICENSE) under Apache 2.0 and the Derived Beer-ware License. The official license will be Apache 2.0 but finally you can choose between one of them if you use this work.
31 changes: 31 additions & 0 deletions packages/ui5-task-copyright/bin/uses-ui5-task-copyright.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env node

const path = require("path");
const fs = require("fs");
const yaml = require("js-yaml");

const ui5YamlPath = process.argv[2] || path.join(process.cwd(), "ui5.yaml");
if (!ui5YamlPath) {
console.error("\x1b[31m[ERROR]\x1b[0m No ui5.yaml provided!");
process.exit(1);
}
if (!fs.existsSync(ui5YamlPath) || !fs.statSync(ui5YamlPath).isFile()) {
console.error(`\x1b[31m[ERROR]\x1b[0m The ${ui5YamlPath} does not exist!`);
process.exit(1);
}

try {
const content = fs.readFileSync(ui5YamlPath, "utf-8");
const ui5Configs = yaml.loadAll(content);
const notFound = (ui5Configs[0]?.builder?.customTasks?.findIndex((task) => task.name === "ui5-task-copyright") || -1) === -1;
if (notFound) {
console.error(`\x1b[31m[ERROR]\x1b[0m ui5-task-copyright is not registered in ${ui5YamlPath}!`);
process.exit(1);
}
} catch (err) {
if (err.name === "YAMLException") {
console.error(`\x1b[31m[ERROR]\x1b[0m Failed to read ${ui5YamlPath}!`);
process.exit(1);
}
throw err;
}
147 changes: 147 additions & 0 deletions packages/ui5-task-copyright/lib/copyright.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
const fs = require("fs");

const { parse } = require("@typescript-eslint/typescript-estree");
const { XMLParser } = require("fast-xml-parser");

/**
* Task to append a copyright header for TypeScript, JavaScript and XML files
* @param {object} parameters Parameters
* @param {module:@ui5/logger/Logger} parameters.log Logger instance
* @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files
* @param {object} parameters.options Options
* @returns {Promise<undefined>} Promise resolving with undefined once data has been written
*/
module.exports = async ({ log, workspace, options }) => {
// disable task if no copyright is configured
if (!options?.configuration?.copyright) {
return Promise.resolve();
}

// determine the copyright and current year placeholders
const { copyrightPlaceholder, currentYearPlaceholder } = Object.assign(
{
copyrightPlaceholder: "copyright",
currentYearPlaceholder: "currentYear",
},
options.configuration
);

// create regular expressions for the placeholders
const copyrightRegExp = new RegExp(`(?:\\$\\{${copyrightPlaceholder}\\}|@${copyrightPlaceholder}@)`, "g");
const currentYearRegExp = new RegExp(`(?:\\$\\{${currentYearPlaceholder}\\}|@${currentYearPlaceholder}@)`, "g");

// determine the actual copyright or default it
let { copyright, excludePatterns } = Object.assign(
{
copyright: `\${${copyrightPlaceholder}}`,
},
options.configuration
);

// check if the file exists (if copyright is a file path)
if (fs.existsSync(copyright)) {
log.verbose(`Reading copyright from file ${copyright}...`);
copyright = fs.readFileSync(copyright, "utf-8").trim();
}

// Replace optional placeholder ${currentYear} with the current year
const copyrightString = copyright.replace(currentYearRegExp, new Date().getFullYear());

// process the script resources
const scriptResources = await workspace.byGlob(`**/*.+(ts|js)`);
if (scriptResources.length > 0) {
await Promise.all(
scriptResources.map(async (resource) => {
const resourcePath = resource.getPath();

// check if the resource should be excluded
if (excludePatterns && excludePatterns.some((pattern) => resourcePath.includes(pattern))) {
return;
}

// detailed logging
log.verbose("Processing file " + resourcePath);

// read the resource and parse the code
const code = await resource.getString();
const ast = parse(code, {
filePath: resourcePath,
loc: true,
range: true,
comment: true,
});

// find the first comment that starts with "!" and has a copyright placeholder
const copyrightComment = ast.comments.find((comment) => {
return comment.type === "Block" && comment.value.startsWith("!") && copyrightRegExp.test(comment.value);
});

// preprend the comment or replace the copyight placeholder
const copyrightForJS = copyrightString
.split(/\r?\n/)
.map((line) => line.trimEnd())
.join("\n * ");
if (!copyrightComment) {
await resource.setString(`/*!\n * ${copyrightForJS}\n */\n${code}`);
} else {
await resource.setString(code.replace(copyrightRegExp, copyrightForJS));
}

// write the resource
await workspace.write(resource);
})
);
}

// process the xml resources
const xmlResources = await workspace.byGlob(`**/*.xml`);
if (xmlResources.length > 0) {
// use fast-xml-parser to parse the xml files (including comments)
const parser = new XMLParser({
commentPropName: "#comment",
});
await Promise.all(
xmlResources.map(async (resource) => {
const resourcePath = resource.getPath();

// check if the resource should be excluded
if (excludePatterns && excludePatterns.some((pattern) => resourcePath.includes(pattern))) {
return;
}

// detailed logging
log.verbose("Processing file " + resourcePath);

// read the resource and parse the xml
const code = await resource.getString();
const xml = parser.parse(code);

// preprend the comment or replace the copyight placeholder
const copyrightForXML = copyrightString
.split(/\r?\n/)
.map((line) => line.trimEnd())
.join("\n ");
if (!(xml["#comment"] && copyrightRegExp.test(xml["#comment"]))) {
await resource.setString(`<!--\n ${copyrightForXML}\n-->\n${code}`);
} else {
await resource.setString(code.replace(copyrightRegExp, copyrightForXML));
}

// write the resource
await workspace.write(resource);
})
);
}
};

/**
* Callback function to define the list of required dependencies
* @returns {Promise<Set>}
* Promise resolving with a Set containing all dependencies
* that should be made available to the task.
* UI5 Tooling will ensure that those dependencies have been
* built before executing the task.
*/
module.exports.determineRequiredDependencies = async function () {
return new Set();
};
38 changes: 38 additions & 0 deletions packages/ui5-task-copyright/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "ui5-task-copyright",
"version": "2.0.0",
"description": "UI5 task for appending copyright headers for TypeScript, JavaScript and XML files",
"author": "Peter Muessig",
"license": "Apache-2.0",
"homepage": "https://github.com/ui5-community/ui5-ecosystem-showcase/tree/main/packages/ui5-task-copyright#readme",
"repository": {
"type": "git",
"url": "https://github.com/ui5-community/ui5-ecosystem-showcase.git",
"directory": "packages/ui5-task-copyright"
},
"bin": {
"uses-ui5-task-copyright": "./bin/uses-ui5-task-copyright.js"
},
"scripts": {
"lint": "eslint lib",
"test": "ava"
},
"ava": {
"files": [
"test/**/*",
"!test/__assets__/**/*",
"!test/__dist__/**/*"
],
"verbose": true,
"timeout": "5m"
},
"devDependencies": {
"@ui5/cli": "^3.8.0",
"ava": "^5.3.1"
},
"dependencies": {
"@typescript-eslint/typescript-estree": "^7.0.1",
"fast-xml-parser": "^4.3.3",
"js-yaml": "^4.1.0"
}
}
24 changes: 24 additions & 0 deletions packages/ui5-task-copyright/test/__assets__/ui5.file.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
specVersion: "3.0"
metadata:
name: ui5.ecosystem.demo.tsapp
type: application
framework:
name: OpenUI5
version: "1.120.1"
libraries:
- name: sap.m
- name: sap.ui.core
- name: sap.ui.unified
- name: themelib_sap_horizon
builder:
customTasks:
- name: ui5-tooling-transpile-task
afterTask: replaceVersion
configuration:
generateDts: true
generateTsInterfaces: true
omitTSFromBuildResult: false
- name: ui5-task-copyright
beforeTask: replaceCopyright
configuration:
copyright: ./copyright.txt
25 changes: 25 additions & 0 deletions packages/ui5-task-copyright/test/__assets__/ui5.inline.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
specVersion: "3.0"
metadata:
name: ui5.ecosystem.demo.tsapp
type: application
framework:
name: OpenUI5
version: "1.120.1"
libraries:
- name: sap.m
- name: sap.ui.core
- name: sap.ui.unified
- name: themelib_sap_horizon
builder:
customTasks:
- name: ui5-tooling-transpile-task
afterTask: replaceVersion
configuration:
generateDts: true
generateTsInterfaces: true
omitTSFromBuildResult: false
- name: ui5-task-copyright
beforeTask: replaceCopyright
configuration:
copyright: "Copyright ${currentYear} UI5 Community
\nAll rights reserved."
22 changes: 22 additions & 0 deletions packages/ui5-task-copyright/test/__assets__/ui5.no.copyright.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
specVersion: "3.0"
metadata:
name: ui5.ecosystem.demo.tsapp
type: application
framework:
name: OpenUI5
version: "1.120.1"
libraries:
- name: sap.m
- name: sap.ui.core
- name: sap.ui.unified
- name: themelib_sap_horizon
builder:
customTasks:
- name: ui5-tooling-transpile-task
afterTask: replaceVersion
configuration:
generateDts: true
generateTsInterfaces: true
omitTSFromBuildResult: false
- name: ui5-task-copyright
beforeTask: replaceCopyright
Empty file.
Loading

0 comments on commit d7e8ad6

Please sign in to comment.