@astrale-os/cli 1.0.0-beta.1 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -11
- package/dist/astrale.js +3737 -3523
- package/dist/public/connect-core.js +396 -392
- package/dist/public/keys/index.js +371 -378
- package/dist/public/paths/index.js +371 -378
- package/dist/types/lib/idp.d.ts +6 -0
- package/dist/types/lib/meta.d.ts +2 -2
- package/package.json +6 -4
- package/src/admin/binding.ts +168 -0
- package/src/admin/catalog/__tests__/client.test.ts +2 -2
- package/src/admin/catalog/client.ts +4 -9
- package/src/admin/instance/__tests__/client.test.ts +2 -2
- package/src/admin/instance/client.ts +6 -11
- package/src/commands/__tests__/auth-login.test.ts +54 -1
- package/src/commands/__tests__/call.test.ts +5 -1
- package/src/commands/__tests__/domain-install-operation.test.ts +23 -7
- package/src/commands/__tests__/domain-install-owned.test.ts +5 -1
- package/src/commands/__tests__/domain-list.test.ts +83 -4
- package/src/commands/__tests__/install-identity-override.test.ts +30 -19
- package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
- package/src/commands/__tests__/update.test.ts +23 -16
- package/src/commands/domain/install.ts +11 -15
- package/src/commands/domain/list.ts +17 -12
- package/src/commands/studio.ts +20 -0
- package/src/commands/update.ts +9 -5
- package/src/connection/__tests__/errors.test.ts +16 -12
- package/src/connection/__tests__/exchange.test.ts +6 -5
- package/src/connection/exchange.ts +5 -4
- package/src/lib/__tests__/domain-publication.test.ts +144 -0
- package/src/lib/__tests__/idp-session.test.ts +22 -0
- package/src/lib/__tests__/idp.test.ts +2 -0
- package/src/lib/__tests__/instance.test.ts +6 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
- package/src/lib/__tests__/update.test.ts +12 -0
- package/src/lib/domain-publication.ts +102 -0
- package/src/lib/idp.ts +5 -1
- package/src/lib/instance.ts +6 -1
- package/src/lib/login-flow.ts +13 -3
- package/src/lib/meta.ts +2 -2
- package/src/lib/update.ts +4 -4
- package/src/program/__tests__/program.test.ts +1 -1
- package/studio/client/dist/assets/{elk-api-D2xgMJvi.js → elk-api-lwhFo7vB.js} +1 -1
- package/studio/client/dist/assets/index-B-c-smo9.js +8 -0
- package/studio/client/dist/assets/index-BckHuAWk.js +81 -0
- package/studio/client/dist/assets/index-C4aNQ6dz.css +1 -0
- package/studio/client/dist/index.html +2 -2
- package/studio/package.json +3 -1
- package/studio/server/agent/bridge/stdio.ts +8 -1
- package/studio/server/agent/conversation.test.ts +10 -5
- package/studio/server/agent/conversation.ts +56 -8
- package/studio/server/agent/harness/codex/loadout.ts +18 -21
- package/studio/server/agent/harness/gateway/config.ts +20 -18
- package/studio/server/agent/harness/selection.ts +9 -1
- package/studio/server/agent/prompts/system.ts +3 -2
- package/studio/server/agent/prompts/turn.ts +13 -4
- package/studio/server/agent/run/completion.ts +2 -2
- package/studio/server/agent/run/preparation.ts +6 -5
- package/studio/server/agent/run/transcript.test.ts +35 -1
- package/studio/server/agent/run/transcript.ts +169 -3
- package/studio/server/agent/run/usage.test.ts +24 -0
- package/studio/server/agent/run/usage.ts +26 -1
- package/studio/server/api/canvas.ts +44 -0
- package/studio/server/api/comments.ts +85 -0
- package/studio/server/api/context.ts +61 -0
- package/studio/server/api/deployment.ts +39 -0
- package/studio/server/api/documents.ts +55 -0
- package/studio/server/api/domain.ts +59 -0
- package/studio/server/api/http.ts +45 -0
- package/studio/server/api/project.ts +57 -0
- package/studio/server/api/schema.ts +12 -0
- package/studio/server/api/views.ts +57 -0
- package/studio/server/api/workspace.ts +60 -0
- package/studio/server/api.test.ts +147 -0
- package/studio/server/api.ts +20 -422
- package/studio/server/cache.test.ts +111 -0
- package/studio/server/cache.ts +163 -9
- package/studio/server/cli-consumers.test.ts +494 -0
- package/studio/server/cli.test.ts +88 -0
- package/studio/server/cli.ts +240 -0
- package/studio/server/client-package.ts +25 -1
- package/studio/server/domain.test.ts +30 -2
- package/studio/server/domain.ts +14 -2
- package/studio/server/environment/dotenv-preview.test.ts +39 -0
- package/studio/server/environment/dotenv-preview.ts +27 -0
- package/studio/server/environment/files.test.ts +53 -0
- package/studio/server/{state/env.ts → environment/files.ts} +7 -48
- package/studio/server/{state → handoff}/copy.ts +21 -6
- package/studio/server/handoff/service.test.ts +22 -0
- package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
- package/studio/server/instances/active.ts +72 -0
- package/studio/server/instances/deploy-record.test.ts +31 -0
- package/studio/server/instances/deploy-record.ts +37 -0
- package/studio/server/instances/deploy.ts +56 -0
- package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
- package/studio/server/instances/probe.ts +67 -0
- package/studio/server/instances/status.test.ts +12 -0
- package/studio/server/instances/status.ts +49 -0
- package/studio/server/introspect/anatomy/client-tree.ts +57 -0
- package/studio/server/introspect/anatomy/env-fields.ts +66 -0
- package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
- package/studio/server/introspect/anatomy/source.ts +181 -0
- package/studio/server/introspect/anatomy/views/legacy.ts +232 -0
- package/studio/server/introspect/anatomy/views/routes.ts +105 -0
- package/studio/server/introspect/anatomy/views.ts +58 -0
- package/studio/server/introspect/anatomy-extras.test.ts +91 -4
- package/studio/server/introspect/anatomy-extras.ts +7 -726
- package/studio/server/introspect/anatomy.ts +46 -19
- package/studio/server/introspect/bundle.ts +15 -6
- package/studio/server/introspect/canonical-schema.test.ts +79 -0
- package/studio/server/introspect/canonical-schema.ts +80 -18
- package/studio/server/introspect/config-preview.test.ts +32 -0
- package/studio/server/introspect/config-preview.ts +119 -0
- package/studio/server/introspect/diff.test.ts +7 -11
- package/studio/server/introspect/diff.ts +28 -37
- package/studio/server/introspect/extractor.ts +8 -6
- package/studio/server/introspect/hash.ts +5 -2
- package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
- package/studio/server/introspect/revision.test.ts +54 -0
- package/studio/server/introspect/revision.ts +49 -0
- package/studio/server/introspect/runtime.test.ts +63 -5
- package/studio/server/introspect/runtime.ts +49 -3
- package/studio/server/introspect/schema-ir-json.ts +181 -0
- package/studio/server/introspect/source-overlay/annotations.ts +14 -0
- package/studio/server/introspect/source-overlay/handlers.ts +681 -0
- package/studio/server/introspect/source-overlay/kernel-calls.ts +49 -0
- package/studio/server/introspect/source-overlay/project.ts +248 -0
- package/studio/server/introspect/source-overlay/spans.ts +360 -0
- package/studio/server/json.ts +46 -0
- package/studio/server/lifecycle.ts +5 -1
- package/studio/server/sse.test.ts +26 -0
- package/studio/server/sse.ts +4 -1
- package/studio/server/state/baseline.test.ts +212 -1
- package/studio/server/state/baseline.ts +145 -29
- package/studio/server/state/comments.test.ts +66 -1
- package/studio/server/state/comments.ts +173 -10
- package/studio/server/state/context.ts +34 -2
- package/studio/server/state/documents.ts +42 -1
- package/studio/server/state/integrations.ts +27 -1
- package/studio/server/state/layout.test.ts +34 -0
- package/studio/server/state/layout.ts +44 -17
- package/studio/server/state/settings.ts +29 -22
- package/studio/server/state/store.test.ts +122 -0
- package/studio/server/state/store.ts +66 -17
- package/studio/server/state/visibility.ts +22 -7
- package/studio/server/views/model.test.ts +57 -0
- package/studio/server/views/model.ts +66 -0
- package/studio/server/views/runtime.ts +40 -0
- package/studio/server/views/selection-repository.test.ts +31 -0
- package/studio/server/views/selection-repository.ts +60 -0
- package/studio/server/views/session.test.ts +73 -0
- package/studio/server/views/session.ts +166 -0
- package/studio/server/{state/views.test.ts → views/target.test.ts} +8 -159
- package/studio/server/views/target.ts +248 -0
- package/studio/server/watch.ts +1 -1
- package/studio/server/workspace/catalog.test.ts +17 -0
- package/studio/server/workspace/catalog.ts +43 -0
- package/studio/server/{state → workspace}/create.test.ts +1 -1
- package/studio/server/{state → workspace}/create.ts +1 -1
- package/studio/server/{state → workspace}/git.ts +1 -1
- package/studio/server/workspace/updates.ts +83 -0
- package/studio/server/workspace-state.ts +1 -1
- package/studio/shared/contracts/README.md +16 -0
- package/studio/shared/contracts/agent.ts +270 -0
- package/studio/shared/contracts/runtime.ts +50 -0
- package/studio/shared/contracts/schema.ts +462 -0
- package/studio/shared/contracts/surface.test.ts +319 -0
- package/studio/shared/contracts/workspace.ts +274 -0
- package/studio/shared/layout.test.ts +116 -0
- package/studio/shared/schema/identity.test.ts +58 -0
- package/studio/shared/schema/identity.ts +82 -0
- package/studio/shared/types.ts +11 -1099
- package/studio/tsconfig.json +1 -1
- package/viewer/dist/main.js +35 -35
- package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
- package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
- package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
- package/studio/server/state/catalog.ts +0 -117
- package/studio/server/state/instance.ts +0 -280
- package/studio/server/state/updates.ts +0 -63
- package/studio/server/state/views.ts +0 -535
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { publication } from '@astrale-os/kernel-protocol'
|
|
2
|
+
import { defineDomain } from '@astrale-os/sdk'
|
|
3
|
+
import { issuer } from '@astrale-os/sdk/auth'
|
|
4
|
+
import { createDeployment } from '@astrale-os/sdk/deployment'
|
|
5
|
+
import { defineSchema } from '@astrale-os/sdk/schema/v1'
|
|
6
|
+
import { describe, expect, test } from 'bun:test'
|
|
7
|
+
|
|
8
|
+
import { fetchDomainPublication } from '../domain-publication'
|
|
9
|
+
|
|
10
|
+
const ROOT = 'https://publication-reader.example.dev'
|
|
11
|
+
const PUBLICATION_URL = `${ROOT}/.well-known/astrale/domain.json`
|
|
12
|
+
const MAXIMUM_PUBLICATION_BYTES = 1024 * 1024
|
|
13
|
+
const schema = defineSchema('publication-reader.example.dev', {})
|
|
14
|
+
const definition = defineDomain({
|
|
15
|
+
schema,
|
|
16
|
+
handlers: { functions: {}, classes: {}, interfaces: {} },
|
|
17
|
+
})
|
|
18
|
+
const deployed = createDeployment({
|
|
19
|
+
definition,
|
|
20
|
+
issuer: issuer.accept(ROOT),
|
|
21
|
+
bundleHref: `${ROOT}/domain.bundle.json`,
|
|
22
|
+
bindings: { callables: [] },
|
|
23
|
+
}).publication
|
|
24
|
+
|
|
25
|
+
describe('Domain Publication reader', () => {
|
|
26
|
+
test('fetches only the canonical path with redirects disabled', async () => {
|
|
27
|
+
const requests: Array<{ readonly url: string; readonly redirect?: RequestRedirect }> = []
|
|
28
|
+
const fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
29
|
+
requests.push({ url: String(input), redirect: init?.redirect })
|
|
30
|
+
return Response.json(deployed)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await expect(fetchDomainPublication(`${ROOT}/`, undefined, fetch)).resolves.toEqual(deployed)
|
|
34
|
+
expect(requests).toEqual([{ url: PUBLICATION_URL, redirect: 'error' }])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('propagates redirect rejection instead of accepting another target', async () => {
|
|
38
|
+
const fetch = async (_input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
39
|
+
expect(init?.redirect).toBe('error')
|
|
40
|
+
throw new TypeError('redirect rejected')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await expect(fetchDomainPublication(ROOT, undefined, fetch)).rejects.toThrow(
|
|
44
|
+
'redirect rejected',
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('stops at one MiB and cancels an over-limit response stream', async () => {
|
|
49
|
+
let pulls = 0
|
|
50
|
+
let cancelled = false
|
|
51
|
+
const body = new ReadableStream<Uint8Array>(
|
|
52
|
+
{
|
|
53
|
+
pull(controller) {
|
|
54
|
+
pulls += 1
|
|
55
|
+
if (pulls === 1) {
|
|
56
|
+
controller.enqueue(new Uint8Array(MAXIMUM_PUBLICATION_BYTES))
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
if (pulls === 2) {
|
|
60
|
+
controller.enqueue(Uint8Array.of(0))
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
controller.error(new Error('reader consumed beyond its byte limit'))
|
|
64
|
+
},
|
|
65
|
+
cancel() {
|
|
66
|
+
cancelled = true
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{ highWaterMark: 0 },
|
|
70
|
+
)
|
|
71
|
+
const fetch = async (): Promise<Response> => new Response(body)
|
|
72
|
+
|
|
73
|
+
await expect(fetchDomainPublication(ROOT, undefined, fetch)).rejects.toThrow(
|
|
74
|
+
`exceeded ${MAXIMUM_PUBLICATION_BYTES} bytes`,
|
|
75
|
+
)
|
|
76
|
+
expect(pulls).toBe(2)
|
|
77
|
+
expect(cancelled).toBe(true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('rejects an oversized Content-Length before pulling and cancels the body', async () => {
|
|
81
|
+
let pulls = 0
|
|
82
|
+
let cancelled = false
|
|
83
|
+
const body = new ReadableStream<Uint8Array>(
|
|
84
|
+
{
|
|
85
|
+
pull() {
|
|
86
|
+
pulls += 1
|
|
87
|
+
},
|
|
88
|
+
cancel() {
|
|
89
|
+
cancelled = true
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{ highWaterMark: 0 },
|
|
93
|
+
)
|
|
94
|
+
const fetch = async (): Promise<Response> =>
|
|
95
|
+
new Response(body, {
|
|
96
|
+
headers: { 'content-length': String(MAXIMUM_PUBLICATION_BYTES + 1) },
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
await expect(fetchDomainPublication(ROOT, undefined, fetch)).rejects.toThrow(
|
|
100
|
+
`exceeded ${MAXIMUM_PUBLICATION_BYTES} bytes`,
|
|
101
|
+
)
|
|
102
|
+
expect(pulls).toBe(0)
|
|
103
|
+
expect(cancelled).toBe(true)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test.each([
|
|
107
|
+
['invalid UTF-8', Uint8Array.of(0xc3, 0x28), TypeError],
|
|
108
|
+
['invalid JSON', new TextEncoder().encode('{'), SyntaxError],
|
|
109
|
+
['invalid Publication', new TextEncoder().encode('{}'), publication.PublicationError],
|
|
110
|
+
] as const)('rejects %s before returning a Publication', async (_label, bytes, Cause) => {
|
|
111
|
+
const fetch = async (): Promise<Response> => new Response(bytes)
|
|
112
|
+
const error = await rejection(fetchDomainPublication(ROOT, undefined, fetch))
|
|
113
|
+
|
|
114
|
+
expect(error.message).toBe(`GET ${PUBLICATION_URL} returned an invalid Domain Publication`)
|
|
115
|
+
expect(error.cause).toBeInstanceOf(Cause)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test.each([
|
|
119
|
+
`${ROOT}/nested`,
|
|
120
|
+
`${ROOT}?deployment=current`,
|
|
121
|
+
`${ROOT}#fragment`,
|
|
122
|
+
'https://user:secret@publication-reader.example.dev',
|
|
123
|
+
'ftp://publication-reader.example.dev',
|
|
124
|
+
])('rejects a non-origin deployment URL before fetch: %s', async (input) => {
|
|
125
|
+
let called = false
|
|
126
|
+
const fetch = async (): Promise<Response> => {
|
|
127
|
+
called = true
|
|
128
|
+
return Response.json(deployed)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await expect(fetchDomainPublication(input, undefined, fetch)).rejects.toBeInstanceOf(TypeError)
|
|
132
|
+
expect(called).toBe(false)
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
async function rejection(input: Promise<unknown>): Promise<Error> {
|
|
137
|
+
try {
|
|
138
|
+
await input
|
|
139
|
+
} catch (cause) {
|
|
140
|
+
if (cause instanceof Error) return cause
|
|
141
|
+
throw new Error('Expected an Error rejection.', { cause })
|
|
142
|
+
}
|
|
143
|
+
throw new Error('Expected the promise to reject.')
|
|
144
|
+
}
|
|
@@ -68,11 +68,29 @@ describe('ensureFreshSession', () => {
|
|
|
68
68
|
expect(result.orgHintCalls).toBe(1)
|
|
69
69
|
expect(server.refreshCount()).toBe(1)
|
|
70
70
|
const session = await readSession()
|
|
71
|
+
expect(session.clientId).toBe('c_1')
|
|
71
72
|
expect(session.refresh_token).toBe('rt-1')
|
|
72
73
|
expect((session.tokens as Record<string, { access_token: string }>)[AUD].access_token).toBe(
|
|
73
74
|
result.token as string,
|
|
74
75
|
)
|
|
75
76
|
expect(server.lastOrganizationId()).toBe('org_1')
|
|
77
|
+
expect(server.lastClientId()).toBe('c_1')
|
|
78
|
+
} finally {
|
|
79
|
+
await server.stop()
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('refreshes with the client that issued the session instead of the IdP default', async () => {
|
|
84
|
+
const server = rotationServer()
|
|
85
|
+
try {
|
|
86
|
+
await writeIdpConfig(server.url)
|
|
87
|
+
await writeSession({ clientId: 'c_session', expires_at: past() })
|
|
88
|
+
|
|
89
|
+
const result = await runDriver('ensure', { DRIVER_AUDIENCE: AUD })
|
|
90
|
+
|
|
91
|
+
expect(result.ok).toBe(true)
|
|
92
|
+
expect(server.lastClientId()).toBe('c_session')
|
|
93
|
+
expect((await readSession()).clientId).toBe('c_session')
|
|
76
94
|
} finally {
|
|
77
95
|
await server.stop()
|
|
78
96
|
}
|
|
@@ -273,6 +291,7 @@ function rotationServer(opts?: {
|
|
|
273
291
|
}): {
|
|
274
292
|
url: string
|
|
275
293
|
refreshCount: () => number
|
|
294
|
+
lastClientId: () => string | undefined
|
|
276
295
|
lastOrganizationId: () => string | undefined
|
|
277
296
|
stop: () => Promise<void>
|
|
278
297
|
} {
|
|
@@ -280,6 +299,7 @@ function rotationServer(opts?: {
|
|
|
280
299
|
// refresh token is invalid_grant.
|
|
281
300
|
let current = 'rt-0'
|
|
282
301
|
let count = 0
|
|
302
|
+
let lastClientId: string | undefined
|
|
283
303
|
let lastOrgId: string | undefined
|
|
284
304
|
const server = Bun.serve({
|
|
285
305
|
port: 0,
|
|
@@ -300,6 +320,7 @@ function rotationServer(opts?: {
|
|
|
300
320
|
if (opts?.delayMs) await new Promise((r) => setTimeout(r, opts.delayMs))
|
|
301
321
|
count += 1
|
|
302
322
|
current = `rt-${count}`
|
|
323
|
+
lastClientId = form.get('client_id') ?? undefined
|
|
303
324
|
lastOrgId = form.get('organization_id') ?? undefined
|
|
304
325
|
return Response.json({
|
|
305
326
|
access_token: unsignedJwt({ aud: AUD, exp: futureEpoch() }),
|
|
@@ -312,6 +333,7 @@ function rotationServer(opts?: {
|
|
|
312
333
|
return {
|
|
313
334
|
url: `http://${server.hostname}:${server.port}`,
|
|
314
335
|
refreshCount: () => count,
|
|
336
|
+
lastClientId: () => lastClientId,
|
|
315
337
|
lastOrganizationId: () => lastOrgId,
|
|
316
338
|
stop: () => server.stop(true),
|
|
317
339
|
}
|
|
@@ -100,6 +100,7 @@ describe('IdP schemas and token helpers', () => {
|
|
|
100
100
|
idp: 'workos',
|
|
101
101
|
issuer: 'https://example.authkit.app',
|
|
102
102
|
subject: 'user_123',
|
|
103
|
+
clientId: 'client_123',
|
|
103
104
|
access_token: 'opaque-token',
|
|
104
105
|
refresh_token: 'refresh-token',
|
|
105
106
|
token_type: 'Bearer',
|
|
@@ -110,6 +111,7 @@ describe('IdP schemas and token helpers', () => {
|
|
|
110
111
|
})
|
|
111
112
|
|
|
112
113
|
expect(session.claims?.email).toBe('alice@example.com')
|
|
114
|
+
expect(session.clientId).toBe('client_123')
|
|
113
115
|
expect(session.refresh_token).toBe('refresh-token')
|
|
114
116
|
})
|
|
115
117
|
|
|
@@ -74,6 +74,12 @@ describe('InstanceStoreSchema', () => {
|
|
|
74
74
|
expect(normalizeInstanceKernelUrl('https://testmarc.eu.astrale.ai/')).toBe(
|
|
75
75
|
'https://testmarc.eu.astrale.ai/api',
|
|
76
76
|
)
|
|
77
|
+
expect(normalizeInstanceKernelUrl('https://testmarc.eu.beta.astrale.ai')).toBe(
|
|
78
|
+
'https://testmarc.eu.beta.astrale.ai/api',
|
|
79
|
+
)
|
|
80
|
+
expect(normalizeInstanceKernelUrl('https://shell.beta.astrale.ai')).toBe(
|
|
81
|
+
'https://shell.beta.astrale.ai',
|
|
82
|
+
)
|
|
77
83
|
})
|
|
78
84
|
|
|
79
85
|
test('does not rewrite explicit kernel or non-managed URLs', () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import { readFileSync } from 'node:fs'
|
|
3
|
-
import { join } from 'node:path'
|
|
2
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
3
|
+
import { join, relative } from 'node:path'
|
|
4
4
|
import { Project } from 'ts-morph'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -19,6 +19,18 @@ import { Project } from 'ts-morph'
|
|
|
19
19
|
|
|
20
20
|
const CLI_ROOT = new URL('../../../', import.meta.url).pathname
|
|
21
21
|
|
|
22
|
+
interface PackageManifest {
|
|
23
|
+
dependencies?: Record<string, string>
|
|
24
|
+
scripts?: Record<string, string>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const cliPackage = JSON.parse(
|
|
28
|
+
readFileSync(join(CLI_ROOT, 'package.json'), 'utf8'),
|
|
29
|
+
) as PackageManifest
|
|
30
|
+
const studioPackage = JSON.parse(
|
|
31
|
+
readFileSync(join(CLI_ROOT, 'studio/package.json'), 'utf8'),
|
|
32
|
+
) as PackageManifest
|
|
33
|
+
|
|
22
34
|
/** Base package of a bare specifier: `@scope/pkg/sub` → `@scope/pkg`, `pkg/sub` → `pkg`. */
|
|
23
35
|
function basePackage(spec: string): string {
|
|
24
36
|
const parts = spec.split('/')
|
|
@@ -29,26 +41,33 @@ function isBuiltin(spec: string): boolean {
|
|
|
29
41
|
return spec.startsWith('node:') || spec === 'bun' || spec.startsWith('bun:')
|
|
30
42
|
}
|
|
31
43
|
|
|
44
|
+
function runtimeFiles(directory: string): string[] {
|
|
45
|
+
return readdirSync(directory).flatMap((entry) => {
|
|
46
|
+
const path = join(directory, entry)
|
|
47
|
+
if (statSync(path).isDirectory()) return runtimeFiles(path)
|
|
48
|
+
return /\.(?:ts|tsx)$/.test(path) && !/\.test\.(?:ts|tsx)$/.test(path) ? [path] : []
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const studioPath = (path: string) => relative(CLI_ROOT, path).replaceAll('\\', '/')
|
|
53
|
+
|
|
32
54
|
describe('studio server runtime deps', () => {
|
|
33
|
-
const deps = new Set(
|
|
34
|
-
Object.keys(
|
|
35
|
-
(
|
|
36
|
-
JSON.parse(readFileSync(join(CLI_ROOT, 'package.json'), 'utf8')) as {
|
|
37
|
-
dependencies?: Record<string, string>
|
|
38
|
-
}
|
|
39
|
-
).dependencies ?? {},
|
|
40
|
-
),
|
|
41
|
-
)
|
|
55
|
+
const deps = new Set(Object.keys(cliPackage.dependencies ?? {}))
|
|
42
56
|
|
|
43
57
|
const project = new Project({ skipAddingFilesFromTsConfig: true })
|
|
44
58
|
project.addSourceFilesAtPaths([
|
|
45
59
|
join(CLI_ROOT, 'studio/server/**/*.ts'),
|
|
60
|
+
join(CLI_ROOT, 'studio/server/**/*.tsx'),
|
|
46
61
|
join(CLI_ROOT, 'studio/shared/**/*.ts'),
|
|
62
|
+
join(CLI_ROOT, 'studio/shared/**/*.tsx'),
|
|
47
63
|
])
|
|
64
|
+
const runtimeSourceFiles = project
|
|
65
|
+
.getSourceFiles()
|
|
66
|
+
.filter((sourceFile) => !/\.test\.(?:ts|tsx)$/.test(sourceFile.getBaseName()))
|
|
48
67
|
|
|
49
68
|
// getImportStringLiterals covers static imports, re-exports, and dynamic import().
|
|
50
69
|
const offenders: Record<string, string[]> = {}
|
|
51
|
-
for (const sf of
|
|
70
|
+
for (const sf of runtimeSourceFiles) {
|
|
52
71
|
const specs = sf.getImportStringLiterals().map((l) => l.getLiteralValue())
|
|
53
72
|
for (const spec of specs) {
|
|
54
73
|
if (spec.startsWith('.') || spec.startsWith('/') || isBuiltin(spec)) continue
|
|
@@ -66,9 +85,27 @@ describe('studio server runtime deps', () => {
|
|
|
66
85
|
).toEqual({})
|
|
67
86
|
})
|
|
68
87
|
|
|
69
|
-
test('
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
88
|
+
test('parses every shipped Studio server and shared runtime source', () => {
|
|
89
|
+
const expected = [
|
|
90
|
+
...runtimeFiles(join(CLI_ROOT, 'studio/server')),
|
|
91
|
+
...runtimeFiles(join(CLI_ROOT, 'studio/shared')),
|
|
92
|
+
]
|
|
93
|
+
.map(studioPath)
|
|
94
|
+
.sort()
|
|
95
|
+
const parsed = runtimeSourceFiles
|
|
96
|
+
.map((sourceFile) => studioPath(sourceFile.getFilePath()))
|
|
97
|
+
.sort()
|
|
98
|
+
|
|
99
|
+
expect(parsed).toEqual(expected)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('studio test gate', () => {
|
|
104
|
+
test('the root and Studio suites include all client, server, and shared tests', () => {
|
|
105
|
+
expect(cliPackage.scripts?.test).toBe(
|
|
106
|
+
'bun test src studio/client/src studio/server studio/shared && node --test scripts/*.test.mjs',
|
|
107
|
+
)
|
|
108
|
+
expect(cliPackage.scripts?.['test:studio']).toBe('pnpm --dir studio test')
|
|
109
|
+
expect(studioPackage.scripts?.test).toBe('bun test client/src server shared')
|
|
73
110
|
})
|
|
74
111
|
})
|
|
@@ -4,6 +4,8 @@ import { tmpdir } from 'node:os'
|
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
DEFAULT_UPDATE_CHANNEL,
|
|
8
|
+
InstallMetadataSchema,
|
|
7
9
|
releaseBase,
|
|
8
10
|
shouldUpdate,
|
|
9
11
|
updateAstrale,
|
|
@@ -84,6 +86,16 @@ async function makeInstall(
|
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
describe('update helpers', () => {
|
|
89
|
+
test('defaults missing install metadata to the beta channel', () => {
|
|
90
|
+
expect(DEFAULT_UPDATE_CHANNEL).toBe('beta')
|
|
91
|
+
expect(
|
|
92
|
+
InstallMetadataSchema.parse({
|
|
93
|
+
method: 'script',
|
|
94
|
+
bin: '/tmp/astrale',
|
|
95
|
+
}).channel,
|
|
96
|
+
).toBe('beta')
|
|
97
|
+
})
|
|
98
|
+
|
|
87
99
|
test('releaseBase resolves exact versions and channels', () => {
|
|
88
100
|
const meta: InstallMetadata = {
|
|
89
101
|
method: 'script',
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { publication } from '@astrale-os/kernel-protocol'
|
|
2
|
+
|
|
3
|
+
const MAXIMUM_PUBLICATION_BYTES = 1024 * 1024
|
|
4
|
+
type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
|
|
5
|
+
|
|
6
|
+
/** Fetch and admit one canonical Domain Publication without following its bundle. */
|
|
7
|
+
export async function fetchDomainPublication(
|
|
8
|
+
input: string,
|
|
9
|
+
signal?: AbortSignal,
|
|
10
|
+
fetchImpl: FetchLike = globalThis.fetch,
|
|
11
|
+
): Promise<publication.Publication> {
|
|
12
|
+
const root = deploymentRoot(input)
|
|
13
|
+
const url = new URL(publication.PATH, root)
|
|
14
|
+
const response = await fetchImpl(url, {
|
|
15
|
+
redirect: 'error',
|
|
16
|
+
...(signal === undefined ? {} : { signal }),
|
|
17
|
+
})
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
await cancel(response.body)
|
|
20
|
+
throw new Error(`GET ${url.href} → ${response.status}`)
|
|
21
|
+
}
|
|
22
|
+
const bytes = await readBounded(response, url)
|
|
23
|
+
try {
|
|
24
|
+
const source = new TextDecoder('utf-8', { fatal: true }).decode(bytes)
|
|
25
|
+
return publication.accept(JSON.parse(source) as unknown)
|
|
26
|
+
} catch (cause) {
|
|
27
|
+
throw new Error(`GET ${url.href} returned an invalid Domain Publication`, { cause })
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function readBounded(response: Response, url: URL): Promise<Uint8Array> {
|
|
32
|
+
const declared = response.headers.get('content-length')
|
|
33
|
+
if (
|
|
34
|
+
declared !== null &&
|
|
35
|
+
(!/^\d+$/u.test(declared) ||
|
|
36
|
+
!Number.isSafeInteger(Number(declared)) ||
|
|
37
|
+
Number(declared) > MAXIMUM_PUBLICATION_BYTES)
|
|
38
|
+
) {
|
|
39
|
+
await cancel(response.body)
|
|
40
|
+
throw sizeError(url)
|
|
41
|
+
}
|
|
42
|
+
if (response.body === null) return new Uint8Array()
|
|
43
|
+
|
|
44
|
+
const reader = response.body.getReader()
|
|
45
|
+
const chunks: Uint8Array[] = []
|
|
46
|
+
let size = 0
|
|
47
|
+
let complete = false
|
|
48
|
+
try {
|
|
49
|
+
for (;;) {
|
|
50
|
+
const next = await reader.read()
|
|
51
|
+
if (next.done) {
|
|
52
|
+
complete = true
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
if (next.value.byteLength === 0) continue
|
|
56
|
+
if (next.value.byteLength > MAXIMUM_PUBLICATION_BYTES - size) {
|
|
57
|
+
throw sizeError(url)
|
|
58
|
+
}
|
|
59
|
+
chunks.push(next.value)
|
|
60
|
+
size += next.value.byteLength
|
|
61
|
+
}
|
|
62
|
+
} finally {
|
|
63
|
+
if (!complete) await reader.cancel().catch(() => undefined)
|
|
64
|
+
reader.releaseLock()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const bytes = new Uint8Array(size)
|
|
68
|
+
let offset = 0
|
|
69
|
+
for (const chunk of chunks) {
|
|
70
|
+
bytes.set(chunk, offset)
|
|
71
|
+
offset += chunk.byteLength
|
|
72
|
+
}
|
|
73
|
+
return bytes
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function sizeError(url: URL): Error {
|
|
77
|
+
return new Error(`GET ${url.href} exceeded ${MAXIMUM_PUBLICATION_BYTES} bytes`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function cancel(body: ReadableStream<Uint8Array> | null): Promise<void> {
|
|
81
|
+
if (body !== null) await body.cancel().catch(() => undefined)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function deploymentRoot(input: string): URL {
|
|
85
|
+
let url: URL
|
|
86
|
+
try {
|
|
87
|
+
url = new URL(input)
|
|
88
|
+
} catch {
|
|
89
|
+
throw new TypeError(`Domain deployment URL is invalid: ${input}`)
|
|
90
|
+
}
|
|
91
|
+
if (
|
|
92
|
+
!['http:', 'https:'].includes(url.protocol) ||
|
|
93
|
+
url.username !== '' ||
|
|
94
|
+
url.password !== '' ||
|
|
95
|
+
url.search !== '' ||
|
|
96
|
+
url.hash !== '' ||
|
|
97
|
+
!['', '/'].includes(url.pathname)
|
|
98
|
+
) {
|
|
99
|
+
throw new TypeError(`Domain deployment URL must be an HTTP(S) origin: ${input}`)
|
|
100
|
+
}
|
|
101
|
+
return new URL(url.origin)
|
|
102
|
+
}
|
package/src/lib/idp.ts
CHANGED
|
@@ -145,6 +145,8 @@ export const IdpSessionSchema = z
|
|
|
145
145
|
idp: z.string(),
|
|
146
146
|
issuer: z.string().url(),
|
|
147
147
|
subject: z.string(),
|
|
148
|
+
/** OAuth client that issued this session's refresh token. */
|
|
149
|
+
clientId: z.string().optional(),
|
|
148
150
|
audience: z.string().optional(),
|
|
149
151
|
organizationId: z.string().optional(),
|
|
150
152
|
access_token: z.string(),
|
|
@@ -644,10 +646,11 @@ export async function refreshSession(
|
|
|
644
646
|
if (!session.refresh_token)
|
|
645
647
|
throw new Error(`IdP session for "${identityName}" has no refresh token`)
|
|
646
648
|
const idp = await readIdpConfig(session.idp)
|
|
649
|
+
const clientId = requireClientId(idp, session.clientId)
|
|
647
650
|
const params = new URLSearchParams({
|
|
648
651
|
grant_type: 'refresh_token',
|
|
649
652
|
refresh_token: session.refresh_token,
|
|
650
|
-
client_id:
|
|
653
|
+
client_id: clientId,
|
|
651
654
|
})
|
|
652
655
|
const audience = opts.audience ?? session.audience
|
|
653
656
|
if (audience) params.set('audience', audience)
|
|
@@ -674,6 +677,7 @@ export async function refreshSession(
|
|
|
674
677
|
const expiresAt = tokenExpiresAt(token)
|
|
675
678
|
const next: IdpSession = {
|
|
676
679
|
...session,
|
|
680
|
+
clientId,
|
|
677
681
|
access_token: token.access_token,
|
|
678
682
|
id_token: token.id_token ?? session.id_token,
|
|
679
683
|
refresh_token: token.refresh_token ?? session.refresh_token,
|
package/src/lib/instance.ts
CHANGED
|
@@ -448,7 +448,12 @@ function isRegionRoutedInstanceRoot(url: URL): boolean {
|
|
|
448
448
|
if (url.search || url.hash) return false
|
|
449
449
|
|
|
450
450
|
const labels = url.hostname.toLowerCase().split('.')
|
|
451
|
-
return
|
|
451
|
+
return (
|
|
452
|
+
(labels.length === 4 || labels.length === 5) &&
|
|
453
|
+
/^[a-z]{2}(?:-[a-z0-9]+)*$/u.test(labels[1] ?? '') &&
|
|
454
|
+
labels.at(-2) === 'astrale' &&
|
|
455
|
+
labels.at(-1) === 'ai'
|
|
456
|
+
)
|
|
452
457
|
}
|
|
453
458
|
|
|
454
459
|
function definedEntry(entry: AddInstanceOpts): AddInstanceOpts {
|
package/src/lib/login-flow.ts
CHANGED
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
normalizeTokenResponse,
|
|
8
8
|
pollDeviceToken,
|
|
9
9
|
readIdpConfigOrBuiltin,
|
|
10
|
+
readIdpSession,
|
|
10
11
|
readIdpStore,
|
|
12
|
+
requireClientId,
|
|
11
13
|
requestClientCredentials,
|
|
12
14
|
requestDeviceAuthorization,
|
|
13
15
|
saveIdpSession,
|
|
@@ -71,10 +73,17 @@ export type LoginResult = {
|
|
|
71
73
|
*/
|
|
72
74
|
export async function loginViaIdp(opts: LoginFlowOpts): Promise<LoginResult> {
|
|
73
75
|
const idpName = await resolveIdpName(opts.idp)
|
|
74
|
-
const
|
|
76
|
+
const previous = opts.name && !opts.clientId ? await readIdpSession(opts.name) : null
|
|
77
|
+
const savedClientId = previous?.idp === idpName ? previous.clientId : undefined
|
|
78
|
+
const requestedClientId = opts.clientId ?? savedClientId
|
|
79
|
+
const idp = await readIdpConfigOrBuiltin(idpName, {
|
|
80
|
+
clientId: requestedClientId,
|
|
81
|
+
persist: true,
|
|
82
|
+
})
|
|
83
|
+
const clientId = requireClientId(idp, requestedClientId)
|
|
75
84
|
const scope = opts.scope ?? idp.client.scope ?? 'openid profile email offline_access'
|
|
76
85
|
|
|
77
|
-
const token = normalizeTokenResponse(await obtainToken(idp, opts, scope))
|
|
86
|
+
const token = normalizeTokenResponse(await obtainToken(idp, { ...opts, clientId }, scope))
|
|
78
87
|
if (!token.access_token) throw new Error('IdP response did not include access_token')
|
|
79
88
|
if (opts.audience && !tokenAudienceMatches(token.access_token, opts.audience)) {
|
|
80
89
|
throw new Error(
|
|
@@ -83,7 +92,7 @@ export async function loginViaIdp(opts: LoginFlowOpts): Promise<LoginResult> {
|
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
const claims = decodeTokenClaims(token.id_token ?? token.access_token)
|
|
86
|
-
const subject = subjectFromToken(token,
|
|
95
|
+
const subject = subjectFromToken(token, clientId)
|
|
87
96
|
const issuer = issuerFromToken(token, idp.metadata.issuer)
|
|
88
97
|
const identityName = opts.name ?? identityNameFromClaims(claims, idpName)
|
|
89
98
|
const session: IdpSession = {
|
|
@@ -91,6 +100,7 @@ export async function loginViaIdp(opts: LoginFlowOpts): Promise<LoginResult> {
|
|
|
91
100
|
idp: idpName,
|
|
92
101
|
issuer,
|
|
93
102
|
subject,
|
|
103
|
+
clientId,
|
|
94
104
|
audience: opts.audience,
|
|
95
105
|
access_token: token.access_token,
|
|
96
106
|
id_token: token.id_token,
|
package/src/lib/meta.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* `jwks_uri`) and JWKS at `/.well-known/jwks.json`. These helpers probe
|
|
7
7
|
* both to verify the issuer is alive and publishes at least one key.
|
|
8
8
|
*
|
|
9
|
-
* Domain
|
|
10
|
-
* outside this
|
|
9
|
+
* Domain deployment readiness uses the canonical Domain Publication; that is
|
|
10
|
+
* outside this issuer-connectivity helper.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { IssuerUnreachableError } from '../errors'
|
package/src/lib/update.ts
CHANGED
|
@@ -9,11 +9,11 @@ import { INSTALL_PATH } from '../state/index'
|
|
|
9
9
|
import { run } from './proc'
|
|
10
10
|
|
|
11
11
|
const DEFAULT_REPO = 'astrale-os/cli'
|
|
12
|
-
const
|
|
12
|
+
export const DEFAULT_UPDATE_CHANNEL = 'beta'
|
|
13
13
|
|
|
14
14
|
export const InstallMetadataSchema = z.object({
|
|
15
15
|
method: z.literal('script'),
|
|
16
|
-
channel: z.string().min(1).default(
|
|
16
|
+
channel: z.string().min(1).default(DEFAULT_UPDATE_CHANNEL),
|
|
17
17
|
version: z.string().min(1).optional(),
|
|
18
18
|
repo: z.string().min(1).default(DEFAULT_REPO),
|
|
19
19
|
bin: z.string().min(1),
|
|
@@ -162,7 +162,7 @@ export function releaseBase(
|
|
|
162
162
|
const version = req.version.replace(/^cli\/v/, '').replace(/^v/, '')
|
|
163
163
|
return `https://github.com/${repo}/releases/download/cli/v${version}`
|
|
164
164
|
}
|
|
165
|
-
return `https://github.com/${repo}/releases/download/${req.channel ?? meta.channel ??
|
|
165
|
+
return `https://github.com/${repo}/releases/download/${req.channel ?? meta.channel ?? DEFAULT_UPDATE_CHANNEL}`
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
export async function fetchManifest(base: string): Promise<UpdateManifest> {
|
|
@@ -177,7 +177,7 @@ export function shouldUpdate(currentVersion: string, manifestVersion: string): b
|
|
|
177
177
|
export async function updateAstrale(req: UpdateRequest): Promise<UpdateResult> {
|
|
178
178
|
const meta = await readInstallMetadata(req.installPath)
|
|
179
179
|
const currentVersion = meta.version ?? req.currentVersion
|
|
180
|
-
const channel = req.channel ?? meta.channel ??
|
|
180
|
+
const channel = req.channel ?? meta.channel ?? DEFAULT_UPDATE_CHANNEL
|
|
181
181
|
const platform = req.platform ?? detectPlatform()
|
|
182
182
|
const key = platformKey(platform)
|
|
183
183
|
const base = releaseBase(meta, { channel, version: req.version })
|
|
@@ -187,7 +187,7 @@ describe('program composition', () => {
|
|
|
187
187
|
'whoami',
|
|
188
188
|
])
|
|
189
189
|
expect(createHash('sha256').update(JSON.stringify(surface)).digest('hex')).toBe(
|
|
190
|
-
'
|
|
190
|
+
'44f78959867171661197043f5b47eb6d6890274090370e071d10f11b4204850d',
|
|
191
191
|
)
|
|
192
192
|
})
|
|
193
193
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{m as L}from"./index-
|
|
1
|
+
import{m as L}from"./index-BckHuAWk.js";function S(y,b){for(var u=0;u<b.length;u++){const a=b[u];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in y)){const n=Object.getOwnPropertyDescriptor(a,i);n&&Object.defineProperty(y,i,n.get?n:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(y,Symbol.toStringTag,{value:"Module"}))}function w(y){throw new Error('Could not dynamically require "'+y+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}var O={exports:{}},j;function C(){return j||(j=1,(function(y,b){(function(u){y.exports=u()})(function(){return(function(){function u(a,i,n){function d(f,_){if(!i[f]){if(!a[f]){var h=typeof w=="function"&&w;if(!_&&h)return h(f,!0);if(g)return g(f,!0);var o=new Error("Cannot find module '"+f+"'");throw o.code="MODULE_NOT_FOUND",o}var e=i[f]={exports:{}};a[f][0].call(e.exports,function(r){var t=a[f][1][r];return d(t||r)},e,e.exports,u,a,i,n)}return i[f].exports}for(var g=typeof w=="function"&&w,m=0;m<n.length;m++)d(n[m]);return d}return u})()({1:[function(u,a,i){Object.defineProperty(i,"__esModule",{value:!0}),i.default=void 0;function n(o){"@babel/helpers - typeof";return n=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(o)}function d(o,e){if(!(o instanceof e))throw new TypeError("Cannot call a class as a function")}function g(o,e){for(var r=0;r<e.length;r++){var t=e[r];t.enumerable=t.enumerable||!1,t.configurable=!0,"value"in t&&(t.writable=!0),Object.defineProperty(o,f(t.key),t)}}function m(o,e,r){return e&&g(o.prototype,e),Object.defineProperty(o,"prototype",{writable:!1}),o}function f(o){var e=_(o,"string");return n(e)=="symbol"?e:e+""}function _(o,e){if(n(o)!="object"||!o)return o;var r=o[Symbol.toPrimitive];if(r!==void 0){var t=r.call(o,e);if(n(t)!="object")return t;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(o)}i.default=(function(){function o(){var e=this,r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},t=r.defaultLayoutOptions,s=t===void 0?{}:t,l=r.algorithms,v=l===void 0?["layered","stress","mrtree","radial","force","disco","sporeOverlap","sporeCompaction","rectpacking"]:l,c=r.workerFactory,p=r.workerUrl;if(d(this,o),this.defaultLayoutOptions=s,this.initialized=!1,typeof p>"u"&&typeof c>"u")throw new Error("Cannot construct an ELK without both 'workerUrl' and 'workerFactory'.");var k=c;typeof p<"u"&&typeof c>"u"&&(k=function(M){return new Worker(M)});var E=k(p);if(typeof E.postMessage!="function")throw new TypeError("Created worker does not provide the required 'postMessage' function.");this.worker=new h(E),this.worker.postMessage({cmd:"register",algorithms:v}).then(function(P){return e.initialized=!0}).catch(console.err)}return m(o,[{key:"layout",value:function(r){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},s=t.layoutOptions,l=s===void 0?this.defaultLayoutOptions:s,v=t.logging,c=v===void 0?!1:v,p=t.measureExecutionTime,k=p===void 0?!1:p;return r?this.worker.postMessage({cmd:"layout",graph:r,layoutOptions:l,options:{logging:c,measureExecutionTime:k}}):Promise.reject(new Error("Missing mandatory parameter 'graph'."))}},{key:"knownLayoutAlgorithms",value:function(){return this.worker.postMessage({cmd:"algorithms"})}},{key:"knownLayoutOptions",value:function(){return this.worker.postMessage({cmd:"options"})}},{key:"knownLayoutCategories",value:function(){return this.worker.postMessage({cmd:"categories"})}},{key:"terminateWorker",value:function(){this.worker&&this.worker.terminate()}}])})();var h=(function(){function o(e){var r=this;if(d(this,o),e===void 0)throw new Error("Missing mandatory parameter 'worker'.");this.resolvers={},this.worker=e,this.worker.onmessage=function(t){setTimeout(function(){r.receive(r,t)},0)}}return m(o,[{key:"postMessage",value:function(r){var t=this.id||0;this.id=t+1,r.id=t;var s=this;return new Promise(function(l,v){s.resolvers[t]=function(c,p){c?(s.convertGwtStyleError(c),v(c)):l(p)},s.worker.postMessage(r)})}},{key:"receive",value:function(r,t){var s=t.data,l=r.resolvers[s.id];l&&(delete r.resolvers[s.id],s.error?l(s.error):l(null,s.data))}},{key:"terminate",value:function(){this.worker&&this.worker.terminate()}},{key:"convertGwtStyleError",value:function(r){if(r){var t=r.__java$exception;t&&(t.cause&&t.cause.backingJsObject&&(r.cause=t.cause.backingJsObject,this.convertGwtStyleError(r.cause)),delete r.__java$exception)}}}])})()},{}],2:[function(u,a,i){var n=u("./elk-api.js").default;Object.defineProperty(a.exports,"__esModule",{value:!0}),a.exports=n,n.default=n},{"./elk-api.js":1}]},{},[2])(2)})})(O)),O.exports}var x=C();const A=L(x),q=S({__proto__:null,default:A},[x]);export{q as e};
|