semantic-release-openapi 1.4.1
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 +21 -0
- package/README.md +61 -0
- package/dist/@types/pluginConfig.d.ts +3 -0
- package/dist/@types/pluginConfig.js +2 -0
- package/dist/@types/pluginConfig.js.map +1 -0
- package/dist/getReplaceInFile.d.ts +10 -0
- package/dist/getReplaceInFile.js +13 -0
- package/dist/getReplaceInFile.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/prepare.d.ts +7 -0
- package/dist/prepare.js +83 -0
- package/dist/prepare.js.map +1 -0
- package/dist/verifyConditions.d.ts +7 -0
- package/dist/verifyConditions.js +30 -0
- package/dist/verifyConditions.js.map +1 -0
- package/package.json +175 -0
- package/src/@types/pluginConfig.ts +3 -0
- package/src/getReplaceInFile.ts +12 -0
- package/src/index.ts +2 -0
- package/src/prepare.ts +85 -0
- package/src/verifyConditions.ts +40 -0
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2022 Andrew Ensley
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# semantic-release-openapi
|
2
|
+
|
3
|
+
[][npm]
|
4
|
+
[][npm]
|
5
|
+
[](https://github.com/aensley/semantic-release-openapi/blob/main/LICENSE)
|
6
|
+
[](https://standardjs.com)
|
7
|
+
[](https://prettier.io)
|
8
|
+
|
9
|
+
[](https://github.com/aensley/semantic-release-openapi/actions/workflows/ci.yml)
|
10
|
+
[](https://codeclimate.com/github/aensley/semantic-release-openapi/maintainability)
|
11
|
+
[](https://codeclimate.com/github/aensley/semantic-release-openapi/test_coverage)
|
12
|
+
[][npm]
|
13
|
+
|
14
|
+
A Semantic Release plugin to update versions in OpenAPI / Swagger specification files.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
This module is distributed via npm and should be installed as one of your project's `devDependencies`:
|
19
|
+
|
20
|
+
```bash
|
21
|
+
npm install --save-dev @aensley/semantic-release-openapi
|
22
|
+
```
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
### Plugin Config
|
27
|
+
|
28
|
+
This plugin has one configuration option which must be supplied.
|
29
|
+
|
30
|
+
- **`apiSpecFiles`**: An array of OpenAPI / Swagger specification file paths. [Glob patterns](https://www.gnu.org/software/bash/manual/bash.html#Pattern-Matching) (e.g. `'*.yaml'`) are supported.
|
31
|
+
|
32
|
+
_All matching files must have a `.json`, `.yaml`, or `.yml` extension._
|
33
|
+
|
34
|
+
### Committing Changes
|
35
|
+
|
36
|
+
**IMPORTANT**: Semantic Release will not commit changes to your OpenAPI spec files unless you add the same files in `assets` for the **@semantic-release/git** plugin! See the example below.
|
37
|
+
|
38
|
+
### Example
|
39
|
+
|
40
|
+
```json
|
41
|
+
{
|
42
|
+
"release": {
|
43
|
+
"plugins": [
|
44
|
+
[
|
45
|
+
"@aensley/semantic-release-openapi",
|
46
|
+
{
|
47
|
+
"apiSpecFiles": ["openapi.yaml", "src/swagger-*.json"]
|
48
|
+
}
|
49
|
+
],
|
50
|
+
[
|
51
|
+
"@semantic-release/git",
|
52
|
+
{
|
53
|
+
"assets": ["package.json", "openapi.yaml", "src/swagger-*.json"]
|
54
|
+
}
|
55
|
+
]
|
56
|
+
]
|
57
|
+
}
|
58
|
+
}
|
59
|
+
```
|
60
|
+
|
61
|
+
[npm]: https://www.npmjs.com/package/@aensley/semantic-release-openapi
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"pluginConfig.js","sourceRoot":"","sources":["../../src/@types/pluginConfig.ts"],"names":[],"mappings":""}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
/**
|
2
|
+
* Dynamically imports and returns the default export from the 'replace-in-file' module.
|
3
|
+
*
|
4
|
+
* This function uses dynamic import to load the 'replace-in-file' package at runtime,
|
5
|
+
* which can help reduce initial load time and dependencies if the functionality is only
|
6
|
+
* needed conditionally.
|
7
|
+
*
|
8
|
+
* @returns A promise that resolves to the default export of the 'replace-in-file' module.
|
9
|
+
*/
|
10
|
+
export default function (): Promise<typeof import('replace-in-file').default>;
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/**
|
2
|
+
* Dynamically imports and returns the default export from the 'replace-in-file' module.
|
3
|
+
*
|
4
|
+
* This function uses dynamic import to load the 'replace-in-file' package at runtime,
|
5
|
+
* which can help reduce initial load time and dependencies if the functionality is only
|
6
|
+
* needed conditionally.
|
7
|
+
*
|
8
|
+
* @returns A promise that resolves to the default export of the 'replace-in-file' module.
|
9
|
+
*/
|
10
|
+
export default async function () {
|
11
|
+
return (await import('replace-in-file')).default;
|
12
|
+
}
|
13
|
+
//# sourceMappingURL=getReplaceInFile.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"getReplaceInFile.js","sourceRoot":"","sources":["../src/getReplaceInFile.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK;IAClB,OAAO,CAAC,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAA;AAClD,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACnE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/prepare.js
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
import { readJsonSync, writeJsonSync } from 'fs-extra';
|
2
|
+
import { fdir } from 'fdir';
|
3
|
+
import getReplaceInFile from './getReplaceInFile.js';
|
4
|
+
/**
|
5
|
+
* Prepare the API Spec files
|
6
|
+
*
|
7
|
+
* @param {string[]} apiSpecFiles List of api spec file paths, globs supported
|
8
|
+
* @param {string} version The version string to write to the files
|
9
|
+
* @param {Context['logger']} logger The semantic release logger instance
|
10
|
+
*
|
11
|
+
* @throws {SemanticReleaseError}
|
12
|
+
*/
|
13
|
+
const prepareApiSpecFiles = async (apiSpecFiles, version, logger) => {
|
14
|
+
const SemanticReleaseError = (await import('@semantic-release/error')).default;
|
15
|
+
try {
|
16
|
+
for (const fileNameGlob of apiSpecFiles) {
|
17
|
+
// eslint-disable-next-line new-cap
|
18
|
+
const fileNames = new fdir().withRelativePaths().glob(fileNameGlob).crawl('.').sync();
|
19
|
+
for (const fileName of fileNames) {
|
20
|
+
let results;
|
21
|
+
if (fileName.split('.').pop() === 'json') {
|
22
|
+
results = prepareApiSpecFileJson(fileName, version);
|
23
|
+
}
|
24
|
+
else {
|
25
|
+
results = await prepareApiSpecFileYml(fileName, version);
|
26
|
+
}
|
27
|
+
results.forEach((resultFileName) => {
|
28
|
+
logger.log('Wrote version %s to %s', version, resultFileName);
|
29
|
+
});
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
33
|
+
catch (error) {
|
34
|
+
throw new SemanticReleaseError(error);
|
35
|
+
}
|
36
|
+
};
|
37
|
+
/**
|
38
|
+
* Prepares a single API spec file in YAML format
|
39
|
+
*
|
40
|
+
* @param {string} apiSpecFile Single spec file to update, no globs
|
41
|
+
* @param {string} version The version string to write to the file
|
42
|
+
*
|
43
|
+
* @returns {string[]} A list of altered files
|
44
|
+
*/
|
45
|
+
const prepareApiSpecFileYml = async (apiSpecFile, version) => {
|
46
|
+
const replace = await getReplaceInFile();
|
47
|
+
return replace
|
48
|
+
.sync({
|
49
|
+
files: apiSpecFile,
|
50
|
+
from: /version: ?.+$/im,
|
51
|
+
to: 'version: ' + version
|
52
|
+
})
|
53
|
+
.filter((result) => result.hasChanged)
|
54
|
+
.map((result) => result.file);
|
55
|
+
};
|
56
|
+
/**
|
57
|
+
* Prepares a single API spec file in JSON format
|
58
|
+
*
|
59
|
+
* @param {string} apiSpecFile Single spec file to update, no globs
|
60
|
+
* @param {string} version The version string to write to the file
|
61
|
+
*
|
62
|
+
* @returns {string[]} A list of altered files
|
63
|
+
*/
|
64
|
+
const prepareApiSpecFileJson = (apiSpecFile, version) => {
|
65
|
+
const specFile = readJsonSync(apiSpecFile);
|
66
|
+
specFile.info.version = version;
|
67
|
+
writeJsonSync(apiSpecFile, specFile, { spaces: 2 });
|
68
|
+
return [apiSpecFile];
|
69
|
+
};
|
70
|
+
/**
|
71
|
+
* prepare hook for semantic release
|
72
|
+
*
|
73
|
+
* @throws {SemanticReleaseError}
|
74
|
+
*/
|
75
|
+
export default async function ({ apiSpecFiles }, { nextRelease, logger }) {
|
76
|
+
const version = nextRelease?.version ?? '';
|
77
|
+
if (version.length < 1) {
|
78
|
+
const SemanticReleaseError = (await import('@semantic-release/error')).default;
|
79
|
+
throw new SemanticReleaseError('Could not determine the version from semantic release.');
|
80
|
+
}
|
81
|
+
await prepareApiSpecFiles(apiSpecFiles, version, logger);
|
82
|
+
}
|
83
|
+
//# sourceMappingURL=prepare.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"prepare.js","sourceRoot":"","sources":["../src/prepare.ts"],"names":[],"mappings":"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"}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { fdir } from 'fdir';
|
2
|
+
/**
|
3
|
+
* verifyConditions hook for semantic release
|
4
|
+
*
|
5
|
+
* @throws {SemanticReleaseError}
|
6
|
+
*/
|
7
|
+
export default async function ({ apiSpecFiles }) {
|
8
|
+
const SemanticReleaseError = (await import('@semantic-release/error')).default;
|
9
|
+
if (apiSpecFiles.length < 1) {
|
10
|
+
throw new SemanticReleaseError('Option "apiSpecFiles" was not included in the plugin config. See the README for instructions.', 'ENOAPISPECFILES');
|
11
|
+
}
|
12
|
+
const expectedExts = ['json', 'yaml', 'yml'];
|
13
|
+
let specFilesFound = false;
|
14
|
+
apiSpecFiles.forEach((fileNameGlob) => {
|
15
|
+
// eslint-disable-next-line new-cap
|
16
|
+
const fileNames = new fdir().glob(fileNameGlob).crawl('.').sync();
|
17
|
+
if (fileNames.length > 0) {
|
18
|
+
specFilesFound = true;
|
19
|
+
fileNames.forEach((fileName) => {
|
20
|
+
if (!expectedExts.includes(fileName.split('.').pop() ?? '')) {
|
21
|
+
throw new SemanticReleaseError('File "' + fileName + '" is not valid. Must be a file with .json, .yaml, or .yml extension', 'EINVALIDAPISPECFILETYPE');
|
22
|
+
}
|
23
|
+
});
|
24
|
+
}
|
25
|
+
});
|
26
|
+
if (!specFilesFound) {
|
27
|
+
throw new SemanticReleaseError('No files match the paths in "apiSpecFiles". Check your plugin config and try again.', 'EINVALIDAPISPECFILES');
|
28
|
+
}
|
29
|
+
}
|
30
|
+
//# sourceMappingURL=verifyConditions.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"verifyConditions.js","sourceRoot":"","sources":["../src/verifyConditions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B;;;;GAIG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,WAAW,EAAE,YAAY,EAAgB;IAC3D,MAAM,oBAAoB,GAAG,CAAC,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAAC,CAAC,OAAO,CAAA;IAC9E,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,oBAAoB,CAC5B,+FAA+F,EAC/F,iBAAiB,CAClB,CAAA;IACH,CAAC;IACD,MAAM,YAAY,GAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;IACtD,IAAI,cAAc,GAAY,KAAK,CAAA;IACnC,YAAY,CAAC,OAAO,CAAC,CAAC,YAAoB,EAAE,EAAE;QAC5C,mCAAmC;QACnC,MAAM,SAAS,GAAa,IAAI,IAAI,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;QAC3E,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,cAAc,GAAG,IAAI,CAAA;YACrB,SAAS,CAAC,OAAO,CAAC,CAAC,QAAgB,EAAE,EAAE;gBACrC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;oBAC5D,MAAM,IAAI,oBAAoB,CAC5B,QAAQ,GAAG,QAAQ,GAAG,qEAAqE,EAC3F,yBAAyB,CAC1B,CAAA;gBACH,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC,CAAC,CAAA;IACF,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,oBAAoB,CAC5B,qFAAqF,EACrF,sBAAsB,CACvB,CAAA;IACH,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
{
|
2
|
+
"name": "semantic-release-openapi",
|
3
|
+
"version": "1.4.1",
|
4
|
+
"description": "A Semantic Release plugin to update versions in OpenAPI / Swagger specification files",
|
5
|
+
"repository": {
|
6
|
+
"type": "git",
|
7
|
+
"url": "git+https://github.com/aensley/semantic-release-openapi.git"
|
8
|
+
},
|
9
|
+
"keywords": [
|
10
|
+
"semantic-release",
|
11
|
+
"plugin",
|
12
|
+
"openapi",
|
13
|
+
"version",
|
14
|
+
"swagger",
|
15
|
+
"asyncapi"
|
16
|
+
],
|
17
|
+
"author": {
|
18
|
+
"name": "Andrew Ensley",
|
19
|
+
"email": "aensley@users.noreply.github.com",
|
20
|
+
"url": "https://andrewensley.com"
|
21
|
+
},
|
22
|
+
"license": "MIT",
|
23
|
+
"bugs": {
|
24
|
+
"url": "https://github.com/aensley/semantic-release-openapi/issues/new?template=bug-report.yml"
|
25
|
+
},
|
26
|
+
"funding": [
|
27
|
+
"https://github.com/sponsors/aensley",
|
28
|
+
"https://paypal.me/AndrewEnsley"
|
29
|
+
],
|
30
|
+
"homepage": "https://github.com/aensley/semantic-release-openapi#readme",
|
31
|
+
"type": "module",
|
32
|
+
"main": "./dist/index.js",
|
33
|
+
"module": "./dist/index.js",
|
34
|
+
"types": "dist/index.d.ts",
|
35
|
+
"source": "src/index.ts",
|
36
|
+
"files": [
|
37
|
+
"dist/**/*",
|
38
|
+
"src/**/*",
|
39
|
+
"package.json",
|
40
|
+
"README.md",
|
41
|
+
"LICENSE"
|
42
|
+
],
|
43
|
+
"engines": {
|
44
|
+
"node": ">=16"
|
45
|
+
},
|
46
|
+
"platform": "node",
|
47
|
+
"dependencies": {
|
48
|
+
"@semantic-release/error": "^4.0.0",
|
49
|
+
"fdir": "^6.4.6",
|
50
|
+
"fs-extra": "^11.3.0",
|
51
|
+
"picomatch": "^4.0.2",
|
52
|
+
"replace-in-file": "^8.3.0"
|
53
|
+
},
|
54
|
+
"devDependencies": {
|
55
|
+
"@semantic-release/git": "^10.0.1",
|
56
|
+
"@types/fs-extra": "^11.0.4",
|
57
|
+
"@types/jest": "^29",
|
58
|
+
"@types/node": "^24.0.10",
|
59
|
+
"@types/picomatch": "^4.0.0",
|
60
|
+
"@types/semantic-release": "^20.0.6",
|
61
|
+
"@types/semantic-release__error": "^3.0.3",
|
62
|
+
"jest": "^29",
|
63
|
+
"pre-commit": "^1.2.2",
|
64
|
+
"prettier": "^3.6.2",
|
65
|
+
"ts-jest": "^29",
|
66
|
+
"ts-node": "^10.9.2",
|
67
|
+
"ts-standard": "^12.0.2",
|
68
|
+
"typescript": "^5.8.3"
|
69
|
+
},
|
70
|
+
"peerDependencies": {
|
71
|
+
"semantic-release": ">=20.0.0"
|
72
|
+
},
|
73
|
+
"scripts": {
|
74
|
+
"pre-commit-msg": "echo Running pre-commit checks...",
|
75
|
+
"build": "tsc --build",
|
76
|
+
"format": "prettier --write .",
|
77
|
+
"test": "ts-standard && prettier --check . && jest --coverage --verbose",
|
78
|
+
"lint": "ts-standard && prettier --check .",
|
79
|
+
"setup": "npm install && npm run prepare-hook",
|
80
|
+
"update": "npx npm-check-updates -u && npm update"
|
81
|
+
},
|
82
|
+
"pre-commit": {
|
83
|
+
"run": [
|
84
|
+
"pre-commit-msg",
|
85
|
+
"test"
|
86
|
+
],
|
87
|
+
"silent": true,
|
88
|
+
"colors": true
|
89
|
+
},
|
90
|
+
"release": {
|
91
|
+
"branches": [
|
92
|
+
"main"
|
93
|
+
],
|
94
|
+
"plugins": [
|
95
|
+
"@semantic-release/commit-analyzer",
|
96
|
+
"@semantic-release/release-notes-generator",
|
97
|
+
"@semantic-release/github",
|
98
|
+
"@semantic-release/npm",
|
99
|
+
[
|
100
|
+
"@semantic-release/git",
|
101
|
+
{
|
102
|
+
"assets": [
|
103
|
+
"package.json"
|
104
|
+
]
|
105
|
+
}
|
106
|
+
]
|
107
|
+
]
|
108
|
+
},
|
109
|
+
"eslintConfig": {
|
110
|
+
"extends": "standard-with-typescript",
|
111
|
+
"parserOptions": {
|
112
|
+
"project": "./tsconfig.json"
|
113
|
+
}
|
114
|
+
},
|
115
|
+
"ts-standard": {
|
116
|
+
"globals": [],
|
117
|
+
"ignore": [
|
118
|
+
"/test/**/*.ts"
|
119
|
+
]
|
120
|
+
},
|
121
|
+
"prettier": {
|
122
|
+
"tabWidth": 2,
|
123
|
+
"printWidth": 120,
|
124
|
+
"useTabs": false,
|
125
|
+
"endOfLine": "lf",
|
126
|
+
"trailingComma": "none",
|
127
|
+
"semi": false,
|
128
|
+
"singleQuote": true,
|
129
|
+
"arrowParens": "always",
|
130
|
+
"bracketSameLine": true,
|
131
|
+
"bracketSpacing": true,
|
132
|
+
"embeddedLanguageFormatting": "auto",
|
133
|
+
"htmlWhitespaceSensitivity": "css",
|
134
|
+
"insertPragma": false,
|
135
|
+
"jsxSingleQuote": false,
|
136
|
+
"proseWrap": "preserve",
|
137
|
+
"quoteProps": "as-needed",
|
138
|
+
"requirePragma": false,
|
139
|
+
"vueIndentScriptAndStyle": false
|
140
|
+
},
|
141
|
+
"jest": {
|
142
|
+
"preset": "ts-jest/presets/default-esm",
|
143
|
+
"testEnvironment": "node",
|
144
|
+
"extensionsToTreatAsEsm": [
|
145
|
+
".ts"
|
146
|
+
],
|
147
|
+
"transform": {
|
148
|
+
"^.+\\.tsx?$": [
|
149
|
+
"ts-jest",
|
150
|
+
{
|
151
|
+
"useESM": true
|
152
|
+
}
|
153
|
+
]
|
154
|
+
},
|
155
|
+
"moduleNameMapper": {
|
156
|
+
"^(\\.{1,2}/.*)\\.js$": "$1"
|
157
|
+
},
|
158
|
+
"testMatch": [
|
159
|
+
"**/test/**/*.test.ts"
|
160
|
+
],
|
161
|
+
"moduleFileExtensions": [
|
162
|
+
"ts",
|
163
|
+
"js",
|
164
|
+
"json",
|
165
|
+
"node"
|
166
|
+
],
|
167
|
+
"coverageDirectory": "./coverage",
|
168
|
+
"collectCoverageFrom": [
|
169
|
+
"src/**/*.{ts,js}"
|
170
|
+
],
|
171
|
+
"transformIgnorePatterns": [
|
172
|
+
"/node_modules/(?!@semantic-release/error)"
|
173
|
+
]
|
174
|
+
}
|
175
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
/**
|
2
|
+
* Dynamically imports and returns the default export from the 'replace-in-file' module.
|
3
|
+
*
|
4
|
+
* This function uses dynamic import to load the 'replace-in-file' package at runtime,
|
5
|
+
* which can help reduce initial load time and dependencies if the functionality is only
|
6
|
+
* needed conditionally.
|
7
|
+
*
|
8
|
+
* @returns A promise that resolves to the default export of the 'replace-in-file' module.
|
9
|
+
*/
|
10
|
+
export default async function (): Promise<typeof import('replace-in-file').default> {
|
11
|
+
return (await import('replace-in-file')).default
|
12
|
+
}
|
package/src/index.ts
ADDED
package/src/prepare.ts
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
import { readJsonSync, writeJsonSync } from 'fs-extra'
|
2
|
+
import PluginConfig from './@types/pluginConfig.js'
|
3
|
+
import { fdir } from 'fdir'
|
4
|
+
import getReplaceInFile from './getReplaceInFile.js'
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Prepare the API Spec files
|
8
|
+
*
|
9
|
+
* @param {string[]} apiSpecFiles List of api spec file paths, globs supported
|
10
|
+
* @param {string} version The version string to write to the files
|
11
|
+
* @param {Context['logger']} logger The semantic release logger instance
|
12
|
+
*
|
13
|
+
* @throws {SemanticReleaseError}
|
14
|
+
*/
|
15
|
+
const prepareApiSpecFiles = async (apiSpecFiles: string[], version: string, logger: any): Promise<any> => {
|
16
|
+
const SemanticReleaseError = (await import('@semantic-release/error')).default
|
17
|
+
try {
|
18
|
+
for (const fileNameGlob of apiSpecFiles) {
|
19
|
+
// eslint-disable-next-line new-cap
|
20
|
+
const fileNames: string[] = new fdir().withRelativePaths().glob(fileNameGlob).crawl('.').sync()
|
21
|
+
for (const fileName of fileNames) {
|
22
|
+
let results: string[]
|
23
|
+
if (fileName.split('.').pop() === 'json') {
|
24
|
+
results = prepareApiSpecFileJson(fileName, version)
|
25
|
+
} else {
|
26
|
+
results = await prepareApiSpecFileYml(fileName, version)
|
27
|
+
}
|
28
|
+
results.forEach((resultFileName: string) => {
|
29
|
+
logger.log('Wrote version %s to %s', version, resultFileName)
|
30
|
+
})
|
31
|
+
}
|
32
|
+
}
|
33
|
+
} catch (error: any) {
|
34
|
+
throw new SemanticReleaseError(error)
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Prepares a single API spec file in YAML format
|
40
|
+
*
|
41
|
+
* @param {string} apiSpecFile Single spec file to update, no globs
|
42
|
+
* @param {string} version The version string to write to the file
|
43
|
+
*
|
44
|
+
* @returns {string[]} A list of altered files
|
45
|
+
*/
|
46
|
+
const prepareApiSpecFileYml = async (apiSpecFile: string, version: string): Promise<string[]> => {
|
47
|
+
const replace = await getReplaceInFile()
|
48
|
+
return (replace as any)
|
49
|
+
.sync({
|
50
|
+
files: apiSpecFile,
|
51
|
+
from: /version: ?.+$/im,
|
52
|
+
to: 'version: ' + version
|
53
|
+
})
|
54
|
+
.filter((result: any) => result.hasChanged)
|
55
|
+
.map((result: any) => result.file)
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Prepares a single API spec file in JSON format
|
60
|
+
*
|
61
|
+
* @param {string} apiSpecFile Single spec file to update, no globs
|
62
|
+
* @param {string} version The version string to write to the file
|
63
|
+
*
|
64
|
+
* @returns {string[]} A list of altered files
|
65
|
+
*/
|
66
|
+
const prepareApiSpecFileJson = (apiSpecFile: string, version: string): string[] => {
|
67
|
+
const specFile = readJsonSync(apiSpecFile)
|
68
|
+
specFile.info.version = version
|
69
|
+
writeJsonSync(apiSpecFile, specFile, { spaces: 2 })
|
70
|
+
return [apiSpecFile]
|
71
|
+
}
|
72
|
+
|
73
|
+
/**
|
74
|
+
* prepare hook for semantic release
|
75
|
+
*
|
76
|
+
* @throws {SemanticReleaseError}
|
77
|
+
*/
|
78
|
+
export default async function ({ apiSpecFiles }: PluginConfig, { nextRelease, logger }: any): Promise<any> {
|
79
|
+
const version = nextRelease?.version ?? ''
|
80
|
+
if (version.length < 1) {
|
81
|
+
const SemanticReleaseError = (await import('@semantic-release/error')).default
|
82
|
+
throw new SemanticReleaseError('Could not determine the version from semantic release.')
|
83
|
+
}
|
84
|
+
await prepareApiSpecFiles(apiSpecFiles, version, logger)
|
85
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { fdir } from 'fdir'
|
2
|
+
import PluginConfig from './@types/pluginConfig.js'
|
3
|
+
|
4
|
+
/**
|
5
|
+
* verifyConditions hook for semantic release
|
6
|
+
*
|
7
|
+
* @throws {SemanticReleaseError}
|
8
|
+
*/
|
9
|
+
export default async function ({ apiSpecFiles }: PluginConfig): Promise<any> {
|
10
|
+
const SemanticReleaseError = (await import('@semantic-release/error')).default
|
11
|
+
if (apiSpecFiles.length < 1) {
|
12
|
+
throw new SemanticReleaseError(
|
13
|
+
'Option "apiSpecFiles" was not included in the plugin config. See the README for instructions.',
|
14
|
+
'ENOAPISPECFILES'
|
15
|
+
)
|
16
|
+
}
|
17
|
+
const expectedExts: string[] = ['json', 'yaml', 'yml']
|
18
|
+
let specFilesFound: boolean = false
|
19
|
+
apiSpecFiles.forEach((fileNameGlob: string) => {
|
20
|
+
// eslint-disable-next-line new-cap
|
21
|
+
const fileNames: string[] = new fdir().glob(fileNameGlob).crawl('.').sync()
|
22
|
+
if (fileNames.length > 0) {
|
23
|
+
specFilesFound = true
|
24
|
+
fileNames.forEach((fileName: string) => {
|
25
|
+
if (!expectedExts.includes(fileName.split('.').pop() ?? '')) {
|
26
|
+
throw new SemanticReleaseError(
|
27
|
+
'File "' + fileName + '" is not valid. Must be a file with .json, .yaml, or .yml extension',
|
28
|
+
'EINVALIDAPISPECFILETYPE'
|
29
|
+
)
|
30
|
+
}
|
31
|
+
})
|
32
|
+
}
|
33
|
+
})
|
34
|
+
if (!specFilesFound) {
|
35
|
+
throw new SemanticReleaseError(
|
36
|
+
'No files match the paths in "apiSpecFiles". Check your plugin config and try again.',
|
37
|
+
'EINVALIDAPISPECFILES'
|
38
|
+
)
|
39
|
+
}
|
40
|
+
}
|