@knpkv/atlassian-common 0.2.0

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.
Files changed (121) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +107 -0
  4. package/dist/Brand.d.ts +118 -0
  5. package/dist/Brand.d.ts.map +1 -0
  6. package/dist/Brand.js +107 -0
  7. package/dist/Brand.js.map +1 -0
  8. package/dist/Hash.d.ts +53 -0
  9. package/dist/Hash.d.ts.map +1 -0
  10. package/dist/Hash.js +63 -0
  11. package/dist/Hash.js.map +1 -0
  12. package/dist/SerializeError.d.ts +48 -0
  13. package/dist/SerializeError.d.ts.map +1 -0
  14. package/dist/SerializeError.js +42 -0
  15. package/dist/SerializeError.js.map +1 -0
  16. package/dist/ast/BlockNode.d.ts +429 -0
  17. package/dist/ast/BlockNode.d.ts.map +1 -0
  18. package/dist/ast/BlockNode.js +279 -0
  19. package/dist/ast/BlockNode.js.map +1 -0
  20. package/dist/ast/Document.d.ts +255 -0
  21. package/dist/ast/Document.d.ts.map +1 -0
  22. package/dist/ast/Document.js +80 -0
  23. package/dist/ast/Document.js.map +1 -0
  24. package/dist/ast/InlineNode.d.ts +483 -0
  25. package/dist/ast/InlineNode.d.ts.map +1 -0
  26. package/dist/ast/InlineNode.js +268 -0
  27. package/dist/ast/InlineNode.js.map +1 -0
  28. package/dist/ast/MacroNode.d.ts +214 -0
  29. package/dist/ast/MacroNode.d.ts.map +1 -0
  30. package/dist/ast/MacroNode.js +110 -0
  31. package/dist/ast/MacroNode.js.map +1 -0
  32. package/dist/ast/index.d.ts +10 -0
  33. package/dist/ast/index.d.ts.map +1 -0
  34. package/dist/ast/index.js +14 -0
  35. package/dist/ast/index.js.map +1 -0
  36. package/dist/auth/OAuthEndpoints.d.ts +95 -0
  37. package/dist/auth/OAuthEndpoints.d.ts.map +1 -0
  38. package/dist/auth/OAuthEndpoints.js +112 -0
  39. package/dist/auth/OAuthEndpoints.js.map +1 -0
  40. package/dist/auth/OAuthErrors.d.ts +76 -0
  41. package/dist/auth/OAuthErrors.d.ts.map +1 -0
  42. package/dist/auth/OAuthErrors.js +85 -0
  43. package/dist/auth/OAuthErrors.js.map +1 -0
  44. package/dist/auth/OAuthOperations.d.ts +87 -0
  45. package/dist/auth/OAuthOperations.d.ts.map +1 -0
  46. package/dist/auth/OAuthOperations.js +169 -0
  47. package/dist/auth/OAuthOperations.js.map +1 -0
  48. package/dist/auth/OAuthResponseSchemas.d.ts +65 -0
  49. package/dist/auth/OAuthResponseSchemas.d.ts.map +1 -0
  50. package/dist/auth/OAuthResponseSchemas.js +47 -0
  51. package/dist/auth/OAuthResponseSchemas.js.map +1 -0
  52. package/dist/auth/index.d.ts +11 -0
  53. package/dist/auth/index.d.ts.map +1 -0
  54. package/dist/auth/index.js +16 -0
  55. package/dist/auth/index.js.map +1 -0
  56. package/dist/auth/uuid.d.ts +14 -0
  57. package/dist/auth/uuid.d.ts.map +1 -0
  58. package/dist/auth/uuid.js +14 -0
  59. package/dist/auth/uuid.js.map +1 -0
  60. package/dist/config/ConfigPaths.d.ts +95 -0
  61. package/dist/config/ConfigPaths.d.ts.map +1 -0
  62. package/dist/config/ConfigPaths.js +102 -0
  63. package/dist/config/ConfigPaths.js.map +1 -0
  64. package/dist/config/OAuthSchemas.d.ts +144 -0
  65. package/dist/config/OAuthSchemas.d.ts.map +1 -0
  66. package/dist/config/OAuthSchemas.js +107 -0
  67. package/dist/config/OAuthSchemas.js.map +1 -0
  68. package/dist/config/TokenStorage.d.ts +96 -0
  69. package/dist/config/TokenStorage.d.ts.map +1 -0
  70. package/dist/config/TokenStorage.js +128 -0
  71. package/dist/config/TokenStorage.js.map +1 -0
  72. package/dist/config/index.d.ts +9 -0
  73. package/dist/config/index.d.ts.map +1 -0
  74. package/dist/config/index.js +12 -0
  75. package/dist/config/index.js.map +1 -0
  76. package/dist/index.d.ts +13 -0
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/index.js +20 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/parsers/index.d.ts +7 -0
  81. package/dist/parsers/index.d.ts.map +1 -0
  82. package/dist/parsers/index.js +8 -0
  83. package/dist/parsers/index.js.map +1 -0
  84. package/dist/serializers/MarkdownSerializer.d.ts +63 -0
  85. package/dist/serializers/MarkdownSerializer.d.ts.map +1 -0
  86. package/dist/serializers/MarkdownSerializer.js +367 -0
  87. package/dist/serializers/MarkdownSerializer.js.map +1 -0
  88. package/dist/serializers/index.d.ts +7 -0
  89. package/dist/serializers/index.d.ts.map +1 -0
  90. package/dist/serializers/index.js +7 -0
  91. package/dist/serializers/index.js.map +1 -0
  92. package/package.json +86 -0
  93. package/src/Brand.ts +142 -0
  94. package/src/Hash.ts +68 -0
  95. package/src/SerializeError.ts +50 -0
  96. package/src/ast/BlockNode.ts +386 -0
  97. package/src/ast/Document.ts +101 -0
  98. package/src/ast/InlineNode.ts +328 -0
  99. package/src/ast/MacroNode.ts +172 -0
  100. package/src/ast/index.ts +87 -0
  101. package/src/auth/OAuthEndpoints.ts +142 -0
  102. package/src/auth/OAuthErrors.ts +101 -0
  103. package/src/auth/OAuthOperations.ts +276 -0
  104. package/src/auth/OAuthResponseSchemas.ts +70 -0
  105. package/src/auth/index.ts +47 -0
  106. package/src/auth/uuid.ts +14 -0
  107. package/src/config/ConfigPaths.ts +179 -0
  108. package/src/config/OAuthSchemas.ts +146 -0
  109. package/src/config/TokenStorage.ts +228 -0
  110. package/src/config/index.ts +43 -0
  111. package/src/index.ts +34 -0
  112. package/src/parsers/index.ts +8 -0
  113. package/src/serializers/MarkdownSerializer.ts +498 -0
  114. package/src/serializers/index.ts +7 -0
  115. package/test/Brand.test.ts +90 -0
  116. package/test/MarkdownSerializer.test.ts +82 -0
  117. package/test/OAuthEndpoints.test.ts +109 -0
  118. package/test/OAuthOperations.test.ts +315 -0
  119. package/tsconfig.json +11 -0
  120. package/tsconfig.tsbuildinfo +1 -0
  121. package/vitest.config.ts +12 -0
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Shared Effect Schema definitions for persisted OAuth credentials and site metadata.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Storage schemas**: {@link OAuthTokenSchema} and {@link OAuthConfigSchema} define the
7
+ * JSON shape written to `auth.json` / `oauth.json` on disk. They are the single source
8
+ * of truth for token and config structure.
9
+ * - **Type derivation**: All companion types (`OAuthToken`, `OAuthConfig`, etc.) are derived
10
+ * from schemas — never duplicated.
11
+ *
12
+ * @module
13
+ */
14
+ import * as Schema from "effect/Schema"
15
+
16
+ /**
17
+ * Schema for OAuth user info from Atlassian.
18
+ *
19
+ * @category Schema
20
+ */
21
+ export const OAuthUserSchema = Schema.Struct({
22
+ /** Atlassian account ID */
23
+ account_id: Schema.String,
24
+ /** Display name */
25
+ name: Schema.String,
26
+ /** Email address */
27
+ email: Schema.String
28
+ })
29
+
30
+ /**
31
+ * Type for OAuth user info.
32
+ *
33
+ * @category Types
34
+ */
35
+ export type OAuthUser = Schema.Schema.Type<typeof OAuthUserSchema>
36
+
37
+ /**
38
+ * Schema for stored OAuth token.
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * import { OAuthTokenSchema } from "@knpkv/atlassian-common/config"
43
+ * import * as Schema from "effect/Schema"
44
+ *
45
+ * const token = Schema.decodeUnknownSync(OAuthTokenSchema)({
46
+ * access_token: "eyJ...",
47
+ * refresh_token: "eyJ...",
48
+ * expires_at: Date.now() + 3600000,
49
+ * scope: "read:confluence-content.all",
50
+ * cloud_id: "abc123",
51
+ * site_url: "https://mysite.atlassian.net"
52
+ * })
53
+ * ```
54
+ *
55
+ * @category Schema
56
+ */
57
+ export const OAuthTokenSchema = Schema.Struct({
58
+ /** OAuth access token */
59
+ access_token: Schema.String,
60
+ /** OAuth refresh token */
61
+ refresh_token: Schema.String,
62
+ /** Token expiration timestamp (Unix ms) */
63
+ expires_at: Schema.Number,
64
+ /** Granted scopes */
65
+ scope: Schema.String,
66
+ /** Atlassian Cloud site ID */
67
+ cloud_id: Schema.String,
68
+ /** Site URL */
69
+ site_url: Schema.String,
70
+ /** Cached user info */
71
+ user: Schema.optional(OAuthUserSchema)
72
+ })
73
+
74
+ /**
75
+ * Type for stored OAuth token.
76
+ *
77
+ * @category Types
78
+ */
79
+ export type OAuthToken = Schema.Schema.Type<typeof OAuthTokenSchema>
80
+
81
+ /**
82
+ * Schema for OAuth client configuration.
83
+ *
84
+ * @category Schema
85
+ */
86
+ export const OAuthConfigSchema = Schema.Struct({
87
+ /** OAuth client ID from Atlassian Developer Console */
88
+ clientId: Schema.String,
89
+ /** OAuth client secret */
90
+ clientSecret: Schema.String
91
+ })
92
+
93
+ /**
94
+ * Type for OAuth client configuration.
95
+ *
96
+ * @category Types
97
+ */
98
+ export type OAuthConfig = Schema.Schema.Type<typeof OAuthConfigSchema>
99
+
100
+ /**
101
+ * Schema for Atlassian Cloud site info.
102
+ *
103
+ * @category Schema
104
+ */
105
+ export const AtlassianSiteSchema = Schema.Struct({
106
+ /** Cloud ID */
107
+ id: Schema.String,
108
+ /** Site name */
109
+ name: Schema.String,
110
+ /** Site URL */
111
+ url: Schema.String,
112
+ /** Available scopes */
113
+ scopes: Schema.Array(Schema.String),
114
+ /** Avatar URL */
115
+ avatarUrl: Schema.optional(Schema.String)
116
+ })
117
+
118
+ /**
119
+ * Type for Atlassian Cloud site.
120
+ *
121
+ * @category Types
122
+ */
123
+ export type AtlassianSite = Schema.Schema.Type<typeof AtlassianSiteSchema>
124
+
125
+ /**
126
+ * Schema for Atlassian user profile.
127
+ *
128
+ * @category Schema
129
+ */
130
+ export const AtlassianUserSchema = Schema.Struct({
131
+ /** Atlassian account ID */
132
+ accountId: Schema.String,
133
+ /** Display name */
134
+ displayName: Schema.String,
135
+ /** Email address (may be empty for privacy) */
136
+ email: Schema.optional(Schema.String),
137
+ /** Public name */
138
+ publicName: Schema.optional(Schema.String)
139
+ })
140
+
141
+ /**
142
+ * Type for Atlassian user profile.
143
+ *
144
+ * @category Types
145
+ */
146
+ export type AtlassianUser = Schema.Schema.Type<typeof AtlassianUserSchema>
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Persistent token and OAuth config storage with schema validation and secure file I/O.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Load returns null, not error**: {@link loadToken} and {@link loadOAuthConfig} return
7
+ * `null` for missing/corrupt files — callers decide whether absence is an error.
8
+ * - **Save is atomic**: Writes go through {@link writeSecureFile} with `0o600` permissions.
9
+ * - **Schema-gated reads**: JSON is parsed then validated via `Schema.decodeUnknown` —
10
+ * corrupt data is treated as absent rather than crashing.
11
+ *
12
+ * **Common tasks**
13
+ *
14
+ * - Load stored token: {@link loadToken}
15
+ * - Persist after refresh: {@link saveToken}
16
+ * - Check expiration: {@link isTokenExpired}
17
+ *
18
+ * @module
19
+ */
20
+ import type * as Error from "@effect/platform/Error"
21
+ import * as FileSystem from "@effect/platform/FileSystem"
22
+ import type * as Path from "@effect/platform/Path"
23
+ import * as Data from "effect/Data"
24
+ import * as Effect from "effect/Effect"
25
+ import * as Schema from "effect/Schema"
26
+ import { type HomeDirectoryError, type HomeDirectoryTag } from "./ConfigPaths.js"
27
+ import { ensureConfigDir, getAuthPath, getOAuthConfigPath, writeSecureFile } from "./ConfigPaths.js"
28
+ import { type OAuthConfig, OAuthConfigSchema, type OAuthToken, OAuthTokenSchema } from "./OAuthSchemas.js"
29
+
30
+ /**
31
+ * Error during file system operations.
32
+ *
33
+ * @category Errors
34
+ */
35
+ export class FileSystemError extends Data.TaggedError("FileSystemError")<{
36
+ readonly operation: string
37
+ readonly path: string
38
+ readonly cause?: unknown
39
+ }> {
40
+ override get message(): string {
41
+ return `Failed to ${this.operation} file at ${this.path}`
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Load stored OAuth token from disk.
47
+ *
48
+ * @param toolName - The tool name (e.g., "confluence", "jira")
49
+ * @returns The stored token or null if not found/invalid
50
+ *
51
+ * @category Token Storage
52
+ */
53
+ export const loadToken = (
54
+ toolName: string
55
+ ): Effect.Effect<
56
+ OAuthToken | null,
57
+ FileSystemError | HomeDirectoryError,
58
+ FileSystem.FileSystem | Path.Path | HomeDirectoryTag
59
+ > =>
60
+ Effect.gen(function*() {
61
+ const fs = yield* FileSystem.FileSystem
62
+ const tokenPath = yield* getAuthPath(toolName)
63
+
64
+ const exists = yield* fs.exists(tokenPath).pipe(
65
+ Effect.catchAll(() => Effect.succeed(false))
66
+ )
67
+ if (!exists) {
68
+ return null
69
+ }
70
+
71
+ const content = yield* fs.readFileString(tokenPath).pipe(
72
+ Effect.mapError((cause) => new FileSystemError({ operation: "read", path: tokenPath, cause }))
73
+ )
74
+
75
+ const parsed = yield* Effect.sync(() => {
76
+ try {
77
+ return JSON.parse(content) as unknown
78
+ } catch {
79
+ return null
80
+ }
81
+ })
82
+ if (parsed === null) {
83
+ yield* Effect.logWarning(`Corrupted auth.json at ${tokenPath} — could not parse JSON`)
84
+ return null
85
+ }
86
+
87
+ return yield* Schema.decodeUnknown(OAuthTokenSchema)(parsed).pipe(
88
+ Effect.catchAll((e) =>
89
+ Effect.logWarning(`Invalid token schema in ${tokenPath}: ${e}`).pipe(
90
+ Effect.map(() => null)
91
+ )
92
+ )
93
+ )
94
+ })
95
+
96
+ /**
97
+ * Save OAuth token to disk with secure permissions.
98
+ *
99
+ * @param toolName - The tool name (e.g., "confluence", "jira")
100
+ * @param token - The token to save
101
+ *
102
+ * @category Token Storage
103
+ */
104
+ export const saveToken = (
105
+ toolName: string,
106
+ token: OAuthToken
107
+ ): Effect.Effect<
108
+ void,
109
+ FileSystemError | HomeDirectoryError | Error.PlatformError,
110
+ FileSystem.FileSystem | Path.Path | HomeDirectoryTag
111
+ > =>
112
+ Effect.gen(function*() {
113
+ const fs = yield* FileSystem.FileSystem
114
+ yield* ensureConfigDir(toolName)
115
+ const tokenPath = yield* getAuthPath(toolName)
116
+
117
+ yield* writeSecureFile(tokenPath, JSON.stringify(token, null, 2)).pipe(
118
+ Effect.provideService(FileSystem.FileSystem, fs)
119
+ )
120
+ })
121
+
122
+ /**
123
+ * Delete stored OAuth token.
124
+ *
125
+ * @param toolName - The tool name (e.g., "confluence", "jira")
126
+ *
127
+ * @category Token Storage
128
+ */
129
+ export const deleteToken = (
130
+ toolName: string
131
+ ): Effect.Effect<void, FileSystemError | HomeDirectoryError, FileSystem.FileSystem | Path.Path | HomeDirectoryTag> =>
132
+ Effect.gen(function*() {
133
+ const fs = yield* FileSystem.FileSystem
134
+ const tokenPath = yield* getAuthPath(toolName)
135
+
136
+ yield* fs.remove(tokenPath).pipe(
137
+ Effect.catchAll(() => Effect.void)
138
+ )
139
+ })
140
+
141
+ /**
142
+ * Load OAuth client configuration from disk.
143
+ *
144
+ * @param toolName - The tool name (e.g., "confluence", "jira")
145
+ * @returns The stored config or null if not found/invalid
146
+ *
147
+ * @category OAuth Config
148
+ */
149
+ export const loadOAuthConfig = (
150
+ toolName: string
151
+ ): Effect.Effect<
152
+ OAuthConfig | null,
153
+ FileSystemError | HomeDirectoryError,
154
+ FileSystem.FileSystem | Path.Path | HomeDirectoryTag
155
+ > =>
156
+ Effect.gen(function*() {
157
+ const fs = yield* FileSystem.FileSystem
158
+ const configPath = yield* getOAuthConfigPath(toolName)
159
+
160
+ const exists = yield* fs.exists(configPath).pipe(
161
+ Effect.catchAll(() => Effect.succeed(false))
162
+ )
163
+ if (!exists) {
164
+ return null
165
+ }
166
+
167
+ const content = yield* fs.readFileString(configPath).pipe(
168
+ Effect.mapError((cause) => new FileSystemError({ operation: "read", path: configPath, cause }))
169
+ )
170
+
171
+ const parsed = yield* Effect.sync(() => {
172
+ try {
173
+ return JSON.parse(content) as unknown
174
+ } catch {
175
+ return null
176
+ }
177
+ })
178
+ if (parsed === null) {
179
+ yield* Effect.logWarning(`Corrupted oauth config at ${configPath} — could not parse JSON`)
180
+ return null
181
+ }
182
+
183
+ return yield* Schema.decodeUnknown(OAuthConfigSchema)(parsed).pipe(
184
+ Effect.catchAll((e) =>
185
+ Effect.logWarning(`Invalid OAuth config schema in ${configPath}: ${e}`).pipe(
186
+ Effect.map(() => null)
187
+ )
188
+ )
189
+ )
190
+ })
191
+
192
+ /**
193
+ * Save OAuth client configuration to disk with secure permissions.
194
+ *
195
+ * @param toolName - The tool name (e.g., "confluence", "jira")
196
+ * @param config - The config to save
197
+ *
198
+ * @category OAuth Config
199
+ */
200
+ export const saveOAuthConfig = (
201
+ toolName: string,
202
+ config: OAuthConfig
203
+ ): Effect.Effect<
204
+ void,
205
+ FileSystemError | HomeDirectoryError | Error.PlatformError,
206
+ FileSystem.FileSystem | Path.Path | HomeDirectoryTag
207
+ > =>
208
+ Effect.gen(function*() {
209
+ const fs = yield* FileSystem.FileSystem
210
+ yield* ensureConfigDir(toolName)
211
+ const configPath = yield* getOAuthConfigPath(toolName)
212
+
213
+ yield* writeSecureFile(configPath, JSON.stringify(config, null, 2)).pipe(
214
+ Effect.provideService(FileSystem.FileSystem, fs)
215
+ )
216
+ })
217
+
218
+ /**
219
+ * Check if token is expired or about to expire.
220
+ *
221
+ * @param token - The token to check
222
+ * @param bufferMs - Buffer time in ms before expiration (default: 5 minutes)
223
+ * @returns True if token is expired or will expire within buffer time
224
+ *
225
+ * @category Utilities
226
+ */
227
+ export const isTokenExpired = (token: OAuthToken, bufferMs: number = 5 * 60 * 1000): boolean =>
228
+ Date.now() >= token.expires_at - bufferMs
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Barrel export for config path utilities, schemas, and token storage.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ // Config paths
8
+ export {
9
+ ensureConfigDir,
10
+ getAuthPath,
11
+ getConfigDir,
12
+ getOAuthConfigPath,
13
+ type HomeDirectory,
14
+ HomeDirectoryError,
15
+ HomeDirectoryLive,
16
+ HomeDirectoryTag,
17
+ writeSecureFile
18
+ } from "./ConfigPaths.js"
19
+
20
+ // OAuth schemas
21
+ export {
22
+ type AtlassianSite,
23
+ AtlassianSiteSchema,
24
+ type AtlassianUser,
25
+ AtlassianUserSchema,
26
+ type OAuthConfig,
27
+ OAuthConfigSchema,
28
+ type OAuthToken,
29
+ OAuthTokenSchema,
30
+ type OAuthUser,
31
+ OAuthUserSchema
32
+ } from "./OAuthSchemas.js"
33
+
34
+ // Token storage
35
+ export {
36
+ deleteToken,
37
+ FileSystemError,
38
+ isTokenExpired,
39
+ loadOAuthConfig,
40
+ loadToken,
41
+ saveOAuthConfig,
42
+ saveToken
43
+ } from "./TokenStorage.js"
package/src/index.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Root barrel export for `@knpkv/atlassian-common` — AST nodes, auth, config, serializers.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ // AST types
8
+ export * from "./ast/index.js"
9
+
10
+ // Brand utilities
11
+ export {
12
+ ContentHash,
13
+ IssueKey,
14
+ makeBrandedNonEmptyString,
15
+ makeBrandedString,
16
+ PageId,
17
+ ProjectKey,
18
+ SpaceKey
19
+ } from "./Brand.js"
20
+
21
+ // Hash utilities
22
+ export { hashBuffer, hashContent, hashContentSync, hashEquals } from "./Hash.js"
23
+
24
+ // Error types
25
+ export { ParseError, SerializeError } from "./SerializeError.js"
26
+
27
+ // Serializers
28
+ export { serializeInlineNodes, type SerializeOptions, serializeToMarkdown } from "./serializers/index.js"
29
+
30
+ // Config utilities
31
+ export * from "./config/index.js"
32
+
33
+ // Auth utilities
34
+ export * from "./auth/index.js"
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Barrel export for content parsers (placeholder — not yet implemented).
3
+ *
4
+ * @module
5
+ */
6
+
7
+ // TODO: Extract MarkdownParser from confluence-to-markdown
8
+ export {}