trilium-api 1.0.0 → 1.0.2
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 +660 -660
- package/README.md +835 -836
- package/dist/index.cjs +307 -0
- package/{src/generated/trilium.d.ts → dist/index.d.cts} +317 -5
- package/dist/index.d.ts +2070 -0
- package/dist/index.js +266 -0
- package/package.json +34 -15
- package/.github/workflows/ci.yml +0 -37
- package/.github/workflows/publish.yml +0 -86
- package/src/client.test.ts +0 -477
- package/src/client.ts +0 -91
- package/src/demo-mapper.ts +0 -166
- package/src/demo-search.ts +0 -108
- package/src/demo.ts +0 -126
- package/src/index.ts +0 -35
- package/src/mapper.test.ts +0 -638
- package/src/mapper.ts +0 -534
- package/tsconfig.json +0 -42
package/dist/index.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import createClient from "openapi-fetch";
|
|
3
|
+
|
|
4
|
+
// src/mapper.ts
|
|
5
|
+
function buildSearchQuery(helpers) {
|
|
6
|
+
if ("AND" in helpers && Array.isArray(helpers.AND)) {
|
|
7
|
+
return helpers.AND.map((h) => {
|
|
8
|
+
const query = buildSearchQuery(h);
|
|
9
|
+
return query.includes(" OR ") ? `(${query})` : query;
|
|
10
|
+
}).join(" AND ");
|
|
11
|
+
}
|
|
12
|
+
if ("OR" in helpers && Array.isArray(helpers.OR)) {
|
|
13
|
+
return helpers.OR.map((h) => {
|
|
14
|
+
const query = buildSearchQuery(h);
|
|
15
|
+
return query.includes(" AND ") || query.includes(" OR ") ? `(${query})` : query;
|
|
16
|
+
}).join(" OR ");
|
|
17
|
+
}
|
|
18
|
+
if ("NOT" in helpers && helpers.NOT !== void 0) {
|
|
19
|
+
const notValue = helpers.NOT;
|
|
20
|
+
if (typeof notValue === "object" && notValue !== null && !("value" in notValue)) {
|
|
21
|
+
const query = buildSearchQuery(notValue);
|
|
22
|
+
return `not(${query})`;
|
|
23
|
+
}
|
|
24
|
+
throw new Error("NOT operator requires a query object, not a simple value");
|
|
25
|
+
}
|
|
26
|
+
const parts = [];
|
|
27
|
+
for (const [key, value] of Object.entries(helpers)) {
|
|
28
|
+
if (value === void 0 || value === null) continue;
|
|
29
|
+
if (key.startsWith("#")) {
|
|
30
|
+
const labelName = key.slice(1);
|
|
31
|
+
if (labelName.includes(".")) {
|
|
32
|
+
if (typeof value === "object" && "value" in value) {
|
|
33
|
+
const operator = value.operator || "=";
|
|
34
|
+
const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
|
|
35
|
+
parts.push(`${key} ${operator} ${val}`);
|
|
36
|
+
} else if (typeof value === "string") {
|
|
37
|
+
parts.push(`${key} = '${value}'`);
|
|
38
|
+
} else {
|
|
39
|
+
parts.push(`${key} = ${value}`);
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
if (value === true) {
|
|
43
|
+
parts.push(`#${labelName}`);
|
|
44
|
+
} else if (value === false) {
|
|
45
|
+
parts.push(`#!${labelName}`);
|
|
46
|
+
} else if (typeof value === "object" && "value" in value) {
|
|
47
|
+
const operator = value.operator || "=";
|
|
48
|
+
const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
|
|
49
|
+
parts.push(`#${labelName} ${operator} ${val}`);
|
|
50
|
+
} else if (typeof value === "string") {
|
|
51
|
+
parts.push(`#${labelName} = '${value}'`);
|
|
52
|
+
} else {
|
|
53
|
+
parts.push(`#${labelName} = ${value}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} else if (key.startsWith("~")) {
|
|
57
|
+
const relationName = key.slice(1);
|
|
58
|
+
if (relationName.includes(".")) {
|
|
59
|
+
if (typeof value === "object" && "value" in value) {
|
|
60
|
+
const operator = value.operator || "=";
|
|
61
|
+
const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
|
|
62
|
+
parts.push(`${key} ${operator} ${val}`);
|
|
63
|
+
} else if (typeof value === "string") {
|
|
64
|
+
parts.push(`${key} = '${value}'`);
|
|
65
|
+
} else {
|
|
66
|
+
parts.push(`${key} = ${value}`);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
if (typeof value === "object" && "value" in value) {
|
|
70
|
+
const operator = value.operator || "*=*";
|
|
71
|
+
const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
|
|
72
|
+
parts.push(`${key} ${operator} ${val}`);
|
|
73
|
+
} else if (typeof value === "string") {
|
|
74
|
+
parts.push(`${key} *=* '${value}'`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
const path = key.startsWith("note.") ? key : `note.${key}`;
|
|
79
|
+
if (typeof value === "object" && "value" in value) {
|
|
80
|
+
const operator = value.operator || "=";
|
|
81
|
+
const val = typeof value.value === "string" ? `'${value.value}'` : value.value;
|
|
82
|
+
parts.push(`${path} ${operator} ${val}`);
|
|
83
|
+
} else if (typeof value === "string") {
|
|
84
|
+
parts.push(`${path} = '${value}'`);
|
|
85
|
+
} else if (typeof value === "boolean") {
|
|
86
|
+
parts.push(`${path} = ${value}`);
|
|
87
|
+
} else {
|
|
88
|
+
parts.push(`${path} = ${value}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return parts.join(" AND ");
|
|
93
|
+
}
|
|
94
|
+
var TriliumMapper = class {
|
|
95
|
+
/** The mapping configuration for this mapper */
|
|
96
|
+
config;
|
|
97
|
+
/**
|
|
98
|
+
* Creates a new TriliumMapper instance
|
|
99
|
+
* @param config - The mapping configuration defining how to map note fields to the target type
|
|
100
|
+
*/
|
|
101
|
+
constructor(config) {
|
|
102
|
+
this.config = config;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Merges multiple mapping configurations into a single configuration
|
|
106
|
+
* Later configs override earlier ones for the same keys
|
|
107
|
+
* Supports merging configs from base types into derived types
|
|
108
|
+
*
|
|
109
|
+
* @template T - The target type for the merged configuration
|
|
110
|
+
* @param configs - One or more mapping configurations to merge
|
|
111
|
+
* @returns A new merged mapping configuration
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* const merged = TriliumMapper.merge<BlogPost>(
|
|
115
|
+
* StandardNoteMapping,
|
|
116
|
+
* BlogSpecificMapping,
|
|
117
|
+
* OverrideMapping
|
|
118
|
+
* );
|
|
119
|
+
*/
|
|
120
|
+
static merge(...configs) {
|
|
121
|
+
return Object.assign({}, ...configs);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Maps one or more Trilium notes to the target type
|
|
125
|
+
* @param noteOrNotes - A single note or array of notes to map
|
|
126
|
+
* @returns A single mapped object or array of mapped objects
|
|
127
|
+
*/
|
|
128
|
+
map(noteOrNotes) {
|
|
129
|
+
return Array.isArray(noteOrNotes) ? noteOrNotes.map((note) => this.mapSingle(note)) : this.mapSingle(noteOrNotes);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Maps a single note to the target type using the configured field mappings
|
|
133
|
+
* Processes in two passes: first regular fields, then computed fields
|
|
134
|
+
* @param note - The Trilium note to map
|
|
135
|
+
* @returns The mapped object
|
|
136
|
+
* @throws Error if a required field is missing
|
|
137
|
+
* @private
|
|
138
|
+
*/
|
|
139
|
+
mapSingle(note) {
|
|
140
|
+
const result = {};
|
|
141
|
+
const computedFields = [];
|
|
142
|
+
for (const [key, fieldMapping] of Object.entries(this.config)) {
|
|
143
|
+
if (!fieldMapping) continue;
|
|
144
|
+
if (typeof fieldMapping === "object" && "computed" in fieldMapping) {
|
|
145
|
+
computedFields.push([key, fieldMapping]);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const mapping = typeof fieldMapping === "string" ? { from: fieldMapping } : fieldMapping;
|
|
149
|
+
let value = typeof mapping.from === "function" ? mapping.from(note) : this.extractValue(note, mapping.from);
|
|
150
|
+
if (mapping.transform) {
|
|
151
|
+
value = mapping.transform(value, note);
|
|
152
|
+
}
|
|
153
|
+
if (value === void 0 && mapping.default !== void 0) {
|
|
154
|
+
value = mapping.default;
|
|
155
|
+
}
|
|
156
|
+
if (mapping.required && value === void 0) {
|
|
157
|
+
throw new Error(`Required field '${String(key)}' missing from note ${note.noteId} (${note.title})`);
|
|
158
|
+
}
|
|
159
|
+
result[key] = value;
|
|
160
|
+
}
|
|
161
|
+
for (const [key, mapping] of computedFields) {
|
|
162
|
+
let value = mapping.computed(result, note);
|
|
163
|
+
if (value === void 0 && mapping.default !== void 0) {
|
|
164
|
+
value = mapping.default;
|
|
165
|
+
}
|
|
166
|
+
result[key] = value;
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Extracts a value from a note using a string path
|
|
172
|
+
*
|
|
173
|
+
* Supports:
|
|
174
|
+
* - Label attributes: #labelName
|
|
175
|
+
* - Relation attributes: ~relationName
|
|
176
|
+
* - Note properties: note.property.path
|
|
177
|
+
*
|
|
178
|
+
* @param note - The Trilium note to extract from
|
|
179
|
+
* @param path - The path string indicating where to extract the value
|
|
180
|
+
* @returns The extracted value or undefined if not found
|
|
181
|
+
* @private
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* extractValue(note, 'note.title') // => note.title
|
|
185
|
+
* extractValue(note, '#slug') // => label attribute 'slug'
|
|
186
|
+
* extractValue(note, '~template') // => relation attribute 'template'
|
|
187
|
+
*/
|
|
188
|
+
extractValue(note, path) {
|
|
189
|
+
if (!path) return void 0;
|
|
190
|
+
if (path.startsWith("#")) {
|
|
191
|
+
return note.attributes?.find((attr) => attr.type === "label" && attr.name === path.slice(1))?.value;
|
|
192
|
+
}
|
|
193
|
+
if (path.startsWith("~")) {
|
|
194
|
+
return note.attributes?.find((attr) => attr.type === "relation" && attr.name === path.slice(1))?.value;
|
|
195
|
+
}
|
|
196
|
+
if (path.startsWith("note.")) {
|
|
197
|
+
return path.slice(5).split(".").reduce((obj, key) => obj?.[key], note);
|
|
198
|
+
}
|
|
199
|
+
return void 0;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
var transforms = {
|
|
203
|
+
/** Convert to number */
|
|
204
|
+
number: (value) => {
|
|
205
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
206
|
+
const num = Number(value);
|
|
207
|
+
return isNaN(num) ? void 0 : num;
|
|
208
|
+
},
|
|
209
|
+
/** Convert to boolean */
|
|
210
|
+
boolean: (value) => {
|
|
211
|
+
if (value === void 0 || value === null) return void 0;
|
|
212
|
+
if (typeof value === "boolean") return value;
|
|
213
|
+
if (typeof value === "string") {
|
|
214
|
+
const lower = value.toLowerCase();
|
|
215
|
+
if (lower === "true" || lower === "1" || lower === "yes") return true;
|
|
216
|
+
if (lower === "false" || lower === "0" || lower === "no") return false;
|
|
217
|
+
}
|
|
218
|
+
return void 0;
|
|
219
|
+
},
|
|
220
|
+
/** Split comma-separated string into array */
|
|
221
|
+
commaSeparated: (value) => {
|
|
222
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
223
|
+
if (typeof value !== "string") return void 0;
|
|
224
|
+
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
225
|
+
},
|
|
226
|
+
/** Parse JSON string */
|
|
227
|
+
json: (value) => {
|
|
228
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
229
|
+
if (typeof value !== "string") return void 0;
|
|
230
|
+
try {
|
|
231
|
+
return JSON.parse(value);
|
|
232
|
+
} catch {
|
|
233
|
+
return void 0;
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
/** Parse date string */
|
|
237
|
+
date: (value) => {
|
|
238
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
239
|
+
const date = new Date(String(value));
|
|
240
|
+
return isNaN(date.getTime()) ? void 0 : date;
|
|
241
|
+
},
|
|
242
|
+
/** Trim whitespace from string */
|
|
243
|
+
trim: (value) => {
|
|
244
|
+
if (value === void 0 || value === null) return void 0;
|
|
245
|
+
return String(value).trim() || void 0;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// src/client.ts
|
|
250
|
+
function createTriliumClient(config) {
|
|
251
|
+
const baseUrl = config.baseUrl.endsWith("/") ? config.baseUrl.slice(0, -1) : config.baseUrl;
|
|
252
|
+
return createClient({
|
|
253
|
+
baseUrl: `${baseUrl}/etapi`,
|
|
254
|
+
headers: {
|
|
255
|
+
Authorization: config.apiKey
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
var client_default = createTriliumClient;
|
|
260
|
+
export {
|
|
261
|
+
TriliumMapper,
|
|
262
|
+
buildSearchQuery,
|
|
263
|
+
client_default as createClient,
|
|
264
|
+
createTriliumClient,
|
|
265
|
+
transforms
|
|
266
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trilium-api",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "A type-safe TypeScript client for the Trilium Notes ETAPI",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "index.
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
26
|
+
"demo": "tsx src/demo.ts",
|
|
27
|
+
"demo:search": "tsx src/demo-search.ts",
|
|
28
|
+
"demo:mapper": "tsx src/demo-mapper.ts",
|
|
29
|
+
"test": "vitest",
|
|
30
|
+
"test:run": "vitest run",
|
|
31
|
+
"test:coverage": "vitest run --coverage",
|
|
32
|
+
"generate-api": "openapi-typescript https://raw.githubusercontent.com/TriliumNext/Trilium/4bd8eeb52ad5b9fe70bbe98765f035e60a6a3bc0/apps/server/etapi.openapi.yaml -o ./src/generated/trilium.d.ts",
|
|
33
|
+
"test:ts": "tsc --noEmit",
|
|
34
|
+
"prepublishOnly": "pnpm build"
|
|
35
|
+
},
|
|
7
36
|
"keywords": [],
|
|
8
37
|
"author": "",
|
|
9
38
|
"license": "AGPL-3.0",
|
|
39
|
+
"packageManager": "pnpm@10.25.0",
|
|
10
40
|
"devDependencies": {
|
|
11
41
|
"@types/node": "^25.0.3",
|
|
12
42
|
"openapi-typescript": "^7.10.1",
|
|
@@ -17,16 +47,5 @@
|
|
|
17
47
|
},
|
|
18
48
|
"dependencies": {
|
|
19
49
|
"openapi-fetch": "^0.15.0"
|
|
20
|
-
},
|
|
21
|
-
"scripts": {
|
|
22
|
-
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
23
|
-
"demo": "tsx src/demo.ts",
|
|
24
|
-
"demo:search": "tsx src/demo-search.ts",
|
|
25
|
-
"demo:mapper": "tsx src/demo-mapper.ts",
|
|
26
|
-
"test": "vitest",
|
|
27
|
-
"test:run": "vitest run",
|
|
28
|
-
"test:coverage": "vitest run --coverage",
|
|
29
|
-
"generate-api": "openapi-typescript https://raw.githubusercontent.com/TriliumNext/Trilium/4bd8eeb52ad5b9fe70bbe98765f035e60a6a3bc0/apps/server/etapi.openapi.yaml -o ./src/generated/trilium.d.ts",
|
|
30
|
-
"test:ts": "tsc --noEmit"
|
|
31
50
|
}
|
|
32
|
-
}
|
|
51
|
+
}
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main, master]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main, master]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
test:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
|
|
13
|
-
strategy:
|
|
14
|
-
matrix:
|
|
15
|
-
node-version: [18, 20, 22]
|
|
16
|
-
|
|
17
|
-
steps:
|
|
18
|
-
- name: Checkout repository
|
|
19
|
-
uses: actions/checkout@v4
|
|
20
|
-
|
|
21
|
-
- name: Install pnpm
|
|
22
|
-
uses: pnpm/action-setup@v4
|
|
23
|
-
|
|
24
|
-
- name: Setup Node.js ${{ matrix.node-version }}
|
|
25
|
-
uses: actions/setup-node@v4
|
|
26
|
-
with:
|
|
27
|
-
node-version: ${{ matrix.node-version }}
|
|
28
|
-
cache: 'pnpm'
|
|
29
|
-
|
|
30
|
-
- name: Install dependencies
|
|
31
|
-
run: pnpm install
|
|
32
|
-
|
|
33
|
-
- name: Type check
|
|
34
|
-
run: pnpm test:ts
|
|
35
|
-
|
|
36
|
-
- name: Run tests
|
|
37
|
-
run: pnpm test:run
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
name: Publish to npm
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_dispatch:
|
|
5
|
-
inputs:
|
|
6
|
-
version:
|
|
7
|
-
description: 'Version bump type'
|
|
8
|
-
required: true
|
|
9
|
-
default: 'patch'
|
|
10
|
-
type: choice
|
|
11
|
-
options:
|
|
12
|
-
- patch
|
|
13
|
-
- minor
|
|
14
|
-
- major
|
|
15
|
-
force:
|
|
16
|
-
description: 'Force publish (overwrite existing version)'
|
|
17
|
-
required: false
|
|
18
|
-
default: false
|
|
19
|
-
type: boolean
|
|
20
|
-
|
|
21
|
-
jobs:
|
|
22
|
-
publish:
|
|
23
|
-
runs-on: ubuntu-latest
|
|
24
|
-
|
|
25
|
-
permissions:
|
|
26
|
-
contents: write
|
|
27
|
-
id-token: write # Required for npm trusted publishing (OIDC)
|
|
28
|
-
|
|
29
|
-
steps:
|
|
30
|
-
- name: Checkout repository
|
|
31
|
-
uses: actions/checkout@v4
|
|
32
|
-
with:
|
|
33
|
-
fetch-depth: 0
|
|
34
|
-
token: ${{ secrets.GITHUB_TOKEN }}
|
|
35
|
-
|
|
36
|
-
- name: Configure Git
|
|
37
|
-
run: |
|
|
38
|
-
git config user.name "github-actions[bot]"
|
|
39
|
-
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
40
|
-
|
|
41
|
-
- name: Install pnpm
|
|
42
|
-
uses: pnpm/action-setup@v4
|
|
43
|
-
|
|
44
|
-
- name: Setup Node.js
|
|
45
|
-
uses: actions/setup-node@v4
|
|
46
|
-
with:
|
|
47
|
-
node-version: 20
|
|
48
|
-
cache: 'pnpm'
|
|
49
|
-
registry-url: 'https://registry.npmjs.org'
|
|
50
|
-
|
|
51
|
-
- name: Install dependencies
|
|
52
|
-
run: pnpm install
|
|
53
|
-
|
|
54
|
-
- name: Type check
|
|
55
|
-
run: pnpm test:ts
|
|
56
|
-
|
|
57
|
-
- name: Run tests
|
|
58
|
-
run: pnpm test:run
|
|
59
|
-
|
|
60
|
-
- name: Bump version
|
|
61
|
-
id: version
|
|
62
|
-
run: |
|
|
63
|
-
pnpm version ${{ inputs.version }} --no-git-tag-version
|
|
64
|
-
VERSION=$(node -p "require('./package.json').version")
|
|
65
|
-
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
66
|
-
|
|
67
|
-
- name: Commit and tag
|
|
68
|
-
run: |
|
|
69
|
-
git add package.json
|
|
70
|
-
git commit -m "chore: release v${{ steps.version.outputs.version }}"
|
|
71
|
-
git tag "v${{ steps.version.outputs.version }}"
|
|
72
|
-
git push && git push --tags
|
|
73
|
-
|
|
74
|
-
- name: Build
|
|
75
|
-
run: pnpm build
|
|
76
|
-
|
|
77
|
-
- name: Publish to npm
|
|
78
|
-
run: pnpm publish --access public --no-git-checks ${{ inputs.force && '--force' || '' }}
|
|
79
|
-
# Uses OIDC trusted publishing - no NPM_TOKEN needed
|
|
80
|
-
|
|
81
|
-
- name: Create GitHub Release
|
|
82
|
-
uses: softprops/action-gh-release@v2
|
|
83
|
-
with:
|
|
84
|
-
tag_name: v${{ steps.version.outputs.version }}
|
|
85
|
-
name: v${{ steps.version.outputs.version }}
|
|
86
|
-
generate_release_notes: true
|