@pikku/core 0.12.23 → 0.12.25
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/CHANGELOG.md +30 -0
- package/dist/data-classification.d.ts +16 -0
- package/dist/data-classification.js +1 -0
- package/dist/function/function-runner.js +62 -15
- package/dist/function/functions.types.d.ts +8 -5
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -0
- package/dist/middleware-runner.d.ts +2 -2
- package/dist/permissions.d.ts +1 -1
- package/dist/services/ai-agent-runner-service.d.ts +3 -0
- package/dist/services/audit-service.d.ts +50 -0
- package/dist/services/audit-service.js +106 -0
- package/dist/services/content-service.d.ts +1 -0
- package/dist/services/email-service.d.ts +36 -0
- package/dist/services/email-service.js +1 -0
- package/dist/services/gopass-secrets.d.ts +2 -35
- package/dist/services/gopass-secrets.js +10 -40
- package/dist/services/index.d.ts +5 -1
- package/dist/services/index.js +2 -0
- package/dist/services/local-content.js +1 -0
- package/dist/services/local-email-service.d.ts +4 -0
- package/dist/services/local-email-service.js +26 -0
- package/dist/services/local-secrets.d.ts +3 -36
- package/dist/services/local-secrets.js +12 -52
- package/dist/services/local-variables.d.ts +3 -5
- package/dist/services/local-variables.js +10 -10
- package/dist/services/meta-service.d.ts +36 -0
- package/dist/services/meta-service.js +59 -0
- package/dist/services/scoped-secret-service.d.ts +2 -3
- package/dist/services/scoped-secret-service.js +2 -6
- package/dist/services/secret-service.d.ts +5 -12
- package/dist/services/typed-secret-service.d.ts +3 -4
- package/dist/services/typed-secret-service.js +2 -5
- package/dist/services/typed-variables-service.d.ts +3 -6
- package/dist/services/typed-variables-service.js +0 -6
- package/dist/services/variables-service.d.ts +2 -4
- package/dist/testing/service-tests.js +13 -13
- package/dist/types/core.types.d.ts +15 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +2 -0
- package/dist/wirings/ai-agent/ai-agent-prepare.js +12 -3
- package/dist/wirings/ai-agent/ai-agent-runner.js +1 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +1 -1
- package/dist/wirings/ai-agent/ai-agent-utils.d.ts +1 -0
- package/dist/wirings/ai-agent/ai-agent-utils.js +1 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +4 -0
- package/dist/wirings/oauth2/oauth2-client.js +3 -3
- package/dist/wirings/rpc/rpc-runner.js +9 -2
- package/package.json +1 -1
- package/src/data-classification.ts +15 -0
- package/src/function/function-runner.test.ts +255 -0
- package/src/function/function-runner.ts +66 -20
- package/src/function/functions.types.ts +26 -11
- package/src/index.ts +44 -0
- package/src/middleware-runner.ts +10 -10
- package/src/permissions.ts +4 -4
- package/src/services/ai-agent-runner-service.ts +3 -0
- package/src/services/audit-service.test.ts +44 -0
- package/src/services/audit-service.ts +198 -0
- package/src/services/content-service.ts +1 -0
- package/src/services/email-service.ts +46 -0
- package/src/services/gopass-secrets.ts +10 -42
- package/src/services/index.ts +33 -0
- package/src/services/local-content.ts +1 -0
- package/src/services/local-email-service.test.ts +108 -0
- package/src/services/local-email-service.ts +30 -0
- package/src/services/local-secrets.test.ts +10 -10
- package/src/services/local-secrets.ts +16 -55
- package/src/services/local-variables.test.ts +4 -4
- package/src/services/local-variables.ts +12 -17
- package/src/services/meta-service.ts +115 -0
- package/src/services/scoped-secret-service.test.ts +10 -11
- package/src/services/scoped-secret-service.ts +4 -9
- package/src/services/secret-service.ts +5 -12
- package/src/services/typed-secret-service.test.ts +16 -17
- package/src/services/typed-secret-service.ts +5 -9
- package/src/services/typed-variables-service.test.ts +5 -5
- package/src/services/typed-variables-service.ts +5 -17
- package/src/services/variables-service.ts +2 -4
- package/src/testing/service-tests.ts +13 -13
- package/src/types/core.types.ts +20 -2
- package/src/wirings/ai-agent/ai-agent-prepare.ts +17 -3
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +105 -0
- package/src/wirings/ai-agent/ai-agent-runner.ts +1 -1
- package/src/wirings/ai-agent/ai-agent-stream.ts +1 -1
- package/src/wirings/ai-agent/ai-agent-utils.ts +1 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +4 -0
- package/src/wirings/oauth2/oauth2-client.test.ts +2 -3
- package/src/wirings/oauth2/oauth2-client.ts +3 -3
- package/src/wirings/rpc/rpc-runner.ts +9 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface EmailTemplateReference {
|
|
2
|
+
name: string
|
|
3
|
+
locale?: string
|
|
4
|
+
data?: Record<string, unknown>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface BaseSendEmailInput {
|
|
8
|
+
to: string | string[]
|
|
9
|
+
from?: string
|
|
10
|
+
cc?: string | string[]
|
|
11
|
+
bcc?: string | string[]
|
|
12
|
+
replyTo?: string | string[]
|
|
13
|
+
headers?: Record<string, string>
|
|
14
|
+
subject?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SendTextEmailInput extends BaseSendEmailInput {
|
|
18
|
+
text: string
|
|
19
|
+
html?: never
|
|
20
|
+
template?: never
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SendHTMLEmailInput extends BaseSendEmailInput {
|
|
24
|
+
html: string
|
|
25
|
+
text?: string
|
|
26
|
+
template?: never
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SendTemplateEmailInput extends BaseSendEmailInput {
|
|
30
|
+
template: EmailTemplateReference
|
|
31
|
+
html?: never
|
|
32
|
+
text?: never
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type SendEmailInput =
|
|
36
|
+
| SendTextEmailInput
|
|
37
|
+
| SendHTMLEmailInput
|
|
38
|
+
| SendTemplateEmailInput
|
|
39
|
+
|
|
40
|
+
export interface SendEmailResult {
|
|
41
|
+
messageId?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface EmailService {
|
|
45
|
+
send(input: SendEmailInput): Promise<SendEmailResult>
|
|
46
|
+
}
|
|
@@ -6,10 +6,6 @@ import type { SecretService } from './secret-service.js'
|
|
|
6
6
|
* Requires gopass to be installed and configured on the system.
|
|
7
7
|
*/
|
|
8
8
|
export class GopassSecretService implements SecretService {
|
|
9
|
-
/**
|
|
10
|
-
* Creates an instance of GopassSecretService.
|
|
11
|
-
* @param prefix - Optional prefix for all secret keys (e.g., 'pikku/' will look up 'pikku/mykey' for key 'mykey')
|
|
12
|
-
*/
|
|
13
9
|
constructor(private prefix: string = '') {}
|
|
14
10
|
|
|
15
11
|
private getFullKey(key: string): string {
|
|
@@ -19,39 +15,22 @@ export class GopassSecretService implements SecretService {
|
|
|
19
15
|
return this.prefix ? `${this.prefix}${key}` : key
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
* Retrieves a secret by key and parses it as JSON.
|
|
24
|
-
* @param key - The key of the secret to retrieve.
|
|
25
|
-
* @returns A promise that resolves to the parsed secret value.
|
|
26
|
-
* @throws {Error} If the secret is not found or gopass fails.
|
|
27
|
-
*/
|
|
28
|
-
public async getSecretJSON<R>(key: string): Promise<R> {
|
|
29
|
-
const value = await this.getSecret(key)
|
|
30
|
-
return JSON.parse(value)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Retrieves a secret by key as a string.
|
|
35
|
-
* @param key - The key of the secret to retrieve.
|
|
36
|
-
* @returns A promise that resolves to the secret value.
|
|
37
|
-
* @throws {Error} If the secret is not found or gopass fails.
|
|
38
|
-
*/
|
|
39
|
-
public async getSecret(key: string): Promise<string> {
|
|
18
|
+
public async getSecret<T = string>(key: string): Promise<T> {
|
|
40
19
|
const fullKey = this.getFullKey(key)
|
|
41
20
|
try {
|
|
42
|
-
|
|
21
|
+
const raw = execFileSync('gopass', ['show', '-o', fullKey], {
|
|
43
22
|
encoding: 'utf8',
|
|
44
23
|
}).trim()
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(raw) as T
|
|
26
|
+
} catch {
|
|
27
|
+
return raw as unknown as T
|
|
28
|
+
}
|
|
45
29
|
} catch (error: any) {
|
|
46
30
|
throw new Error(`Secret Not Found: ${key}`, { cause: error })
|
|
47
31
|
}
|
|
48
32
|
}
|
|
49
33
|
|
|
50
|
-
/**
|
|
51
|
-
* Checks if a secret exists without throwing.
|
|
52
|
-
* @param key - The key of the secret to check.
|
|
53
|
-
* @returns A promise that resolves to true if the secret exists.
|
|
54
|
-
*/
|
|
55
34
|
public async hasSecret(key: string): Promise<boolean> {
|
|
56
35
|
const fullKey = this.getFullKey(key)
|
|
57
36
|
try {
|
|
@@ -62,19 +41,13 @@ export class GopassSecretService implements SecretService {
|
|
|
62
41
|
}
|
|
63
42
|
}
|
|
64
43
|
|
|
65
|
-
|
|
66
|
-
* Stores a JSON value as a secret in gopass.
|
|
67
|
-
* @param key - The key to store the secret under.
|
|
68
|
-
* @param value - The JSON value to store.
|
|
69
|
-
* @returns A promise that resolves when the secret is stored.
|
|
70
|
-
*/
|
|
71
|
-
public async setSecretJSON(key: string, value: unknown): Promise<void> {
|
|
44
|
+
public async setSecret(key: string, value: unknown): Promise<void> {
|
|
72
45
|
const fullKey = this.getFullKey(key)
|
|
73
|
-
const
|
|
46
|
+
const encoded = typeof value === 'string' ? value : JSON.stringify(value)
|
|
74
47
|
try {
|
|
75
48
|
execFileSync('gopass', ['insert', '-f', fullKey], {
|
|
76
49
|
encoding: 'utf8',
|
|
77
|
-
input:
|
|
50
|
+
input: encoded,
|
|
78
51
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
79
52
|
})
|
|
80
53
|
} catch (error: any) {
|
|
@@ -82,11 +55,6 @@ export class GopassSecretService implements SecretService {
|
|
|
82
55
|
}
|
|
83
56
|
}
|
|
84
57
|
|
|
85
|
-
/**
|
|
86
|
-
* Deletes a secret from gopass.
|
|
87
|
-
* @param key - The key of the secret to delete.
|
|
88
|
-
* @returns A promise that resolves when the secret is deleted.
|
|
89
|
-
*/
|
|
90
58
|
public async deleteSecret(key: string): Promise<void> {
|
|
91
59
|
const fullKey = this.getFullKey(key)
|
|
92
60
|
try {
|
package/src/services/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export {
|
|
|
17
17
|
} from './credential-wire-service.js'
|
|
18
18
|
export { TypedVariablesService } from './typed-variables-service.js'
|
|
19
19
|
export { LocalSecretService } from './local-secrets.js'
|
|
20
|
+
export { LocalEmailService } from './local-email-service.js'
|
|
20
21
|
export { LocalCredentialService } from './local-credential-service.js'
|
|
21
22
|
export { LocalVariablesService } from './local-variables.js'
|
|
22
23
|
export { ConsoleLogger, JsonConsoleLogger } from './logger-console.js'
|
|
@@ -36,6 +37,15 @@ export type {
|
|
|
36
37
|
CopyFileArgs,
|
|
37
38
|
} from './content-service.js'
|
|
38
39
|
export type { JWTService } from './jwt-service.js'
|
|
40
|
+
export type {
|
|
41
|
+
EmailService,
|
|
42
|
+
EmailTemplateReference,
|
|
43
|
+
SendEmailInput,
|
|
44
|
+
SendEmailResult,
|
|
45
|
+
SendHTMLEmailInput,
|
|
46
|
+
SendTemplateEmailInput,
|
|
47
|
+
SendTextEmailInput,
|
|
48
|
+
} from './email-service.js'
|
|
39
49
|
export type { Logger } from './logger.js'
|
|
40
50
|
export type { SecretService } from './secret-service.js'
|
|
41
51
|
export type { VariablesService } from './variables-service.js'
|
|
@@ -78,6 +88,25 @@ export type {
|
|
|
78
88
|
export type { VariableStatus, VariableMeta } from './typed-variables-service.js'
|
|
79
89
|
export type { MetaService } from './meta-service.js'
|
|
80
90
|
export type { SessionStore } from './session-store.js'
|
|
91
|
+
export {
|
|
92
|
+
NoopAuditService,
|
|
93
|
+
createInvocationAudit,
|
|
94
|
+
resolveAuditActorFromWire,
|
|
95
|
+
resolveAuditConfig,
|
|
96
|
+
} from './audit-service.js'
|
|
97
|
+
export type {
|
|
98
|
+
AuditActor,
|
|
99
|
+
AuditConfig,
|
|
100
|
+
AuditDurability,
|
|
101
|
+
AuditEvent,
|
|
102
|
+
AuditEventBatch,
|
|
103
|
+
AuditLog,
|
|
104
|
+
AuditLogWriteInput,
|
|
105
|
+
AuditOutcome,
|
|
106
|
+
AuditService,
|
|
107
|
+
AuditSource,
|
|
108
|
+
ResolvedAuditConfig,
|
|
109
|
+
} from './audit-service.js'
|
|
81
110
|
export { InMemorySessionStore } from './in-memory-session-store.js'
|
|
82
111
|
export type {
|
|
83
112
|
MCPMeta,
|
|
@@ -96,4 +125,8 @@ export type {
|
|
|
96
125
|
PermissionMeta,
|
|
97
126
|
AgentsMeta,
|
|
98
127
|
AgentMeta,
|
|
128
|
+
EmailsMeta,
|
|
129
|
+
EmailTemplateMeta,
|
|
130
|
+
EmailTemplateLocaleMeta,
|
|
131
|
+
EmailTemplateAssets,
|
|
99
132
|
} from './meta-service.js'
|
|
@@ -106,6 +106,7 @@ export class LocalContent implements ContentService {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
public async getUploadURL(args: GetUploadURLArgs): Promise<UploadURLResult> {
|
|
109
|
+
void args.visibility
|
|
109
110
|
const fullKey = this.joinKey(args.bucket, args.fileKey)
|
|
110
111
|
this.logger.debug(`Going to upload with key: ${fullKey}`)
|
|
111
112
|
return {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { LocalEmailService } from './local-email-service.js'
|
|
5
|
+
|
|
6
|
+
test('LocalEmailService logs sent email metadata as JSON', async () => {
|
|
7
|
+
const service = new LocalEmailService()
|
|
8
|
+
const writes: string[] = []
|
|
9
|
+
const originalInfo = console.info
|
|
10
|
+
console.info = (value?: unknown) => {
|
|
11
|
+
writes.push(String(value))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await service.send({
|
|
16
|
+
to: 'user@example.com',
|
|
17
|
+
from: 'hello@example.com',
|
|
18
|
+
subject: 'Welcome',
|
|
19
|
+
template: {
|
|
20
|
+
name: 'welcome',
|
|
21
|
+
locale: 'en',
|
|
22
|
+
data: { userName: 'Yasser' },
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
} finally {
|
|
26
|
+
console.info = originalInfo
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
assert.equal(writes.length, 1)
|
|
30
|
+
const payload = JSON.parse(writes[0])
|
|
31
|
+
assert.equal(payload.type, 'email')
|
|
32
|
+
assert.equal(payload.message, 'this email was sent')
|
|
33
|
+
assert.equal(payload.to, 'user@example.com')
|
|
34
|
+
assert.equal(payload.from, 'hello@example.com')
|
|
35
|
+
assert.equal(payload.subject, 'Welcome')
|
|
36
|
+
assert.deepEqual(payload.template, {
|
|
37
|
+
name: 'welcome',
|
|
38
|
+
locale: 'en',
|
|
39
|
+
data: { userName: 'Yasser' },
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('LocalEmailService logs htmlLength and textLength for HTML email', async () => {
|
|
44
|
+
const service = new LocalEmailService()
|
|
45
|
+
const writes: string[] = []
|
|
46
|
+
const originalInfo = console.info
|
|
47
|
+
console.info = (value?: unknown) => {
|
|
48
|
+
writes.push(String(value))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
await service.send({
|
|
53
|
+
to: 'user@example.com',
|
|
54
|
+
html: '<p>Hello</p>',
|
|
55
|
+
text: 'Hello',
|
|
56
|
+
})
|
|
57
|
+
} finally {
|
|
58
|
+
console.info = originalInfo
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const payload = JSON.parse(writes[0])
|
|
62
|
+
assert.equal(payload.htmlLength, '<p>Hello</p>'.length)
|
|
63
|
+
assert.equal(payload.textLength, 'Hello'.length)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('LocalEmailService logs textLength only for plain text email', async () => {
|
|
67
|
+
const service = new LocalEmailService()
|
|
68
|
+
const writes: string[] = []
|
|
69
|
+
const originalInfo = console.info
|
|
70
|
+
console.info = (value?: unknown) => {
|
|
71
|
+
writes.push(String(value))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
await service.send({
|
|
76
|
+
to: 'user@example.com',
|
|
77
|
+
text: 'Plain text body',
|
|
78
|
+
})
|
|
79
|
+
} finally {
|
|
80
|
+
console.info = originalInfo
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const payload = JSON.parse(writes[0])
|
|
84
|
+
assert.equal(payload.textLength, 'Plain text body'.length)
|
|
85
|
+
assert.equal(payload.htmlLength, undefined)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('LocalEmailService logs null textLength for HTML email without plain text', async () => {
|
|
89
|
+
const service = new LocalEmailService()
|
|
90
|
+
const writes: string[] = []
|
|
91
|
+
const originalInfo = console.info
|
|
92
|
+
console.info = (value?: unknown) => {
|
|
93
|
+
writes.push(String(value))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
await service.send({
|
|
98
|
+
to: 'user@example.com',
|
|
99
|
+
html: '<p>Hello</p>',
|
|
100
|
+
})
|
|
101
|
+
} finally {
|
|
102
|
+
console.info = originalInfo
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const payload = JSON.parse(writes[0])
|
|
106
|
+
assert.equal(payload.htmlLength, '<p>Hello</p>'.length)
|
|
107
|
+
assert.equal(payload.textLength, null)
|
|
108
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { EmailService, SendEmailInput, SendEmailResult } from './email-service.js'
|
|
2
|
+
|
|
3
|
+
export class LocalEmailService implements EmailService {
|
|
4
|
+
async send(input: SendEmailInput): Promise<SendEmailResult> {
|
|
5
|
+
const payload: Record<string, unknown> = {
|
|
6
|
+
type: 'email',
|
|
7
|
+
message: 'this email was sent',
|
|
8
|
+
to: input.to,
|
|
9
|
+
from: input.from ?? null,
|
|
10
|
+
cc: input.cc ?? null,
|
|
11
|
+
bcc: input.bcc ?? null,
|
|
12
|
+
replyTo: input.replyTo ?? null,
|
|
13
|
+
subject: input.subject ?? null,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if ('template' in input && input.template) {
|
|
17
|
+
payload.template = input.template
|
|
18
|
+
}
|
|
19
|
+
if ('html' in input && typeof input.html === 'string') {
|
|
20
|
+
payload.htmlLength = input.html.length
|
|
21
|
+
payload.textLength = input.text?.length ?? null
|
|
22
|
+
}
|
|
23
|
+
if ('text' in input && typeof input.text === 'string') {
|
|
24
|
+
payload.textLength = input.text.length
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.info(JSON.stringify(payload))
|
|
28
|
+
return {}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -6,9 +6,9 @@ import { LocalVariablesService } from './local-variables.js'
|
|
|
6
6
|
describe('LocalSecretService', () => {
|
|
7
7
|
test('should get secret from local storage', async () => {
|
|
8
8
|
const service = new LocalSecretService()
|
|
9
|
-
await service.
|
|
9
|
+
await service.setSecret('MY_KEY', { token: 'abc' })
|
|
10
10
|
const result = await service.getSecret('MY_KEY')
|
|
11
|
-
assert.
|
|
11
|
+
assert.deepStrictEqual(result, { token: 'abc' })
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
test('should get secret from environment variables', async () => {
|
|
@@ -28,22 +28,22 @@ describe('LocalSecretService', () => {
|
|
|
28
28
|
|
|
29
29
|
test('should get JSON secret from local storage', async () => {
|
|
30
30
|
const service = new LocalSecretService()
|
|
31
|
-
await service.
|
|
32
|
-
const result = await service.
|
|
31
|
+
await service.setSecret('JSON_KEY', { data: 42 })
|
|
32
|
+
const result = await service.getSecret('JSON_KEY')
|
|
33
33
|
assert.deepStrictEqual(result, { data: 42 })
|
|
34
34
|
})
|
|
35
35
|
|
|
36
36
|
test('should get JSON secret from environment variables', async () => {
|
|
37
37
|
const vars = new LocalVariablesService({ CONFIG: '{"port":3000}' })
|
|
38
38
|
const service = new LocalSecretService(vars)
|
|
39
|
-
const result = await service.
|
|
39
|
+
const result = await service.getSecret('CONFIG')
|
|
40
40
|
assert.deepStrictEqual(result, { port: 3000 })
|
|
41
41
|
})
|
|
42
42
|
|
|
43
43
|
test('should throw when JSON secret not found', async () => {
|
|
44
44
|
const vars = new LocalVariablesService({})
|
|
45
45
|
const service = new LocalSecretService(vars)
|
|
46
|
-
await assert.rejects(() => service.
|
|
46
|
+
await assert.rejects(() => service.getSecret('MISSING'), {
|
|
47
47
|
message: 'Requested secret not found',
|
|
48
48
|
})
|
|
49
49
|
})
|
|
@@ -51,15 +51,15 @@ describe('LocalSecretService', () => {
|
|
|
51
51
|
test('should prefer local storage over env variables', async () => {
|
|
52
52
|
const vars = new LocalVariablesService({ KEY: 'env-value' })
|
|
53
53
|
const service = new LocalSecretService(vars)
|
|
54
|
-
await service.
|
|
54
|
+
await service.setSecret('KEY', 'local-value')
|
|
55
55
|
const result = await service.getSecret('KEY')
|
|
56
|
-
assert.strictEqual(result, '
|
|
56
|
+
assert.strictEqual(result, 'local-value')
|
|
57
57
|
})
|
|
58
58
|
|
|
59
59
|
test('should check hasSecret in local storage', async () => {
|
|
60
60
|
const vars = new LocalVariablesService({})
|
|
61
61
|
const service = new LocalSecretService(vars)
|
|
62
|
-
await service.
|
|
62
|
+
await service.setSecret('EXISTS', 'yes')
|
|
63
63
|
assert.strictEqual(await service.hasSecret('EXISTS'), true)
|
|
64
64
|
assert.strictEqual(await service.hasSecret('NOPE'), false)
|
|
65
65
|
})
|
|
@@ -72,7 +72,7 @@ describe('LocalSecretService', () => {
|
|
|
72
72
|
|
|
73
73
|
test('should delete secret from local storage', async () => {
|
|
74
74
|
const service = new LocalSecretService()
|
|
75
|
-
await service.
|
|
75
|
+
await service.setSecret('DEL_KEY', 'value')
|
|
76
76
|
assert.strictEqual(await service.hasSecret('DEL_KEY'), true)
|
|
77
77
|
await service.deleteSecret('DEL_KEY')
|
|
78
78
|
assert.strictEqual(await service.hasSecret('DEL_KEY'), false)
|
|
@@ -9,72 +9,38 @@ import type { VariablesService } from './variables-service.js'
|
|
|
9
9
|
export class LocalSecretService implements SecretService {
|
|
10
10
|
private localSecrets: Map<string, string> = new Map()
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
private parseSecret<T>(raw: string): T {
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(raw) as T
|
|
15
|
+
} catch {
|
|
16
|
+
return raw as unknown as T
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
constructor(
|
|
16
21
|
private variables: VariablesService = new LocalVariablesService()
|
|
17
22
|
) {}
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
* Retrieves a secret by key.
|
|
21
|
-
* Checks local storage first, then falls back to environment variables.
|
|
22
|
-
* @param key - The key of the secret to retrieve.
|
|
23
|
-
* @returns A promise that resolves to the secret value.
|
|
24
|
-
* @throws {Error} If the secret is not found.
|
|
25
|
-
*/
|
|
26
|
-
public async getSecretJSON<R>(key: string): Promise<R> {
|
|
27
|
-
// Check local storage first
|
|
28
|
-
const localValue = this.localSecrets.get(key)
|
|
29
|
-
if (localValue) {
|
|
30
|
-
return JSON.parse(localValue)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Fall back to environment variables
|
|
34
|
-
const value = await this.variables.get(key)
|
|
35
|
-
if (value) {
|
|
36
|
-
return JSON.parse(value)
|
|
37
|
-
}
|
|
38
|
-
throw new Error('Requested secret not found')
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Retrieves a secret by key.
|
|
43
|
-
* Checks local storage first, then falls back to environment variables.
|
|
44
|
-
* @param key - The key of the secret to retrieve.
|
|
45
|
-
* @returns A promise that resolves to the secret value.
|
|
46
|
-
* @throws {Error} If the secret is not found.
|
|
47
|
-
*/
|
|
48
|
-
public async getSecret(key: string): Promise<string> {
|
|
49
|
-
// Check local storage first
|
|
24
|
+
public async getSecret<T = string>(key: string): Promise<T> {
|
|
50
25
|
const localValue = this.localSecrets.get(key)
|
|
51
26
|
if (localValue) {
|
|
52
|
-
return localValue
|
|
27
|
+
return this.parseSecret<T>(localValue)
|
|
53
28
|
}
|
|
54
29
|
|
|
55
|
-
// Fall back to environment variables
|
|
56
30
|
const value = await this.variables.get(key)
|
|
57
31
|
if (value) {
|
|
58
|
-
return value
|
|
32
|
+
return this.parseSecret<T>(value)
|
|
59
33
|
}
|
|
60
34
|
throw new Error('Requested secret not found')
|
|
61
35
|
}
|
|
62
36
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
*/
|
|
69
|
-
public async setSecretJSON(key: string, value: unknown): Promise<void> {
|
|
70
|
-
this.localSecrets.set(key, JSON.stringify(value))
|
|
37
|
+
public async setSecret(key: string, value: unknown): Promise<void> {
|
|
38
|
+
this.localSecrets.set(
|
|
39
|
+
key,
|
|
40
|
+
typeof value === 'string' ? value : JSON.stringify(value)
|
|
41
|
+
)
|
|
71
42
|
}
|
|
72
43
|
|
|
73
|
-
/**
|
|
74
|
-
* Checks if a secret exists without throwing.
|
|
75
|
-
* @param key - The key of the secret to check.
|
|
76
|
-
* @returns A promise that resolves to true if the secret exists.
|
|
77
|
-
*/
|
|
78
44
|
public async hasSecret(key: string): Promise<boolean> {
|
|
79
45
|
if (this.localSecrets.has(key)) {
|
|
80
46
|
return true
|
|
@@ -83,11 +49,6 @@ export class LocalSecretService implements SecretService {
|
|
|
83
49
|
return value !== undefined && value !== null && value !== ''
|
|
84
50
|
}
|
|
85
51
|
|
|
86
|
-
/**
|
|
87
|
-
* Deletes a secret from local storage.
|
|
88
|
-
* @param key - The key of the secret to delete.
|
|
89
|
-
* @returns A promise that resolves when the secret is deleted.
|
|
90
|
-
*/
|
|
91
52
|
public async deleteSecret(key: string): Promise<void> {
|
|
92
53
|
this.localSecrets.delete(key)
|
|
93
54
|
}
|
|
@@ -39,19 +39,19 @@ describe('LocalVariablesService', () => {
|
|
|
39
39
|
|
|
40
40
|
test('should get JSON variable', () => {
|
|
41
41
|
const service = new LocalVariablesService({ DATA: '{"key":"val"}' })
|
|
42
|
-
const result = service.
|
|
42
|
+
const result = service.get('DATA')
|
|
43
43
|
assert.deepStrictEqual(result, { key: 'val' })
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
test('should return undefined for missing JSON variable', () => {
|
|
47
47
|
const service = new LocalVariablesService({})
|
|
48
|
-
assert.strictEqual(service.
|
|
48
|
+
assert.strictEqual(service.get('MISSING'), undefined)
|
|
49
49
|
})
|
|
50
50
|
|
|
51
51
|
test('should set JSON variable', () => {
|
|
52
52
|
const service = new LocalVariablesService({})
|
|
53
|
-
service.
|
|
54
|
-
assert.
|
|
53
|
+
service.set('DATA', { key: 'val' })
|
|
54
|
+
assert.deepStrictEqual(service.get('DATA'), { key: 'val' })
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
test('should handle has with undefined value', () => {
|
|
@@ -5,28 +5,23 @@ export class LocalVariablesService implements VariablesService {
|
|
|
5
5
|
private variables: Record<string, string | undefined> = process.env
|
|
6
6
|
) {}
|
|
7
7
|
|
|
8
|
-
public getAll():
|
|
9
|
-
| Promise<Record<string, string | undefined>>
|
|
10
|
-
| Record<string, string | undefined> {
|
|
8
|
+
public getAll(): Record<string, string | undefined> {
|
|
11
9
|
return this.variables || {}
|
|
12
10
|
}
|
|
13
11
|
|
|
14
|
-
public get(name: string):
|
|
15
|
-
|
|
12
|
+
public get<T = string>(name: string): T | undefined {
|
|
13
|
+
const raw = this.variables[name]
|
|
14
|
+
if (raw === undefined) return undefined
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(raw) as T
|
|
17
|
+
} catch {
|
|
18
|
+
return raw as unknown as T
|
|
19
|
+
}
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
public
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return JSON.parse(value)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
public set(name: string, value: string): void {
|
|
25
|
-
this.variables[name] = value
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
public setJSON(name: string, value: unknown): void {
|
|
29
|
-
this.variables[name] = JSON.stringify(value)
|
|
22
|
+
public set(name: string, value: unknown): void {
|
|
23
|
+
this.variables[name] =
|
|
24
|
+
typeof value === 'string' ? value : JSON.stringify(value)
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
public has(name: string): boolean {
|