@astrale-os/cli 1.0.0-beta.17 → 1.0.0-beta.18
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/dist/astrale.js +2017 -1179
- package/dist/public/connect-core.js +4 -4
- package/package.json +2 -2
- package/src/commands/__tests__/domain-install-operation.test.ts +4 -8
- package/src/commands/__tests__/install-direct.test.ts +115 -8
- package/src/commands/__tests__/introspect-parse.test.ts +80 -1
- package/src/commands/domain/install.ts +54 -35
- package/src/commands/introspect.ts +40 -20
- package/studio/package.json +1 -1
- package/viewer/dist/main.js +40 -40
|
@@ -8242,14 +8242,14 @@ function isPidAlive(pid) {
|
|
|
8242
8242
|
function sleep(milliseconds) {
|
|
8243
8243
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
8244
8244
|
}
|
|
8245
|
-
// node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.
|
|
8245
|
+
// node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.12/node_modules/@astrale-os/kernel-dsl/dist/value/limits.js
|
|
8246
8246
|
var defaultLimits = Object.freeze({
|
|
8247
8247
|
maxBytes: 16 * 1024 * 1024,
|
|
8248
8248
|
maxDepth: 64,
|
|
8249
8249
|
maxMembers: 1e5,
|
|
8250
8250
|
maxStringBytes: 1024 * 1024
|
|
8251
8251
|
});
|
|
8252
|
-
// node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.
|
|
8252
|
+
// node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.12/node_modules/@astrale-os/kernel-dsl/dist/value/canonical.js
|
|
8253
8253
|
var encoder2 = new TextEncoder;
|
|
8254
8254
|
// node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
8255
8255
|
var CharacterCodes;
|
|
@@ -8429,7 +8429,7 @@ var ParseErrorCode;
|
|
|
8429
8429
|
ParseErrorCode2[ParseErrorCode2["InvalidCharacter"] = 16] = "InvalidCharacter";
|
|
8430
8430
|
})(ParseErrorCode || (ParseErrorCode = {}));
|
|
8431
8431
|
|
|
8432
|
-
// node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.
|
|
8432
|
+
// node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.12/node_modules/@astrale-os/kernel-dsl/dist/value/decode.js
|
|
8433
8433
|
var decoder2 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
8434
8434
|
// node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
8435
8435
|
var exports_external = {};
|
|
@@ -23659,7 +23659,7 @@ var log = {
|
|
|
23659
23659
|
dim: (msg) => console.log(source_default.dim(msg))
|
|
23660
23660
|
};
|
|
23661
23661
|
var IS_CI = !!(process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.NO_SPINNER);
|
|
23662
|
-
// node_modules/.pnpm/@astrale-os+kernel-core@0.9.0-beta.
|
|
23662
|
+
// node_modules/.pnpm/@astrale-os+kernel-core@0.9.0-beta.15/node_modules/@astrale-os/kernel-core/dist/dns-label.js
|
|
23663
23663
|
var DNS_LABEL_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
23664
23664
|
function isDnsLabel(value) {
|
|
23665
23665
|
return DNS_LABEL_RE.test(value);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrale-os/cli",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.18",
|
|
4
4
|
"description": "Astrale CLI — connect to existing Astrale kernels",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astrale",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@astrale-os/ox": ">=0.1.3 <1.0.0",
|
|
65
|
-
"@astrale-os/sdk": "0.5.0-beta.
|
|
65
|
+
"@astrale-os/sdk": "0.5.0-beta.49",
|
|
66
66
|
"@astrale-os/shell": "0.4.2-beta.3",
|
|
67
67
|
"@astrale/commitlint-config": "npm:@jsr/astrale__commitlint-config@~2.0.1",
|
|
68
68
|
"@commitlint/cli": "21.2.2",
|
|
@@ -38,9 +38,7 @@ describe('direct domain install operation identity', () => {
|
|
|
38
38
|
recovery = input.recovery
|
|
39
39
|
requests.push(
|
|
40
40
|
await input.fn({
|
|
41
|
-
session: {
|
|
42
|
-
call: async (call: { input: unknown }) => call.input,
|
|
43
|
-
},
|
|
41
|
+
session: { schema: { install: async (input: unknown) => input } },
|
|
44
42
|
} as never),
|
|
45
43
|
)
|
|
46
44
|
},
|
|
@@ -51,7 +49,7 @@ describe('direct domain install operation identity', () => {
|
|
|
51
49
|
expect(requests).toEqual([
|
|
52
50
|
{
|
|
53
51
|
operation: GENERATED,
|
|
54
|
-
domains: [{
|
|
52
|
+
domains: [{ publication: { url: 'https://crm.test' } }],
|
|
55
53
|
},
|
|
56
54
|
])
|
|
57
55
|
expect(recovery).toEqual({
|
|
@@ -78,9 +76,7 @@ describe('direct domain install operation identity', () => {
|
|
|
78
76
|
runKernelCommand: async (input) => {
|
|
79
77
|
requests.push(
|
|
80
78
|
await input.fn({
|
|
81
|
-
session: {
|
|
82
|
-
call: async (call: { input: unknown }) => call.input,
|
|
83
|
-
},
|
|
79
|
+
session: { schema: { install: async (input: unknown) => input } },
|
|
84
80
|
} as never),
|
|
85
81
|
)
|
|
86
82
|
},
|
|
@@ -92,7 +88,7 @@ describe('direct domain install operation identity', () => {
|
|
|
92
88
|
expect(requests).toEqual([
|
|
93
89
|
{
|
|
94
90
|
operation: RETRY,
|
|
95
|
-
domains: [{
|
|
91
|
+
domains: [{ publication: { url: 'https://crm.test' } }],
|
|
96
92
|
},
|
|
97
93
|
])
|
|
98
94
|
})
|
|
@@ -1,21 +1,128 @@
|
|
|
1
|
+
import type { InstallResult } from '@astrale-os/sdk/client/schema'
|
|
2
|
+
|
|
3
|
+
import { defineSchema, schema } from '@astrale-os/sdk/schema'
|
|
1
4
|
import { describe, expect, test } from 'bun:test'
|
|
2
5
|
|
|
3
|
-
import { directInstallCallInput } from '../domain/install'
|
|
6
|
+
import { directInstallCallInput, directInstallPresentation } from '../domain/install'
|
|
7
|
+
|
|
8
|
+
const currentRevision = schema.revision(defineSchema('tasks.astrale.ai', {}))
|
|
4
9
|
|
|
5
10
|
describe('directInstallCallInput', () => {
|
|
6
11
|
test('sends the current remote install syscall, not a legacy url list', () => {
|
|
7
12
|
const operation = '4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8'
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
const input = directInstallCallInput('https://tasks.example.test', operation, 'secret')
|
|
14
|
+
|
|
15
|
+
expect(String(input.operation)).toBe(operation)
|
|
16
|
+
expect(input.domains).toEqual([
|
|
17
|
+
{
|
|
18
|
+
publication: {
|
|
19
|
+
url: 'https://tasks.example.test',
|
|
20
|
+
token: 'secret',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
])
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('presents a committed install from its receipt', () => {
|
|
27
|
+
const operation = directInstallCallInput(
|
|
28
|
+
'https://tasks.example.test',
|
|
29
|
+
'4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8',
|
|
30
|
+
).operation
|
|
31
|
+
const result = {
|
|
32
|
+
changed: true,
|
|
33
|
+
receipt: {
|
|
34
|
+
operation,
|
|
35
|
+
transitions: [
|
|
36
|
+
{
|
|
37
|
+
intent: {
|
|
38
|
+
transition: 'transition-1',
|
|
39
|
+
operation,
|
|
40
|
+
origin: 'tasks.astrale.ai',
|
|
41
|
+
previous: null,
|
|
42
|
+
generation: {
|
|
43
|
+
origin: 'tasks.astrale.ai',
|
|
44
|
+
revision: currentRevision,
|
|
45
|
+
generation: 'sha256:generation',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
phase: 'cutover',
|
|
49
|
+
state: 'committed',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
} satisfies InstallResult
|
|
54
|
+
|
|
55
|
+
expect(directInstallPresentation(result, 'ignored')).toEqual({
|
|
56
|
+
operation: '4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8',
|
|
57
|
+
origin: 'tasks.astrale.ai',
|
|
58
|
+
revision: currentRevision,
|
|
59
|
+
status: 'installed',
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('presents an idempotent install from the current Domain observation', () => {
|
|
64
|
+
const operation = '4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8'
|
|
65
|
+
const result = {
|
|
66
|
+
changed: false,
|
|
10
67
|
domains: [
|
|
11
68
|
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
69
|
+
origin: 'tasks.astrale.ai',
|
|
70
|
+
revision: currentRevision,
|
|
71
|
+
generation: 'sha256:generation',
|
|
72
|
+
publication: null,
|
|
73
|
+
readiness: 'sha256:readiness',
|
|
74
|
+
capabilities: { requested: {}, materialized: {} },
|
|
75
|
+
bindings: { callables: [], views: [] },
|
|
17
76
|
},
|
|
18
77
|
],
|
|
78
|
+
} satisfies InstallResult
|
|
79
|
+
|
|
80
|
+
expect(directInstallPresentation(result, operation)).toEqual({
|
|
81
|
+
operation,
|
|
82
|
+
origin: 'tasks.astrale.ai',
|
|
83
|
+
revision: currentRevision,
|
|
84
|
+
status: 'already current',
|
|
19
85
|
})
|
|
20
86
|
})
|
|
87
|
+
|
|
88
|
+
test('rejects a changed result without a committed transition', () => {
|
|
89
|
+
const result = {
|
|
90
|
+
changed: true,
|
|
91
|
+
receipt: { operation: '4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8', transitions: [] },
|
|
92
|
+
} as unknown as InstallResult
|
|
93
|
+
|
|
94
|
+
expect(() => directInstallPresentation(result, 'ignored')).toThrow(
|
|
95
|
+
'Kernel install returned no committed Domain transition.',
|
|
96
|
+
)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('rejects a committed transition without an installed generation', () => {
|
|
100
|
+
const operation = directInstallCallInput(
|
|
101
|
+
'https://tasks.example.test',
|
|
102
|
+
'4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8',
|
|
103
|
+
).operation
|
|
104
|
+
const result = {
|
|
105
|
+
changed: true,
|
|
106
|
+
receipt: {
|
|
107
|
+
operation,
|
|
108
|
+
transitions: [
|
|
109
|
+
{
|
|
110
|
+
intent: {
|
|
111
|
+
transition: 'transition-1',
|
|
112
|
+
operation,
|
|
113
|
+
origin: 'tasks.astrale.ai',
|
|
114
|
+
previous: null,
|
|
115
|
+
generation: null,
|
|
116
|
+
},
|
|
117
|
+
phase: 'cutover',
|
|
118
|
+
state: 'committed',
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
} satisfies InstallResult
|
|
123
|
+
|
|
124
|
+
expect(() => directInstallPresentation(result, 'ignored')).toThrow(
|
|
125
|
+
'Kernel install returned a committed transition without a Domain generation.',
|
|
126
|
+
)
|
|
127
|
+
})
|
|
21
128
|
})
|
|
@@ -1,6 +1,44 @@
|
|
|
1
|
+
import type { DomainBundle, DomainInfo } from '@astrale-os/sdk/client/schema'
|
|
2
|
+
|
|
3
|
+
import { bundle, defineSchema, func, method, nodeClass, schema } from '@astrale-os/sdk/schema'
|
|
1
4
|
import { describe, expect, test } from 'bun:test'
|
|
2
5
|
|
|
3
|
-
import { parseIntrospectTarget } from '../introspect'
|
|
6
|
+
import { introspectCommand, parseIntrospectTarget } from '../introspect'
|
|
7
|
+
|
|
8
|
+
const source = defineSchema('host.astrale.ai', {
|
|
9
|
+
classes: {
|
|
10
|
+
Manager: nodeClass({
|
|
11
|
+
methods: {
|
|
12
|
+
createInstance: method({
|
|
13
|
+
auth: 'authenticated',
|
|
14
|
+
input: { type: 'object', properties: {}, additionalProperties: false },
|
|
15
|
+
output: { type: 'object', additionalProperties: true },
|
|
16
|
+
static: true,
|
|
17
|
+
}),
|
|
18
|
+
},
|
|
19
|
+
}),
|
|
20
|
+
},
|
|
21
|
+
functions: {
|
|
22
|
+
journal: func({
|
|
23
|
+
auth: 'authenticated',
|
|
24
|
+
input: { type: 'object', properties: {}, additionalProperties: false },
|
|
25
|
+
output: { type: 'object', additionalProperties: true },
|
|
26
|
+
}),
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const info = {
|
|
31
|
+
origin: source.origin,
|
|
32
|
+
revision: schema.revision(source),
|
|
33
|
+
generation: 'sha256:generation',
|
|
34
|
+
publication: null,
|
|
35
|
+
readiness: 'sha256:readiness',
|
|
36
|
+
capabilities: { requested: {}, materialized: {} },
|
|
37
|
+
bindings: { callables: [], views: [] },
|
|
38
|
+
} satisfies DomainInfo
|
|
39
|
+
|
|
40
|
+
const installed = bundle.create(source)
|
|
41
|
+
const bundled = { domain: info, bundle: installed } satisfies DomainBundle
|
|
4
42
|
|
|
5
43
|
describe('parseIntrospectTarget', () => {
|
|
6
44
|
test('accepts a bare origin', () => {
|
|
@@ -18,4 +56,45 @@ describe('parseIntrospectTarget', () => {
|
|
|
18
56
|
test('rejects an @id', () => {
|
|
19
57
|
expect(() => parseIntrospectTarget('@abc')).toThrow('not an @id')
|
|
20
58
|
})
|
|
59
|
+
|
|
60
|
+
test('routes bare, bundle, Method, and Function commands through the current Schema API', async () => {
|
|
61
|
+
const calls: string[] = []
|
|
62
|
+
const schema = {
|
|
63
|
+
inspect: async (origin: string) => {
|
|
64
|
+
calls.push(`inspect:${origin}`)
|
|
65
|
+
return info
|
|
66
|
+
},
|
|
67
|
+
bundle: async (origin: string) => {
|
|
68
|
+
calls.push(`bundle:${origin}`)
|
|
69
|
+
return bundled
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
const results: unknown[] = []
|
|
73
|
+
const runKernelCommand = async (input: {
|
|
74
|
+
fn: (context: unknown) => Promise<unknown>
|
|
75
|
+
}): Promise<void> => {
|
|
76
|
+
results.push(await input.fn({ session: { schema } }))
|
|
77
|
+
}
|
|
78
|
+
const dependencies = { runKernelCommand: runKernelCommand as never }
|
|
79
|
+
|
|
80
|
+
await introspectCommand('host.astrale.ai', { json: true }, dependencies)
|
|
81
|
+
await introspectCommand('host.astrale.ai', { bundle: true, json: true }, dependencies)
|
|
82
|
+
await introspectCommand(
|
|
83
|
+
'/:host.astrale.ai:class.Manager:createInstance',
|
|
84
|
+
{ json: true },
|
|
85
|
+
dependencies,
|
|
86
|
+
)
|
|
87
|
+
await introspectCommand('/:host.astrale.ai:function.journal', { json: true }, dependencies)
|
|
88
|
+
|
|
89
|
+
expect(results[0]).toBe(info)
|
|
90
|
+
expect(results[1]).toBe(bundled)
|
|
91
|
+
expect(results[2]).toMatchObject({ class: 'Manager', method: 'createInstance' })
|
|
92
|
+
expect(results[3]).toMatchObject({ function: 'journal' })
|
|
93
|
+
expect(calls).toEqual([
|
|
94
|
+
'inspect:host.astrale.ai',
|
|
95
|
+
'bundle:host.astrale.ai',
|
|
96
|
+
'bundle:host.astrale.ai',
|
|
97
|
+
'bundle:host.astrale.ai',
|
|
98
|
+
])
|
|
99
|
+
})
|
|
21
100
|
})
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type { InstallRequest, InstallResult, OperationId } from '@astrale-os/sdk/client/schema'
|
|
2
|
+
|
|
3
3
|
import chalk from 'chalk'
|
|
4
4
|
|
|
5
5
|
import type { KernelCommandOpts } from '../../connection'
|
|
6
6
|
import type { CommandDefinition } from '../../program/index'
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { runKernelCommand, withAdminClientSession } from '../../connection'
|
|
9
9
|
import { formatKernelError } from '../../connection/errors'
|
|
10
10
|
import { AstraleError } from '../../errors'
|
|
11
11
|
import {
|
|
@@ -23,35 +23,61 @@ import { confirmWithInput, promptText, selectFrom } from '../../lib/prompt'
|
|
|
23
23
|
import { isHttpUrl } from '../../lib/validation'
|
|
24
24
|
|
|
25
25
|
/** Public Kernel install syscall input for one remote URL. */
|
|
26
|
-
export function directInstallCallInput(
|
|
26
|
+
export function directInstallCallInput(
|
|
27
|
+
url: string,
|
|
28
|
+
operation: string,
|
|
29
|
+
token?: string,
|
|
30
|
+
): InstallRequest {
|
|
31
|
+
const domain = Object.freeze({
|
|
32
|
+
publication: Object.freeze({
|
|
33
|
+
url,
|
|
34
|
+
...(token === undefined ? {} : { token }),
|
|
35
|
+
}),
|
|
36
|
+
})
|
|
27
37
|
return Object.freeze({
|
|
28
|
-
operation,
|
|
29
|
-
domains: [
|
|
30
|
-
Object.freeze({
|
|
31
|
-
source: Object.freeze({
|
|
32
|
-
kind: 'remote' as const,
|
|
33
|
-
url,
|
|
34
|
-
...(token === undefined ? {} : { token }),
|
|
35
|
-
}),
|
|
36
|
-
}),
|
|
37
|
-
],
|
|
38
|
+
operation: acceptOperationId(operation),
|
|
39
|
+
domains: Object.freeze([domain] as const),
|
|
38
40
|
})
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
export interface DirectInstallPresentation {
|
|
42
44
|
readonly operation: string
|
|
43
|
-
readonly
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
readonly origin: string
|
|
46
|
+
readonly revision: string
|
|
47
|
+
readonly status: 'installed' | 'already current'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Stable CLI presentation derived from the canonical binary install result. */
|
|
51
|
+
export function directInstallPresentation(
|
|
52
|
+
result: InstallResult,
|
|
53
|
+
requestedOperation: string,
|
|
54
|
+
): DirectInstallPresentation {
|
|
55
|
+
if (result.changed) {
|
|
56
|
+
const installed = result.receipt.transitions[0]?.intent
|
|
57
|
+
if (!installed) throw new Error('Kernel install returned no committed Domain transition.')
|
|
58
|
+
if (!installed.generation) {
|
|
59
|
+
throw new Error('Kernel install returned a committed transition without a Domain generation.')
|
|
47
60
|
}
|
|
48
|
-
|
|
61
|
+
return Object.freeze({
|
|
62
|
+
operation: result.receipt.operation,
|
|
63
|
+
origin: installed.origin,
|
|
64
|
+
revision: installed.generation.revision,
|
|
65
|
+
status: 'installed' as const,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
const installed = result.domains[0]
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
operation: requestedOperation,
|
|
71
|
+
origin: installed.origin,
|
|
72
|
+
revision: installed.revision,
|
|
73
|
+
status: 'already current' as const,
|
|
74
|
+
})
|
|
49
75
|
}
|
|
50
76
|
|
|
51
77
|
const OPERATION_ID_PATTERN =
|
|
52
78
|
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u
|
|
53
79
|
|
|
54
|
-
function acceptOperationId(input: unknown):
|
|
80
|
+
function acceptOperationId(input: unknown): OperationId {
|
|
55
81
|
if (typeof input !== 'string' || !OPERATION_ID_PATTERN.test(input)) {
|
|
56
82
|
throw new AstraleError(
|
|
57
83
|
'INVALID_FLAG',
|
|
@@ -59,10 +85,10 @@ function acceptOperationId(input: unknown): string {
|
|
|
59
85
|
'Omit --operation for a fresh install; use it only with the exact UUID printed for recovery.',
|
|
60
86
|
)
|
|
61
87
|
}
|
|
62
|
-
return input
|
|
88
|
+
return input as OperationId
|
|
63
89
|
}
|
|
64
90
|
|
|
65
|
-
function createOperationId():
|
|
91
|
+
function createOperationId(): OperationId {
|
|
66
92
|
return acceptOperationId(globalThis.crypto.randomUUID())
|
|
67
93
|
}
|
|
68
94
|
|
|
@@ -407,27 +433,20 @@ export async function installDirect(
|
|
|
407
433
|
const url = target as string
|
|
408
434
|
const retry = directInstallRetry(url, operation, opts)
|
|
409
435
|
|
|
410
|
-
await direct.runKernelCommand<
|
|
436
|
+
await direct.runKernelCommand<InstallResult>({
|
|
411
437
|
opts,
|
|
412
438
|
label: `Installing domain from ${url} (operation ${operation})`,
|
|
413
439
|
recovery: { operation, retry },
|
|
414
440
|
fn: async ({ session }) =>
|
|
415
|
-
(
|
|
416
|
-
createPathCall(
|
|
417
|
-
Path.project(K.functions.install.ref).raw,
|
|
418
|
-
directInstallCallInput(url, operation, opts.token),
|
|
419
|
-
),
|
|
420
|
-
)) as DirectInstallResult,
|
|
441
|
+
session.schema.install(directInstallCallInput(url, operation, opts.token)),
|
|
421
442
|
format: (result, fmtOpts, isRaw) => {
|
|
422
443
|
if (isRaw) {
|
|
423
444
|
output(result, fmtOpts)
|
|
424
445
|
return
|
|
425
446
|
}
|
|
426
|
-
const installed = result
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
log.success(`Domain installed: ${installed.origin}@${revision}`)
|
|
430
|
-
log.dim(` operation: ${result.operation}`)
|
|
447
|
+
const installed = directInstallPresentation(result, operation)
|
|
448
|
+
log.success(`Domain ${installed.status}: ${installed.origin}@${installed.revision}`)
|
|
449
|
+
log.dim(` operation: ${installed.operation}`)
|
|
431
450
|
// Belt-and-braces: the kernel-confirmed origin is authoritative. If it
|
|
432
451
|
// aliases the host and the pre-install gate never consented to THAT
|
|
433
452
|
// origin (lying or unavailable Publication), say so loudly after the fact.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { DomainBundle, DomainInfo, SchemaApi } from '@astrale-os/sdk/client/schema'
|
|
2
|
+
|
|
1
3
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
2
4
|
|
|
3
5
|
import type { KernelCommandOpts } from '../connection'
|
|
@@ -11,7 +13,18 @@ import { describeCallableFromBundle, missingCallableDescription } from './call-d
|
|
|
11
13
|
|
|
12
14
|
type IntrospectOpts = KernelCommandOpts & { bundle?: boolean }
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
interface IntrospectDependencies {
|
|
17
|
+
readonly runKernelCommand: typeof runKernelCommand
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const defaultIntrospectDependencies = Object.freeze({ runKernelCommand })
|
|
21
|
+
|
|
22
|
+
export async function introspectCommand(
|
|
23
|
+
target: string,
|
|
24
|
+
opts: IntrospectOpts,
|
|
25
|
+
dependencies: Partial<IntrospectDependencies> = {},
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
const introspect = { ...defaultIntrospectDependencies, ...dependencies }
|
|
15
28
|
let origin: string
|
|
16
29
|
let path: Path
|
|
17
30
|
try {
|
|
@@ -21,30 +34,14 @@ export async function introspectCommand(target: string, opts: IntrospectOpts): P
|
|
|
21
34
|
}
|
|
22
35
|
const wantsCallable = isCallablePath(path)
|
|
23
36
|
|
|
24
|
-
await runKernelCommand({
|
|
37
|
+
await introspect.runKernelCommand({
|
|
25
38
|
opts,
|
|
26
39
|
label: `Introspect ${origin}`,
|
|
27
40
|
fn: async ({ session }) => {
|
|
28
41
|
const includeBundle = wantsCallable || opts.bundle === true
|
|
29
|
-
const result = await session.schema
|
|
30
|
-
from: { kind: 'installation', origin },
|
|
31
|
-
select: {
|
|
32
|
-
state: true,
|
|
33
|
-
target: true,
|
|
34
|
-
source: true,
|
|
35
|
-
readiness: true,
|
|
36
|
-
capabilities: true,
|
|
37
|
-
...(includeBundle ? { bundle: true as const } : {}),
|
|
38
|
-
},
|
|
39
|
-
})
|
|
40
|
-
if (result === null) {
|
|
41
|
-
throw new AstraleError(
|
|
42
|
-
'DOMAIN_NOT_INSTALLED',
|
|
43
|
-
`Domain ${origin} is not installed on this Kernel.`,
|
|
44
|
-
)
|
|
45
|
-
}
|
|
42
|
+
const result = await readInstalledDomain(session.schema, origin, includeBundle)
|
|
46
43
|
if (wantsCallable) {
|
|
47
|
-
const described = describeCallableFromBundle(path, result.bundle)
|
|
44
|
+
const described = describeCallableFromBundle(path, (result as DomainBundle).bundle)
|
|
48
45
|
if (described === undefined) throw missingCallableDescription(path.raw)
|
|
49
46
|
return described
|
|
50
47
|
}
|
|
@@ -54,6 +51,29 @@ export async function introspectCommand(target: string, opts: IntrospectOpts): P
|
|
|
54
51
|
})
|
|
55
52
|
}
|
|
56
53
|
|
|
54
|
+
export function readInstalledDomain(
|
|
55
|
+
schema: Pick<SchemaApi, 'inspect' | 'bundle'>,
|
|
56
|
+
origin: string,
|
|
57
|
+
includeBundle: true,
|
|
58
|
+
): Promise<DomainBundle>
|
|
59
|
+
export function readInstalledDomain(
|
|
60
|
+
schema: Pick<SchemaApi, 'inspect' | 'bundle'>,
|
|
61
|
+
origin: string,
|
|
62
|
+
includeBundle: false,
|
|
63
|
+
): Promise<DomainInfo>
|
|
64
|
+
export function readInstalledDomain(
|
|
65
|
+
schema: Pick<SchemaApi, 'inspect' | 'bundle'>,
|
|
66
|
+
origin: string,
|
|
67
|
+
includeBundle: boolean,
|
|
68
|
+
): Promise<DomainInfo | DomainBundle>
|
|
69
|
+
export function readInstalledDomain(
|
|
70
|
+
schema: Pick<SchemaApi, 'inspect' | 'bundle'>,
|
|
71
|
+
origin: string,
|
|
72
|
+
includeBundle: boolean,
|
|
73
|
+
): Promise<DomainInfo | DomainBundle> {
|
|
74
|
+
return includeBundle ? schema.bundle(origin) : schema.inspect(origin)
|
|
75
|
+
}
|
|
76
|
+
|
|
57
77
|
export function parseIntrospectTarget(target: string): { origin: string; path: Path } {
|
|
58
78
|
if (target.startsWith('@')) {
|
|
59
79
|
throw new AstraleError(
|
package/studio/package.json
CHANGED