@astrale-os/cli 1.0.0-beta.2 → 1.0.0-beta.20

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 (282) hide show
  1. package/README.md +16 -1
  2. package/dist/astrale.js +51105 -58057
  3. package/dist/public/connect-core.js +2085 -30702
  4. package/dist/public/keys/index.js +884 -29476
  5. package/dist/public/paths/index.js +13101 -41452
  6. package/dist/types/admin/contract.d.ts +26 -0
  7. package/dist/types/admin/instance/client.d.ts +20 -0
  8. package/dist/types/admin/instance/index.d.ts +2 -0
  9. package/dist/types/admin/instance/model.d.ts +6 -10
  10. package/dist/types/connection/auth.d.ts +3 -0
  11. package/dist/types/connection/call.d.ts +3 -0
  12. package/dist/types/connection/command.d.ts +27 -0
  13. package/dist/types/connection/credential.d.ts +14 -0
  14. package/dist/types/connection/errors.d.ts +1 -0
  15. package/dist/types/connection/exchange.d.ts +9 -0
  16. package/dist/types/connection/failure/classify.d.ts +2 -0
  17. package/dist/types/connection/failure/index.d.ts +5 -0
  18. package/dist/types/connection/failure/model.d.ts +25 -0
  19. package/dist/types/connection/failure/render.d.ts +3 -0
  20. package/dist/types/connection/index.d.ts +8 -0
  21. package/dist/types/connection/reasons.d.ts +37 -0
  22. package/dist/types/connection/self.d.ts +37 -0
  23. package/dist/types/connection/session.d.ts +31 -0
  24. package/dist/types/connection/target.d.ts +38 -0
  25. package/dist/types/errors.d.ts +1 -1
  26. package/dist/types/lib/admin-instance.d.ts +17 -0
  27. package/dist/types/lib/admin-target.d.ts +11 -36
  28. package/dist/types/lib/config.d.ts +7 -59
  29. package/dist/types/lib/failure-debug.d.ts +1 -0
  30. package/dist/types/lib/idp.d.ts +25 -173
  31. package/dist/types/lib/instance-target.d.ts +1 -1
  32. package/dist/types/lib/instance.d.ts +30 -88
  33. package/dist/types/lib/log.d.ts +5 -3
  34. package/dist/types/lib/output.d.ts +1 -1
  35. package/dist/types/lib/proc.d.ts +43 -0
  36. package/dist/types/lib/self.d.ts +9 -0
  37. package/dist/types/lib/update.d.ts +109 -0
  38. package/dist/types/lib/validation.d.ts +4 -1
  39. package/package.json +15 -20
  40. package/src/__tests__/fixtures/publication.ts +20 -0
  41. package/src/admin/.spec/architecture.md +12 -5
  42. package/src/admin/__tests__/fixture.ts +25 -0
  43. package/src/admin/catalog/.spec/api.d.ts +1 -3
  44. package/src/admin/catalog/.spec/architecture.md +5 -4
  45. package/src/admin/catalog/__tests__/client.test.ts +143 -69
  46. package/src/admin/catalog/client.ts +40 -54
  47. package/src/admin/catalog/model.ts +3 -4
  48. package/src/admin/contract.ts +46 -0
  49. package/src/admin/graph/.spec/api.d.ts +4 -10
  50. package/src/admin/graph/nodes.ts +1 -1
  51. package/src/admin/instance/.spec/api.d.ts +5 -9
  52. package/src/admin/instance/.spec/architecture.md +7 -7
  53. package/src/admin/instance/__tests__/client.test.ts +157 -175
  54. package/src/admin/instance/client.ts +39 -128
  55. package/src/admin/instance/index.ts +1 -2
  56. package/src/admin/instance/model.ts +12 -18
  57. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  58. package/src/commands/__tests__/call-describe.test.ts +46 -33
  59. package/src/commands/__tests__/call.test.ts +34 -0
  60. package/src/commands/__tests__/domain-install-operation.test.ts +7 -22
  61. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  62. package/src/commands/__tests__/domain-list.test.ts +5 -25
  63. package/src/commands/__tests__/install-direct.test.ts +115 -8
  64. package/src/commands/__tests__/install-identity-override.test.ts +3 -14
  65. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  66. package/src/commands/__tests__/instance-status.test.ts +139 -0
  67. package/src/commands/__tests__/introspect-parse.test.ts +80 -1
  68. package/src/commands/__tests__/logs.test.ts +250 -2
  69. package/src/commands/__tests__/read-commands.test.ts +62 -10
  70. package/src/commands/__tests__/token-ttl.test.ts +49 -4
  71. package/src/commands/__tests__/update.test.ts +25 -3
  72. package/src/commands/__tests__/view.test.ts +49 -0
  73. package/src/commands/call-describe.ts +60 -68
  74. package/src/commands/call.ts +71 -59
  75. package/src/commands/domain/install.ts +54 -35
  76. package/src/commands/domain/list.ts +1 -1
  77. package/src/commands/domain/publish.ts +14 -11
  78. package/src/commands/domain/uninstall.ts +2 -2
  79. package/src/commands/get.ts +14 -15
  80. package/src/commands/identity/__tests__/register.test.ts +3 -3
  81. package/src/commands/identity/register.ts +5 -4
  82. package/src/commands/instance/create.ts +8 -16
  83. package/src/commands/instance/delete.ts +12 -9
  84. package/src/commands/instance/list.ts +2 -2
  85. package/src/commands/instance/status.ts +66 -20
  86. package/src/commands/instance/use.ts +16 -12
  87. package/src/commands/introspect.ts +48 -35
  88. package/src/commands/logs.ts +123 -33
  89. package/src/commands/mutate.ts +6 -5
  90. package/src/commands/query.ts +35 -32
  91. package/src/commands/token.ts +42 -27
  92. package/src/commands/ui/__tests__/commands.test.ts +152 -0
  93. package/src/commands/ui/add.ts +51 -0
  94. package/src/commands/ui/doctor.ts +13 -0
  95. package/src/commands/ui/init.ts +38 -0
  96. package/src/commands/ui/list.ts +26 -0
  97. package/src/commands/ui/preset-apply.ts +19 -0
  98. package/src/commands/ui/preset-list.ts +12 -0
  99. package/src/commands/ui/shared.ts +25 -0
  100. package/src/commands/update.ts +31 -11
  101. package/src/commands/view.ts +82 -37
  102. package/src/connection/.spec/api.d.ts +7 -8
  103. package/src/connection/.spec/architecture.md +5 -4
  104. package/src/connection/.spec/flows/session.ts +2 -2
  105. package/src/connection/.spec/laws/connection.ts +6 -2
  106. package/src/connection/.spec/layout.ts +1 -0
  107. package/src/connection/__tests__/credential.test.ts +20 -1
  108. package/src/connection/__tests__/errors.test.ts +27 -20
  109. package/src/connection/__tests__/exchange.test.ts +30 -1
  110. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  111. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  112. package/src/connection/__tests__/self.test.ts +31 -11
  113. package/src/connection/__tests__/session.test.ts +3 -3
  114. package/src/connection/__tests__/target.test.ts +1 -0
  115. package/src/connection/auth.ts +13 -0
  116. package/src/connection/call.ts +1 -1
  117. package/src/connection/credential.ts +2 -2
  118. package/src/connection/errors.ts +1 -355
  119. package/src/connection/exchange.ts +47 -23
  120. package/src/connection/failure/classify.ts +110 -0
  121. package/src/connection/failure/index.ts +18 -0
  122. package/src/connection/failure/model.ts +28 -0
  123. package/src/connection/failure/render.ts +116 -0
  124. package/src/connection/reasons.ts +16 -0
  125. package/src/connection/self.ts +39 -21
  126. package/src/connection/session.ts +19 -8
  127. package/src/errors.ts +3 -2
  128. package/src/graph/.spec/api.d.ts +12 -5
  129. package/src/graph/.spec/layout.ts +9 -1
  130. package/src/graph/__tests__/mutation.test.ts +2 -2
  131. package/src/graph/__tests__/query.test.ts +25 -6
  132. package/src/graph/class.ts +30 -0
  133. package/src/graph/index.ts +1 -0
  134. package/src/graph/query.ts +21 -32
  135. package/src/identity/.spec/api.d.ts +2 -2
  136. package/src/identity/__tests__/registration.test.ts +1 -1
  137. package/src/identity/registration.ts +3 -3
  138. package/src/keys/__tests__/keys.test.ts +4 -0
  139. package/src/keys/credential.ts +1 -0
  140. package/src/lib/__tests__/admin-target.test.ts +12 -0
  141. package/src/lib/__tests__/binary.test.ts +16 -1
  142. package/src/lib/__tests__/command-dx.test.ts +1 -1
  143. package/src/lib/__tests__/domain-publication.test.ts +10 -16
  144. package/src/lib/__tests__/idp.test.ts +34 -4
  145. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  146. package/src/lib/__tests__/instance-target.test.ts +7 -39
  147. package/src/lib/__tests__/log-errors.test.ts +64 -0
  148. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  149. package/src/lib/__tests__/update.test.ts +264 -11
  150. package/src/lib/__tests__/view-assets.test.ts +14 -0
  151. package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
  152. package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
  153. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  154. package/src/lib/__tests__/view-server.test.ts +26 -3
  155. package/src/lib/__tests__/view-session.test.ts +2 -1
  156. package/src/lib/admin-instance.ts +14 -8
  157. package/src/lib/admin-target.ts +21 -2
  158. package/src/lib/binary.ts +22 -5
  159. package/src/lib/command-dx.ts +9 -9
  160. package/src/lib/domain-publication.ts +9 -7
  161. package/src/lib/failure-debug.ts +26 -0
  162. package/src/lib/idp.ts +34 -3
  163. package/src/lib/instance-candidates.ts +2 -2
  164. package/src/lib/instance-target.ts +3 -14
  165. package/src/lib/instance.ts +16 -11
  166. package/src/lib/log.ts +16 -9
  167. package/src/lib/output.ts +1 -1
  168. package/src/lib/proc.ts +7 -2
  169. package/src/lib/provision-instance.ts +34 -57
  170. package/src/lib/update.ts +298 -45
  171. package/src/lib/validation.ts +2 -2
  172. package/src/lib/view/assets.ts +11 -2
  173. package/src/lib/view/external-open-intent.ts +41 -0
  174. package/src/lib/view/external-open-origins.ts +37 -0
  175. package/src/lib/view/host-capabilities.ts +17 -0
  176. package/src/lib/view/resolve.ts +1 -1
  177. package/src/lib/view/server.ts +18 -3
  178. package/src/lib/view/session.ts +2 -0
  179. package/src/program/.spec/api.d.ts +1 -0
  180. package/src/program/__tests__/program.test.ts +163 -6
  181. package/src/program/argv.ts +14 -0
  182. package/src/program/build.ts +26 -4
  183. package/src/program/command.ts +1 -0
  184. package/src/program/index.ts +1 -0
  185. package/src/program/registry.ts +2 -1
  186. package/src/setup/__tests__/instance-step.test.ts +27 -10
  187. package/src/setup/steps/instance.ts +22 -15
  188. package/src/telemetry/__tests__/recorder.test.ts +1 -0
  189. package/src/telemetry/recorder.ts +9 -1
  190. package/src/ui/.spec/api.d.ts +14 -0
  191. package/src/ui/.spec/architecture.md +10 -0
  192. package/src/ui/.spec/laws.ts +36 -0
  193. package/src/ui/.spec/layout.ts +16 -0
  194. package/src/ui/__tests__/ui.test.ts +981 -0
  195. package/src/ui/index.ts +13 -0
  196. package/src/ui/lock.ts +87 -0
  197. package/src/ui/model.ts +83 -0
  198. package/src/ui/operations.ts +710 -0
  199. package/src/ui/project.ts +219 -0
  200. package/src/ui/release.ts +321 -0
  201. package/src/ui/runner.ts +18 -0
  202. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  203. package/studio/client/dist/assets/index-7YHdeCxp.js +81 -0
  204. package/studio/client/dist/assets/index-C0FAnwNw.css +2 -0
  205. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  206. package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +1 -0
  207. package/studio/client/dist/assets/schema-studio-CABpTfvH.js +8 -0
  208. package/studio/client/dist/index.html +3 -2
  209. package/studio/package.json +7 -13
  210. package/studio/server/agent/ask.test.ts +1 -1
  211. package/studio/server/agent/bridge/grant.test.ts +1 -1
  212. package/studio/server/agent/bridge/routes.test.ts +1 -1
  213. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  214. package/studio/server/agent/harness/gateway/token.test.ts +1 -1
  215. package/studio/server/agent/harness/gateway/token.ts +3 -3
  216. package/studio/server/agent/prompts/anchors.test.ts +32 -36
  217. package/studio/server/agent/prompts/anchors.ts +20 -49
  218. package/studio/server/agent/prompts/system.test.ts +3 -2
  219. package/studio/server/agent/prompts/system.ts +3 -3
  220. package/studio/server/agent/routes.test.ts +1 -1
  221. package/studio/server/agent/run/coordinator.test.ts +1 -1
  222. package/studio/server/api/canvas.ts +0 -1
  223. package/studio/server/api-agent-loadout.test.ts +1 -1
  224. package/studio/server/api.test.ts +1 -1
  225. package/studio/server/cache.test.ts +23 -12
  226. package/studio/server/cache.ts +12 -15
  227. package/studio/server/domain.test.ts +50 -68
  228. package/studio/server/domain.ts +82 -63
  229. package/studio/server/handoff/copy.ts +1 -1
  230. package/studio/server/index.ts +1 -1
  231. package/studio/server/instances/probe.test.ts +1 -1
  232. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  233. package/studio/server/introspect/anatomy/views.ts +9 -14
  234. package/studio/server/introspect/anatomy-extras.test.ts +26 -42
  235. package/studio/server/introspect/anatomy.test.ts +22 -14
  236. package/studio/server/introspect/anatomy.ts +6 -9
  237. package/studio/server/introspect/bundle.ts +0 -3
  238. package/studio/server/introspect/canonical-schema.test.ts +94 -389
  239. package/studio/server/introspect/canonical-schema.ts +227 -457
  240. package/studio/server/introspect/core-extractor.ts +18 -127
  241. package/studio/server/introspect/core.ts +1 -2
  242. package/studio/server/introspect/diff.test.ts +11 -7
  243. package/studio/server/introspect/diff.ts +18 -55
  244. package/studio/server/introspect/extractor.ts +22 -117
  245. package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
  246. package/studio/server/introspect/overlay.test.ts +26 -54
  247. package/studio/server/introspect/overlay.ts +10 -37
  248. package/studio/server/introspect/revision.test.ts +1 -1
  249. package/studio/server/introspect/revision.ts +1 -1
  250. package/studio/server/introspect/runtime.test.ts +65 -183
  251. package/studio/server/introspect/runtime.ts +8 -25
  252. package/studio/server/introspect/schema-ir-json.test.ts +70 -0
  253. package/studio/server/introspect/schema-ir-json.ts +38 -68
  254. package/studio/server/introspect/schema-refs.test.ts +43 -88
  255. package/studio/server/introspect/schema-refs.ts +14 -49
  256. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  257. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  258. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  259. package/studio/server/state/baseline.test.ts +14 -9
  260. package/studio/server/state/baseline.ts +3 -5
  261. package/studio/server/state/visibility.ts +1 -5
  262. package/studio/server/views/model.ts +3 -3
  263. package/studio/server/views/target.test.ts +17 -74
  264. package/studio/server/views/target.ts +24 -50
  265. package/studio/server/watch.test.ts +11 -11
  266. package/studio/server/watch.ts +8 -4
  267. package/studio/server/workspace-watch.ts +2 -2
  268. package/studio/shared/contracts/schema.ts +38 -107
  269. package/studio/shared/contracts/surface.test.ts +13 -15
  270. package/studio/shared/contracts/workspace.ts +2 -11
  271. package/studio/shared/schema/identity.test.ts +20 -44
  272. package/studio/shared/schema/identity.ts +9 -23
  273. package/viewer/dist/main.js +30 -51
  274. package/.check-workspace.cjs +0 -40
  275. package/src/admin/binding.ts +0 -168
  276. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  277. package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
  278. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  279. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  280. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  281. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  282. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
@@ -1,22 +1,27 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { chmod, mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises'
2
+ import { chmod, mkdir, mkdtemp, readFile, rename, rm, symlink, writeFile } from 'node:fs/promises'
3
3
  import { tmpdir } from 'node:os'
4
- import { join } from 'node:path'
4
+ import { dirname, join } from 'node:path'
5
5
 
6
6
  import {
7
+ admitScriptInstall,
8
+ classifyUpdateExecution,
7
9
  DEFAULT_UPDATE_CHANNEL,
8
10
  InstallMetadataSchema,
11
+ readInstallMetadata,
9
12
  releaseBase,
13
+ replaceStandaloneCohort,
10
14
  shouldUpdate,
11
15
  updateAstrale,
12
16
  writeInstallMetadata,
13
17
  type InstallMetadata,
18
+ type UpdateExecution,
14
19
  } from '../update'
15
20
 
16
21
  async function makeFakeRelease(
17
22
  root: string,
18
23
  version: string,
19
- options: { binaryVersion?: string; legacyManifest?: boolean } = {},
24
+ options: { binaryVersion?: string; legacyManifest?: boolean; omitViewerIndex?: boolean } = {},
20
25
  ): Promise<string> {
21
26
  const release = join(root, 'release')
22
27
  const payload = join(root, 'payload')
@@ -28,9 +33,14 @@ async function makeFakeRelease(
28
33
  `#!/usr/bin/env sh\nif [ "$1" = "--version" ]; then echo "${binaryVersion}"; exit 0; fi\necho astrale\n`,
29
34
  )
30
35
  await chmod(join(payload, 'astrale'), 0o755)
36
+ await mkdir(join(payload, 'viewer', 'dist'), { recursive: true })
37
+ await writeFile(join(payload, 'viewer', 'dist', 'main.js'), 'viewer main\n')
38
+ if (!options.omitViewerIndex) {
39
+ await writeFile(join(payload, 'viewer', 'dist', 'index.html'), '<!doctype html>\n')
40
+ }
31
41
 
32
42
  const asset = join(release, 'astrale-darwin-arm64.tar.gz')
33
- const tar = Bun.spawn(['tar', '-C', payload, '-czf', asset, 'astrale'])
43
+ const tar = Bun.spawn(['tar', '-C', payload, '-czf', asset, 'astrale', 'viewer'])
34
44
  expect(await tar.exited).toBe(0)
35
45
  const shaProc = Bun.spawn(['shasum', '-a', '256', asset], { stdout: 'pipe' })
36
46
  const sha = (await new Response(shaProc.stdout).text()).trim().split(/\s+/)[0]
@@ -65,7 +75,7 @@ async function makeFakeRelease(
65
75
  async function makeInstall(
66
76
  root: string,
67
77
  version: string,
68
- ): Promise<{ meta: InstallMetadata; path: string }> {
78
+ ): Promise<{ meta: InstallMetadata; path: string; execution: UpdateExecution }> {
69
79
  const bin = join(root, 'bin', 'astrale')
70
80
  await mkdir(join(root, 'bin'), { recursive: true })
71
81
  await writeFile(
@@ -73,6 +83,9 @@ async function makeInstall(
73
83
  `#!/usr/bin/env sh\nif [ "$1" = "--version" ]; then echo "${version}"; exit 0; fi\necho old\n`,
74
84
  )
75
85
  await chmod(bin, 0o755)
86
+ await mkdir(join(root, 'bin', 'viewer', 'dist'), { recursive: true })
87
+ await writeFile(join(root, 'bin', 'viewer', 'dist', 'main.js'), 'old viewer main\n')
88
+ await writeFile(join(root, 'bin', 'viewer', 'dist', 'index.html'), 'old viewer html\n')
76
89
  const path = join(root, 'home', 'install.json')
77
90
  const meta: InstallMetadata = {
78
91
  method: 'script',
@@ -82,10 +95,33 @@ async function makeInstall(
82
95
  bin,
83
96
  }
84
97
  await writeInstallMetadata(meta, path)
85
- return { meta, path }
98
+ return { meta, path, execution: { kind: 'standalone', executable: bin } }
86
99
  }
87
100
 
88
101
  describe('update helpers', () => {
102
+ test('classifies only a Bun-compiled executable as standalone', () => {
103
+ expect(
104
+ classifyUpdateExecution({
105
+ bunVersion: '1.3.14',
106
+ executable: '/tmp/astrale',
107
+ entry: '/$bunfs/root/astrale',
108
+ }),
109
+ ).toEqual({ kind: 'standalone', executable: '/tmp/astrale' })
110
+ expect(
111
+ classifyUpdateExecution({
112
+ bunVersion: '1.3.14',
113
+ executable: '/opt/homebrew/bin/bun',
114
+ entry: '/tmp/astrale.ts',
115
+ }),
116
+ ).toEqual({ kind: 'package-managed', executable: '/opt/homebrew/bin/bun' })
117
+ expect(
118
+ classifyUpdateExecution({
119
+ executable: '/opt/homebrew/bin/node',
120
+ entry: '/tmp/astrale.js',
121
+ }),
122
+ ).toEqual({ kind: 'package-managed', executable: '/opt/homebrew/bin/node' })
123
+ })
124
+
89
125
  test('defaults missing install metadata to the beta channel', () => {
90
126
  expect(DEFAULT_UPDATE_CHANNEL).toBe('beta')
91
127
  expect(
@@ -118,10 +154,103 @@ describe('update helpers', () => {
118
154
  })
119
155
  })
120
156
 
157
+ describe('script install admission', () => {
158
+ test('a metadata commit failure restores the exact previous file', async () => {
159
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
160
+ const path = join(root, 'install.json')
161
+ const original = '{"exact":"previous metadata"}\n'
162
+ await writeFile(path, original)
163
+
164
+ await expect(
165
+ writeInstallMetadata(
166
+ {
167
+ method: 'script',
168
+ channel: 'beta',
169
+ version: '1.1.0',
170
+ repo: 'astrale-os/cli',
171
+ bin: '/tmp/astrale',
172
+ },
173
+ path,
174
+ {
175
+ mkdir,
176
+ rm,
177
+ writeFile,
178
+ rename: async (from, to) => {
179
+ if (String(from).endsWith('.next')) throw new Error('injected metadata commit failure')
180
+ await rename(from, to)
181
+ },
182
+ },
183
+ ),
184
+ ).rejects.toThrow('injected metadata commit failure')
185
+
186
+ expect(await readFile(path, 'utf8')).toBe(original)
187
+ await expect(readFile(`${path}.next`, 'utf8')).rejects.toMatchObject({ code: 'ENOENT' })
188
+ await expect(readFile(`${path}.previous`, 'utf8')).rejects.toMatchObject({ code: 'ENOENT' })
189
+ })
190
+
191
+ test('rejects malformed JSON with the stable metadata error', async () => {
192
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
193
+ const path = join(root, 'install.json')
194
+ await writeFile(path, '{')
195
+
196
+ await expect(readInstallMetadata(path)).rejects.toMatchObject({
197
+ code: 'UPDATE_BAD_INSTALL_METADATA',
198
+ })
199
+ })
200
+
201
+ test('admits a symlink only when it resolves to the recorded binary', async () => {
202
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
203
+ const { meta } = await makeInstall(root, '1.0.0')
204
+ const alias = join(root, 'astrale-alias')
205
+ await symlink(meta.bin, alias)
206
+
207
+ const admitted = await admitScriptInstall(meta, {
208
+ kind: 'standalone',
209
+ executable: alias,
210
+ })
211
+
212
+ expect(admitted.metadata).toEqual(meta)
213
+ })
214
+
215
+ test('rejects a standalone binary that does not own the recorded target', async () => {
216
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
217
+ const { meta } = await makeInstall(root, '1.0.0')
218
+ const other = join(root, 'other-astrale')
219
+ await writeFile(other, '#!/bin/sh\n')
220
+
221
+ await expect(
222
+ admitScriptInstall(meta, { kind: 'standalone', executable: other }),
223
+ ).rejects.toMatchObject({ code: 'UPDATE_INSTALL_MISMATCH' })
224
+ })
225
+ })
226
+
121
227
  describe('updateAstrale', () => {
228
+ test('a package-managed process never consults or mutates a coexisting script install', async () => {
229
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
230
+ const { path, meta } = await makeInstall(root, '1.0.0')
231
+ const before = await readFile(meta.bin, 'utf8')
232
+ process.env.ASTRALE_UPDATE_BASE = 'file:///definitely-not-a-release'
233
+ try {
234
+ const result = await updateAstrale({
235
+ currentVersion: '2.0.0',
236
+ installPath: path,
237
+ execution: { kind: 'package-managed', executable: '/opt/homebrew/bin/node' },
238
+ })
239
+
240
+ expect(result).toEqual({
241
+ status: 'managed',
242
+ currentVersion: '2.0.0',
243
+ executable: '/opt/homebrew/bin/node',
244
+ })
245
+ expect(await readFile(meta.bin, 'utf8')).toBe(before)
246
+ } finally {
247
+ delete process.env.ASTRALE_UPDATE_BASE
248
+ }
249
+ })
250
+
122
251
  test('check compares the installed release identity from metadata', async () => {
123
252
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
124
- const { path } = await makeInstall(root, 'main-abc123')
253
+ const { path, execution } = await makeInstall(root, 'main-abc123')
125
254
  const release = await makeFakeRelease(root, 'main-abc123', { binaryVersion: '1.0.0' })
126
255
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
127
256
  try {
@@ -130,6 +259,7 @@ describe('updateAstrale', () => {
130
259
  currentVersion: '1.0.0',
131
260
  platform: { os: 'darwin', arch: 'arm64' },
132
261
  installPath: path,
262
+ execution,
133
263
  })
134
264
 
135
265
  expect(result).toMatchObject({
@@ -144,7 +274,7 @@ describe('updateAstrale', () => {
144
274
 
145
275
  test('check reports available update without replacing the binary', async () => {
146
276
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
147
- const { path, meta } = await makeInstall(root, '1.0.0')
277
+ const { path, meta, execution } = await makeInstall(root, '1.0.0')
148
278
  const release = await makeFakeRelease(root, '1.1.0')
149
279
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
150
280
  try {
@@ -153,6 +283,7 @@ describe('updateAstrale', () => {
153
283
  currentVersion: '1.0.0',
154
284
  platform: { os: 'darwin', arch: 'arm64' },
155
285
  installPath: path,
286
+ execution,
156
287
  })
157
288
 
158
289
  expect(result).toMatchObject({
@@ -168,7 +299,7 @@ describe('updateAstrale', () => {
168
299
 
169
300
  test('supports legacy string asset manifests', async () => {
170
301
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
171
- const { path, meta } = await makeInstall(root, '1.0.0')
302
+ const { path, meta, execution } = await makeInstall(root, '1.0.0')
172
303
  const release = await makeFakeRelease(root, '1.1.0', { legacyManifest: true })
173
304
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
174
305
  try {
@@ -177,6 +308,7 @@ describe('updateAstrale', () => {
177
308
  currentVersion: '1.0.0',
178
309
  platform: { os: 'darwin', arch: 'arm64' },
179
310
  installPath: path,
311
+ execution,
180
312
  })
181
313
 
182
314
  expect(check).toMatchObject({
@@ -188,6 +320,7 @@ describe('updateAstrale', () => {
188
320
  currentVersion: '1.0.0',
189
321
  platform: { os: 'darwin', arch: 'arm64' },
190
322
  installPath: path,
323
+ execution,
191
324
  })
192
325
 
193
326
  expect(result).toMatchObject({
@@ -204,7 +337,7 @@ describe('updateAstrale', () => {
204
337
 
205
338
  test('updates the binary and install metadata', async () => {
206
339
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
207
- const { path, meta } = await makeInstall(root, '1.0.0')
340
+ const { path, meta, execution } = await makeInstall(root, '1.0.0')
208
341
  const release = await makeFakeRelease(root, '1.1.0')
209
342
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
210
343
  try {
@@ -212,6 +345,7 @@ describe('updateAstrale', () => {
212
345
  currentVersion: '1.0.0',
213
346
  platform: { os: 'darwin', arch: 'arm64' },
214
347
  installPath: path,
348
+ execution,
215
349
  })
216
350
 
217
351
  expect(result).toMatchObject({
@@ -224,6 +358,124 @@ describe('updateAstrale', () => {
224
358
  expect(await versionProc.exited).toBe(0)
225
359
  const updatedMeta = JSON.parse(await readFile(path, 'utf8')) as InstallMetadata
226
360
  expect(updatedMeta.version).toBe('1.1.0')
361
+ expect(await readFile(join(dirname(meta.bin), 'viewer', 'dist', 'main.js'), 'utf8')).toBe(
362
+ 'viewer main\n',
363
+ )
364
+ expect(await readFile(join(dirname(meta.bin), 'viewer', 'dist', 'index.html'), 'utf8')).toBe(
365
+ '<!doctype html>\n',
366
+ )
367
+ } finally {
368
+ delete process.env.ASTRALE_UPDATE_BASE
369
+ }
370
+ })
371
+
372
+ test('a partial Viewer archive leaves the installed cohort and metadata unchanged', async () => {
373
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
374
+ const { path, meta, execution } = await makeInstall(root, '1.0.0')
375
+ const release = await makeFakeRelease(root, '1.1.0', { omitViewerIndex: true })
376
+ process.env.ASTRALE_UPDATE_BASE = `file://${release}`
377
+ const beforeBinary = await readFile(meta.bin, 'utf8')
378
+ const beforeMetadata = await readFile(path, 'utf8')
379
+ try {
380
+ await expect(
381
+ updateAstrale({
382
+ currentVersion: '1.0.0',
383
+ platform: { os: 'darwin', arch: 'arm64' },
384
+ installPath: path,
385
+ execution,
386
+ }),
387
+ ).rejects.toThrow()
388
+
389
+ expect(await readFile(meta.bin, 'utf8')).toBe(beforeBinary)
390
+ expect(await readFile(join(dirname(meta.bin), 'viewer', 'dist', 'main.js'), 'utf8')).toBe(
391
+ 'old viewer main\n',
392
+ )
393
+ expect(await readFile(join(dirname(meta.bin), 'viewer', 'dist', 'index.html'), 'utf8')).toBe(
394
+ 'old viewer html\n',
395
+ )
396
+ expect(await readFile(path, 'utf8')).toBe(beforeMetadata)
397
+ } finally {
398
+ delete process.env.ASTRALE_UPDATE_BASE
399
+ }
400
+ })
401
+
402
+ test('a Viewer commit failure rolls back binary, Viewer, and metadata', async () => {
403
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
404
+ const { path, meta, execution } = await makeInstall(root, '1.0.0')
405
+ const release = await makeFakeRelease(root, '1.1.0')
406
+ process.env.ASTRALE_UPDATE_BASE = `file://${release}`
407
+ const beforeBinary = await readFile(meta.bin, 'utf8')
408
+ const beforeMetadata = await readFile(path, 'utf8')
409
+ try {
410
+ await expect(
411
+ updateAstrale(
412
+ {
413
+ currentVersion: '1.0.0',
414
+ platform: { os: 'darwin', arch: 'arm64' },
415
+ installPath: path,
416
+ execution,
417
+ },
418
+ {
419
+ replaceStandaloneCohort: (installed, next, viewer) =>
420
+ replaceStandaloneCohort(installed, next, viewer, {
421
+ rename: async (from, to) => {
422
+ if (String(from).endsWith('viewer.next')) {
423
+ throw Object.assign(new Error('injected Viewer commit failure'), {
424
+ code: 'EIO',
425
+ })
426
+ }
427
+ await rename(from, to)
428
+ },
429
+ }),
430
+ },
431
+ ),
432
+ ).rejects.toThrow('injected Viewer commit failure')
433
+
434
+ expect(await readFile(meta.bin, 'utf8')).toBe(beforeBinary)
435
+ expect(await readFile(join(dirname(meta.bin), 'viewer', 'dist', 'main.js'), 'utf8')).toBe(
436
+ 'old viewer main\n',
437
+ )
438
+ expect(await readFile(join(dirname(meta.bin), 'viewer', 'dist', 'index.html'), 'utf8')).toBe(
439
+ 'old viewer html\n',
440
+ )
441
+ expect(await readFile(path, 'utf8')).toBe(beforeMetadata)
442
+ } finally {
443
+ delete process.env.ASTRALE_UPDATE_BASE
444
+ }
445
+ })
446
+
447
+ test('a metadata failure after cohort commit restores binary, Viewer, and metadata', async () => {
448
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
449
+ const { path, meta, execution } = await makeInstall(root, '1.0.0')
450
+ const release = await makeFakeRelease(root, '1.1.0')
451
+ process.env.ASTRALE_UPDATE_BASE = `file://${release}`
452
+ const beforeBinary = await readFile(meta.bin, 'utf8')
453
+ const beforeMetadata = await readFile(path, 'utf8')
454
+ try {
455
+ await expect(
456
+ updateAstrale(
457
+ {
458
+ currentVersion: '1.0.0',
459
+ platform: { os: 'darwin', arch: 'arm64' },
460
+ installPath: path,
461
+ execution,
462
+ },
463
+ {
464
+ writeInstallMetadata: async () => {
465
+ throw new Error('injected metadata write failure')
466
+ },
467
+ },
468
+ ),
469
+ ).rejects.toThrow('injected metadata write failure')
470
+
471
+ expect(await readFile(meta.bin, 'utf8')).toBe(beforeBinary)
472
+ expect(await readFile(join(dirname(meta.bin), 'viewer', 'dist', 'main.js'), 'utf8')).toBe(
473
+ 'old viewer main\n',
474
+ )
475
+ expect(await readFile(join(dirname(meta.bin), 'viewer', 'dist', 'index.html'), 'utf8')).toBe(
476
+ 'old viewer html\n',
477
+ )
478
+ expect(await readFile(path, 'utf8')).toBe(beforeMetadata)
227
479
  } finally {
228
480
  delete process.env.ASTRALE_UPDATE_BASE
229
481
  }
@@ -231,7 +483,7 @@ describe('updateAstrale', () => {
231
483
 
232
484
  test('updates canary-style releases whose binary reports the package version', async () => {
233
485
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
234
- const { path, meta } = await makeInstall(root, 'main-old123')
486
+ const { path, meta, execution } = await makeInstall(root, 'main-old123')
235
487
  const release = await makeFakeRelease(root, 'main-new456', { binaryVersion: '1.0.0' })
236
488
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
237
489
  try {
@@ -239,6 +491,7 @@ describe('updateAstrale', () => {
239
491
  currentVersion: '1.0.0',
240
492
  platform: { os: 'darwin', arch: 'arm64' },
241
493
  installPath: path,
494
+ execution,
242
495
  })
243
496
 
244
497
  expect(result).toMatchObject({
@@ -143,4 +143,18 @@ describe('viewer asset resolution', () => {
143
143
  expect(stderr).toBe('')
144
144
  expect(stdout.trim()).toBe(await realpath(viewer))
145
145
  })
146
+
147
+ test('resolves assets shipped beside a Bun-compiled standalone executable', async () => {
148
+ const root = await mkdtemp(join(tmpdir(), 'astrale-view-standalone-'))
149
+ temporaryDirectories.push(root)
150
+ const executable = join(root, 'bin', 'astrale')
151
+ const viewer = join(root, 'bin', 'viewer', 'dist')
152
+ await mkdir(viewer, { recursive: true })
153
+ await writeFile(join(viewer, 'main.js'), '')
154
+ await writeFile(join(viewer, 'index.html'), '')
155
+
156
+ expect(viewerDistDir('file:///$bunfs/root/assets.ts', '/$bunfs/root/astrale', executable)).toBe(
157
+ viewer,
158
+ )
159
+ })
146
160
  })
@@ -0,0 +1,150 @@
1
+ import type { ChildrenChannel, ExternalOpenRequest, Shell } from '@astrale-os/shell'
2
+
3
+ import { capabilitiesMiddleware, createIntentPipeline, createIntentRouter } from '@astrale-os/shell'
4
+ import { describe, expect, mock, test } from 'bun:test'
5
+
6
+ import type { ExternalOpenIntentMessage } from '../view/external-open-intent'
7
+
8
+ import {
9
+ installExternalOpenIntentHandler,
10
+ openExternalBrowserWindow,
11
+ } from '../view/external-open-intent'
12
+ import { viewHostCapabilities } from '../view/host-capabilities'
13
+
14
+ describe('View external navigation host effect', () => {
15
+ test('the real Shell pipeline opens only an exact granted origin and replies to the physical child', async () => {
16
+ const sent: Array<{ windowId: string; message: unknown }> = []
17
+ const children = {
18
+ send: (windowId: string, value: unknown) => sent.push({ windowId, message: value }),
19
+ has: () => true,
20
+ on: () => () => undefined,
21
+ onClose: () => () => undefined,
22
+ } as ChildrenChannel
23
+ const capabilities = viewHostCapabilities(['https://connect.nango.dev'])
24
+ const pipeline = createIntentPipeline()
25
+ pipeline.use(
26
+ capabilitiesMiddleware({
27
+ lookup: (sender) => (sender === 'physical-child' ? capabilities : undefined),
28
+ }),
29
+ )
30
+ const router = createIntentRouter({
31
+ selfWindowId: 'root',
32
+ pipeline,
33
+ parent: null,
34
+ children,
35
+ })
36
+ const open = mock((_request: ExternalOpenRequest) => true)
37
+ installExternalOpenIntentHandler(
38
+ {
39
+ children,
40
+ onIntent: router.onLocal.bind(router),
41
+ } as unknown as Shell,
42
+ { open },
43
+ )
44
+
45
+ await router.handleInbound(
46
+ 'child',
47
+ message('https://connect.nango.dev/session', 'forged-sibling'),
48
+ 'physical-child',
49
+ )
50
+
51
+ expect(open).toHaveBeenCalledTimes(1)
52
+ expect(sent).toEqual([
53
+ {
54
+ windowId: 'physical-child',
55
+ message: expect.objectContaining({
56
+ envelope: expect.objectContaining({
57
+ payload: { correlationId: 'request-1', result: { outcome: 'opened' } },
58
+ }),
59
+ }),
60
+ },
61
+ ])
62
+
63
+ for (const denied of [
64
+ 'http://connect.nango.dev/session',
65
+ 'https://sub.connect.nango.dev/session',
66
+ 'https://connect.nango.dev:444/session',
67
+ 'https://evil.example/session',
68
+ ]) {
69
+ await router.handleInbound('child', message(denied), 'physical-child')
70
+ }
71
+ await router.handleInbound('child', message('https://connect.nango.dev/session'), 'ungranted')
72
+ expect(open).toHaveBeenCalledTimes(1)
73
+ expect(sent).toHaveLength(1)
74
+ })
75
+
76
+ test('reports a popup block through the admitted pipeline', async () => {
77
+ const sent = mock((_windowId: string, _message: unknown) => undefined)
78
+ const children = {
79
+ send: sent,
80
+ has: () => true,
81
+ on: () => () => undefined,
82
+ onClose: () => () => undefined,
83
+ } as ChildrenChannel
84
+ const pipeline = createIntentPipeline()
85
+ pipeline.use(
86
+ capabilitiesMiddleware({
87
+ lookup: () => viewHostCapabilities(['https://connect.nango.dev']),
88
+ }),
89
+ )
90
+ const router = createIntentRouter({ selfWindowId: 'root', pipeline, parent: null, children })
91
+ installExternalOpenIntentHandler(
92
+ { children, onIntent: router.onLocal.bind(router) } as unknown as Shell,
93
+ { open: () => false },
94
+ )
95
+
96
+ await router.handleInbound('child', message('https://connect.nango.dev/session'), 'child-1')
97
+
98
+ expect(sent).toHaveBeenCalledWith(
99
+ 'child-1',
100
+ expect.objectContaining({
101
+ envelope: expect.objectContaining({
102
+ payload: { correlationId: 'request-1', result: { outcome: 'blocked' } },
103
+ }),
104
+ }),
105
+ )
106
+ })
107
+
108
+ test('isolates a fresh inert context before navigating and closes it when isolation fails', () => {
109
+ const replace = mock((_url: string) => undefined)
110
+ const opened = { opener: {}, location: { replace }, close: mock(() => undefined) }
111
+ const open = mock(() => opened)
112
+
113
+ expect(
114
+ openExternalBrowserWindow({ open } as never, {
115
+ url: 'https://connect.nango.dev/session',
116
+ mode: 'popup',
117
+ }),
118
+ ).toBe(true)
119
+ expect(open).toHaveBeenCalledWith('', '_blank', 'popup,width=720,height=760')
120
+ expect(opened.opener).toBeNull()
121
+ expect(replace).toHaveBeenCalledWith('https://connect.nango.dev/session')
122
+
123
+ const close = mock(() => undefined)
124
+ const unsafe = Object.defineProperty({ close, location: { replace: mock() } }, 'opener', {
125
+ set: () => {
126
+ throw new Error('opener isolation refused')
127
+ },
128
+ })
129
+ expect(
130
+ openExternalBrowserWindow({ open: () => unsafe } as never, {
131
+ url: 'https://connect.nango.dev/session',
132
+ mode: 'tab',
133
+ }),
134
+ ).toBe(false)
135
+ expect(close).toHaveBeenCalledTimes(1)
136
+ })
137
+ })
138
+
139
+ function message(url: string, sender = 'child-1'): ExternalOpenIntentMessage {
140
+ return {
141
+ type: 'intent',
142
+ version: 1,
143
+ envelope: {
144
+ name: 'browser.openExternal',
145
+ payload: { url, mode: 'popup' },
146
+ sender: { windowId: sender },
147
+ correlationId: 'request-1',
148
+ },
149
+ }
150
+ }
@@ -0,0 +1,27 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import { admitExternalOpenOrigins } from '../view/external-open-origins'
4
+
5
+ describe('View external navigation grants', () => {
6
+ test('defaults to no authority and canonicalizes exact HTTPS origins', () => {
7
+ expect(admitExternalOpenOrigins(undefined)).toEqual([])
8
+ expect(
9
+ admitExternalOpenOrigins([
10
+ 'https://connect.nango.dev',
11
+ 'https://connect.nango.dev/',
12
+ 'https://connect.composio.dev:443',
13
+ ]),
14
+ ).toEqual(['https://connect.nango.dev', 'https://connect.composio.dev'])
15
+ })
16
+
17
+ test.each([
18
+ 'http://connect.nango.dev',
19
+ 'https://user@connect.nango.dev',
20
+ 'https://connect.nango.dev/path',
21
+ 'https://connect.nango.dev?session=secret',
22
+ 'https://*.example.com',
23
+ 'not-a-url',
24
+ ])('rejects a non-origin grant: %s', (candidate) => {
25
+ expect(() => admitExternalOpenOrigins([candidate])).toThrow('exact HTTPS origin')
26
+ })
27
+ })
@@ -23,7 +23,6 @@ const profile: ResolvedView = {
23
23
  kind: 'definition',
24
24
  definitions: [{ origin: 'shell.test', kind: 'class', name: 'Person' }],
25
25
  },
26
- auth: 'required',
27
26
  },
28
27
  href: 'https://shell.test/profile',
29
28
  handshake: 'shell',
@@ -41,7 +40,6 @@ const card: ResolvedView = {
41
40
  kind: 'definition',
42
41
  definitions: [{ origin: 'shell.test', kind: 'class', name: 'Person' }],
43
42
  },
44
- auth: 'public',
45
43
  },
46
44
  href: 'https://shell.test/card',
47
45
  handshake: 'none',
@@ -1,10 +1,12 @@
1
- import { describe, expect, test } from 'bun:test'
1
+ import type { AuthApi } from '@astrale-os/sdk/auth'
2
+
3
+ import { describe, expect, mock, test } from 'bun:test'
2
4
  import { once } from 'node:events'
3
5
 
4
6
  import type { ViewServeConfig } from '../view/session'
5
7
 
6
8
  import { findFreePort } from '../port'
7
- import { startViewServer } from '../view/server'
9
+ import { mintViewCredential, startViewServer } from '../view/server'
8
10
 
9
11
  const digest = (character: string) => `sha256:${character.repeat(64)}` as const
10
12
  const target = (value: string) => value as ViewServeConfig['session']['view']['target']
@@ -13,6 +15,19 @@ const revision = (character: string) =>
13
15
  digest(character) as ViewServeConfig['session']['view']['route']['revision']
14
16
 
15
17
  describe('view session server credentials', () => {
18
+ test('mints a proof-bounded credential for the Kernel audience', async () => {
19
+ const mint = mock(async () => 'minted-credential')
20
+ const auth = { mint } as unknown as Pick<AuthApi, 'mint'>
21
+
22
+ await expect(mintViewCredential(auth, issuer('https://kernel.test'))).resolves.toBe(
23
+ 'minted-credential',
24
+ )
25
+ expect(mint).toHaveBeenCalledWith({
26
+ audience: 'https://kernel.test',
27
+ ttlSeconds: 240,
28
+ })
29
+ })
30
+
16
31
  /** @evidence TEST-CLI-PLAIN-VIEW-RECEIVES-NO-CREDENTIAL */
17
32
  test('refuses to mint a token for a handshake-none View', async () => {
18
33
  const nonce = 'plain-view'
@@ -29,7 +44,7 @@ describe('view session server credentials', () => {
29
44
  target: target('/:example.test'),
30
45
  route: {
31
46
  key: 'example.test:view.public',
32
- declaration: { target: { kind: 'domain' }, auth: 'required' },
47
+ declaration: { target: { kind: 'domain' } },
33
48
  href: 'https://example.test/ui/public',
34
49
  handshake: 'none',
35
50
  issuer: issuer('https://example.test'),
@@ -45,12 +60,20 @@ describe('view session server credentials', () => {
45
60
  issuer: 'https://kernel.test',
46
61
  direct: true,
47
62
  },
63
+ externalOrigins: ['https://connect.nango.dev'],
48
64
  idleMs: 60_000,
49
65
  } satisfies ViewServeConfig
50
66
  const server = startViewServer(config)
51
67
  await once(server, 'listening')
52
68
 
53
69
  try {
70
+ const configResponse = await fetch(`http://127.0.0.1:${port}/s/${nonce}/config.json`)
71
+ expect(configResponse.status).toBe(200)
72
+ expect(await configResponse.json()).toMatchObject({
73
+ sessionId: 'v-plain',
74
+ externalOrigins: ['https://connect.nango.dev'],
75
+ })
76
+
54
77
  const response = await fetch(`http://127.0.0.1:${port}/s/${nonce}/token`, {
55
78
  method: 'POST',
56
79
  })