@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.
Files changed (90) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/data-classification.d.ts +16 -0
  3. package/dist/data-classification.js +1 -0
  4. package/dist/function/function-runner.js +62 -15
  5. package/dist/function/functions.types.d.ts +8 -5
  6. package/dist/index.d.ts +5 -0
  7. package/dist/index.js +1 -0
  8. package/dist/middleware-runner.d.ts +2 -2
  9. package/dist/permissions.d.ts +1 -1
  10. package/dist/services/ai-agent-runner-service.d.ts +3 -0
  11. package/dist/services/audit-service.d.ts +50 -0
  12. package/dist/services/audit-service.js +106 -0
  13. package/dist/services/content-service.d.ts +1 -0
  14. package/dist/services/email-service.d.ts +36 -0
  15. package/dist/services/email-service.js +1 -0
  16. package/dist/services/gopass-secrets.d.ts +2 -35
  17. package/dist/services/gopass-secrets.js +10 -40
  18. package/dist/services/index.d.ts +5 -1
  19. package/dist/services/index.js +2 -0
  20. package/dist/services/local-content.js +1 -0
  21. package/dist/services/local-email-service.d.ts +4 -0
  22. package/dist/services/local-email-service.js +26 -0
  23. package/dist/services/local-secrets.d.ts +3 -36
  24. package/dist/services/local-secrets.js +12 -52
  25. package/dist/services/local-variables.d.ts +3 -5
  26. package/dist/services/local-variables.js +10 -10
  27. package/dist/services/meta-service.d.ts +36 -0
  28. package/dist/services/meta-service.js +59 -0
  29. package/dist/services/scoped-secret-service.d.ts +2 -3
  30. package/dist/services/scoped-secret-service.js +2 -6
  31. package/dist/services/secret-service.d.ts +5 -12
  32. package/dist/services/typed-secret-service.d.ts +3 -4
  33. package/dist/services/typed-secret-service.js +2 -5
  34. package/dist/services/typed-variables-service.d.ts +3 -6
  35. package/dist/services/typed-variables-service.js +0 -6
  36. package/dist/services/variables-service.d.ts +2 -4
  37. package/dist/testing/service-tests.js +13 -13
  38. package/dist/types/core.types.d.ts +15 -1
  39. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +2 -0
  40. package/dist/wirings/ai-agent/ai-agent-prepare.js +12 -3
  41. package/dist/wirings/ai-agent/ai-agent-runner.js +1 -1
  42. package/dist/wirings/ai-agent/ai-agent-stream.js +1 -1
  43. package/dist/wirings/ai-agent/ai-agent-utils.d.ts +1 -0
  44. package/dist/wirings/ai-agent/ai-agent-utils.js +1 -0
  45. package/dist/wirings/ai-agent/ai-agent.types.d.ts +4 -0
  46. package/dist/wirings/oauth2/oauth2-client.js +3 -3
  47. package/dist/wirings/rpc/rpc-runner.js +9 -2
  48. package/package.json +1 -1
  49. package/src/data-classification.ts +15 -0
  50. package/src/function/function-runner.test.ts +255 -0
  51. package/src/function/function-runner.ts +66 -20
  52. package/src/function/functions.types.ts +26 -11
  53. package/src/index.ts +44 -0
  54. package/src/middleware-runner.ts +10 -10
  55. package/src/permissions.ts +4 -4
  56. package/src/services/ai-agent-runner-service.ts +3 -0
  57. package/src/services/audit-service.test.ts +44 -0
  58. package/src/services/audit-service.ts +198 -0
  59. package/src/services/content-service.ts +1 -0
  60. package/src/services/email-service.ts +46 -0
  61. package/src/services/gopass-secrets.ts +10 -42
  62. package/src/services/index.ts +33 -0
  63. package/src/services/local-content.ts +1 -0
  64. package/src/services/local-email-service.test.ts +108 -0
  65. package/src/services/local-email-service.ts +30 -0
  66. package/src/services/local-secrets.test.ts +10 -10
  67. package/src/services/local-secrets.ts +16 -55
  68. package/src/services/local-variables.test.ts +4 -4
  69. package/src/services/local-variables.ts +12 -17
  70. package/src/services/meta-service.ts +115 -0
  71. package/src/services/scoped-secret-service.test.ts +10 -11
  72. package/src/services/scoped-secret-service.ts +4 -9
  73. package/src/services/secret-service.ts +5 -12
  74. package/src/services/typed-secret-service.test.ts +16 -17
  75. package/src/services/typed-secret-service.ts +5 -9
  76. package/src/services/typed-variables-service.test.ts +5 -5
  77. package/src/services/typed-variables-service.ts +5 -17
  78. package/src/services/variables-service.ts +2 -4
  79. package/src/testing/service-tests.ts +13 -13
  80. package/src/types/core.types.ts +20 -2
  81. package/src/wirings/ai-agent/ai-agent-prepare.ts +17 -3
  82. package/src/wirings/ai-agent/ai-agent-runner.test.ts +105 -0
  83. package/src/wirings/ai-agent/ai-agent-runner.ts +1 -1
  84. package/src/wirings/ai-agent/ai-agent-stream.ts +1 -1
  85. package/src/wirings/ai-agent/ai-agent-utils.ts +1 -0
  86. package/src/wirings/ai-agent/ai-agent.types.ts +4 -0
  87. package/src/wirings/oauth2/oauth2-client.test.ts +2 -3
  88. package/src/wirings/oauth2/oauth2-client.ts +3 -3
  89. package/src/wirings/rpc/rpc-runner.ts +9 -2
  90. 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
- return execFileSync('gopass', ['show', '-o', fullKey], {
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 jsonValue = JSON.stringify(value)
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: jsonValue,
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 {
@@ -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.setSecretJSON('MY_KEY', { token: 'abc' })
9
+ await service.setSecret('MY_KEY', { token: 'abc' })
10
10
  const result = await service.getSecret('MY_KEY')
11
- assert.strictEqual(result, '{"token":"abc"}')
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.setSecretJSON('JSON_KEY', { data: 42 })
32
- const result = await service.getSecretJSON('JSON_KEY')
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.getSecretJSON('CONFIG')
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.getSecretJSON('MISSING'), {
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.setSecretJSON('KEY', 'local-value')
54
+ await service.setSecret('KEY', 'local-value')
55
55
  const result = await service.getSecret('KEY')
56
- assert.strictEqual(result, '"local-value"')
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.setSecretJSON('EXISTS', 'yes')
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.setSecretJSON('DEL_KEY', 'value')
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
- * Creates an instance of LocalSecretService.
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
- * Stores a JSON value as a secret in local storage.
65
- * @param key - The key to store the secret under.
66
- * @param value - The JSON value to store.
67
- * @returns A promise that resolves when the secret is stored.
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.getJSON('DATA')
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.getJSON('MISSING'), undefined)
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.setJSON('DATA', { key: 'val' })
54
- assert.strictEqual(service.get('DATA'), '{"key":"val"}')
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): Promise<string | undefined> | string | undefined {
15
- return this.variables[name]
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 getJSON<T = unknown>(name: string): T | undefined {
19
- const value = this.variables[name]
20
- if (value === undefined) return undefined
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 {