@knpkv/jira-api-client 0.2.0 → 0.3.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/.specs/VERSION_V3 +1 -1
- package/.specs/jira-v3-1001.0.0-SNAPSHOT-05f20e18d9bde3de68f911b4915970482f536d4e.json +56275 -0
- package/.specs/jira-v3-1001.0.0-SNAPSHOT-d7cd03e966bb408ad32ffe870114289c528f56fd.json +56129 -0
- package/CHANGELOG.md +25 -0
- package/dist/JiraApiClient.d.ts +1 -1
- package/dist/JiraApiClient.d.ts.map +1 -1
- package/dist/JiraApiClient.js +1 -1
- package/dist/JiraApiClient.js.map +1 -1
- package/dist/JiraApiConfig.d.ts +1 -1
- package/dist/JiraApiConfig.d.ts.map +1 -1
- package/dist/JiraApiConfig.js +1 -1
- package/dist/JiraApiConfig.js.map +1 -1
- package/dist/generated/v3/schema.d.ts +1537 -1436
- package/package.json +8 -9
- package/scripts/regenerate.ts +174 -66
- package/src/JiraApiClient.ts +2 -2
- package/src/JiraApiConfig.ts +2 -2
- package/src/generated/v3/schema.d.ts +1537 -1436
- package/test/JiraApiClient.test.ts +19 -12
- package/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knpkv/jira-api-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Effect-based Jira Cloud REST API v3 client",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "knpkv",
|
|
@@ -39,19 +39,18 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"effect": "
|
|
42
|
+
"effect": "4.0.0-beta.93"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@effect/
|
|
46
|
-
"@effect/
|
|
47
|
-
"@effect/vitest": "latest",
|
|
45
|
+
"@effect/platform-node": "4.0.0-beta.93",
|
|
46
|
+
"@effect/vitest": "4.0.0-beta.93",
|
|
48
47
|
"@types/node": "latest",
|
|
49
|
-
"effect": "
|
|
48
|
+
"effect": "4.0.0-beta.93",
|
|
50
49
|
"openapi-typescript": "^7.13.0",
|
|
51
50
|
"openapi-typescript-helpers": "^0.1.0",
|
|
52
|
-
"tsx": "^4.
|
|
53
|
-
"typescript": "~
|
|
54
|
-
"vitest": "^4.
|
|
51
|
+
"tsx": "^4.23.0",
|
|
52
|
+
"typescript": "~6.0.3",
|
|
53
|
+
"vitest": "^4.1.9"
|
|
55
54
|
},
|
|
56
55
|
"keywords": [
|
|
57
56
|
"effect",
|
package/scripts/regenerate.ts
CHANGED
|
@@ -4,21 +4,21 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Fetches OpenAPI specs from Atlassian and regenerates openapi-typescript types.
|
|
6
6
|
*/
|
|
7
|
-
import { Command, Options } from "
|
|
8
|
-
import {
|
|
7
|
+
import { Command, Flag as Options } from "effect/unstable/cli"
|
|
8
|
+
import { NodeRuntime, NodeServices } from "@effect/platform-node"
|
|
9
|
+
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient"
|
|
9
10
|
import * as Console from "effect/Console"
|
|
11
|
+
import * as Data from "effect/Data"
|
|
10
12
|
import * as Effect from "effect/Effect"
|
|
11
|
-
import
|
|
12
|
-
import * as
|
|
13
|
-
import * as
|
|
14
|
-
import
|
|
13
|
+
import * as FileSystem from "effect/FileSystem"
|
|
14
|
+
import * as Path from "effect/Path"
|
|
15
|
+
import * as PlatformError from "effect/PlatformError"
|
|
16
|
+
import * as Schema from "effect/Schema"
|
|
17
|
+
import { HttpClient } from "effect/unstable/http"
|
|
18
|
+
import * as ChildProcess from "effect/unstable/process/ChildProcess"
|
|
19
|
+
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"
|
|
15
20
|
import pkg from "../package.json" with { type: "json" }
|
|
16
21
|
|
|
17
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
18
|
-
const SPECS_DIR = path.join(__dirname, "..", ".specs")
|
|
19
|
-
const VERSION_V3_FILE = path.join(SPECS_DIR, "VERSION_V3")
|
|
20
|
-
const GENERATED_DIR = path.join(__dirname, "..", "src", "generated", "v3")
|
|
21
|
-
|
|
22
22
|
const SPEC_URLS = {
|
|
23
23
|
v3: "https://dac-static.atlassian.com/cloud/jira/platform/swagger-v3.v3.json"
|
|
24
24
|
} as const
|
|
@@ -28,61 +28,165 @@ interface SpecInfo {
|
|
|
28
28
|
title: string
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
interface ScriptPaths {
|
|
32
|
+
readonly packageDir: string
|
|
33
|
+
readonly specsDir: string
|
|
34
|
+
readonly versionV3File: string
|
|
35
|
+
readonly generatedDir: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class RegenerateError extends Data.TaggedError("RegenerateError")<{
|
|
39
|
+
readonly message: string
|
|
40
|
+
readonly cause?: unknown
|
|
41
|
+
}> {}
|
|
42
|
+
|
|
43
|
+
const JsonString = Schema.fromJsonString(Schema.Json)
|
|
44
|
+
|
|
45
|
+
const SpecInfoResponse = Schema.Struct({
|
|
46
|
+
info: Schema.Struct({
|
|
47
|
+
version: Schema.String,
|
|
48
|
+
title: Schema.String
|
|
42
49
|
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const fetchText = (url: string): Effect.Effect<string, RegenerateError, HttpClient.HttpClient> =>
|
|
53
|
+
HttpClient.get(url).pipe(
|
|
54
|
+
Effect.flatMap((response) =>
|
|
55
|
+
response.status >= 200 && response.status < 300
|
|
56
|
+
? response.text
|
|
57
|
+
: Effect.fail(new RegenerateError({ message: `Failed to fetch ${url}`, cause: { status: response.status } }))
|
|
58
|
+
),
|
|
59
|
+
Effect.mapError((cause) => new RegenerateError({ message: `Fetch failed: ${url}`, cause }))
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const fetchSpecInfo = (url: string): Effect.Effect<SpecInfo, RegenerateError, HttpClient.HttpClient> =>
|
|
63
|
+
fetchText(url).pipe(
|
|
64
|
+
Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(SpecInfoResponse))),
|
|
65
|
+
Effect.map(({ info }) => ({ version: info.version, title: info.title })),
|
|
66
|
+
Effect.mapError((cause) => new RegenerateError({ message: "JSON decode failed", cause }))
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
const formatJson = (text: string): Effect.Effect<string, RegenerateError> =>
|
|
70
|
+
Schema.decodeUnknownEffect(JsonString)(text).pipe(
|
|
71
|
+
Effect.flatMap(Schema.encodeEffect(JsonString)),
|
|
72
|
+
Effect.map((encoded) => `${encoded}\n`),
|
|
73
|
+
Effect.mapError((cause) => new RegenerateError({ message: "JSON format failed", cause }))
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const scriptPaths: Effect.Effect<ScriptPaths, RegenerateError, Path.Path> = Effect.gen(function*() {
|
|
77
|
+
const path = yield* Path.Path
|
|
78
|
+
const scriptFile = yield* path.fromFileUrl(new URL(import.meta.url)).pipe(
|
|
79
|
+
Effect.mapError((cause) => new RegenerateError({ message: "Script path resolution failed", cause }))
|
|
80
|
+
)
|
|
81
|
+
const scriptDir = path.dirname(scriptFile)
|
|
82
|
+
const packageDir = path.join(scriptDir, "..")
|
|
83
|
+
const specsDir = path.join(packageDir, ".specs")
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
packageDir,
|
|
87
|
+
specsDir,
|
|
88
|
+
versionV3File: path.join(specsDir, "VERSION_V3"),
|
|
89
|
+
generatedDir: path.join(packageDir, "src", "generated", "v3")
|
|
90
|
+
}
|
|
91
|
+
})
|
|
43
92
|
|
|
44
|
-
const fetchAndSaveSpec = (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
93
|
+
const fetchAndSaveSpec = (
|
|
94
|
+
packageDir: string,
|
|
95
|
+
url: string,
|
|
96
|
+
outputPath: string
|
|
97
|
+
): Effect.Effect<
|
|
98
|
+
void,
|
|
99
|
+
RegenerateError,
|
|
100
|
+
ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem | HttpClient.HttpClient
|
|
101
|
+
> =>
|
|
102
|
+
Effect.gen(function*() {
|
|
103
|
+
const fs = yield* FileSystem.FileSystem
|
|
104
|
+
const spec = yield* fetchText(url)
|
|
105
|
+
const formattedSpec = yield* formatJson(spec)
|
|
106
|
+
yield* fs.writeFileString(outputPath, formattedSpec).pipe(
|
|
107
|
+
Effect.mapError((cause) => new RegenerateError({ message: "Save failed", cause }))
|
|
108
|
+
)
|
|
109
|
+
yield* formatFile(packageDir, outputPath)
|
|
55
110
|
})
|
|
56
111
|
|
|
57
|
-
const readVersion = (file: string): Effect.Effect<string | null> =>
|
|
58
|
-
Effect.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
112
|
+
const readVersion = (file: string): Effect.Effect<string | null, RegenerateError, FileSystem.FileSystem> =>
|
|
113
|
+
Effect.gen(function*() {
|
|
114
|
+
const fs = yield* FileSystem.FileSystem
|
|
115
|
+
const exists = yield* fs.exists(file).pipe(
|
|
116
|
+
Effect.mapError((cause) => new RegenerateError({ message: "Version lookup failed", cause }))
|
|
117
|
+
)
|
|
118
|
+
if (!exists) return null
|
|
119
|
+
|
|
120
|
+
return yield* fs.readFileString(file).pipe(
|
|
121
|
+
Effect.map((version) => version.trim()),
|
|
122
|
+
Effect.mapError((cause) => new RegenerateError({ message: "Version read failed", cause }))
|
|
123
|
+
)
|
|
64
124
|
})
|
|
65
125
|
|
|
66
|
-
const writeVersion = (file: string, version: string): Effect.Effect<void> =>
|
|
67
|
-
Effect.
|
|
126
|
+
const writeVersion = (file: string, version: string): Effect.Effect<void, RegenerateError, FileSystem.FileSystem> =>
|
|
127
|
+
Effect.gen(function*() {
|
|
128
|
+
const fs = yield* FileSystem.FileSystem
|
|
129
|
+
yield* fs.writeFileString(file, version).pipe(
|
|
130
|
+
Effect.mapError((cause) => new RegenerateError({ message: "Version write failed", cause }))
|
|
131
|
+
)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const ensureDir = (dir: string): Effect.Effect<void, RegenerateError, FileSystem.FileSystem> =>
|
|
135
|
+
Effect.gen(function*() {
|
|
136
|
+
const fs = yield* FileSystem.FileSystem
|
|
137
|
+
yield* fs.makeDirectory(dir, { recursive: true }).pipe(
|
|
138
|
+
Effect.mapError((cause) => new RegenerateError({ message: "Directory creation failed", cause }))
|
|
139
|
+
)
|
|
140
|
+
})
|
|
68
141
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
142
|
+
const generateTypes = (
|
|
143
|
+
packageDir: string,
|
|
144
|
+
generatedDir: string,
|
|
145
|
+
specPath: string
|
|
146
|
+
): Effect.Effect<void, RegenerateError, ChildProcessSpawner.ChildProcessSpawner | Path.Path> =>
|
|
147
|
+
Effect.gen(function*() {
|
|
148
|
+
const path = yield* Path.Path
|
|
149
|
+
const outputFile = path.join(generatedDir, "schema.d.ts")
|
|
150
|
+
const command = ChildProcess.make("pnpm", ["exec", "openapi-typescript", specPath, "-o", outputFile], {
|
|
151
|
+
cwd: packageDir,
|
|
152
|
+
stdin: "inherit",
|
|
153
|
+
stdout: "inherit",
|
|
154
|
+
stderr: "inherit"
|
|
155
|
+
})
|
|
156
|
+
const exitCode = yield* commandExitCode(command).pipe(
|
|
157
|
+
Effect.mapError((cause) => new RegenerateError({ message: "Type generation failed", cause }))
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if (exitCode !== 0) {
|
|
161
|
+
return yield* Effect.fail(new RegenerateError({ message: "Type generation failed", cause: { exitCode } }))
|
|
73
162
|
}
|
|
74
163
|
})
|
|
75
164
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
165
|
+
const commandExitCode = (
|
|
166
|
+
command: ChildProcess.Command
|
|
167
|
+
): Effect.Effect<ChildProcessSpawner.ExitCode, PlatformError.PlatformError, ChildProcessSpawner.ChildProcessSpawner> =>
|
|
168
|
+
ChildProcessSpawner.ChildProcessSpawner.pipe(
|
|
169
|
+
Effect.flatMap((spawner) => spawner.exitCode(command))
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
const formatFile = (
|
|
173
|
+
packageDir: string,
|
|
174
|
+
file: string
|
|
175
|
+
): Effect.Effect<void, RegenerateError, ChildProcessSpawner.ChildProcessSpawner> =>
|
|
176
|
+
Effect.gen(function*() {
|
|
177
|
+
const command = ChildProcess.make("pnpm", ["exec", "prettier", "--write", file], {
|
|
178
|
+
cwd: packageDir,
|
|
179
|
+
stdin: "inherit",
|
|
180
|
+
stdout: "inherit",
|
|
181
|
+
stderr: "inherit"
|
|
182
|
+
})
|
|
183
|
+
const exitCode = yield* commandExitCode(command).pipe(
|
|
184
|
+
Effect.mapError((cause) => new RegenerateError({ message: "Formatting failed", cause }))
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if (exitCode !== 0) {
|
|
188
|
+
return yield* Effect.fail(new RegenerateError({ message: "Formatting failed", cause: { exitCode } }))
|
|
189
|
+
}
|
|
86
190
|
})
|
|
87
191
|
|
|
88
192
|
const checkOnly = Options.boolean("check").pipe(
|
|
@@ -92,10 +196,13 @@ const checkOnly = Options.boolean("check").pipe(
|
|
|
92
196
|
|
|
93
197
|
const regenerate = Command.make("regenerate", { checkOnly }, ({ checkOnly }) =>
|
|
94
198
|
Effect.gen(function*() {
|
|
199
|
+
const path = yield* Path.Path
|
|
200
|
+
const paths = yield* scriptPaths
|
|
201
|
+
|
|
95
202
|
yield* Console.log("Fetching Jira API spec version...")
|
|
96
203
|
|
|
97
204
|
const v3Info = yield* fetchSpecInfo(SPEC_URLS.v3)
|
|
98
|
-
const currentV3 = yield* readVersion(
|
|
205
|
+
const currentV3 = yield* readVersion(paths.versionV3File)
|
|
99
206
|
|
|
100
207
|
yield* Console.log(`V3: current=${currentV3 ?? "none"}, remote=${v3Info.version}`)
|
|
101
208
|
|
|
@@ -109,23 +216,23 @@ const regenerate = Command.make("regenerate", { checkOnly }, ({ checkOnly }) =>
|
|
|
109
216
|
if (checkOnly) {
|
|
110
217
|
yield* Console.log("Spec is outdated!")
|
|
111
218
|
yield* Console.log(` V3: ${currentV3} -> ${v3Info.version}`)
|
|
112
|
-
return yield* Effect.fail(new
|
|
219
|
+
return yield* Effect.fail(new RegenerateError({ message: "Spec outdated" }))
|
|
113
220
|
}
|
|
114
221
|
|
|
115
|
-
yield* ensureDir(
|
|
116
|
-
yield* ensureDir(
|
|
222
|
+
yield* ensureDir(paths.specsDir)
|
|
223
|
+
yield* ensureDir(paths.generatedDir)
|
|
117
224
|
|
|
118
|
-
const specPath = path.join(
|
|
225
|
+
const specPath = path.join(paths.specsDir, `jira-v3-${v3Info.version}.json`)
|
|
119
226
|
|
|
120
227
|
yield* Console.log(`Fetching V3 spec (${v3Info.version})...`)
|
|
121
|
-
yield* fetchAndSaveSpec(SPEC_URLS.v3, specPath)
|
|
228
|
+
yield* fetchAndSaveSpec(paths.packageDir, SPEC_URLS.v3, specPath)
|
|
122
229
|
yield* Console.log(`Saved: ${specPath}`)
|
|
123
230
|
|
|
124
231
|
yield* Console.log("Generating types...")
|
|
125
|
-
yield* generateTypes(specPath)
|
|
232
|
+
yield* generateTypes(paths.packageDir, paths.generatedDir, specPath)
|
|
126
233
|
yield* Console.log(`Generated: src/generated/v3/schema.d.ts`)
|
|
127
234
|
|
|
128
|
-
yield* writeVersion(
|
|
235
|
+
yield* writeVersion(paths.versionV3File, v3Info.version)
|
|
129
236
|
yield* Console.log("Done!")
|
|
130
237
|
})
|
|
131
238
|
).pipe(Command.withDescription("Regenerate Jira API types from OpenAPI specs"))
|
|
@@ -135,7 +242,8 @@ const cli = Command.run(regenerate, {
|
|
|
135
242
|
version: pkg.version
|
|
136
243
|
})
|
|
137
244
|
|
|
138
|
-
|
|
139
|
-
Effect.provide(
|
|
245
|
+
cli.pipe(
|
|
246
|
+
Effect.provide(NodeHttpClient.layerFetch),
|
|
247
|
+
Effect.provide(NodeServices.layer),
|
|
140
248
|
NodeRuntime.runMain
|
|
141
249
|
)
|
package/src/JiraApiClient.ts
CHANGED
|
@@ -85,9 +85,9 @@ export interface JiraApiClientShape {
|
|
|
85
85
|
*
|
|
86
86
|
* @category Client
|
|
87
87
|
*/
|
|
88
|
-
export class JiraApiClient extends Context.
|
|
88
|
+
export class JiraApiClient extends Context.Service<JiraApiClient, JiraApiClientShape>()(
|
|
89
89
|
"@knpkv/jira-api-client/JiraApiClient"
|
|
90
|
-
)
|
|
90
|
+
) {
|
|
91
91
|
/**
|
|
92
92
|
* Layer that provides JiraApiClient.
|
|
93
93
|
*
|
package/src/JiraApiConfig.ts
CHANGED
|
@@ -46,6 +46,6 @@ export interface JiraApiConfigShape {
|
|
|
46
46
|
*
|
|
47
47
|
* @category Config
|
|
48
48
|
*/
|
|
49
|
-
export class JiraApiConfig extends Context.
|
|
49
|
+
export class JiraApiConfig extends Context.Service<JiraApiConfig, JiraApiConfigShape>()(
|
|
50
50
|
"@knpkv/jira-api-client/JiraApiConfig"
|
|
51
|
-
)
|
|
51
|
+
) {}
|