latchkey 3.7.0 → 3.9.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 (133) hide show
  1. package/dist/scripts/recordBrowserSession.js +4 -3
  2. package/dist/scripts/recordBrowserSession.js.map +1 -1
  3. package/dist/src/apiCredentials/base.d.ts +7 -1
  4. package/dist/src/apiCredentials/base.d.ts.map +1 -1
  5. package/dist/src/apiCredentials/base.js.map +1 -1
  6. package/dist/src/apiCredentials/store.d.ts +5 -0
  7. package/dist/src/apiCredentials/store.d.ts.map +1 -1
  8. package/dist/src/apiCredentials/store.js +7 -0
  9. package/dist/src/apiCredentials/store.js.map +1 -1
  10. package/dist/src/atomicWrite.d.ts +4 -1
  11. package/dist/src/atomicWrite.d.ts.map +1 -1
  12. package/dist/src/atomicWrite.js +34 -3
  13. package/dist/src/atomicWrite.js.map +1 -1
  14. package/dist/src/cli.js +0 -3
  15. package/dist/src/cli.js.map +1 -1
  16. package/dist/src/cliCommands.d.ts +8 -0
  17. package/dist/src/cliCommands.d.ts.map +1 -1
  18. package/dist/src/cliCommands.js +88 -13
  19. package/dist/src/cliCommands.js.map +1 -1
  20. package/dist/src/curl.d.ts +36 -0
  21. package/dist/src/curl.d.ts.map +1 -1
  22. package/dist/src/curl.js +42 -0
  23. package/dist/src/curl.js.map +1 -1
  24. package/dist/src/curlInjection.d.ts +5 -4
  25. package/dist/src/curlInjection.d.ts.map +1 -1
  26. package/dist/src/curlInjection.js +8 -7
  27. package/dist/src/curlInjection.js.map +1 -1
  28. package/dist/src/gateway/gatewayEndpoint.d.ts.map +1 -1
  29. package/dist/src/gateway/gatewayEndpoint.js +6 -3
  30. package/dist/src/gateway/gatewayEndpoint.js.map +1 -1
  31. package/dist/src/gateway/server.d.ts.map +1 -1
  32. package/dist/src/gateway/server.js +14 -3
  33. package/dist/src/gateway/server.js.map +1 -1
  34. package/dist/src/index.d.ts +1 -1
  35. package/dist/src/index.d.ts.map +1 -1
  36. package/dist/src/index.js +1 -1
  37. package/dist/src/index.js.map +1 -1
  38. package/dist/src/migrations.d.ts +6 -0
  39. package/dist/src/migrations.d.ts.map +1 -1
  40. package/dist/src/migrations.js +21 -8
  41. package/dist/src/migrations.js.map +1 -1
  42. package/dist/src/playwrightUtils.d.ts +57 -0
  43. package/dist/src/playwrightUtils.d.ts.map +1 -1
  44. package/dist/src/playwrightUtils.js +247 -8
  45. package/dist/src/playwrightUtils.js.map +1 -1
  46. package/dist/src/serviceRegistry.d.ts +14 -1
  47. package/dist/src/serviceRegistry.d.ts.map +1 -1
  48. package/dist/src/serviceRegistry.js +22 -2
  49. package/dist/src/serviceRegistry.js.map +1 -1
  50. package/dist/src/services/aws.d.ts +11 -2
  51. package/dist/src/services/aws.d.ts.map +1 -1
  52. package/dist/src/services/aws.js +72 -5
  53. package/dist/src/services/aws.js.map +1 -1
  54. package/dist/src/services/core/base.d.ts +55 -16
  55. package/dist/src/services/core/base.d.ts.map +1 -1
  56. package/dist/src/services/core/base.js +119 -31
  57. package/dist/src/services/core/base.js.map +1 -1
  58. package/dist/src/services/github.d.ts +2 -1
  59. package/dist/src/services/github.d.ts.map +1 -1
  60. package/dist/src/services/github.js +13 -0
  61. package/dist/src/services/github.js.map +1 -1
  62. package/dist/src/services/huggingface.d.ts +2 -1
  63. package/dist/src/services/huggingface.d.ts.map +1 -1
  64. package/dist/src/services/huggingface.js +13 -0
  65. package/dist/src/services/huggingface.js.map +1 -1
  66. package/dist/src/services/linear.d.ts +2 -1
  67. package/dist/src/services/linear.d.ts.map +1 -1
  68. package/dist/src/services/linear.js +14 -0
  69. package/dist/src/services/linear.js.map +1 -1
  70. package/dist/src/services/ngrok.d.ts +2 -1
  71. package/dist/src/services/ngrok.d.ts.map +1 -1
  72. package/dist/src/services/ngrok.js +12 -0
  73. package/dist/src/services/ngrok.js.map +1 -1
  74. package/dist/src/services/openrouter.d.ts +2 -1
  75. package/dist/src/services/openrouter.d.ts.map +1 -1
  76. package/dist/src/services/openrouter.js +12 -0
  77. package/dist/src/services/openrouter.js.map +1 -1
  78. package/dist/src/services/tailscale.d.ts +2 -1
  79. package/dist/src/services/tailscale.d.ts.map +1 -1
  80. package/dist/src/services/tailscale.js +18 -0
  81. package/dist/src/services/tailscale.js.map +1 -1
  82. package/dist/src/services/todoist.d.ts +2 -1
  83. package/dist/src/services/todoist.d.ts.map +1 -1
  84. package/dist/src/services/todoist.js +6 -0
  85. package/dist/src/services/todoist.js.map +1 -1
  86. package/dist/src/services/zoom.d.ts +2 -1
  87. package/dist/src/services/zoom.d.ts.map +1 -1
  88. package/dist/src/services/zoom.js +55 -55
  89. package/dist/src/services/zoom.js.map +1 -1
  90. package/dist/src/version.d.ts +1 -1
  91. package/dist/src/version.js +1 -1
  92. package/dist/tests/apiCredentialStore.test.js +8 -0
  93. package/dist/tests/apiCredentialStore.test.js.map +1 -1
  94. package/dist/tests/apiCredentials.test.js +84 -1
  95. package/dist/tests/apiCredentials.test.js.map +1 -1
  96. package/dist/tests/atomicWrite.test.js +42 -1
  97. package/dist/tests/atomicWrite.test.js.map +1 -1
  98. package/dist/tests/builtinServiceRegistry.d.ts +10 -0
  99. package/dist/tests/builtinServiceRegistry.d.ts.map +1 -0
  100. package/dist/tests/builtinServiceRegistry.js +10 -0
  101. package/dist/tests/builtinServiceRegistry.js.map +1 -0
  102. package/dist/tests/cli.test.js +155 -7
  103. package/dist/tests/cli.test.js.map +1 -1
  104. package/dist/tests/devShim.test.d.ts +2 -0
  105. package/dist/tests/devShim.test.d.ts.map +1 -0
  106. package/dist/tests/devShim.test.js +240 -0
  107. package/dist/tests/devShim.test.js.map +1 -0
  108. package/dist/tests/gateway.test.js +87 -2
  109. package/dist/tests/gateway.test.js.map +1 -1
  110. package/dist/tests/gatewayExtensions.test.js +2 -0
  111. package/dist/tests/gatewayExtensions.test.js.map +1 -1
  112. package/dist/tests/github.test.js +2 -2
  113. package/dist/tests/github.test.js.map +1 -1
  114. package/dist/tests/huggingface.test.js +2 -2
  115. package/dist/tests/huggingface.test.js.map +1 -1
  116. package/dist/tests/latchkeyEndpoint.test.js +9 -5
  117. package/dist/tests/latchkeyEndpoint.test.js.map +1 -1
  118. package/dist/tests/lint.test.js +1 -1
  119. package/dist/tests/manualCompletion.test.d.ts +10 -0
  120. package/dist/tests/manualCompletion.test.d.ts.map +1 -0
  121. package/dist/tests/manualCompletion.test.js +221 -0
  122. package/dist/tests/manualCompletion.test.js.map +1 -0
  123. package/dist/tests/manualCredentialForms.test.d.ts +8 -0
  124. package/dist/tests/manualCredentialForms.test.d.ts.map +1 -0
  125. package/dist/tests/manualCredentialForms.test.js +64 -0
  126. package/dist/tests/manualCredentialForms.test.js.map +1 -0
  127. package/dist/tests/serviceRegistry.test.js +104 -20
  128. package/dist/tests/serviceRegistry.test.js.map +1 -1
  129. package/dist/tests/servicesAgainstRecordings.test.js +2 -2
  130. package/dist/tests/servicesAgainstRecordings.test.js.map +1 -1
  131. package/dist/tests/tailscale.test.js +2 -2
  132. package/dist/tests/tailscale.test.js.map +1 -1
  133. package/package.json +4 -1
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A registry over the services latchkey ships with.
3
+ *
4
+ * The CLI and the gateway each build their own registry with
5
+ * `createServiceRegistry`, since theirs also carry whatever config.json holds.
6
+ * Tests that only care about the built-in services share this one.
7
+ */
8
+ import { ServiceRegistry } from '../src/serviceRegistry.js';
9
+ export declare const BUILTIN_SERVICE_REGISTRY: ServiceRegistry;
10
+ //# sourceMappingURL=builtinServiceRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtinServiceRegistry.d.ts","sourceRoot":"","sources":["../../tests/builtinServiceRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAoB,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE9E,eAAO,MAAM,wBAAwB,iBAAwC,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A registry over the services latchkey ships with.
3
+ *
4
+ * The CLI and the gateway each build their own registry with
5
+ * `createServiceRegistry`, since theirs also carry whatever config.json holds.
6
+ * Tests that only care about the built-in services share this one.
7
+ */
8
+ import { BUILTIN_SERVICES, ServiceRegistry } from '../src/serviceRegistry.js';
9
+ export const BUILTIN_SERVICE_REGISTRY = new ServiceRegistry(BUILTIN_SERVICES);
10
+ //# sourceMappingURL=builtinServiceRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtinServiceRegistry.js","sourceRoot":"","sources":["../../tests/builtinServiceRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE9E,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,eAAe,CAAC,gBAAgB,CAAC,CAAC"}
@@ -5,12 +5,13 @@ import { tmpdir } from 'node:os';
5
5
  import { execSync } from 'node:child_process';
6
6
  import { Command } from 'commander';
7
7
  import { registerCommands } from '../src/cliCommands.js';
8
- import { CurlParseError, extractUrlFromCurlArguments } from '../src/curl.js';
8
+ import { CurlParseError, extractUrlFromCurlArguments, resolveCurlRequestBodySource, } from '../src/curl.js';
9
9
  import { hasGraphicalEnvironment } from '../src/playwrightUtils.js';
10
10
  import { EncryptedStorage } from '../src/encryptedStorage.js';
11
11
  import { Config } from '../src/config.js';
12
12
  import { ServiceRegistry } from '../src/serviceRegistry.js';
13
13
  import { ApiCredentialStatus } from '../src/apiCredentials/base.js';
14
+ import { LATEST_VERSION } from '../src/migrations.js';
14
15
  import { Service } from '../src/services/core/base.js';
15
16
  import { createMockService, MockService } from './mockService.js';
16
17
  import { RegisteredService } from '../src/services/core/registered.js';
@@ -183,6 +184,26 @@ describe('extractUrlFromCurlArguments', () => {
183
184
  expect(extractUrlFromCurlArguments(arguments_)).toBe('https://example.com/api?query=from%3Ame');
184
185
  });
185
186
  });
187
+ describe('resolveCurlRequestBodySource', () => {
188
+ it('should report an inline body when no file reference is present', () => {
189
+ expect(resolveCurlRequestBodySource(['-d', '{"a":1}', 'https://example.com'], '{"a":1}')).toEqual({ kind: 'inline', text: '{"a":1}' });
190
+ });
191
+ it('should report an inline body for --data-raw, which treats @ literally', () => {
192
+ expect(resolveCurlRequestBodySource(['--data-raw', '@literal', 'https://example.com'], '@literal')).toEqual({ kind: 'inline', text: '@literal' });
193
+ });
194
+ it('should report a file reference', () => {
195
+ expect(resolveCurlRequestBodySource(['--data-binary', '@/tmp/payload.json', 'https://example.com'], '@/tmp/payload.json')).toEqual({ kind: 'file', path: '/tmp/payload.json' });
196
+ });
197
+ it('should report stdin for @-', () => {
198
+ expect(resolveCurlRequestBodySource(['--data-binary', '@-', 'https://example.com'], '@-')).toEqual({ kind: 'stdin' });
199
+ });
200
+ it('should report an unreconstructable body for combined data arguments', () => {
201
+ expect(resolveCurlRequestBodySource(['-d', '@/tmp/payload.json', '-d', 'extra=1', 'https://example.com'], '@/tmp/payload.json&extra=1')).toEqual({ kind: 'unreconstructable' });
202
+ });
203
+ it('should report an unreconstructable body for url-encoded file data', () => {
204
+ expect(resolveCurlRequestBodySource(['--data-urlencode', '@/tmp/payload.json', 'https://example.com'], '@/tmp/payload.json')).toEqual({ kind: 'unreconstructable' });
205
+ });
206
+ });
186
207
  describe('hasGraphicalEnvironment', () => {
187
208
  const originalPlatform = process.platform;
188
209
  afterEach(() => {
@@ -345,6 +366,7 @@ describe('CLI commands with dependency injection', () => {
345
366
  const mockRegistry = new ServiceRegistry([mockSlackService]);
346
367
  return {
347
368
  registry: mockRegistry,
369
+ builtinServices: [mockSlackService],
348
370
  config: createMockConfig(),
349
371
  runCurl: (args) => {
350
372
  capturedArgs.push(...args);
@@ -973,11 +995,27 @@ describe('CLI commands with dependency injection', () => {
973
995
  function withStdinKey(key, overrides = {}) {
974
996
  return createMockDependencies({ readStdin: () => Promise.resolve(key), ...overrides });
975
997
  }
976
- // The credential store filename used by the mock config.
998
+ // The filenames used by the mock config.
977
999
  const STORE_FILENAME = 'credentials.json';
978
- function writeStore(contents) {
979
- writeSecureFile(join(tempDir, STORE_FILENAME), JSON.stringify(nestAccounts(contents)));
1000
+ const BROWSER_STATE_FILENAME = 'browser_state.json';
1001
+ function writeStore(contents, preparations = {}) {
1002
+ writeSecureFile(join(tempDir, STORE_FILENAME), JSON.stringify({ ...nestAccounts(contents), preparations }));
1003
+ }
1004
+ function readPreparationsWithKey(path, key) {
1005
+ const raw = JSON.parse(new EncryptedStorage(key).readFile(path) ?? '{}');
1006
+ return raw.preparations ?? {};
980
1007
  }
1008
+ // A destination that already holds a store must be in the current data
1009
+ // format, which real Latchkey directories record in this file.
1010
+ function stampDataFormatVersion(directory) {
1011
+ mkdirSync(directory, { recursive: true });
1012
+ writeFileSync(join(directory, 'data-format-version'), String(LATEST_VERSION));
1013
+ }
1014
+ const GITLAB_PREPARATION = {
1015
+ objectType: 'oauth',
1016
+ clientId: 'gitlab-client-id',
1017
+ clientSecret: 'gitlab-client-secret',
1018
+ };
981
1019
  it('re-encrypts all stored credentials with the new key', async () => {
982
1020
  writeStore({
983
1021
  slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' },
@@ -1025,7 +1063,7 @@ describe('CLI commands with dependency injection', () => {
1025
1063
  const reEncrypted = readWithKey(join(destinationDirectory, STORE_FILENAME), NEW_ENCRYPTION_KEY);
1026
1064
  expect(Object.keys(reEncrypted)).toEqual(['slack']);
1027
1065
  });
1028
- it('errors when a requested service has no stored credentials', async () => {
1066
+ it('errors when a requested service has neither credentials nor a preparation', async () => {
1029
1067
  writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } });
1030
1068
  const destinationDirectory = join(tempDir, 'export');
1031
1069
  const deps = withStdinKey(NEW_ENCRYPTION_KEY);
@@ -1033,6 +1071,60 @@ describe('CLI commands with dependency injection', () => {
1033
1071
  expect(exitCode).toBe(1);
1034
1072
  expect(existsSync(join(destinationDirectory, STORE_FILENAME))).toBe(false);
1035
1073
  });
1074
+ it('carries the preparations of the re-encrypted services', async () => {
1075
+ writeStore({ gitlab: { objectType: 'authorizationBearer', token: 'gitlab-token' } }, { gitlab: GITLAB_PREPARATION });
1076
+ const destinationDirectory = join(tempDir, 'export');
1077
+ const deps = withStdinKey(NEW_ENCRYPTION_KEY);
1078
+ await runCommand(['auth', 're-encrypt', destinationDirectory], deps);
1079
+ expect(exitCode).toBeNull();
1080
+ const destination = join(destinationDirectory, STORE_FILENAME);
1081
+ expect(readPreparationsWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({
1082
+ gitlab: GITLAB_PREPARATION,
1083
+ });
1084
+ });
1085
+ it('includes services that only have a preparation', async () => {
1086
+ writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } }, { gitlab: GITLAB_PREPARATION });
1087
+ const destinationDirectory = join(tempDir, 'export');
1088
+ const deps = withStdinKey(NEW_ENCRYPTION_KEY);
1089
+ await runCommand(['auth', 're-encrypt', destinationDirectory], deps);
1090
+ expect(exitCode).toBeNull();
1091
+ const destination = join(destinationDirectory, STORE_FILENAME);
1092
+ expect(Object.keys(readWithKey(destination, NEW_ENCRYPTION_KEY))).toEqual(['slack']);
1093
+ expect(readPreparationsWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({
1094
+ gitlab: GITLAB_PREPARATION,
1095
+ });
1096
+ expect(logs.join('\n')).toContain('2 service(s)');
1097
+ });
1098
+ it('selects a service that only has a preparation with --services', async () => {
1099
+ writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } }, { gitlab: GITLAB_PREPARATION });
1100
+ const destinationDirectory = join(tempDir, 'export');
1101
+ const deps = withStdinKey(NEW_ENCRYPTION_KEY);
1102
+ await runCommand(['auth', 're-encrypt', destinationDirectory, '--services', 'gitlab'], deps);
1103
+ expect(exitCode).toBeNull();
1104
+ const destination = join(destinationDirectory, STORE_FILENAME);
1105
+ expect(readWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({});
1106
+ expect(readPreparationsWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({
1107
+ gitlab: GITLAB_PREPARATION,
1108
+ });
1109
+ });
1110
+ it('replaces an existing preparation in the destination', async () => {
1111
+ writeStore({}, { gitlab: GITLAB_PREPARATION });
1112
+ const destinationDirectory = join(tempDir, 'export');
1113
+ const destination = join(destinationDirectory, STORE_FILENAME);
1114
+ stampDataFormatVersion(destinationDirectory);
1115
+ new EncryptedStorage(NEW_ENCRYPTION_KEY).writeFile(destination, JSON.stringify({
1116
+ credentials: {},
1117
+ preparations: {
1118
+ gitlab: { objectType: 'oauth', clientId: 'old-id', clientSecret: 'old-secret' },
1119
+ },
1120
+ }));
1121
+ const deps = withStdinKey(NEW_ENCRYPTION_KEY);
1122
+ await runCommand(['auth', 're-encrypt', destinationDirectory], deps);
1123
+ expect(exitCode).toBeNull();
1124
+ expect(readPreparationsWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({
1125
+ gitlab: GITLAB_PREPARATION,
1126
+ });
1127
+ });
1036
1128
  it('errors for an invalid encryption key read from stdin', async () => {
1037
1129
  writeStore({});
1038
1130
  const destinationDirectory = join(tempDir, 'export');
@@ -1049,10 +1141,36 @@ describe('CLI commands with dependency injection', () => {
1049
1141
  expect(exitCode).toBeNull();
1050
1142
  expect(Object.keys(readWithKey(join(destinationDirectory, STORE_FILENAME), NEW_ENCRYPTION_KEY))).toEqual(['slack']);
1051
1143
  });
1052
- it('refuses to overwrite an existing credential store in the destination', async () => {
1144
+ it('adds the services into an existing credential store, overwriting by service', async () => {
1145
+ writeStore({
1146
+ slack: { objectType: 'slack', token: 'new-tok', dCookie: 'new-cookie' },
1147
+ });
1148
+ const destinationDirectory = join(tempDir, 'export');
1149
+ const destination = join(destinationDirectory, STORE_FILENAME);
1150
+ stampDataFormatVersion(destinationDirectory);
1151
+ new EncryptedStorage(NEW_ENCRYPTION_KEY).writeFile(destination, JSON.stringify(nestAccounts({
1152
+ slack: { objectType: 'slack', token: 'old-tok', dCookie: 'old-cookie' },
1153
+ discord: { objectType: 'authorizationBearer', token: 'discord-token' },
1154
+ })));
1155
+ const deps = withStdinKey(NEW_ENCRYPTION_KEY);
1156
+ await runCommand(['auth', 're-encrypt', destinationDirectory], deps);
1157
+ expect(exitCode).toBeNull();
1158
+ const reEncrypted = readWithKey(destination, NEW_ENCRYPTION_KEY);
1159
+ expect(Object.keys(reEncrypted).sort()).toEqual(['discord', 'slack']);
1160
+ expect(reEncrypted.slack).toEqual({
1161
+ objectType: 'slack',
1162
+ token: 'new-tok',
1163
+ dCookie: 'new-cookie',
1164
+ });
1165
+ expect(reEncrypted.discord).toEqual({
1166
+ objectType: 'authorizationBearer',
1167
+ token: 'discord-token',
1168
+ });
1169
+ });
1170
+ it('errors when the existing credential store cannot be read with the new key', async () => {
1053
1171
  writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } });
1054
1172
  const destinationDirectory = join(tempDir, 'export');
1055
- mkdirSync(destinationDirectory);
1173
+ stampDataFormatVersion(destinationDirectory);
1056
1174
  const destination = join(destinationDirectory, STORE_FILENAME);
1057
1175
  writeFileSync(destination, 'existing');
1058
1176
  const deps = withStdinKey(NEW_ENCRYPTION_KEY);
@@ -1060,6 +1178,36 @@ describe('CLI commands with dependency injection', () => {
1060
1178
  expect(exitCode).toBe(1);
1061
1179
  expect(readFileSync(destination, 'utf-8')).toBe('existing');
1062
1180
  });
1181
+ it('re-encrypts the browser state with --browser-state', async () => {
1182
+ writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } });
1183
+ new EncryptedStorage(TEST_ENCRYPTION_KEY).writeFile(join(tempDir, BROWSER_STATE_FILENAME), '{"cookies":[]}');
1184
+ const destinationDirectory = join(tempDir, 'export');
1185
+ const deps = withStdinKey(NEW_ENCRYPTION_KEY);
1186
+ await runCommand(['auth', 're-encrypt', destinationDirectory, '--browser-state'], deps);
1187
+ expect(exitCode).toBeNull();
1188
+ const browserStateDestination = join(destinationDirectory, BROWSER_STATE_FILENAME);
1189
+ expect(new EncryptedStorage(NEW_ENCRYPTION_KEY).readFile(browserStateDestination)).toBe('{"cookies":[]}');
1190
+ expect(Object.keys(readWithKey(join(destinationDirectory, STORE_FILENAME), NEW_ENCRYPTION_KEY))).toEqual(['slack']);
1191
+ });
1192
+ it('errors with --browser-state when there is no browser state', async () => {
1193
+ writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } });
1194
+ const destinationDirectory = join(tempDir, 'export');
1195
+ const deps = withStdinKey(NEW_ENCRYPTION_KEY);
1196
+ await runCommand(['auth', 're-encrypt', destinationDirectory, '--browser-state'], deps);
1197
+ expect(exitCode).toBe(1);
1198
+ expect(existsSync(join(destinationDirectory, STORE_FILENAME))).toBe(false);
1199
+ });
1200
+ it('overwrites an existing browser state in the destination', async () => {
1201
+ writeStore({});
1202
+ new EncryptedStorage(TEST_ENCRYPTION_KEY).writeFile(join(tempDir, BROWSER_STATE_FILENAME), '{"cookies":["new"]}');
1203
+ const destinationDirectory = join(tempDir, 'export');
1204
+ const browserStateDestination = join(destinationDirectory, BROWSER_STATE_FILENAME);
1205
+ new EncryptedStorage(NEW_ENCRYPTION_KEY).writeFile(browserStateDestination, 'old');
1206
+ const deps = withStdinKey(NEW_ENCRYPTION_KEY);
1207
+ await runCommand(['auth', 're-encrypt', destinationDirectory, '--browser-state'], deps);
1208
+ expect(exitCode).toBeNull();
1209
+ expect(new EncryptedStorage(NEW_ENCRYPTION_KEY).readFile(browserStateDestination)).toBe('{"cookies":["new"]}');
1210
+ });
1063
1211
  it('errors when the destination is an existing file, not a directory', async () => {
1064
1212
  writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } });
1065
1213
  const destinationDirectory = join(tempDir, 'export');