@harborclient/sdk 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.
Files changed (87) hide show
  1. package/dist/build/index.d.ts.map +1 -1
  2. package/dist/build/index.js +10 -1
  3. package/dist/client.d.ts +2 -2
  4. package/dist/components/Autocomplete/AutocompleteInput.d.ts +23 -0
  5. package/dist/components/Autocomplete/AutocompleteInput.d.ts.map +1 -0
  6. package/dist/components/Autocomplete/AutocompleteInput.js +26 -0
  7. package/dist/components/Autocomplete/SuggestionList.d.ts +45 -0
  8. package/dist/components/Autocomplete/SuggestionList.d.ts.map +1 -0
  9. package/dist/components/Autocomplete/SuggestionList.js +72 -0
  10. package/dist/components/Autocomplete/index.d.ts +6 -0
  11. package/dist/components/Autocomplete/index.d.ts.map +1 -0
  12. package/dist/components/Autocomplete/index.js +3 -0
  13. package/dist/components/Autocomplete/types.d.ts +19 -0
  14. package/dist/components/Autocomplete/types.d.ts.map +1 -0
  15. package/dist/components/Autocomplete/types.js +1 -0
  16. package/dist/components/Autocomplete/useAutocomplete.d.ts +84 -0
  17. package/dist/components/Autocomplete/useAutocomplete.d.ts.map +1 -0
  18. package/dist/components/Autocomplete/useAutocomplete.js +191 -0
  19. package/dist/components/Button/index.d.ts.map +1 -1
  20. package/dist/components/Button/index.js +8 -7
  21. package/dist/components/KeyValueEditor/index.d.ts +10 -1
  22. package/dist/components/KeyValueEditor/index.d.ts.map +1 -1
  23. package/dist/components/KeyValueEditor/index.js +4 -3
  24. package/dist/components/VariableInput/index.d.ts +6 -1
  25. package/dist/components/VariableInput/index.d.ts.map +1 -1
  26. package/dist/components/VariableInput/index.js +19 -3
  27. package/dist/components/index.d.ts +1 -0
  28. package/dist/components/index.d.ts.map +1 -1
  29. package/dist/components/index.js +1 -0
  30. package/dist/components/portalToBody.d.ts +2 -1
  31. package/dist/components/portalToBody.d.ts.map +1 -1
  32. package/dist/components/portalToBody.js +5 -4
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/main.d.ts +1 -1
  36. package/dist/main.d.ts.map +1 -1
  37. package/dist/runtime/hcBridge.js +0 -3
  38. package/dist/runtime/index.d.ts +1 -1
  39. package/dist/runtime/jsx-dev-runtime.d.ts +1 -1
  40. package/dist/runtime/store.d.ts +6 -1
  41. package/dist/runtime/store.d.ts.map +1 -1
  42. package/dist/runtime/store.js +8 -2
  43. package/dist/runtime/store.ts +11 -3
  44. package/package.json +3 -2
  45. package/dist/components/enhanceControl.test.d.ts +0 -2
  46. package/dist/components/enhanceControl.test.d.ts.map +0 -1
  47. package/dist/components/enhanceControl.test.js +0 -83
  48. package/dist/components/utils.test.d.ts +0 -2
  49. package/dist/components/utils.test.d.ts.map +0 -1
  50. package/dist/components/utils.test.js +0 -44
  51. package/dist/http/resolveRequest.test.d.ts +0 -2
  52. package/dist/http/resolveRequest.test.d.ts.map +0 -1
  53. package/dist/http/resolveRequest.test.js +0 -41
  54. package/dist/http/substitute.test.d.ts +0 -2
  55. package/dist/http/substitute.test.d.ts.map +0 -1
  56. package/dist/http/substitute.test.js +0 -93
  57. package/dist/pluginDatabaseApi.test.d.ts +0 -2
  58. package/dist/pluginDatabaseApi.test.d.ts.map +0 -1
  59. package/dist/pluginDatabaseApi.test.js +0 -65
  60. package/dist/runtime/index.test.d.ts +0 -2
  61. package/dist/runtime/index.test.d.ts.map +0 -1
  62. package/dist/runtime/index.test.js +0 -41
  63. package/dist/runtime/index.test.ts +0 -53
  64. package/dist/runtime-utils.test.d.ts +0 -2
  65. package/dist/runtime-utils.test.d.ts.map +0 -1
  66. package/dist/runtime-utils.test.js +0 -177
  67. package/dist/signing/signing.test.d.ts +0 -2
  68. package/dist/signing/signing.test.d.ts.map +0 -1
  69. package/dist/signing/signing.test.js +0 -100
  70. package/dist/storage/cappedList.test.d.ts +0 -2
  71. package/dist/storage/cappedList.test.d.ts.map +0 -1
  72. package/dist/storage/cappedList.test.js +0 -11
  73. package/dist/storage/validate.test.d.ts +0 -2
  74. package/dist/storage/validate.test.d.ts.map +0 -1
  75. package/dist/storage/validate.test.js +0 -78
  76. package/dist/store.test.d.ts +0 -2
  77. package/dist/store.test.d.ts.map +0 -1
  78. package/dist/store.test.js +0 -248
  79. package/dist/utilities.test.d.ts +0 -2
  80. package/dist/utilities.test.d.ts.map +0 -1
  81. package/dist/utilities.test.js +0 -16
  82. package/dist/variables/dynamic.test.d.ts +0 -2
  83. package/dist/variables/dynamic.test.d.ts.map +0 -1
  84. package/dist/variables/dynamic.test.js +0 -60
  85. package/dist/variables/tokens.test.d.ts +0 -2
  86. package/dist/variables/tokens.test.d.ts.map +0 -1
  87. package/dist/variables/tokens.test.js +0 -160
@@ -1,93 +0,0 @@
1
- import { describe, expect, it } from '@jest/globals';
2
- import { resolveAuthVariables, substituteKeyValueRows, substituteVariables } from './substitute.js';
3
- describe('substituteVariables', () => {
4
- it('replaces known placeholders', () => {
5
- expect(substituteVariables('{{host}}/api', { host: 'https://example.com' })).toBe('https://example.com/api');
6
- });
7
- it('allows whitespace inside braces', () => {
8
- expect(substituteVariables('{{ host }}/api', { host: 'https://example.com' })).toBe('https://example.com/api');
9
- });
10
- it('supports dotted and hyphenated keys', () => {
11
- expect(substituteVariables('{{api.base}}-{{api-key}}', {
12
- 'api.base': 'https://example.com',
13
- 'api-key': 'secret'
14
- })).toBe('https://example.com-secret');
15
- });
16
- it('replaces multiple placeholders in one string', () => {
17
- expect(substituteVariables('{{scheme}}://{{host}}/{{path}}', {
18
- scheme: 'https',
19
- host: 'example.com',
20
- path: 'users'
21
- })).toBe('https://example.com/users');
22
- });
23
- it('leaves unknown placeholders unchanged', () => {
24
- expect(substituteVariables('{{missing}}/api', { host: 'https://example.com' })).toBe('{{missing}}/api');
25
- });
26
- it('replaces with empty string when variable is defined as empty', () => {
27
- expect(substituteVariables('prefix{{token}}suffix', { token: '' })).toBe('prefixsuffix');
28
- });
29
- it('returns text unchanged when there are no placeholders', () => {
30
- expect(substituteVariables('plain text', { host: 'https://example.com' })).toBe('plain text');
31
- });
32
- it('resolves dynamic variables when not in the runtime map', () => {
33
- const result = substituteVariables('prefix{{$timestamp}}suffix', {});
34
- expect(result).not.toContain('{{');
35
- expect(result).toMatch(/^prefix\d+suffix$/);
36
- });
37
- it('prefers runtime variables over dynamic variables', () => {
38
- expect(substituteVariables('{{$timestamp}}', { $timestamp: 'fixed' })).toBe('fixed');
39
- });
40
- });
41
- describe('resolveAuthVariables', () => {
42
- const auth = {
43
- type: 'basic',
44
- basic: {
45
- username: '{{user}}',
46
- password: '{{pass}}'
47
- },
48
- bearer: {
49
- token: '{{token}}'
50
- }
51
- };
52
- it('substitutes basic and bearer credential fields', () => {
53
- const substitute = (text) => text.replace('{{user}}', 'alice').replace('{{pass}}', 'secret').replace('{{token}}', 'tok');
54
- expect(resolveAuthVariables(auth, substitute)).toEqual({
55
- type: 'basic',
56
- basic: {
57
- username: 'alice',
58
- password: 'secret'
59
- },
60
- bearer: {
61
- token: 'tok'
62
- }
63
- });
64
- });
65
- it('preserves auth type and structure', () => {
66
- const substitute = (text) => text;
67
- const resolved = resolveAuthVariables(auth, substitute);
68
- expect(resolved.type).toBe('basic');
69
- expect(resolved.basic).toEqual(auth.basic);
70
- expect(resolved.bearer).toEqual(auth.bearer);
71
- });
72
- });
73
- describe('substituteKeyValueRows', () => {
74
- it('substitutes values while preserving row metadata', () => {
75
- const rows = [
76
- { key: 'Accept', value: '{{accept}}', enabled: true },
77
- { key: 'X-Api-Key', value: 'static', enabled: false }
78
- ];
79
- expect(substituteKeyValueRows(rows, { accept: 'application/json' })).toEqual([
80
- { key: 'Accept', value: 'application/json', enabled: true },
81
- { key: 'X-Api-Key', value: 'static', enabled: false }
82
- ]);
83
- });
84
- it('leaves unknown placeholders in row values', () => {
85
- const rows = [{ key: 'Host', value: '{{host}}', enabled: true }];
86
- expect(substituteKeyValueRows(rows, {})).toEqual([
87
- { key: 'Host', value: '{{host}}', enabled: true }
88
- ]);
89
- });
90
- it('returns an empty array for no rows', () => {
91
- expect(substituteKeyValueRows([], { host: 'example.com' })).toEqual([]);
92
- });
93
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=pluginDatabaseApi.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pluginDatabaseApi.test.d.ts","sourceRoot":"","sources":["../src/pluginDatabaseApi.test.ts"],"names":[],"mappings":""}
@@ -1,65 +0,0 @@
1
- import { describe, expect, it, jest } from '@jest/globals';
2
- import { createPluginDatabaseApi } from './runtime/pluginDatabaseApi.js';
3
- /**
4
- * Builds a mock database backend for unit tests.
5
- */
6
- function createMockBackend() {
7
- return {
8
- query: jest.fn(async () => undefined),
9
- exec: jest.fn(async () => undefined),
10
- beginTransaction: jest.fn(async () => 'txn-1'),
11
- endTransaction: jest.fn(async () => undefined)
12
- };
13
- }
14
- describe('createPluginDatabaseApi', () => {
15
- it('forwards get, all, and run with the correct query modes', async () => {
16
- const backend = createMockBackend();
17
- backend.query
18
- .mockResolvedValueOnce({ id: 1 })
19
- .mockResolvedValueOnce([{ id: 1 }, { id: 2 }])
20
- .mockResolvedValueOnce({ changes: 1, lastInsertRowid: 42 });
21
- const db = createPluginDatabaseApi(backend);
22
- await expect(db.get('SELECT 1', [1])).resolves.toEqual({ id: 1 });
23
- await expect(db.all('SELECT *', [2])).resolves.toEqual([{ id: 1 }, { id: 2 }]);
24
- await expect(db.run('INSERT INTO t VALUES (?)', [3])).resolves.toEqual({
25
- changes: 1,
26
- lastInsertRowid: 42
27
- });
28
- expect(backend.query).toHaveBeenNthCalledWith(1, 'get', 'SELECT 1', [1], undefined);
29
- expect(backend.query).toHaveBeenNthCalledWith(2, 'all', 'SELECT *', [2], undefined);
30
- expect(backend.query).toHaveBeenNthCalledWith(3, 'run', 'INSERT INTO t VALUES (?)', [3], undefined);
31
- });
32
- it('forwards exec unchanged', async () => {
33
- const backend = createMockBackend();
34
- const db = createPluginDatabaseApi(backend);
35
- await db.exec('CREATE TABLE t (id INTEGER)');
36
- expect(backend.exec).toHaveBeenCalledWith('CREATE TABLE t (id INTEGER)');
37
- });
38
- it('commits successful transactions and passes txnId to tx helpers', async () => {
39
- const backend = createMockBackend();
40
- backend.query.mockResolvedValue({ changes: 1, lastInsertRowid: 1 });
41
- const db = createPluginDatabaseApi(backend);
42
- const result = await db.transaction(async (tx) => {
43
- await tx.run('INSERT INTO t VALUES (?)', [1]);
44
- await tx.all('SELECT * FROM t');
45
- return 'done';
46
- });
47
- expect(result).toBe('done');
48
- expect(backend.beginTransaction).toHaveBeenCalledTimes(1);
49
- expect(backend.endTransaction).toHaveBeenCalledWith('txn-1', 'commit');
50
- expect(backend.query).toHaveBeenCalledWith('run', 'INSERT INTO t VALUES (?)', [1], 'txn-1');
51
- expect(backend.query).toHaveBeenCalledWith('all', 'SELECT * FROM t', undefined, 'txn-1');
52
- });
53
- it('rolls back failed transactions', async () => {
54
- const backend = createMockBackend();
55
- const db = createPluginDatabaseApi(backend);
56
- const boom = new Error('boom');
57
- await expect(db.transaction(async (tx) => {
58
- await tx.run('INSERT INTO t VALUES (?)', [1]);
59
- throw boom;
60
- })).rejects.toThrow(boom);
61
- expect(backend.beginTransaction).toHaveBeenCalledTimes(1);
62
- expect(backend.endTransaction).toHaveBeenCalledWith('txn-1', 'rollback');
63
- expect(backend.endTransaction).not.toHaveBeenCalledWith('txn-1', 'commit');
64
- });
65
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=index.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/runtime/index.test.ts"],"names":[],"mappings":""}
@@ -1,41 +0,0 @@
1
- import { describe, expect, it, jest } from '@jest/globals';
2
- import { defineTheme, registerTheme } from './index.js';
3
- /**
4
- * Builds a minimal plugin context mock for registerTheme tests.
5
- */
6
- function createMockPluginContext() {
7
- const disposable = { dispose: jest.fn() };
8
- const registerMock = jest.fn(() => disposable);
9
- const subscriptions = [];
10
- return {
11
- subscriptions,
12
- themes: { register: registerMock },
13
- registerMock
14
- };
15
- }
16
- describe('registerTheme', () => {
17
- it('registers the theme, pushes the disposable onto subscriptions, and returns it', () => {
18
- const hc = createMockPluginContext();
19
- const theme = {
20
- id: 'solarized',
21
- title: 'Solarized Dark',
22
- type: 'dark',
23
- colors: { surface: '#002b36' }
24
- };
25
- const disposable = registerTheme(hc, theme);
26
- expect(hc.registerMock).toHaveBeenCalledWith(theme);
27
- expect(hc.subscriptions).toHaveLength(1);
28
- expect(hc.subscriptions[0]).toBe(disposable);
29
- });
30
- });
31
- describe('defineTheme', () => {
32
- it('returns the theme argument unchanged', () => {
33
- const theme = {
34
- id: 'nord',
35
- title: 'Nord',
36
- type: 'dark',
37
- stylesheet: 'dist/theme.css'
38
- };
39
- expect(defineTheme(theme)).toBe(theme);
40
- });
41
- });
@@ -1,53 +0,0 @@
1
- import { describe, expect, it, jest } from '@jest/globals';
2
- import type { PluginContext, ThemeContribution } from '../types';
3
- import { defineTheme, registerTheme } from './index.js';
4
-
5
- /**
6
- * Builds a minimal plugin context mock for registerTheme tests.
7
- */
8
- function createMockPluginContext(): PluginContext & {
9
- registerMock: jest.MockedFunction<PluginContext['themes']['register']>;
10
- } {
11
- const disposable = { dispose: jest.fn() };
12
- const registerMock = jest.fn(() => disposable);
13
- const subscriptions: PluginContext['subscriptions'] = [];
14
-
15
- return {
16
- subscriptions,
17
- themes: { register: registerMock },
18
- registerMock
19
- } as unknown as PluginContext & {
20
- registerMock: jest.MockedFunction<PluginContext['themes']['register']>;
21
- };
22
- }
23
-
24
- describe('registerTheme', () => {
25
- it('registers the theme, pushes the disposable onto subscriptions, and returns it', () => {
26
- const hc = createMockPluginContext();
27
- const theme: ThemeContribution = {
28
- id: 'solarized',
29
- title: 'Solarized Dark',
30
- type: 'dark',
31
- colors: { surface: '#002b36' }
32
- };
33
-
34
- const disposable = registerTheme(hc, theme);
35
-
36
- expect(hc.registerMock).toHaveBeenCalledWith(theme);
37
- expect(hc.subscriptions).toHaveLength(1);
38
- expect(hc.subscriptions[0]).toBe(disposable);
39
- });
40
- });
41
-
42
- describe('defineTheme', () => {
43
- it('returns the theme argument unchanged', () => {
44
- const theme: ThemeContribution = {
45
- id: 'nord',
46
- title: 'Nord',
47
- type: 'dark',
48
- stylesheet: 'dist/theme.css'
49
- };
50
-
51
- expect(defineTheme(theme)).toBe(theme);
52
- });
53
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=runtime-utils.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runtime-utils.test.d.ts","sourceRoot":"","sources":["../src/runtime-utils.test.ts"],"names":[],"mappings":""}
@@ -1,177 +0,0 @@
1
- import { afterEach, describe, expect, it, jest } from '@jest/globals';
2
- import { byteLength, createLogger, randomId, truncateBody, truncateToBytes } from './runtime-utils.js';
3
- const originalTextEncoder = globalThis.TextEncoder;
4
- const originalCrypto = globalThis.crypto;
5
- afterEach(() => {
6
- globalThis.TextEncoder = originalTextEncoder;
7
- globalThis.crypto = originalCrypto;
8
- jest.restoreAllMocks();
9
- });
10
- function withoutTextEncoder(run) {
11
- // @ts-expect-error — exercise manual UTF-8 fallback used in SES main runtime
12
- globalThis.TextEncoder = undefined;
13
- return run();
14
- }
15
- describe('randomId', () => {
16
- it('returns a UUID when crypto.randomUUID is available', () => {
17
- const spy = jest
18
- .spyOn(globalThis.crypto, 'randomUUID')
19
- .mockReturnValue('00000000-0000-4000-8000-000000000000');
20
- expect(randomId()).toBe('00000000-0000-4000-8000-000000000000');
21
- spy.mockRestore();
22
- });
23
- it('falls back to a prefixed id when randomUUID is unavailable', () => {
24
- // @ts-expect-error — exercise SES main-runtime fallback
25
- globalThis.crypto = undefined;
26
- expect(randomId('req')).toMatch(/^req-\d+-[a-z0-9]+$/);
27
- });
28
- it('uses the default prefix in the fallback path', () => {
29
- // @ts-expect-error — exercise SES main-runtime fallback
30
- globalThis.crypto = undefined;
31
- expect(randomId()).toMatch(/^id-\d+-[a-z0-9]+$/);
32
- });
33
- });
34
- describe('byteLength', () => {
35
- it('counts ASCII as one byte per character', () => {
36
- expect(byteLength('abc')).toBe(3);
37
- expect(byteLength('')).toBe(0);
38
- });
39
- it('counts two-byte UTF-8 characters', () => {
40
- expect(byteLength('é')).toBe(2);
41
- });
42
- it('counts three-byte UTF-8 characters', () => {
43
- expect(byteLength('中')).toBe(3);
44
- });
45
- it('counts surrogate pairs as four bytes', () => {
46
- expect(byteLength('😀')).toBe(4);
47
- });
48
- it('matches TextEncoder when the manual fallback is used', () => {
49
- const samples = ['plain', 'é', '中', '😀', 'mix é 中 😀'];
50
- for (const sample of samples) {
51
- const encodedLength = new originalTextEncoder().encode(sample).length;
52
- withoutTextEncoder(() => {
53
- expect(byteLength(sample)).toBe(encodedLength);
54
- });
55
- }
56
- });
57
- });
58
- describe('truncateToBytes', () => {
59
- it('returns the original string when it fits', () => {
60
- expect(truncateToBytes('hello', 10)).toBe('hello');
61
- expect(truncateToBytes('é', 2)).toBe('é');
62
- });
63
- it('truncates multibyte text without splitting code units', () => {
64
- expect(truncateToBytes('éé', 2)).toBe('é');
65
- expect(truncateToBytes('abc', 2)).toBe('ab');
66
- });
67
- it('returns an empty string when maxBytes is zero', () => {
68
- expect(truncateToBytes('hello', 0)).toBe('');
69
- });
70
- it('excludes an incomplete surrogate pair in the manual fallback', () => {
71
- withoutTextEncoder(() => {
72
- expect(truncateToBytes('a😀b', 2)).toBe('a');
73
- });
74
- });
75
- it('does not split multibyte characters in the manual fallback', () => {
76
- withoutTextEncoder(() => {
77
- expect(truncateToBytes('mix é end', 5)).toBe('mix ');
78
- expect(truncateToBytes('中文字', 3)).toBe('中');
79
- });
80
- });
81
- });
82
- describe('truncateBody', () => {
83
- it('returns the body unchanged when within the byte limit', () => {
84
- expect(truncateBody('{"ok":true}', 100)).toEqual({
85
- body: '{"ok":true}',
86
- truncated: false
87
- });
88
- });
89
- it('truncates and appends the default suffix when over the limit', () => {
90
- const body = 'x'.repeat(20);
91
- const maxBytes = 10;
92
- const result = truncateBody(body, maxBytes);
93
- expect(result.truncated).toBe(true);
94
- expect(result.body.endsWith(`[truncated — body exceeded ${maxBytes} bytes]`)).toBe(true);
95
- expect(byteLength(result.body.replace(/\n\n\[truncated — body exceeded \d+ bytes\]$/, ''))).toBeLessThanOrEqual(maxBytes);
96
- });
97
- it('uses a custom suffix when provided', () => {
98
- const result = truncateBody('0123456789', 5, '…');
99
- expect(result).toEqual({
100
- body: '01234…',
101
- truncated: true
102
- });
103
- });
104
- });
105
- describe('createLogger', () => {
106
- it('prefixes messages with [pluginId]', () => {
107
- const logSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
108
- const logger = createLogger('my-plugin');
109
- logger.info('hello');
110
- expect(logSpy).toHaveBeenCalledWith('[my-plugin]', 'hello');
111
- logSpy.mockRestore();
112
- });
113
- it('filters messages below the active level', () => {
114
- const logSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
115
- const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
116
- const logger = createLogger('my-plugin', { level: 'warn' });
117
- logger.debug('hidden');
118
- logger.info('hidden');
119
- logger.warn('visible');
120
- expect(logSpy).not.toHaveBeenCalled();
121
- expect(warnSpy).toHaveBeenCalledTimes(1);
122
- expect(warnSpy).toHaveBeenCalledWith('[my-plugin]', 'visible');
123
- logSpy.mockRestore();
124
- warnSpy.mockRestore();
125
- });
126
- it('updates the active level via setLevel', () => {
127
- const logSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
128
- const logger = createLogger('my-plugin');
129
- logger.info('before');
130
- logger.setLevel('silent');
131
- logger.info('after');
132
- expect(logSpy).toHaveBeenCalledTimes(1);
133
- expect(logSpy).toHaveBeenCalledWith('[my-plugin]', 'before');
134
- logSpy.mockRestore();
135
- });
136
- it('suppresses all output at silent level', () => {
137
- const logSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
138
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
139
- const logger = createLogger('my-plugin', { level: 'silent' });
140
- logger.debug('a');
141
- logger.info('b');
142
- logger.warn('c');
143
- logger.error('d');
144
- expect(logSpy).not.toHaveBeenCalled();
145
- expect(errorSpy).not.toHaveBeenCalled();
146
- logSpy.mockRestore();
147
- errorSpy.mockRestore();
148
- });
149
- it('routes warn and error through console.warn and console.error when available', () => {
150
- const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
151
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
152
- const logger = createLogger('my-plugin', { level: 'debug' });
153
- logger.warn('warned');
154
- logger.error('failed');
155
- expect(warnSpy).toHaveBeenCalledWith('[my-plugin]', 'warned');
156
- expect(errorSpy).toHaveBeenCalledWith('[my-plugin]', 'failed');
157
- warnSpy.mockRestore();
158
- errorSpy.mockRestore();
159
- });
160
- it('falls back to console.log when console.warn and console.error are unavailable', () => {
161
- const logSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
162
- const originalWarn = console.warn;
163
- const originalError = console.error;
164
- // @ts-expect-error — exercise SES main-runtime fallback
165
- console.warn = undefined;
166
- // @ts-expect-error — exercise SES main-runtime fallback
167
- console.error = undefined;
168
- const logger = createLogger('my-plugin', { level: 'debug' });
169
- logger.warn('warned');
170
- logger.error('failed');
171
- expect(logSpy).toHaveBeenCalledWith('[my-plugin]', 'warned');
172
- expect(logSpy).toHaveBeenCalledWith('[my-plugin]', 'failed');
173
- console.warn = originalWarn;
174
- console.error = originalError;
175
- logSpy.mockRestore();
176
- });
177
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=signing.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"signing.test.d.ts","sourceRoot":"","sources":["../../src/signing/signing.test.ts"],"names":[],"mappings":""}
@@ -1,100 +0,0 @@
1
- import { describe, expect, it } from '@jest/globals';
2
- import { writeFileSync } from 'node:fs';
3
- import { join } from 'node:path';
4
- import { signPlugin } from './sign.js';
5
- import { createTestPluginDir, createTestSigningKeys } from './testFixtures.js';
6
- import { verifyPlugin } from './verify.js';
7
- describe('signPlugin', () => {
8
- it('writes signature.json for a valid plugin directory', async () => {
9
- const keys = createTestSigningKeys();
10
- const fixture = createTestPluginDir();
11
- try {
12
- const result = await signPlugin({
13
- pluginDir: fixture.pluginDir,
14
- privateKeyPem: keys.privateKeyPem,
15
- keyId: 'test-key'
16
- });
17
- expect(result.signature.pluginId).toBe('com.example.test-plugin');
18
- expect(result.signature.pluginVersion).toBe('1.0.0');
19
- expect(result.signature.keyId).toBe('test-key');
20
- expect(result.signature.files.map((file) => file.path)).toEqual([
21
- 'dist/renderer.js',
22
- 'manifest.json'
23
- ]);
24
- const verification = await verifyPlugin({
25
- pluginDir: fixture.pluginDir,
26
- trustedPublicKeysPem: [keys.publicKeyPem]
27
- });
28
- expect(verification.status).toBe('valid');
29
- }
30
- finally {
31
- fixture.cleanup();
32
- }
33
- });
34
- it('rejects invalid private keys', async () => {
35
- const fixture = createTestPluginDir();
36
- try {
37
- await expect(signPlugin({
38
- pluginDir: fixture.pluginDir,
39
- privateKeyPem: 'not-a-key'
40
- })).rejects.toThrow(/invalid ed25519 private key/i);
41
- }
42
- finally {
43
- fixture.cleanup();
44
- }
45
- });
46
- });
47
- describe('verifyPlugin', () => {
48
- it('returns unsigned when signature.json is absent', async () => {
49
- const fixture = createTestPluginDir();
50
- try {
51
- const result = await verifyPlugin({
52
- pluginDir: fixture.pluginDir,
53
- trustedPublicKeysPem: [createTestSigningKeys().publicKeyPem]
54
- });
55
- expect(result.status).toBe('unsigned');
56
- }
57
- finally {
58
- fixture.cleanup();
59
- }
60
- });
61
- it('returns invalid when a signed file is tampered with', async () => {
62
- const keys = createTestSigningKeys();
63
- const fixture = createTestPluginDir();
64
- try {
65
- await signPlugin({
66
- pluginDir: fixture.pluginDir,
67
- privateKeyPem: keys.privateKeyPem
68
- });
69
- writeFileSync(join(fixture.pluginDir, 'dist', 'renderer.js'), 'export function activate() { return 1; }');
70
- const result = await verifyPlugin({
71
- pluginDir: fixture.pluginDir,
72
- trustedPublicKeysPem: [keys.publicKeyPem]
73
- });
74
- expect(result.status).toBe('invalid');
75
- expect(result.error).toMatch(/signed inventory/i);
76
- }
77
- finally {
78
- fixture.cleanup();
79
- }
80
- });
81
- it('returns invalid when verified with the wrong public key', async () => {
82
- const keys = createTestSigningKeys();
83
- const otherKeys = createTestSigningKeys();
84
- const fixture = createTestPluginDir();
85
- try {
86
- await signPlugin({
87
- pluginDir: fixture.pluginDir,
88
- privateKeyPem: keys.privateKeyPem
89
- });
90
- const result = await verifyPlugin({
91
- pluginDir: fixture.pluginDir,
92
- trustedPublicKeysPem: [otherKeys.publicKeyPem]
93
- });
94
- expect(result.status).toBe('invalid');
95
- }
96
- finally {
97
- fixture.cleanup();
98
- }
99
- });
100
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=cappedList.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cappedList.test.d.ts","sourceRoot":"","sources":["../../src/storage/cappedList.test.ts"],"names":[],"mappings":""}
@@ -1,11 +0,0 @@
1
- import { describe, expect, it } from '@jest/globals';
2
- import { mergeById } from './cappedList.js';
3
- describe('mergeById', () => {
4
- it('dedupes and caps newest-first', () => {
5
- const merged = mergeById([{ id: 'b' }, { id: 'a' }], [{ id: 'a' }, { id: 'c' }], {
6
- cap: 3,
7
- idOf: (entry) => entry.id
8
- });
9
- expect(merged.map((entry) => entry.id)).toEqual(['b', 'a', 'c']);
10
- });
11
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=validate.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validate.test.d.ts","sourceRoot":"","sources":["../../src/storage/validate.test.ts"],"names":[],"mappings":""}
@@ -1,78 +0,0 @@
1
- import { describe, expect, it } from '@jest/globals';
2
- import { arrayOf, asRecord, bool, isRecord, num, numArray, oneOf, recordOf, str, strArray } from './validate.js';
3
- describe('isRecord', () => {
4
- it('accepts plain objects and rejects arrays and primitives', () => {
5
- expect(isRecord({ a: 1 })).toBe(true);
6
- expect(isRecord([])).toBe(false);
7
- expect(isRecord(null)).toBe(false);
8
- expect(isRecord('x')).toBe(false);
9
- });
10
- });
11
- describe('asRecord', () => {
12
- it('returns the record or null', () => {
13
- const value = { key: 'v' };
14
- expect(asRecord(value)).toBe(value);
15
- expect(asRecord(null)).toBeNull();
16
- expect(asRecord([1])).toBeNull();
17
- });
18
- });
19
- describe('str', () => {
20
- it('returns strings and falls back otherwise', () => {
21
- expect(str('hello', '')).toBe('hello');
22
- expect(str(42, 'fallback')).toBe('fallback');
23
- expect(str(undefined, null)).toBeNull();
24
- });
25
- });
26
- describe('num', () => {
27
- it('returns finite numbers and falls back otherwise', () => {
28
- expect(num(3, 0)).toBe(3);
29
- expect(num(NaN, 0)).toBe(0);
30
- expect(num(Infinity, 0)).toBe(0);
31
- expect(num('3', null)).toBeNull();
32
- });
33
- });
34
- describe('bool', () => {
35
- it('returns booleans and falls back otherwise', () => {
36
- expect(bool(true, false)).toBe(true);
37
- expect(bool(false, true)).toBe(false);
38
- expect(bool('true', true)).toBe(true);
39
- });
40
- });
41
- describe('oneOf', () => {
42
- it('accepts allowed literals and falls back otherwise', () => {
43
- expect(oneOf('delay', ['interval', 'delay'], 'interval')).toBe('delay');
44
- expect(oneOf('other', ['interval', 'delay'], 'interval')).toBe('interval');
45
- expect(oneOf(1, ['interval', 'delay'], 'interval')).toBe('interval');
46
- });
47
- });
48
- describe('numArray', () => {
49
- it('filters to finite numbers', () => {
50
- expect(numArray([1, NaN, 2, 'x', Infinity])).toEqual([1, 2]);
51
- expect(numArray(null)).toEqual([]);
52
- });
53
- });
54
- describe('strArray', () => {
55
- it('filters to strings', () => {
56
- expect(strArray(['a', 1, 'b'])).toEqual(['a', 'b']);
57
- expect(strArray(undefined)).toEqual([]);
58
- });
59
- });
60
- describe('arrayOf', () => {
61
- it('filters through a guard', () => {
62
- const isNum = (entry) => typeof entry === 'number' && Number.isFinite(entry);
63
- expect(arrayOf([1, 'x', 2], isNum)).toEqual([1, 2]);
64
- expect(arrayOf('nope', isNum)).toEqual([]);
65
- });
66
- });
67
- describe('recordOf', () => {
68
- it('keeps entries that pass the guard', () => {
69
- const isNum = (entry) => typeof entry === 'number' && Number.isFinite(entry);
70
- expect(recordOf({
71
- a: 1,
72
- b: 'x',
73
- c: 2
74
- }, isNum)).toEqual({ a: 1, c: 2 });
75
- expect(recordOf([], isNum)).toEqual({});
76
- expect(recordOf(null, isNum)).toEqual({});
77
- });
78
- });