t3code-cli 0.10.0 → 0.12.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 (328) hide show
  1. package/README.md +75 -11
  2. package/dist/application.js +2 -2
  3. package/dist/auth.js +1 -1
  4. package/dist/bin.js +86754 -2675
  5. package/dist/cli.js +15 -2
  6. package/dist/config.js +120 -2
  7. package/dist/connection.js +2 -12
  8. package/dist/index.js +1 -1
  9. package/dist/node.js +6 -0
  10. package/dist/orchestration.js +2 -2
  11. package/dist/rpc.js +2 -2
  12. package/dist/runtime.js +2 -2
  13. package/dist/shared.js +43850 -38040
  14. package/dist/src/application/actions.d.ts +25 -0
  15. package/dist/src/application/index.d.ts +3 -2
  16. package/dist/src/application/layer.d.ts +99 -842
  17. package/dist/src/application/model-selection.d.ts +17 -2
  18. package/dist/src/application/models.d.ts +1 -87
  19. package/dist/src/application/project-commands.d.ts +31 -2
  20. package/dist/src/application/projects.d.ts +15 -137
  21. package/dist/src/application/service.d.ts +147 -5
  22. package/dist/src/application/shell-sequence.d.ts +5 -4
  23. package/dist/src/application/terminals.d.ts +66 -0
  24. package/dist/src/application/thread-commands.d.ts +48 -1
  25. package/dist/src/application/thread-update.d.ts +8 -0
  26. package/dist/src/application/thread-wait.d.ts +4 -6
  27. package/dist/src/application/threads.d.ts +36 -14
  28. package/dist/src/auth/error.d.ts +2 -1
  29. package/dist/src/auth/index.d.ts +1 -1
  30. package/dist/src/auth/layer.d.ts +34 -6
  31. package/dist/src/auth/local-base-dir.d.ts +5 -2
  32. package/dist/src/auth/local-origin.d.ts +3 -3
  33. package/dist/src/auth/local-token.d.ts +3 -3
  34. package/dist/src/auth/service.d.ts +15 -3
  35. package/dist/src/auth/type.d.ts +40 -1
  36. package/dist/src/cli/env/flag.d.ts +2 -0
  37. package/dist/src/cli/env/index.d.ts +2 -0
  38. package/dist/src/cli/env/selection-layer.d.ts +3 -0
  39. package/dist/src/cli/flags.d.ts +5 -0
  40. package/dist/src/cli/format/index.d.ts +1 -0
  41. package/dist/src/cli/format/output.d.ts +12 -0
  42. package/dist/src/cli/index.d.ts +4 -0
  43. package/dist/src/cli/runtime/index.d.ts +1 -0
  44. package/dist/src/cli/runtime/service.d.ts +16 -0
  45. package/dist/src/{scope → cli/scope}/resolve.d.ts +5 -4
  46. package/dist/src/config/config.d.ts +49 -0
  47. package/dist/src/config/credential/cipher-node.d.ts +3 -0
  48. package/dist/src/config/credential/cipher-web.d.ts +3 -0
  49. package/dist/src/config/credential/cipher.d.ts +29 -0
  50. package/dist/src/config/credential/env.d.ts +3 -0
  51. package/dist/src/config/credential/error.d.ts +8 -0
  52. package/dist/src/config/credential/index.d.ts +6 -0
  53. package/dist/src/config/credential/service.d.ts +34 -0
  54. package/dist/src/config/env/env.d.ts +55 -0
  55. package/dist/src/config/env/index.d.ts +2 -0
  56. package/dist/src/{layout/base-dir.d.ts → config/env/layout.d.ts} +0 -1
  57. package/dist/src/config/environment-name/index.d.ts +1 -0
  58. package/dist/src/config/environment-name/name.d.ts +10 -0
  59. package/dist/src/config/error.d.ts +4 -11
  60. package/dist/src/config/index.d.ts +10 -5
  61. package/dist/src/config/keystore/error.d.ts +8 -0
  62. package/dist/src/config/keystore/file.d.ts +9 -0
  63. package/dist/src/config/keystore/index.d.ts +4 -0
  64. package/dist/src/config/keystore/keyring-node.d.ts +4 -0
  65. package/dist/src/config/keystore/service.d.ts +33 -0
  66. package/dist/src/config/paths/index.d.ts +1 -0
  67. package/dist/src/config/paths/paths.d.ts +6 -0
  68. package/dist/src/config/persist/file-mode.d.ts +4 -0
  69. package/dist/src/config/persist/migration.d.ts +68 -0
  70. package/dist/src/config/persist/persist.d.ts +40 -0
  71. package/dist/src/config/persist/schema.d.ts +68 -0
  72. package/dist/src/config/resolve/resolve.d.ts +40 -0
  73. package/dist/src/config/selection/index.d.ts +1 -0
  74. package/dist/src/config/selection/resolve.d.ts +4 -0
  75. package/dist/src/config/selection/service.d.ts +10 -0
  76. package/dist/src/config/types.d.ts +36 -0
  77. package/dist/src/config/url/error.d.ts +10 -0
  78. package/dist/src/config/url/index.d.ts +2 -0
  79. package/dist/src/connection/index.d.ts +0 -1
  80. package/dist/src/contracts/index.d.ts +1 -1
  81. package/dist/src/domain/error.d.ts +27 -1
  82. package/dist/src/domain/helpers.d.ts +8 -2
  83. package/dist/src/domain/model-config.d.ts +30 -1
  84. package/dist/src/domain/thread-activities.d.ts +1 -1
  85. package/dist/src/domain/thread-lifecycle.d.ts +3 -2
  86. package/dist/src/index.d.ts +1 -2
  87. package/dist/src/{connection/node.d.ts → node/connection.d.ts} +1 -1
  88. package/dist/src/node/index.d.ts +1 -0
  89. package/dist/src/orchestration/index.d.ts +1 -1
  90. package/dist/src/orchestration/layer.d.ts +120 -829
  91. package/dist/src/orchestration/service.d.ts +2 -1
  92. package/dist/src/rpc/error.d.ts +5 -3
  93. package/dist/src/rpc/index.d.ts +5 -1
  94. package/dist/src/rpc/layer.d.ts +1166 -40
  95. package/dist/src/rpc/operation.d.ts +6280 -0
  96. package/dist/src/rpc/ws-group.d.ts +372 -27
  97. package/dist/src/runtime/index.d.ts +1 -2
  98. package/dist/src/runtime/layer.d.ts +12 -8
  99. package/dist/src/t3tools/index.d.ts +1 -1
  100. package/dist/t3tools.js +2 -2
  101. package/package.json +27 -19
  102. package/src/application/actions.test.ts +328 -0
  103. package/src/application/actions.ts +475 -0
  104. package/src/application/index.ts +32 -2
  105. package/src/application/layer.ts +43 -5
  106. package/src/application/model-selection.ts +70 -4
  107. package/src/application/models.ts +14 -14
  108. package/src/application/project-commands.ts +38 -5
  109. package/src/application/projects.ts +49 -20
  110. package/src/application/service.ts +275 -78
  111. package/src/application/shell-sequence.ts +6 -8
  112. package/src/application/terminals.ts +208 -0
  113. package/src/application/thread-commands.test.ts +43 -0
  114. package/src/application/thread-commands.ts +82 -1
  115. package/src/application/thread-update.ts +72 -0
  116. package/src/application/thread-wait.ts +47 -48
  117. package/src/application/threads.test.ts +172 -0
  118. package/src/application/threads.ts +128 -45
  119. package/src/auth/error.ts +2 -1
  120. package/src/auth/index.ts +7 -0
  121. package/src/auth/layer.test-utils.ts +34 -0
  122. package/src/auth/layer.ts +116 -34
  123. package/src/auth/local-base-dir.ts +13 -6
  124. package/src/auth/local-origin.ts +10 -7
  125. package/src/auth/local-token.ts +28 -23
  126. package/src/auth/service.test.ts +200 -0
  127. package/src/auth/service.ts +27 -3
  128. package/src/auth/transport.ts +2 -2
  129. package/src/auth/type.ts +47 -1
  130. package/src/bin.ts +26 -6
  131. package/src/cli/action-format.ts +110 -0
  132. package/src/cli/action.ts +398 -0
  133. package/src/cli/app.ts +8 -0
  134. package/src/cli/auth.ts +76 -23
  135. package/src/cli/env/flag.ts +8 -0
  136. package/src/cli/env/index.ts +2 -0
  137. package/src/cli/env/selection-layer.test.ts +49 -0
  138. package/src/cli/env/selection-layer.ts +27 -0
  139. package/src/cli/env.ts +107 -0
  140. package/src/cli/error.ts +26 -3
  141. package/src/cli/extra-args.test.ts +32 -0
  142. package/src/cli/extra-args.ts +22 -0
  143. package/src/cli/flags.ts +26 -1
  144. package/src/cli/format/auth.ts +92 -0
  145. package/src/cli/format/human.ts +131 -0
  146. package/src/cli/format/index.ts +12 -0
  147. package/src/cli/format/model.ts +29 -0
  148. package/src/cli/format/output.ts +51 -0
  149. package/src/cli/format/project.ts +32 -0
  150. package/src/cli/format/terminal.ts +87 -0
  151. package/src/cli/format/thread.test.ts +34 -0
  152. package/src/cli/format/thread.ts +185 -0
  153. package/src/cli/index.ts +20 -0
  154. package/src/cli/input/layer.ts +20 -0
  155. package/src/cli/input/service.ts +1 -0
  156. package/src/cli/interaction/confirm.ts +64 -0
  157. package/src/cli/interaction/self-action.ts +36 -0
  158. package/src/cli/message-input.ts +1 -1
  159. package/src/cli/model.ts +9 -5
  160. package/src/cli/project.ts +15 -8
  161. package/src/cli/projects/delete.ts +51 -0
  162. package/src/cli/require.ts +9 -7
  163. package/src/cli/runtime/index.ts +1 -0
  164. package/src/cli/runtime/service.ts +28 -0
  165. package/src/{scope → cli/scope}/resolve.ts +11 -9
  166. package/src/cli/terminal/attach.ts +29 -0
  167. package/src/cli/terminal/create.ts +55 -0
  168. package/src/cli/terminal/destroy.ts +62 -0
  169. package/src/cli/terminal/encoding.test.ts +63 -0
  170. package/src/cli/terminal/encoding.ts +23 -0
  171. package/src/cli/terminal/error.ts +14 -0
  172. package/src/cli/terminal/io-node-layer.ts +82 -0
  173. package/src/cli/terminal/io-service.ts +25 -0
  174. package/src/cli/terminal/list.ts +36 -0
  175. package/src/cli/terminal/read.test-utils.ts +48 -0
  176. package/src/cli/terminal/read.test.ts +35 -0
  177. package/src/cli/terminal/read.ts +99 -0
  178. package/src/cli/terminal/scope.ts +35 -0
  179. package/src/cli/terminal/shared.ts +250 -0
  180. package/src/cli/terminal/stream.ts +61 -0
  181. package/src/cli/terminal/wait.test.ts +148 -0
  182. package/src/cli/terminal/wait.ts +223 -0
  183. package/src/cli/terminal/write.test.ts +34 -0
  184. package/src/cli/terminal/write.ts +133 -0
  185. package/src/cli/terminal.ts +26 -0
  186. package/src/cli/thread.ts +8 -0
  187. package/src/cli/threads/approve.ts +11 -7
  188. package/src/cli/threads/archive.ts +23 -25
  189. package/src/cli/threads/callback.ts +6 -4
  190. package/src/cli/threads/delete.ts +67 -0
  191. package/src/cli/threads/interrupt.ts +57 -0
  192. package/src/cli/threads/list.test.ts +71 -0
  193. package/src/cli/threads/list.ts +25 -13
  194. package/src/cli/threads/messages.ts +11 -7
  195. package/src/cli/threads/respond.ts +11 -7
  196. package/src/cli/threads/send.ts +39 -9
  197. package/src/cli/threads/show.ts +11 -7
  198. package/src/cli/threads/start.ts +18 -13
  199. package/src/cli/threads/unarchive.ts +48 -0
  200. package/src/cli/threads/update.ts +155 -0
  201. package/src/cli/threads/wait.ts +11 -7
  202. package/src/cli/wait-events.ts +1 -1
  203. package/src/config/config.ts +260 -0
  204. package/src/config/credential/cipher-node.ts +40 -0
  205. package/src/config/credential/cipher-web.ts +82 -0
  206. package/src/config/credential/cipher.ts +39 -0
  207. package/src/config/credential/env.ts +13 -0
  208. package/src/config/credential/error.ts +9 -0
  209. package/src/config/credential/index.ts +6 -0
  210. package/src/config/credential/service.test-utils.ts +25 -0
  211. package/src/config/credential/service.test.ts +116 -0
  212. package/src/config/credential/service.ts +205 -0
  213. package/src/config/env/env.test-utils.ts +14 -0
  214. package/src/config/env/env.ts +81 -0
  215. package/src/config/env/index.ts +2 -0
  216. package/src/{layout/base-dir.ts → config/env/layout.ts} +0 -9
  217. package/src/config/environment-name/index.ts +7 -0
  218. package/src/config/environment-name/name.test.ts +66 -0
  219. package/src/config/environment-name/name.ts +49 -0
  220. package/src/config/error.ts +6 -8
  221. package/src/config/index.ts +16 -5
  222. package/src/config/keystore/error.ts +9 -0
  223. package/src/config/keystore/file.ts +96 -0
  224. package/src/config/keystore/index.ts +4 -0
  225. package/src/config/keystore/keyring-node.test.ts +12 -0
  226. package/src/config/keystore/keyring-node.ts +100 -0
  227. package/src/config/keystore/service.test-utils.ts +15 -0
  228. package/src/config/keystore/service.test.ts +18 -0
  229. package/src/config/keystore/service.ts +29 -0
  230. package/src/config/layer.test-utils.ts +45 -0
  231. package/src/config/layer.test.ts +209 -0
  232. package/src/config/paths/index.ts +1 -0
  233. package/src/config/paths/paths.ts +36 -0
  234. package/src/config/persist/file-mode.ts +21 -0
  235. package/src/config/persist/migration.test.ts +71 -0
  236. package/src/config/persist/migration.ts +82 -0
  237. package/src/config/persist/persist.test.ts +113 -0
  238. package/src/config/persist/persist.ts +65 -0
  239. package/src/config/persist/schema.ts +37 -0
  240. package/src/config/platform.test-utils.ts +20 -0
  241. package/src/config/resolve/resolve.test-utils.ts +20 -0
  242. package/src/config/resolve/resolve.test.ts +137 -0
  243. package/src/config/resolve/resolve.ts +104 -0
  244. package/src/config/selection/index.ts +1 -0
  245. package/src/config/selection/resolve.ts +14 -0
  246. package/src/config/selection/service.ts +29 -0
  247. package/src/config/temp-home.test-utils.ts +7 -0
  248. package/src/config/types.ts +49 -0
  249. package/src/config/url/error.ts +8 -0
  250. package/src/config/url/index.ts +7 -0
  251. package/src/config/url/url.test.ts +31 -0
  252. package/src/connection/index.ts +0 -1
  253. package/src/contracts/index.ts +1 -1
  254. package/src/domain/error.ts +39 -1
  255. package/src/domain/helpers.test.ts +1 -1
  256. package/src/domain/helpers.ts +1 -1
  257. package/src/domain/model-config.ts +5 -1
  258. package/src/domain/thread-activities.test.ts +1 -1
  259. package/src/domain/thread-activities.ts +1 -1
  260. package/src/domain/thread-lifecycle.test.ts +1 -1
  261. package/src/domain/thread-lifecycle.ts +15 -1
  262. package/src/effect.test-utils.ts +25 -0
  263. package/src/index.ts +1 -1
  264. package/src/{connection/node.ts → node/connection.ts} +1 -1
  265. package/src/node/index.ts +1 -0
  266. package/src/orchestration/index.ts +1 -1
  267. package/src/orchestration/layer.ts +34 -86
  268. package/src/orchestration/service.ts +5 -1
  269. package/src/rpc/error.ts +12 -2
  270. package/src/rpc/index.ts +5 -1
  271. package/src/rpc/layer.ts +1 -1
  272. package/src/rpc/operation.ts +83 -0
  273. package/src/rpc/ws-group.ts +25 -1
  274. package/src/runtime/index.ts +2 -1
  275. package/src/runtime/layer.test-utils.ts +32 -0
  276. package/src/runtime/layer.test.ts +66 -0
  277. package/src/runtime/layer.ts +22 -11
  278. package/src/t3tools/index.ts +1 -1
  279. package/src/types/marked-terminal.d.ts +11 -0
  280. package/dist/layout.js +0 -2
  281. package/dist/scope.js +0 -2
  282. package/dist/src/cli/output-format.d.ts +0 -9
  283. package/dist/src/config/layer.d.ts +0 -22
  284. package/dist/src/config/service.d.ts +0 -22
  285. package/dist/src/environment/layer.d.ts +0 -3
  286. package/dist/src/environment/service.d.ts +0 -12
  287. package/dist/src/layout/index.d.ts +0 -1
  288. package/dist/upstream-t3code/packages/contracts/src/auth.d.ts +0 -441
  289. package/dist/upstream-t3code/packages/contracts/src/baseSchemas.d.ts +0 -38
  290. package/dist/upstream-t3code/packages/contracts/src/desktopBootstrap.d.ts +0 -14
  291. package/dist/upstream-t3code/packages/contracts/src/editor.d.ts +0 -124
  292. package/dist/upstream-t3code/packages/contracts/src/environment.d.ts +0 -64
  293. package/dist/upstream-t3code/packages/contracts/src/environmentHttp.d.ts +0 -785
  294. package/dist/upstream-t3code/packages/contracts/src/filesystem.d.ts +0 -26
  295. package/dist/upstream-t3code/packages/contracts/src/git.d.ts +0 -500
  296. package/dist/upstream-t3code/packages/contracts/src/index.d.ts +0 -25
  297. package/dist/upstream-t3code/packages/contracts/src/ipc.d.ts +0 -496
  298. package/dist/upstream-t3code/packages/contracts/src/keybindings.d.ts +0 -133
  299. package/dist/upstream-t3code/packages/contracts/src/model.d.ts +0 -112
  300. package/dist/upstream-t3code/packages/contracts/src/orchestration.d.ts +0 -7338
  301. package/dist/upstream-t3code/packages/contracts/src/project.d.ts +0 -45
  302. package/dist/upstream-t3code/packages/contracts/src/provider.d.ts +0 -130
  303. package/dist/upstream-t3code/packages/contracts/src/providerInstance.d.ts +0 -99
  304. package/dist/upstream-t3code/packages/contracts/src/providerRuntime.d.ts +0 -4450
  305. package/dist/upstream-t3code/packages/contracts/src/relay.d.ts +0 -1262
  306. package/dist/upstream-t3code/packages/contracts/src/relayClient.d.ts +0 -48
  307. package/dist/upstream-t3code/packages/contracts/src/remoteAccess.d.ts +0 -45
  308. package/dist/upstream-t3code/packages/contracts/src/review.d.ts +0 -37
  309. package/dist/upstream-t3code/packages/contracts/src/rpc.d.ts +0 -8710
  310. package/dist/upstream-t3code/packages/contracts/src/server.d.ts +0 -2336
  311. package/dist/upstream-t3code/packages/contracts/src/settings.d.ts +0 -292
  312. package/dist/upstream-t3code/packages/contracts/src/sourceControl.d.ts +0 -177
  313. package/dist/upstream-t3code/packages/contracts/src/terminal.d.ts +0 -333
  314. package/dist/upstream-t3code/packages/contracts/src/vcs.d.ts +0 -148
  315. package/src/cli/auth-format.ts +0 -45
  316. package/src/cli/model-format.ts +0 -18
  317. package/src/cli/output-format.ts +0 -41
  318. package/src/cli/project-format.ts +0 -11
  319. package/src/cli/thread-format.ts +0 -121
  320. package/src/config/layer.ts +0 -105
  321. package/src/config/service.ts +0 -26
  322. package/src/environment/layer.ts +0 -12
  323. package/src/environment/service.ts +0 -13
  324. package/src/layout/index.ts +0 -1
  325. /package/dist/src/{scope → cli/scope}/index.d.ts +0 -0
  326. /package/dist/src/config/{url.d.ts → url/url.d.ts} +0 -0
  327. /package/src/{scope → cli/scope}/index.ts +0 -0
  328. /package/src/config/{url.ts → url/url.ts} +0 -0
@@ -0,0 +1,209 @@
1
+ import "vite-plus/test/config";
2
+
3
+ import * as Effect from "effect/Effect";
4
+ import { assert, describe, it } from "@effect/vitest";
5
+
6
+ import { T3Config } from "./config.ts";
7
+ import { ConfigPlatformLayer } from "./platform.test-utils.ts";
8
+ import { makeTempHomeScoped } from "./temp-home.test-utils.ts";
9
+ import { t3ConfigDepsLayer } from "./layer.test-utils.ts";
10
+
11
+ describe("T3Config", () => {
12
+ it.effect(
13
+ "keeps default unchanged when upserting an existing environment without makeDefault",
14
+ () =>
15
+ Effect.gen(function* () {
16
+ return yield* makeTempHomeScoped("t3cli-config-");
17
+ }).pipe(
18
+ Effect.flatMap((homeDir) =>
19
+ Effect.gen(function* () {
20
+ const config = yield* T3Config;
21
+ yield* config.upsertEnvironment({
22
+ name: "home",
23
+ url: "https://home.example",
24
+ token: "home-token",
25
+ local: false,
26
+ });
27
+ yield* config.upsertEnvironment({
28
+ name: "work",
29
+ url: "https://work.example",
30
+ token: "work-token",
31
+ local: false,
32
+ });
33
+ yield* config.upsertEnvironment({
34
+ name: "work",
35
+ url: "https://work.example",
36
+ token: "work-token-2",
37
+ local: false,
38
+ });
39
+ const listed = yield* config.listEnvironments();
40
+ assert.equal(listed.find((environment) => environment.name === "home")?.default, true);
41
+ assert.equal(listed.find((environment) => environment.name === "work")?.default, false);
42
+ }).pipe(Effect.provide(t3ConfigDepsLayer(homeDir))),
43
+ ),
44
+ Effect.provide(ConfigPlatformLayer),
45
+ Effect.scoped,
46
+ ),
47
+ );
48
+
49
+ it.effect("promotes default when upserting with makeDefault", () =>
50
+ Effect.gen(function* () {
51
+ return yield* makeTempHomeScoped("t3cli-config-");
52
+ }).pipe(
53
+ Effect.flatMap((homeDir) =>
54
+ Effect.gen(function* () {
55
+ const config = yield* T3Config;
56
+ yield* config.upsertEnvironment({
57
+ name: "home",
58
+ url: "https://home.example",
59
+ token: "home-token",
60
+ local: false,
61
+ });
62
+ yield* config.upsertEnvironment({
63
+ name: "work",
64
+ url: "https://work.example",
65
+ token: "work-token",
66
+ local: false,
67
+ });
68
+ yield* config.upsertEnvironment({
69
+ name: "work",
70
+ url: "https://work.example",
71
+ token: "work-token-2",
72
+ local: false,
73
+ makeDefault: true,
74
+ });
75
+ const listed = yield* config.listEnvironments();
76
+ assert.equal(listed.find((environment) => environment.name === "home")?.default, false);
77
+ assert.equal(listed.find((environment) => environment.name === "work")?.default, true);
78
+ }).pipe(Effect.provide(t3ConfigDepsLayer(homeDir))),
79
+ ),
80
+ Effect.provide(ConfigPlatformLayer),
81
+ Effect.scoped,
82
+ ),
83
+ );
84
+
85
+ it.effect("clears default when removing the default environment", () =>
86
+ Effect.gen(function* () {
87
+ return yield* makeTempHomeScoped("t3cli-config-");
88
+ }).pipe(
89
+ Effect.flatMap((homeDir) =>
90
+ Effect.gen(function* () {
91
+ const config = yield* T3Config;
92
+ yield* config.upsertEnvironment({
93
+ name: "home",
94
+ url: "https://home.example",
95
+ token: "home-token",
96
+ local: false,
97
+ });
98
+ yield* config.upsertEnvironment({
99
+ name: "work",
100
+ url: "https://work.example",
101
+ token: "work-token",
102
+ local: false,
103
+ });
104
+ yield* config.removeEnvironment("home");
105
+ const defaultName = yield* config.getDefaultEnvironmentName();
106
+ assert.equal(defaultName, undefined);
107
+ const listed = yield* config.listEnvironments();
108
+ assert.equal(
109
+ listed.every((environment) => !environment.default),
110
+ true,
111
+ );
112
+ }).pipe(Effect.provide(t3ConfigDepsLayer(homeDir))),
113
+ ),
114
+ Effect.provide(ConfigPlatformLayer),
115
+ Effect.scoped,
116
+ ),
117
+ );
118
+
119
+ it.effect("resolves selected environment from config selection service", () =>
120
+ Effect.gen(function* () {
121
+ return yield* makeTempHomeScoped("t3cli-config-");
122
+ }).pipe(
123
+ Effect.flatMap((homeDir) =>
124
+ Effect.gen(function* () {
125
+ const config = yield* T3Config;
126
+ yield* config.upsertEnvironment({
127
+ name: "home",
128
+ url: "https://home.example",
129
+ token: "home-token",
130
+ local: false,
131
+ });
132
+ yield* config.upsertEnvironment({
133
+ name: "work",
134
+ url: "https://work.example",
135
+ token: "work-token",
136
+ local: true,
137
+ });
138
+ const resolved = yield* config.resolve();
139
+ assert.equal(resolved.source, "config");
140
+ if (resolved.source === "config") {
141
+ assert.equal(resolved.environment, "work");
142
+ assert.equal(resolved.token, "work-token");
143
+ }
144
+ }).pipe(Effect.provide(t3ConfigDepsLayer(homeDir, { selection: "work" }))),
145
+ ),
146
+ Effect.provide(ConfigPlatformLayer),
147
+ Effect.scoped,
148
+ ),
149
+ );
150
+
151
+ it.effect(
152
+ "resolves env override with local=false even when selected stored environment is local",
153
+ () =>
154
+ Effect.gen(function* () {
155
+ return yield* makeTempHomeScoped("t3cli-config-");
156
+ }).pipe(
157
+ Effect.flatMap((homeDir) =>
158
+ Effect.gen(function* () {
159
+ const config = yield* T3Config;
160
+ yield* config.upsertEnvironment({
161
+ name: "work",
162
+ url: "http://localhost:8787",
163
+ token: "work-token",
164
+ local: true,
165
+ });
166
+ const resolved = yield* config.resolve();
167
+ assert.equal(resolved.source, "env");
168
+ assert.equal(resolved.local, false);
169
+ assert.equal(resolved.url, "https://remote.example");
170
+ assert.equal(resolved.token, "env-token");
171
+ }).pipe(
172
+ Effect.provide(
173
+ t3ConfigDepsLayer(homeDir, {
174
+ selection: "work",
175
+ env: {
176
+ T3CODE_URL: "https://remote.example",
177
+ T3CODE_TOKEN: "env-token",
178
+ },
179
+ }),
180
+ ),
181
+ ),
182
+ ),
183
+ Effect.provide(ConfigPlatformLayer),
184
+ Effect.scoped,
185
+ ),
186
+ );
187
+
188
+ it.effect("reads default environment name without decrypting tokens", () =>
189
+ Effect.gen(function* () {
190
+ return yield* makeTempHomeScoped("t3cli-config-");
191
+ }).pipe(
192
+ Effect.flatMap((homeDir) =>
193
+ Effect.gen(function* () {
194
+ const config = yield* T3Config;
195
+ yield* config.upsertEnvironment({
196
+ name: "home",
197
+ url: "https://home.example",
198
+ token: "home-token",
199
+ local: false,
200
+ });
201
+ const defaultName = yield* config.getDefaultEnvironmentName();
202
+ assert.equal(defaultName, "home");
203
+ }).pipe(Effect.provide(t3ConfigDepsLayer(homeDir))),
204
+ ),
205
+ Effect.provide(ConfigPlatformLayer),
206
+ Effect.scoped,
207
+ ),
208
+ );
209
+ });
@@ -0,0 +1 @@
1
+ export { resolveConfigFilePath, resolveKeyFilePath, resolveT3cliConfigDir } from "./paths.ts";
@@ -0,0 +1,36 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import * as Path from "effect/Path";
4
+
5
+ import { loadT3CliEnv } from "../env/env.ts";
6
+ import { ConfigError } from "../error.ts";
7
+
8
+ export const resolveT3cliConfigDir = Effect.fn("resolveT3cliConfigDir")(function* () {
9
+ const path = yield* Path.Path;
10
+ const t3CliEnv = yield* loadT3CliEnv.pipe(
11
+ Effect.mapError(
12
+ (error) => new ConfigError({ message: "failed to load CLI environment", cause: error }),
13
+ ),
14
+ );
15
+ const home = yield* Option.match(t3CliEnv.home, {
16
+ onNone: () => Effect.die("HOME is not set"),
17
+ onSome: Effect.succeed,
18
+ });
19
+ const root = Option.match(t3CliEnv.xdgConfigHome, {
20
+ onNone: () => path.join(home, ".config"),
21
+ onSome: (xdgConfigHome) => xdgConfigHome,
22
+ });
23
+ return path.join(root, "t3cli");
24
+ });
25
+
26
+ export const resolveConfigFilePath = Effect.fn("resolveConfigFilePath")(function* () {
27
+ const path = yield* Path.Path;
28
+ const configDir = yield* resolveT3cliConfigDir();
29
+ return path.join(configDir, "config.json");
30
+ });
31
+
32
+ export const resolveKeyFilePath = Effect.fn("resolveKeyFilePath")(function* () {
33
+ const path = yield* Path.Path;
34
+ const configDir = yield* resolveT3cliConfigDir();
35
+ return path.join(configDir, "key");
36
+ });
@@ -0,0 +1,21 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as FileSystem from "effect/FileSystem";
3
+
4
+ import { ConfigError } from "../error.ts";
5
+
6
+ const privateFileMode = 0o600;
7
+
8
+ export const hardenPrivateFileMode = Effect.fn("hardenPrivateFileMode")(function* (
9
+ filePath: string,
10
+ label: "config" | "credential key",
11
+ ) {
12
+ const fs = yield* FileSystem.FileSystem;
13
+ yield* fs
14
+ .chmod(filePath, privateFileMode)
15
+ .pipe(
16
+ Effect.mapError(
17
+ (error) =>
18
+ new ConfigError({ message: `failed to set ${label} file permissions`, cause: error }),
19
+ ),
20
+ );
21
+ });
@@ -0,0 +1,71 @@
1
+ import "vite-plus/test/config";
2
+
3
+ import * as Cause from "effect/Cause";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Exit from "effect/Exit";
6
+ import * as Option from "effect/Option";
7
+ import { assert, describe, it } from "@effect/vitest";
8
+
9
+ import { ConfigError } from "../error.ts";
10
+ import { T3CredentialCrypto } from "../credential/service.ts";
11
+ import { ConfigPlatformLayer } from "../platform.test-utils.ts";
12
+ import { makeTempHomeScoped } from "../temp-home.test-utils.ts";
13
+ import { t3CredentialCryptoDepsLayer } from "../credential/service.test-utils.ts";
14
+ import { migrateV1FileToEncrypted } from "./migration.ts";
15
+
16
+ describe("migrateV1FileToEncrypted", () => {
17
+ it.effect("migrates v1 flat config and roundtrips encrypted tokens", () =>
18
+ Effect.gen(function* () {
19
+ return yield* makeTempHomeScoped("t3cli-migration-");
20
+ }).pipe(
21
+ Effect.flatMap((homeDir) =>
22
+ Effect.gen(function* () {
23
+ const migrated = yield* migrateV1FileToEncrypted({
24
+ url: "https://app.example.com",
25
+ token: "secret-token",
26
+ local: false,
27
+ });
28
+ assert.equal(migrated.default, "app.example.com");
29
+ const crypto = yield* T3CredentialCrypto;
30
+ const token = yield* crypto.decrypt({
31
+ environmentName: "app.example.com",
32
+ url: "https://app.example.com",
33
+ local: false,
34
+ token: migrated.environments["app.example.com"]!.token,
35
+ });
36
+ assert.equal(token, "secret-token");
37
+ }).pipe(Effect.provide(t3CredentialCryptoDepsLayer(homeDir))),
38
+ ),
39
+ Effect.provide(ConfigPlatformLayer),
40
+ Effect.scoped,
41
+ ),
42
+ );
43
+
44
+ it.effect("maps invalid v1 urls to ConfigError when migrating directly", () =>
45
+ Effect.gen(function* () {
46
+ return yield* makeTempHomeScoped("t3cli-migrate-url-error-");
47
+ }).pipe(
48
+ Effect.flatMap((homeDir) =>
49
+ Effect.gen(function* () {
50
+ const exit = yield* migrateV1FileToEncrypted({
51
+ url: "not-a-url",
52
+ token: "secret-token",
53
+ local: false,
54
+ }).pipe(Effect.provide(t3CredentialCryptoDepsLayer(homeDir)), Effect.exit);
55
+ assert.isTrue(Exit.isFailure(exit));
56
+ if (!Exit.isFailure(exit)) {
57
+ return;
58
+ }
59
+ const error = Cause.findErrorOption(exit.cause);
60
+ assert.isTrue(Option.isSome(error));
61
+ if (Option.isSome(error)) {
62
+ assert.instanceOf(error.value, ConfigError);
63
+ assert.equal(error.value.message, "invalid url");
64
+ }
65
+ }),
66
+ ),
67
+ Effect.provide(ConfigPlatformLayer),
68
+ Effect.scoped,
69
+ ),
70
+ );
71
+ });
@@ -0,0 +1,82 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import * as Schema from "effect/Schema";
4
+
5
+ import { T3CredentialCrypto } from "../credential/service.ts";
6
+ import { migrateV1EnvironmentName, validateEnvironmentName } from "../environment-name/name.ts";
7
+ import { ConfigError } from "../error.ts";
8
+ import {
9
+ StoredConfigV1FileSchema,
10
+ StoredConfigV2FileSchema,
11
+ type StoredConfigV1File,
12
+ } from "./schema.ts";
13
+ import type { EncryptedConfig } from "../types.ts";
14
+ import { normalizeHttpBaseUrl } from "../url/url.ts";
15
+
16
+ export const emptyEncryptedConfig = (): EncryptedConfig => ({
17
+ version: 2,
18
+ environments: {},
19
+ });
20
+
21
+ export const readEncryptedConfigFromValue = Effect.fn("readEncryptedConfigFromValue")(function* (
22
+ value: unknown,
23
+ ) {
24
+ return yield* Effect.gen(function* () {
25
+ const v2 = yield* Schema.decodeUnknownEffect(StoredConfigV2FileSchema)(value).pipe(
26
+ Effect.option,
27
+ );
28
+ if (Option.isSome(v2)) {
29
+ return { config: v2.value, migratedFromV1: false as const };
30
+ }
31
+ const v1 = yield* Schema.decodeUnknownEffect(StoredConfigV1FileSchema)(value);
32
+ const migrated = yield* migrateV1FileToEncrypted(v1);
33
+ return { config: migrated, migratedFromV1: true as const };
34
+ }).pipe(
35
+ Effect.catchTags({
36
+ SchemaError: (error) =>
37
+ Effect.fail(new ConfigError({ message: "failed to read config", cause: error })),
38
+ }),
39
+ );
40
+ });
41
+
42
+ export const migrateV1FileToEncrypted = Effect.fn("migrateV1FileToEncrypted")(function* (
43
+ config: StoredConfigV1File,
44
+ ) {
45
+ if (
46
+ (config.url === undefined || config.url.length === 0) &&
47
+ (config.token === undefined || config.token.length === 0)
48
+ ) {
49
+ return emptyEncryptedConfig();
50
+ }
51
+ if (config.url === undefined || config.token === undefined) {
52
+ return yield* Effect.fail(
53
+ new ConfigError({ message: "failed to read config: incomplete v1 credentials" }),
54
+ );
55
+ }
56
+ const name = yield* migrateV1EnvironmentName(config).pipe(
57
+ Effect.flatMap((migratedName) =>
58
+ validateEnvironmentName(migratedName).pipe(Effect.as(migratedName)),
59
+ ),
60
+ );
61
+ const normalizedUrl = yield* normalizeHttpBaseUrl(config.url).pipe(
62
+ Effect.mapError((error) => new ConfigError({ message: error.message, cause: error.cause })),
63
+ );
64
+ const crypto = yield* T3CredentialCrypto;
65
+ const token = yield* crypto.encrypt({
66
+ environmentName: name,
67
+ url: normalizedUrl,
68
+ local: config.local ?? false,
69
+ token: config.token,
70
+ });
71
+ return {
72
+ version: 2 as const,
73
+ default: name,
74
+ environments: {
75
+ [name]: {
76
+ url: normalizedUrl,
77
+ local: config.local ?? false,
78
+ token,
79
+ },
80
+ },
81
+ } satisfies EncryptedConfig;
82
+ });
@@ -0,0 +1,113 @@
1
+ import "vite-plus/test/config";
2
+
3
+ import * as Effect from "effect/Effect";
4
+ import * as FileSystem from "effect/FileSystem";
5
+ import * as Path from "effect/Path";
6
+ import * as Schema from "effect/Schema";
7
+ import { assert, describe, it } from "@effect/vitest";
8
+
9
+ import { T3Config } from "../config.ts";
10
+ import { ConfigPlatformLayer } from "../platform.test-utils.ts";
11
+ import { makeTempHomeScoped } from "../temp-home.test-utils.ts";
12
+ import { t3ConfigDepsLayer } from "../layer.test-utils.ts";
13
+ import { StoredConfigV1FileJson, StoredConfigV2FileJson } from "./schema.ts";
14
+
15
+ describe("config file persistence", () => {
16
+ it.effect("persists v1 config as encrypted v2 on first read", () =>
17
+ Effect.gen(function* () {
18
+ return yield* makeTempHomeScoped("t3cli-persist-");
19
+ }).pipe(
20
+ Effect.flatMap((homeDir) =>
21
+ Effect.gen(function* () {
22
+ const fs = yield* FileSystem.FileSystem;
23
+ const path = yield* Path.Path;
24
+ const secretToken = "legacy-plaintext-token";
25
+ const configPath = path.join(homeDir, ".config", "t3cli", "config.json");
26
+ yield* fs.makeDirectory(path.dirname(configPath), { recursive: true });
27
+ const legacyConfig = yield* Schema.encodeEffect(StoredConfigV1FileJson)({
28
+ url: "https://home.example",
29
+ token: secretToken,
30
+ local: false,
31
+ });
32
+ yield* fs.writeFileString(configPath, `${legacyConfig}\n`, { mode: 0o600 });
33
+ yield* Effect.gen(function* () {
34
+ const config = yield* T3Config;
35
+ yield* config.listEnvironments();
36
+ }).pipe(Effect.provide(t3ConfigDepsLayer(homeDir)));
37
+ const raw = yield* fs.readFileString(configPath);
38
+ assert.equal(raw.includes(secretToken), false);
39
+ const persisted = yield* Schema.decodeUnknownEffect(StoredConfigV2FileJson)(raw);
40
+ assert.equal(persisted.version, 2);
41
+ const environment = Object.values(persisted.environments)[0];
42
+ assert.isDefined(environment);
43
+ assert.equal(environment.token.kind, "encrypted");
44
+ }),
45
+ ),
46
+ Effect.provide(ConfigPlatformLayer),
47
+ Effect.scoped,
48
+ ),
49
+ );
50
+
51
+ it.effect("never writes plaintext tokens to config.json", () =>
52
+ Effect.gen(function* () {
53
+ return yield* makeTempHomeScoped("t3cli-persist-");
54
+ }).pipe(
55
+ Effect.flatMap((homeDir) =>
56
+ Effect.gen(function* () {
57
+ const fs = yield* FileSystem.FileSystem;
58
+ const path = yield* Path.Path;
59
+ const secretToken = "super-secret-token-value";
60
+ yield* Effect.gen(function* () {
61
+ const config = yield* T3Config;
62
+ yield* config.upsertEnvironment({
63
+ name: "home",
64
+ url: "https://home.example",
65
+ token: secretToken,
66
+ local: false,
67
+ });
68
+ }).pipe(Effect.provide(t3ConfigDepsLayer(homeDir)));
69
+ const configPath = path.join(homeDir, ".config", "t3cli", "config.json");
70
+ const raw = yield* fs.readFileString(configPath);
71
+ assert.equal(raw.includes(secretToken), false);
72
+ const persisted = yield* Schema.decodeUnknownEffect(StoredConfigV2FileJson)(raw);
73
+ assert.equal(persisted.environments.home?.token.kind, "encrypted");
74
+ }),
75
+ ),
76
+ Effect.provide(ConfigPlatformLayer),
77
+ Effect.scoped,
78
+ ),
79
+ );
80
+
81
+ it.effect("hardens existing config file permissions to 0600 on write", () =>
82
+ Effect.gen(function* () {
83
+ return yield* makeTempHomeScoped("t3cli-persist-");
84
+ }).pipe(
85
+ Effect.flatMap((homeDir) =>
86
+ Effect.gen(function* () {
87
+ const fs = yield* FileSystem.FileSystem;
88
+ const path = yield* Path.Path;
89
+ const configPath = path.join(homeDir, ".config", "t3cli", "config.json");
90
+ yield* fs.makeDirectory(path.dirname(configPath), { recursive: true });
91
+ const emptyConfig = yield* Schema.encodeEffect(StoredConfigV2FileJson)({
92
+ version: 2,
93
+ environments: {},
94
+ });
95
+ yield* fs.writeFileString(configPath, `${emptyConfig}\n`, { mode: 0o644 });
96
+ yield* Effect.gen(function* () {
97
+ const config = yield* T3Config;
98
+ yield* config.upsertEnvironment({
99
+ name: "home",
100
+ url: "https://home.example",
101
+ token: "home-token",
102
+ local: false,
103
+ });
104
+ }).pipe(Effect.provide(t3ConfigDepsLayer(homeDir)));
105
+ const configStat = yield* fs.stat(configPath);
106
+ assert.equal(configStat.mode & 0o777, 0o600);
107
+ }),
108
+ ),
109
+ Effect.provide(ConfigPlatformLayer),
110
+ Effect.scoped,
111
+ ),
112
+ );
113
+ });
@@ -0,0 +1,65 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as FileSystem from "effect/FileSystem";
3
+ import * as Path from "effect/Path";
4
+ import * as Schema from "effect/Schema";
5
+
6
+ import { ConfigError, isPlatformNotFoundError } from "../error.ts";
7
+ import { hardenPrivateFileMode } from "./file-mode.ts";
8
+ import { emptyEncryptedConfig, readEncryptedConfigFromValue } from "./migration.ts";
9
+ import { resolveConfigFilePath } from "../paths/paths.ts";
10
+ import { UnknownConfigFileJson, StoredConfigV2FileJson } from "./schema.ts";
11
+ import type { EncryptedConfig } from "../types.ts";
12
+
13
+ export const readEncryptedConfigFile = Effect.fn("readEncryptedConfigFile")(function* () {
14
+ const fs = yield* FileSystem.FileSystem;
15
+ const configFilePath = yield* resolveConfigFilePath();
16
+ const raw = yield* fs.readFileString(configFilePath).pipe(
17
+ Effect.catchTags({
18
+ PlatformError: (error) =>
19
+ isPlatformNotFoundError(error)
20
+ ? Effect.succeed(undefined)
21
+ : Effect.fail(new ConfigError({ message: "failed to read config", cause: error })),
22
+ }),
23
+ );
24
+ if (raw === undefined) {
25
+ return emptyEncryptedConfig();
26
+ }
27
+ const value = yield* Schema.decodeUnknownEffect(UnknownConfigFileJson)(raw).pipe(
28
+ Effect.catchTags({
29
+ SchemaError: (error) =>
30
+ Effect.fail(new ConfigError({ message: "failed to read config", cause: error })),
31
+ }),
32
+ );
33
+ const read = yield* readEncryptedConfigFromValue(value);
34
+ if (read.migratedFromV1) {
35
+ yield* writeEncryptedConfigFile(read.config);
36
+ }
37
+ return read.config;
38
+ });
39
+
40
+ export const writeEncryptedConfigFile = Effect.fn("writeEncryptedConfigFile")(function* (
41
+ config: EncryptedConfig,
42
+ ) {
43
+ const fs = yield* FileSystem.FileSystem;
44
+ const path = yield* Path.Path;
45
+ const configFilePath = yield* resolveConfigFilePath();
46
+ yield* fs.makeDirectory(path.dirname(configFilePath), { recursive: true, mode: 0o700 }).pipe(
47
+ Effect.catchTags({
48
+ PlatformError: (error) =>
49
+ Effect.fail(new ConfigError({ message: "failed to write config", cause: error })),
50
+ }),
51
+ );
52
+ const encoded = yield* Schema.encodeEffect(StoredConfigV2FileJson)(config).pipe(
53
+ Effect.catchTags({
54
+ SchemaError: (error) =>
55
+ Effect.fail(new ConfigError({ message: "failed to write config", cause: error })),
56
+ }),
57
+ );
58
+ yield* fs.writeFileString(configFilePath, `${encoded}\n`, { mode: 0o600 }).pipe(
59
+ Effect.catchTags({
60
+ PlatformError: (error) =>
61
+ Effect.fail(new ConfigError({ message: "failed to write config", cause: error })),
62
+ }),
63
+ );
64
+ yield* hardenPrivateFileMode(configFilePath, "config");
65
+ });
@@ -0,0 +1,37 @@
1
+ import * as Schema from "effect/Schema";
2
+
3
+ export const EncryptedTokenSchema = Schema.Struct({
4
+ kind: Schema.Literal("encrypted"),
5
+ alg: Schema.Literal("aes-256-gcm"),
6
+ key: Schema.Literal("default"),
7
+ nonce: Schema.String,
8
+ ciphertext: Schema.String,
9
+ tag: Schema.String,
10
+ });
11
+
12
+ export const StoredEnvironmentFileSchema = Schema.Struct({
13
+ url: Schema.String,
14
+ local: Schema.Boolean,
15
+ token: EncryptedTokenSchema,
16
+ });
17
+
18
+ export const StoredConfigV2FileSchema = Schema.Struct({
19
+ version: Schema.Literal(2),
20
+ default: Schema.optionalKey(Schema.String),
21
+ environments: Schema.Record(Schema.String, StoredEnvironmentFileSchema),
22
+ });
23
+
24
+ export const StoredConfigV1FileSchema = Schema.Struct({
25
+ url: Schema.optionalKey(Schema.String),
26
+ token: Schema.optionalKey(Schema.String),
27
+ local: Schema.optionalKey(Schema.Boolean),
28
+ });
29
+
30
+ export type EncryptedToken = Schema.Schema.Type<typeof EncryptedTokenSchema>;
31
+ export type StoredEnvironmentFile = Schema.Schema.Type<typeof StoredEnvironmentFileSchema>;
32
+ export type StoredConfigV2File = Schema.Schema.Type<typeof StoredConfigV2FileSchema>;
33
+ export type StoredConfigV1File = Schema.Schema.Type<typeof StoredConfigV1FileSchema>;
34
+
35
+ export const StoredConfigV1FileJson = Schema.fromJsonString(StoredConfigV1FileSchema);
36
+ export const StoredConfigV2FileJson = Schema.fromJsonString(StoredConfigV2FileSchema);
37
+ export const UnknownConfigFileJson = Schema.UnknownFromJsonString;
@@ -0,0 +1,20 @@
1
+ import * as Layer from "effect/Layer";
2
+ import * as NodeChildProcessSpawner from "@effect/platform-node/NodeChildProcessSpawner";
3
+ import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
4
+ import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
5
+ import * as NodePath from "@effect/platform-node/NodePath";
6
+ import * as NodeStdio from "@effect/platform-node/NodeStdio";
7
+ import * as NodeTerminal from "@effect/platform-node/NodeTerminal";
8
+
9
+ export const ConfigPlatformLayer = Layer.mergeAll(
10
+ NodeFileSystem.layer,
11
+ NodePath.layer,
12
+ NodeCrypto.layer,
13
+ );
14
+
15
+ export const CliCommandPlatformLayer = Layer.mergeAll(
16
+ ConfigPlatformLayer,
17
+ NodeStdio.layer,
18
+ NodeTerminal.layer,
19
+ NodeChildProcessSpawner.layer,
20
+ );
@@ -0,0 +1,20 @@
1
+ import type { EncryptedConfig } from "../types.ts";
2
+
3
+ export const sampleEncrypted = (input: {
4
+ readonly default?: string;
5
+ readonly environments?: EncryptedConfig["environments"];
6
+ }): EncryptedConfig => ({
7
+ version: 2,
8
+ ...(input.default !== undefined ? { default: input.default } : {}),
9
+ environments: input.environments ?? {},
10
+ });
11
+
12
+ export const sampleEncryptedToken = () =>
13
+ ({
14
+ kind: "encrypted" as const,
15
+ alg: "aes-256-gcm" as const,
16
+ key: "default" as const,
17
+ nonce: "n",
18
+ ciphertext: "c",
19
+ tag: "t",
20
+ }) as const;