@astrale-os/cli 1.0.0-beta.0 → 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 +3769 -3549
- 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 +5 -3
- package/src/admin/instance/client.ts +9 -27
- 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 +73 -0
- 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 +39 -13
- 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
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineDomain } from '@astrale-os/sdk'
|
|
2
|
+
import { issuer } from '@astrale-os/sdk/auth'
|
|
3
|
+
import { createDeployment } from '@astrale-os/sdk/deployment'
|
|
4
|
+
import { defineSchema } from '@astrale-os/sdk/schema/v1'
|
|
5
|
+
import { afterAll, describe, expect, test } from 'bun:test'
|
|
2
6
|
|
|
3
7
|
import type { DomainInfo } from '../../lib/admin-domain'
|
|
4
8
|
|
|
5
|
-
import { byDefaultThenName, domainProjection, type DomainRow } from '../domain/list'
|
|
9
|
+
import { byDefaultThenName, domainProjection, probe, type DomainRow } from '../domain/list'
|
|
6
10
|
|
|
7
11
|
const strip = (s: string): string => s.replace(/\[[0-9;]*m/g, '')
|
|
8
12
|
|
|
@@ -68,7 +72,82 @@ describe('domain list — projection', () => {
|
|
|
68
72
|
const live: DomainRow = { ...base, reachable: true, checkError: null }
|
|
69
73
|
expect(strip(domainProjection([live]).rows[0].status)).toBe('● live')
|
|
70
74
|
|
|
71
|
-
const down: DomainRow = { ...base, reachable: false, checkError: '
|
|
72
|
-
expect(strip(domainProjection([down]).rows[0].status)).toBe('○
|
|
75
|
+
const down: DomainRow = { ...base, reachable: false, checkError: 'Publication HTTP 502' }
|
|
76
|
+
expect(strip(domainProjection([down]).rows[0].status)).toBe('○ Publication HTTP 502')
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('domain list — canonical Publication check', () => {
|
|
81
|
+
const servers: { stop(): void }[] = []
|
|
82
|
+
afterAll(() => {
|
|
83
|
+
for (const server of servers) server.stop()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('reports the admitted schema revision in machine data', async () => {
|
|
87
|
+
const schema = defineSchema('catalog-check.example.dev', {})
|
|
88
|
+
const definition = defineDomain({
|
|
89
|
+
schema,
|
|
90
|
+
handlers: { functions: {}, classes: {}, interfaces: {} },
|
|
91
|
+
})
|
|
92
|
+
const deployed = createDeployment({
|
|
93
|
+
definition,
|
|
94
|
+
issuer: issuer.accept('https://catalog-check.example.dev'),
|
|
95
|
+
bundleHref: 'https://catalog-check.example.dev/domain.bundle.json',
|
|
96
|
+
bindings: { callables: [] },
|
|
97
|
+
}).publication
|
|
98
|
+
const server = Bun.serve({
|
|
99
|
+
port: 0,
|
|
100
|
+
fetch(request) {
|
|
101
|
+
return new URL(request.url).pathname === '/.well-known/astrale/domain.json'
|
|
102
|
+
? Response.json(deployed)
|
|
103
|
+
: new Response('not found', { status: 404 })
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
servers.push(server)
|
|
107
|
+
|
|
108
|
+
await expect(
|
|
109
|
+
probe(
|
|
110
|
+
entry({
|
|
111
|
+
origin: schema.origin,
|
|
112
|
+
url: `http://localhost:${server.port}`,
|
|
113
|
+
}),
|
|
114
|
+
),
|
|
115
|
+
).resolves.toMatchObject({
|
|
116
|
+
reachable: true,
|
|
117
|
+
schemaRevision: definition.domain.$.revision,
|
|
118
|
+
checkError: null,
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('rejects a Publication whose origin differs from the catalog entry', async () => {
|
|
123
|
+
const schema = defineSchema('deployed.example.dev', {})
|
|
124
|
+
const definition = defineDomain({
|
|
125
|
+
schema,
|
|
126
|
+
handlers: { functions: {}, classes: {}, interfaces: {} },
|
|
127
|
+
})
|
|
128
|
+
const deployed = createDeployment({
|
|
129
|
+
definition,
|
|
130
|
+
issuer: issuer.accept('https://deployed.example.dev'),
|
|
131
|
+
bundleHref: 'https://deployed.example.dev/domain.bundle.json',
|
|
132
|
+
bindings: { callables: [] },
|
|
133
|
+
}).publication
|
|
134
|
+
const server = Bun.serve({
|
|
135
|
+
port: 0,
|
|
136
|
+
fetch: () => Response.json(deployed),
|
|
137
|
+
})
|
|
138
|
+
servers.push(server)
|
|
139
|
+
|
|
140
|
+
await expect(
|
|
141
|
+
probe(
|
|
142
|
+
entry({
|
|
143
|
+
origin: 'catalog.example.dev',
|
|
144
|
+
url: `http://localhost:${server.port}`,
|
|
145
|
+
}),
|
|
146
|
+
),
|
|
147
|
+
).resolves.toMatchObject({
|
|
148
|
+
reachable: false,
|
|
149
|
+
checkError:
|
|
150
|
+
'Domain origin mismatch: deployed=deployed.example.dev expected=catalog.example.dev',
|
|
151
|
+
})
|
|
73
152
|
})
|
|
74
153
|
})
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { defineDomain } from '@astrale-os/sdk'
|
|
2
|
+
import { issuer } from '@astrale-os/sdk/auth'
|
|
3
|
+
import { createDeployment } from '@astrale-os/sdk/deployment'
|
|
4
|
+
import { defineSchema } from '@astrale-os/sdk/schema/v1'
|
|
1
5
|
import { afterAll, describe, expect, test } from 'bun:test'
|
|
2
6
|
|
|
3
7
|
import { domainRefFromTarget, isIdentityOverride, probeDeclaredOrigin } from '../domain/install'
|
|
@@ -29,46 +33,53 @@ describe('identity-override detection', () => {
|
|
|
29
33
|
})
|
|
30
34
|
})
|
|
31
35
|
|
|
32
|
-
describe('declared-origin probe
|
|
36
|
+
describe('declared-origin Publication probe', () => {
|
|
33
37
|
const servers: { stop(): void }[] = []
|
|
34
38
|
afterAll(() => {
|
|
35
39
|
for (const s of servers) s.stop()
|
|
36
40
|
})
|
|
37
41
|
|
|
38
|
-
function
|
|
42
|
+
function servePublication(handler: (req: Request) => Response): string {
|
|
39
43
|
const server = Bun.serve({ port: 0, fetch: handler })
|
|
40
44
|
servers.push(server)
|
|
41
45
|
return `http://localhost:${server.port}`
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
test('reads origin from a well-formed
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
test('reads origin from a well-formed canonical Publication', async () => {
|
|
49
|
+
const schema = defineSchema('crm.acme.dev', {})
|
|
50
|
+
const definition = defineDomain({
|
|
51
|
+
schema,
|
|
52
|
+
handlers: { functions: {}, classes: {}, interfaces: {} },
|
|
53
|
+
})
|
|
54
|
+
const deployed = createDeployment({
|
|
55
|
+
definition,
|
|
56
|
+
issuer: issuer.accept('https://crm.acme.dev'),
|
|
57
|
+
bundleHref: 'https://crm.acme.dev/domain.bundle.json',
|
|
58
|
+
bindings: { callables: [] },
|
|
59
|
+
}).publication
|
|
60
|
+
const url = servePublication((req) =>
|
|
61
|
+
new URL(req.url).pathname === '/.well-known/astrale/domain.json'
|
|
62
|
+
? Response.json(deployed)
|
|
54
63
|
: new Response('nope', { status: 404 }),
|
|
55
64
|
)
|
|
56
65
|
expect(await probeDeclaredOrigin(url)).toBe('crm.acme.dev')
|
|
57
66
|
})
|
|
58
67
|
|
|
59
|
-
test('
|
|
60
|
-
const legacy =
|
|
61
|
-
|
|
68
|
+
test('does not accept the removed pre-Kernel-V2 domainName shape', async () => {
|
|
69
|
+
const legacy = servePublication(() =>
|
|
70
|
+
Response.json({ iss: 'https://x', domainName: 'crm.acme.dev' }),
|
|
71
|
+
)
|
|
72
|
+
expect(await probeDeclaredOrigin(legacy)).toBeUndefined()
|
|
62
73
|
})
|
|
63
74
|
|
|
64
|
-
test('degrades to undefined on
|
|
65
|
-
const noName =
|
|
75
|
+
test('degrades to undefined on invalid Publication, non-200, bad JSON, or dead host', async () => {
|
|
76
|
+
const noName = servePublication(() => Response.json({ iss: 'https://x' }))
|
|
66
77
|
expect(await probeDeclaredOrigin(noName)).toBeUndefined()
|
|
67
78
|
|
|
68
|
-
const error =
|
|
79
|
+
const error = servePublication(() => new Response('boom', { status: 500 }))
|
|
69
80
|
expect(await probeDeclaredOrigin(error)).toBeUndefined()
|
|
70
81
|
|
|
71
|
-
const badJson =
|
|
82
|
+
const badJson = servePublication(() => new Response('<html>', { status: 200 }))
|
|
72
83
|
expect(await probeDeclaredOrigin(badJson)).toBeUndefined()
|
|
73
84
|
|
|
74
85
|
expect(await probeDeclaredOrigin('http://127.0.0.1:1')).toBeUndefined()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
import { realpathSync } from 'node:fs'
|
|
3
|
+
|
|
4
|
+
import { encodeStudioCliDescriptor } from '../studio'
|
|
5
|
+
|
|
6
|
+
test('Studio descriptor preserves the exact runtime and script entrypoint', () => {
|
|
7
|
+
expect(JSON.parse(encodeStudioCliDescriptor(process.execPath, import.meta.path))).toEqual({
|
|
8
|
+
version: 1,
|
|
9
|
+
executable: process.execPath,
|
|
10
|
+
args: [realpathSync(import.meta.path)],
|
|
11
|
+
})
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('Studio descriptor reinvokes a compiled executable without its virtual bunfs entry', () => {
|
|
15
|
+
expect(JSON.parse(encodeStudioCliDescriptor('/opt/bin/astrale', '/$bunfs/root/astrale'))).toEqual(
|
|
16
|
+
{
|
|
17
|
+
version: 1,
|
|
18
|
+
executable: '/opt/bin/astrale',
|
|
19
|
+
args: [],
|
|
20
|
+
},
|
|
21
|
+
)
|
|
22
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, expect, mock, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { cliStale, fetchNpmTargetVersion } from '../update'
|
|
4
|
+
|
|
5
|
+
describe('CLI update staleness', () => {
|
|
6
|
+
test('trusts the release manifest for a script install without consulting npm latest', async () => {
|
|
7
|
+
const fetchPackageVersion = mock(async () => '0.8.1-alpha.7')
|
|
8
|
+
const result = await cliStale(
|
|
9
|
+
{},
|
|
10
|
+
{
|
|
11
|
+
update: async ({ channel }) => {
|
|
12
|
+
expect(channel).toBe('beta')
|
|
13
|
+
return {
|
|
14
|
+
status: 'up-to-date',
|
|
15
|
+
currentVersion: '1.0.0-beta.0',
|
|
16
|
+
latestVersion: '1.0.0-beta.0',
|
|
17
|
+
channel: 'beta',
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
fetchPackageVersion,
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
expect(result).toEqual({
|
|
25
|
+
stale: false,
|
|
26
|
+
managed: false,
|
|
27
|
+
current: '1.0.0-beta.0',
|
|
28
|
+
latest: '1.0.0-beta.0',
|
|
29
|
+
channel: 'beta',
|
|
30
|
+
})
|
|
31
|
+
expect(fetchPackageVersion).not.toHaveBeenCalled()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('uses an explicit npm dist-tag only for package-managed installs', async () => {
|
|
35
|
+
const result = await cliStale(
|
|
36
|
+
{ channel: 'canary' },
|
|
37
|
+
{
|
|
38
|
+
update: async () => {
|
|
39
|
+
throw new Error('package managed')
|
|
40
|
+
},
|
|
41
|
+
fetchPackageVersion: async ({ channel }) => {
|
|
42
|
+
expect(channel).toBe('canary')
|
|
43
|
+
return '1.0.0-canary.0'
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
expect(result).toMatchObject({
|
|
49
|
+
managed: true,
|
|
50
|
+
latest: '1.0.0-canary.0',
|
|
51
|
+
channel: 'npm',
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('maps the default to beta and the stable channel to npm latest', async () => {
|
|
56
|
+
const originalFetch = globalThis.fetch
|
|
57
|
+
const fetchMock = mock(async (input: RequestInfo | URL) => {
|
|
58
|
+
const target = String(input).split('/').at(-1)
|
|
59
|
+
return Response.json({ version: target === 'beta' ? '1.0.0-beta.0' : '1.0.0' })
|
|
60
|
+
})
|
|
61
|
+
globalThis.fetch = fetchMock as unknown as typeof fetch
|
|
62
|
+
try {
|
|
63
|
+
expect(await fetchNpmTargetVersion({})).toBe('1.0.0-beta.0')
|
|
64
|
+
expect(await fetchNpmTargetVersion({ channel: 'stable' })).toBe('1.0.0')
|
|
65
|
+
expect(fetchMock.mock.calls.map(([input]) => String(input))).toEqual([
|
|
66
|
+
'https://registry.npmjs.org/@astrale-os/cli/beta',
|
|
67
|
+
'https://registry.npmjs.org/@astrale-os/cli/latest',
|
|
68
|
+
])
|
|
69
|
+
} finally {
|
|
70
|
+
globalThis.fetch = originalFetch
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
})
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from '../../lib/admin-domain'
|
|
16
16
|
import { listOwnedInstancesInContext, type OwnedInstanceInfo } from '../../lib/admin-instance'
|
|
17
17
|
import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
|
|
18
|
+
import { fetchDomainPublication } from '../../lib/domain-publication'
|
|
18
19
|
import { getActive } from '../../lib/instance'
|
|
19
20
|
import { fatal, log, withSpinner } from '../../lib/log'
|
|
20
21
|
import { isMachine, output } from '../../lib/output'
|
|
@@ -429,11 +430,11 @@ export async function installDirect(
|
|
|
429
430
|
log.dim(` operation: ${result.operation}`)
|
|
430
431
|
// Belt-and-braces: the kernel-confirmed origin is authoritative. If it
|
|
431
432
|
// aliases the host and the pre-install gate never consented to THAT
|
|
432
|
-
// origin (lying or
|
|
433
|
+
// origin (lying or unavailable Publication), say so loudly after the fact.
|
|
433
434
|
if (isIdentityOverride(installed.origin, host) && installed.origin !== consentedOrigin) {
|
|
434
435
|
log.warn(
|
|
435
436
|
`Installed origin "${installed.origin}" differs from the serving host "${host}" ` +
|
|
436
|
-
`and was not confirmed before install (the worker
|
|
437
|
+
`and was not confirmed before install (the worker Publication was unavailable). ` +
|
|
437
438
|
`Every ${installed.origin}/* call on this instance now routes to ${host}.`,
|
|
438
439
|
)
|
|
439
440
|
}
|
|
@@ -478,10 +479,10 @@ export function isIdentityOverride(origin: string, host: string): boolean {
|
|
|
478
479
|
* Claiming an origin that differs from the serving host is an explicit actAs
|
|
479
480
|
* and needs typed consent (or `--allow-identity-override` in scripts).
|
|
480
481
|
*
|
|
481
|
-
* The pre-install check reads the worker's
|
|
482
|
+
* The pre-install check reads the worker's canonical Publication origin,
|
|
482
483
|
* so it is consent UX, not enforcement — a hostile worker can lie here, and
|
|
483
484
|
* the kernel anchors the cryptographic identity (`iss`) on the real URL
|
|
484
|
-
* regardless. When
|
|
485
|
+
* regardless. When the Publication is unreachable or invalid, the gate
|
|
485
486
|
* degrades to a warning and the kernel-confirmed origin is re-checked after
|
|
486
487
|
* install (see `format` above).
|
|
487
488
|
*
|
|
@@ -496,7 +497,8 @@ async function ensureIdentityOverrideConsent(
|
|
|
496
497
|
const origin = await probeDeclaredOrigin(url)
|
|
497
498
|
if (origin === undefined) {
|
|
498
499
|
log.warn(
|
|
499
|
-
`Could not read a declared origin from
|
|
500
|
+
`Could not read a declared origin from ` +
|
|
501
|
+
`${new URL('/.well-known/astrale/domain.json', url).href} — ` +
|
|
500
502
|
`skipping the pre-install identity check (the installed origin is verified after install).`,
|
|
501
503
|
)
|
|
502
504
|
return undefined
|
|
@@ -534,19 +536,13 @@ async function ensureIdentityOverrideConsent(
|
|
|
534
536
|
return origin
|
|
535
537
|
}
|
|
536
538
|
|
|
537
|
-
/** Best-effort read of the worker's
|
|
539
|
+
/** Best-effort read of the worker's origin from its admitted canonical Publication. */
|
|
538
540
|
export async function probeDeclaredOrigin(url: string): Promise<string | undefined> {
|
|
539
541
|
try {
|
|
540
|
-
|
|
541
|
-
if (!res.ok) return undefined
|
|
542
|
-
// SDK workers serve `origin`; `domainName` is the pre-Kernel-V2 name, kept
|
|
543
|
-
// as a fallback for workers deployed before the rename.
|
|
544
|
-
const body = (await res.json()) as { origin?: unknown; domainName?: unknown }
|
|
545
|
-
const declared = body.origin ?? body.domainName
|
|
546
|
-
return typeof declared === 'string' && declared.length > 0 ? declared : undefined
|
|
542
|
+
return (await fetchDomainPublication(url, AbortSignal.timeout(10_000))).origin
|
|
547
543
|
} catch {
|
|
548
|
-
//
|
|
549
|
-
// itself will surface a dead worker with its own error.
|
|
544
|
+
// An unreachable or invalid Publication is not fatal here: the caller warns
|
|
545
|
+
// and the install itself will surface a dead worker with its own error.
|
|
550
546
|
return undefined
|
|
551
547
|
}
|
|
552
548
|
}
|
|
@@ -6,6 +6,7 @@ import type { CommandDefinition } from '../../program/index'
|
|
|
6
6
|
|
|
7
7
|
import { listAdminDomains, type DomainInfo } from '../../lib/admin-domain'
|
|
8
8
|
import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
|
|
9
|
+
import { fetchDomainPublication } from '../../lib/domain-publication'
|
|
9
10
|
import { fatal, withSpinner } from '../../lib/log'
|
|
10
11
|
import { isMachine, presentList } from '../../lib/output'
|
|
11
12
|
|
|
@@ -23,7 +24,7 @@ type ListOpts = KernelCommandOpts &
|
|
|
23
24
|
/** A catalog entry, optionally enriched with a live `--check` probe. */
|
|
24
25
|
export type DomainRow = DomainInfo & {
|
|
25
26
|
reachable?: boolean
|
|
26
|
-
|
|
27
|
+
schemaRevision?: string
|
|
27
28
|
checkError?: string | null
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -73,8 +74,8 @@ Behavior:
|
|
|
73
74
|
one install URL per line (pipeable into \`domain install\`); --count prints
|
|
74
75
|
only the number. --default-only keeps the install-by-default entries (what
|
|
75
76
|
every new instance gets via alphaCreate). --check probes each published URL's
|
|
76
|
-
|
|
77
|
-
machine output).
|
|
77
|
+
canonical Publication and adds a live/unreachable STATUS column
|
|
78
|
+
(+ reachable/schemaRevision in machine output).
|
|
78
79
|
|
|
79
80
|
The admin kernel is selected like every admin op — the configured default,
|
|
80
81
|
or --admin <bookmark> / --admin-url <url>.
|
|
@@ -87,7 +88,10 @@ Examples:
|
|
|
87
88
|
`,
|
|
88
89
|
options: [
|
|
89
90
|
...ADMIN_TARGET_OPTIONS,
|
|
90
|
-
{
|
|
91
|
+
{
|
|
92
|
+
flags: '--check',
|
|
93
|
+
description: "Probe each domain's canonical Publication + schema revision",
|
|
94
|
+
},
|
|
91
95
|
{ flags: '--default-only', description: 'Only show install-by-default domains' },
|
|
92
96
|
{ flags: '-q, --quiet', description: 'One install URL per line (unix-pipeable)' },
|
|
93
97
|
{ flags: '--count', description: 'Print only the number of published domains' },
|
|
@@ -103,7 +107,7 @@ Examples:
|
|
|
103
107
|
const filtered = opts.defaultOnly ? list.filter((d) => d.installByDefault) : list
|
|
104
108
|
filtered.sort(byDefaultThenName)
|
|
105
109
|
if (!opts.check) return filtered as DomainRow[]
|
|
106
|
-
// Reachability is a direct client-side
|
|
110
|
+
// Reachability is a direct client-side Publication fetch per entry, in
|
|
107
111
|
// parallel — no admin round-trip, and version-independent of the
|
|
108
112
|
// admin worker (mirrors `domain install`'s probeDeclaredOrigin).
|
|
109
113
|
return Promise.all(filtered.map(probe))
|
|
@@ -129,19 +133,20 @@ export function byDefaultThenName(a: DomainInfo, b: DomainInfo): number {
|
|
|
129
133
|
|
|
130
134
|
/**
|
|
131
135
|
* Enrich one entry with a reachability probe: fetch the published worker's
|
|
132
|
-
*
|
|
133
|
-
*
|
|
136
|
+
* canonical Publication and read its schema revision. A dead or missing URL is
|
|
137
|
+
* itself a result, not a throw.
|
|
134
138
|
*/
|
|
135
|
-
async function probe(d: DomainInfo): Promise<DomainRow> {
|
|
139
|
+
export async function probe(d: DomainInfo): Promise<DomainRow> {
|
|
136
140
|
if (!d.url) return { ...d, reachable: false, checkError: 'no url published' }
|
|
137
141
|
try {
|
|
138
|
-
const
|
|
139
|
-
if (
|
|
140
|
-
|
|
142
|
+
const deployed = await fetchDomainPublication(d.url, AbortSignal.timeout(10_000))
|
|
143
|
+
if (deployed.origin !== d.origin) {
|
|
144
|
+
throw new Error(`Domain origin mismatch: deployed=${deployed.origin} expected=${d.origin}`)
|
|
145
|
+
}
|
|
141
146
|
return {
|
|
142
147
|
...d,
|
|
143
148
|
reachable: true,
|
|
144
|
-
|
|
149
|
+
schemaRevision: deployed.schema.revision,
|
|
145
150
|
checkError: null,
|
|
146
151
|
}
|
|
147
152
|
} catch (err) {
|
package/src/commands/studio.ts
CHANGED
|
@@ -25,6 +25,23 @@ type StudioOpts = RawOutputOpts & {
|
|
|
25
25
|
const STUDIO_PORT_BASE = 4319
|
|
26
26
|
const VITE_PORT_BASE = 5273
|
|
27
27
|
const PORT_SPAN = 20
|
|
28
|
+
const STUDIO_CLI_DESCRIPTOR_ENV = 'DOMAIN_STUDIO_CLI_DESCRIPTOR'
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Describe this exact CLI invocation for the Studio server. A source/published
|
|
32
|
+
* script needs its entrypoint after node/bun; a Bun-compiled executable is
|
|
33
|
+
* self-contained and therefore has no prefix argument.
|
|
34
|
+
*/
|
|
35
|
+
export function encodeStudioCliDescriptor(
|
|
36
|
+
executable = process.execPath,
|
|
37
|
+
entry = process.argv[1],
|
|
38
|
+
): string {
|
|
39
|
+
const args =
|
|
40
|
+
entry && entry !== executable && !entry.startsWith('/$bunfs') && existsSync(entry)
|
|
41
|
+
? [realpathSync(entry)]
|
|
42
|
+
: []
|
|
43
|
+
return JSON.stringify({ version: 1, executable, args })
|
|
44
|
+
}
|
|
28
45
|
|
|
29
46
|
/**
|
|
30
47
|
* Locate the studio package shipped with / alongside the CLI. Anchored to the
|
|
@@ -217,6 +234,7 @@ Examples:
|
|
|
217
234
|
log.dim(` using ASTRALE_STUDIO_DIR=${studioDir}`)
|
|
218
235
|
}
|
|
219
236
|
await ensureBun()
|
|
237
|
+
const cliDescriptor = encodeStudioCliDescriptor()
|
|
220
238
|
|
|
221
239
|
// Default to PROD — serve the prebuilt client. It always renders, is what a
|
|
222
240
|
// published/global install runs, and avoids dev's heavy source-module graph.
|
|
@@ -342,6 +360,7 @@ Examples:
|
|
|
342
360
|
VITE_URL: `http://127.0.0.1:${vitePort}`,
|
|
343
361
|
PORT: String(studioPort),
|
|
344
362
|
DOMAIN_STUDIO_HOST: '127.0.0.1',
|
|
363
|
+
[STUDIO_CLI_DESCRIPTOR_ENV]: cliDescriptor,
|
|
345
364
|
...(opts.harness ? { DOMAIN_STUDIO_HARNESS: opts.harness } : {}),
|
|
346
365
|
},
|
|
347
366
|
},
|
|
@@ -367,6 +386,7 @@ Examples:
|
|
|
367
386
|
DOMAIN_STUDIO_DIST: dist,
|
|
368
387
|
PORT: String(studioPort),
|
|
369
388
|
DOMAIN_STUDIO_HOST: '127.0.0.1',
|
|
389
|
+
[STUDIO_CLI_DESCRIPTOR_ENV]: cliDescriptor,
|
|
370
390
|
...(opts.harness ? { DOMAIN_STUDIO_HARNESS: opts.harness } : {}),
|
|
371
391
|
},
|
|
372
392
|
},
|
package/src/commands/update.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type SdkOutdated,
|
|
13
13
|
} from '../lib/sdk-deps'
|
|
14
14
|
import { ASTRALE_CLI_SKILL, detectSkill, installSkills, SKILL_INSTALL_HINT } from '../lib/skills'
|
|
15
|
-
import { updateAstrale } from '../lib/update'
|
|
15
|
+
import { DEFAULT_UPDATE_CHANNEL, updateAstrale } from '../lib/update'
|
|
16
16
|
|
|
17
17
|
type UpdateOpts = RawOutputOpts & {
|
|
18
18
|
check?: boolean
|
|
@@ -115,27 +115,47 @@ export type StaleReport = {
|
|
|
115
115
|
sdk: { stale: boolean; inProject: boolean; outdated: SdkOutdated[] }
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
type CliStaleDependencies = {
|
|
119
|
+
update: typeof updateAstrale
|
|
120
|
+
fetchPackageVersion: (opts: Pick<UpdateOpts, 'channel' | 'version'>) => Promise<string>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const CLI_STALE_DEPENDENCIES: CliStaleDependencies = {
|
|
124
|
+
update: updateAstrale,
|
|
125
|
+
fetchPackageVersion: fetchNpmTargetVersion,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function cliStale(
|
|
129
|
+
opts: Pick<UpdateOpts, 'channel' | 'version'>,
|
|
130
|
+
dependencies: CliStaleDependencies = CLI_STALE_DEPENDENCIES,
|
|
131
|
+
): Promise<StaleReport['cli']> {
|
|
119
132
|
const running = pkg.version
|
|
120
|
-
const
|
|
133
|
+
const target = { ...opts, channel: opts.channel ?? DEFAULT_UPDATE_CHANNEL }
|
|
121
134
|
try {
|
|
122
|
-
const r = await
|
|
135
|
+
const r = await dependencies.update({
|
|
123
136
|
check: true,
|
|
124
|
-
channel:
|
|
125
|
-
version:
|
|
137
|
+
channel: target.channel,
|
|
138
|
+
version: target.version,
|
|
126
139
|
currentVersion: running,
|
|
127
140
|
})
|
|
128
141
|
if (r.status === 'updated') {
|
|
129
|
-
return {
|
|
142
|
+
return {
|
|
143
|
+
stale: false,
|
|
144
|
+
managed: false,
|
|
145
|
+
current: r.currentVersion,
|
|
146
|
+
latest: r.currentVersion,
|
|
147
|
+
channel: r.channel,
|
|
148
|
+
}
|
|
130
149
|
}
|
|
131
150
|
return {
|
|
132
|
-
stale:
|
|
151
|
+
stale: r.status === 'available',
|
|
133
152
|
managed: false,
|
|
134
|
-
current:
|
|
135
|
-
latest:
|
|
136
|
-
channel:
|
|
153
|
+
current: r.currentVersion,
|
|
154
|
+
latest: r.latestVersion,
|
|
155
|
+
channel: r.channel,
|
|
137
156
|
}
|
|
138
157
|
} catch {
|
|
158
|
+
const latest = await dependencies.fetchPackageVersion(target).catch(() => undefined)
|
|
139
159
|
return {
|
|
140
160
|
stale: latest !== undefined && latest !== running,
|
|
141
161
|
managed: true,
|
|
@@ -145,8 +165,12 @@ async function cliStale(opts: UpdateOpts): Promise<StaleReport['cli']> {
|
|
|
145
165
|
}
|
|
146
166
|
}
|
|
147
167
|
|
|
148
|
-
async function
|
|
149
|
-
|
|
168
|
+
export async function fetchNpmTargetVersion(
|
|
169
|
+
opts: Pick<UpdateOpts, 'channel' | 'version'>,
|
|
170
|
+
): Promise<string> {
|
|
171
|
+
const channel = opts.channel ?? DEFAULT_UPDATE_CHANNEL
|
|
172
|
+
const target = opts.version ?? (channel === 'stable' ? 'latest' : channel)
|
|
173
|
+
const response = await fetch(`https://registry.npmjs.org/@astrale-os/cli/${target}`)
|
|
150
174
|
if (!response.ok) throw new Error(`npm registry HTTP ${response.status}`)
|
|
151
175
|
const body: unknown = await response.json()
|
|
152
176
|
if (
|
|
@@ -175,6 +199,7 @@ export default {
|
|
|
175
199
|
{
|
|
176
200
|
flags: '--channel <name>',
|
|
177
201
|
description: 'Update from a release channel (alpha, beta, rc, canary, stable)',
|
|
202
|
+
default: DEFAULT_UPDATE_CHANNEL,
|
|
178
203
|
},
|
|
179
204
|
{ flags: '--version <version>', description: 'Update to an exact version tag' },
|
|
180
205
|
{ flags: '--no-skills', description: 'Skip refreshing the astrale agent skills' },
|
|
@@ -199,6 +224,7 @@ Behavior:
|
|
|
199
224
|
the lockfile, honoring your registry + supply-chain age policy; run "pnpm
|
|
200
225
|
install" to materialize).
|
|
201
226
|
|
|
227
|
+
The default release channel is beta; --channel overrides it for one run.
|
|
202
228
|
--check is a dry run (binary + SDK deps; exit 10 if anything is available) and
|
|
203
229
|
never writes. With --json it emits a unified staleness report
|
|
204
230
|
({ stale, cli, sdk }) for tooling — non-throwing, skills omitted (they ride
|