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 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](https://img.shields.io/npm/v/@aensley/semantic-release-openapi)][npm]
4
+ [![npm types](https://badgen.net/npm/types/@aensley/semantic-release-openapi?icon=typescript)][npm]
5
+ [![license](https://img.shields.io/github/license/aensley/semantic-release-openapi.svg)](https://github.com/aensley/semantic-release-openapi/blob/main/LICENSE)
6
+ [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat)](https://standardjs.com)
7
+ [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat)](https://prettier.io)
8
+
9
+ [![ci](https://github.com/aensley/semantic-release-openapi/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/aensley/semantic-release-openapi/actions/workflows/ci.yml)
10
+ [![Maintainability](https://api.codeclimate.com/v1/badges/ac7dbc9a2d5e0bf8bd7d/maintainability)](https://codeclimate.com/github/aensley/semantic-release-openapi/maintainability)
11
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/ac7dbc9a2d5e0bf8bd7d/test_coverage)](https://codeclimate.com/github/aensley/semantic-release-openapi/test_coverage)
12
+ [![npm downloads](https://img.shields.io/npm/dw/@aensley/semantic-release-openapi)][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,3 @@
1
+ export default interface PluginConfig {
2
+ apiSpecFiles: string[];
3
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pluginConfig.js.map
@@ -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"}
@@ -0,0 +1,2 @@
1
+ export { default as verifyConditions } from './verifyConditions.js';
2
+ export { default as prepare } from './prepare.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { default as verifyConditions } from './verifyConditions.js';
2
+ export { default as prepare } from './prepare.js';
3
+ //# sourceMappingURL=index.js.map
@@ -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"}
@@ -0,0 +1,7 @@
1
+ import PluginConfig from './@types/pluginConfig.js';
2
+ /**
3
+ * prepare hook for semantic release
4
+ *
5
+ * @throws {SemanticReleaseError}
6
+ */
7
+ export default function ({ apiSpecFiles }: PluginConfig, { nextRelease, logger }: any): Promise<any>;
@@ -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":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAEtD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,gBAAgB,MAAM,uBAAuB,CAAA;AAEpD;;;;;;;;GAQG;AACH,MAAM,mBAAmB,GAAG,KAAK,EAAE,YAAsB,EAAE,OAAe,EAAE,MAAW,EAAgB,EAAE;IACvG,MAAM,oBAAoB,GAAG,CAAC,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAAC,CAAC,OAAO,CAAA;IAC9E,IAAI,CAAC;QACH,KAAK,MAAM,YAAY,IAAI,YAAY,EAAE,CAAC;YACxC,mCAAmC;YACnC,MAAM,SAAS,GAAa,IAAI,IAAI,EAAE,CAAC,iBAAiB,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;YAC/F,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACjC,IAAI,OAAiB,CAAA;gBACrB,IAAI,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,MAAM,EAAE,CAAC;oBACzC,OAAO,GAAG,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;gBACrD,CAAC;qBAAM,CAAC;oBACN,OAAO,GAAG,MAAM,qBAAqB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;gBAC1D,CAAC;gBACD,OAAO,CAAC,OAAO,CAAC,CAAC,cAAsB,EAAE,EAAE;oBACzC,MAAM,CAAC,GAAG,CAAC,wBAAwB,EAAE,OAAO,EAAE,cAAc,CAAC,CAAA;gBAC/D,CAAC,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,MAAM,IAAI,oBAAoB,CAAC,KAAK,CAAC,CAAA;IACvC,CAAC;AACH,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,qBAAqB,GAAG,KAAK,EAAE,WAAmB,EAAE,OAAe,EAAqB,EAAE;IAC9F,MAAM,OAAO,GAAG,MAAM,gBAAgB,EAAE,CAAA;IACxC,OAAQ,OAAe;SACpB,IAAI,CAAC;QACJ,KAAK,EAAE,WAAW;QAClB,IAAI,EAAE,iBAAiB;QACvB,EAAE,EAAE,WAAW,GAAG,OAAO;KAC1B,CAAC;SACD,MAAM,CAAC,CAAC,MAAW,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC;SAC1C,GAAG,CAAC,CAAC,MAAW,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;AACtC,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,sBAAsB,GAAG,CAAC,WAAmB,EAAE,OAAe,EAAY,EAAE;IAChF,MAAM,QAAQ,GAAG,YAAY,CAAC,WAAW,CAAC,CAAA;IAC1C,QAAQ,CAAC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IAC/B,aAAa,CAAC,WAAW,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAA;IACnD,OAAO,CAAC,WAAW,CAAC,CAAA;AACtB,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,WAAW,EAAE,YAAY,EAAgB,EAAE,EAAE,WAAW,EAAE,MAAM,EAAO;IACzF,MAAM,OAAO,GAAG,WAAW,EAAE,OAAO,IAAI,EAAE,CAAA;IAC1C,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,oBAAoB,GAAG,CAAC,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAAC,CAAC,OAAO,CAAA;QAC9E,MAAM,IAAI,oBAAoB,CAAC,wDAAwD,CAAC,CAAA;IAC1F,CAAC;IACD,MAAM,mBAAmB,CAAC,YAAY,EAAE,OAAO,EAAE,MAAM,CAAC,CAAA;AAC1D,CAAC"}
@@ -0,0 +1,7 @@
1
+ import PluginConfig from './@types/pluginConfig.js';
2
+ /**
3
+ * verifyConditions hook for semantic release
4
+ *
5
+ * @throws {SemanticReleaseError}
6
+ */
7
+ export default function ({ apiSpecFiles }: PluginConfig): Promise<any>;
@@ -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,3 @@
1
+ export default interface PluginConfig {
2
+ apiSpecFiles: string[]
3
+ }
@@ -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
@@ -0,0 +1,2 @@
1
+ export { default as verifyConditions } from './verifyConditions.js'
2
+ export { default as prepare } from './prepare.js'
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
+ }