@astrale-os/cli 1.0.0-beta.3 → 1.0.0-beta.5
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 +15 -0
- package/dist/astrale.js +29710 -35141
- package/dist/public/connect-core.js +24238 -30088
- package/dist/public/keys/index.js +24086 -30014
- package/dist/public/paths/index.js +23680 -29365
- package/dist/types/lib/admin-target.d.ts +6 -2
- package/dist/types/lib/idp.d.ts +1 -0
- package/dist/types/lib/proc.d.ts +43 -0
- package/dist/types/lib/update.d.ts +131 -0
- package/package.json +8 -5
- package/src/admin/__tests__/binding.test.ts +38 -0
- package/src/admin/binding.ts +9 -159
- package/src/admin/catalog/.spec/api.d.ts +1 -1
- package/src/admin/instance/.spec/api.d.ts +1 -1
- package/src/commands/__tests__/update.test.ts +25 -3
- package/src/commands/update.ts +31 -11
- package/src/lib/__tests__/admin-target.test.ts +12 -0
- package/src/lib/__tests__/idp.test.ts +34 -4
- package/src/lib/__tests__/update.test.ts +101 -8
- package/src/lib/__tests__/view-open-intent.test.ts +0 -2
- package/src/lib/__tests__/view-server.test.ts +1 -1
- package/src/lib/__tests__/view-session.test.ts +1 -1
- package/src/lib/admin-target.ts +21 -2
- package/src/lib/idp.ts +34 -3
- package/src/lib/update.ts +120 -26
- package/studio/client/dist/assets/{elk-api-lwhFo7vB.js → elk-api-YEDvaNU-.js} +1 -1
- package/studio/client/dist/assets/{index-BckHuAWk.js → index-C-PvAXV4.js} +2 -2
- package/studio/client/dist/assets/{index-B-c-smo9.js → index-CzpmJD0j.js} +4 -4
- package/studio/client/dist/index.html +1 -1
- package/studio/package.json +7 -9
- package/studio/server/introspect/runtime.test.ts +2 -2
- package/studio/shared/contracts/surface.test.ts +1 -0
- package/viewer/dist/main.js +27 -51
|
@@ -2,8 +2,12 @@ import { z } from 'zod';
|
|
|
2
2
|
import type { AstraleConfig } from './config';
|
|
3
3
|
import type { InstanceStore } from './instance';
|
|
4
4
|
export declare const DEFAULT_ADMIN_TARGET_NAME = "admin";
|
|
5
|
-
export declare
|
|
6
|
-
|
|
5
|
+
export declare function defaultAdminTargetForChannel(channel: string): {
|
|
6
|
+
readonly url: string;
|
|
7
|
+
readonly domainIssuer: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const DEFAULT_ADMIN_TARGET_URL: string;
|
|
10
|
+
export declare const DEFAULT_ADMIN_DOMAIN_ISSUER: string;
|
|
7
11
|
export declare const DEFAULT_ADMIN_TARGET_CONFIG: {
|
|
8
12
|
name: string;
|
|
9
13
|
url: string;
|
package/dist/types/lib/idp.d.ts
CHANGED
|
@@ -322,6 +322,7 @@ export declare function fetchOidcMetadata(issuer: string): Promise<OidcMetadata>
|
|
|
322
322
|
export declare function workosAuthKitMetadata(apiHost?: string, clientId?: string): OidcMetadata;
|
|
323
323
|
export declare function builtinIdpConfig(name: string, clientIdOverride?: string, env?: NodeJS.ProcessEnv): IdpConfig | null;
|
|
324
324
|
export declare function workosClientIdFromEnv(env?: NodeJS.ProcessEnv): string | undefined;
|
|
325
|
+
export declare function legacyBuiltinWorkosReplacement(existing: IdpConfig, clientIdOverride?: string, env?: NodeJS.ProcessEnv): IdpConfig | undefined;
|
|
325
326
|
export declare function postForm(url: string, params: URLSearchParams): Promise<TokenResponse>;
|
|
326
327
|
export declare function postJson(url: string, bodyInput: Record<string, string>): Promise<TokenResponse>;
|
|
327
328
|
export declare function tokenExpiresAt(token: TokenResponse): string | undefined;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type ChildProcess, type StdioOptions } from 'node:child_process';
|
|
2
|
+
export type RunResult = {
|
|
3
|
+
/** Exit code, or -1 when the process was killed by a signal. */
|
|
4
|
+
code: number;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Spawn a child process and capture stdout/stderr as UTF-8 text.
|
|
10
|
+
*
|
|
11
|
+
* Uses `node:child_process` rather than `Bun.spawn` so the exact same code runs
|
|
12
|
+
* under both the Bun-compiled standalone binary (Linux/macOS) and the Node/npm
|
|
13
|
+
* build (Windows and any Node user). The promise rejects only when the process
|
|
14
|
+
* fails to spawn (e.g. ENOENT); a non-zero exit resolves with that `code` so
|
|
15
|
+
* callers can branch on it.
|
|
16
|
+
*/
|
|
17
|
+
export declare function run(file: string, args?: string[], opts?: {
|
|
18
|
+
cwd?: string;
|
|
19
|
+
}): Promise<RunResult>;
|
|
20
|
+
/**
|
|
21
|
+
* Spawn a child process with inherited stdio so its output streams live to the
|
|
22
|
+
* user's terminal. Used for hand-held installs (npm / npx) where progress
|
|
23
|
+
* matters and the output is for the human, not for parsing. Resolves with the
|
|
24
|
+
* exit code; rejects only when the process fails to spawn (e.g. ENOENT).
|
|
25
|
+
*/
|
|
26
|
+
export declare function runInherit(file: string, args?: string[], opts?: {
|
|
27
|
+
cwd?: string;
|
|
28
|
+
}): Promise<number>;
|
|
29
|
+
/**
|
|
30
|
+
* Spawn a LONG-LIVED child and return the handle immediately so the caller can
|
|
31
|
+
* supervise it (wait for exit, forward signals, tear down siblings). Unlike
|
|
32
|
+
* run()/runInherit() — which resolve only once the child closes — this is for
|
|
33
|
+
* servers the command keeps attached to (e.g. `astrale studio`'s Bun server and
|
|
34
|
+
* Vite dev process). Same node:child_process basis (cross-runtime) and bare
|
|
35
|
+
* PATH-name lookup convention. Default stdio streams stdout/stderr live but
|
|
36
|
+
* ignores stdin, so two supervised children never contend for the TTY.
|
|
37
|
+
*/
|
|
38
|
+
export declare function spawnHandle(file: string, args?: string[], opts?: {
|
|
39
|
+
cwd?: string;
|
|
40
|
+
env?: NodeJS.ProcessEnv;
|
|
41
|
+
stdio?: StdioOptions;
|
|
42
|
+
detached?: boolean;
|
|
43
|
+
}): ChildProcess;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AstraleError } from '../errors';
|
|
3
|
+
export declare const DEFAULT_UPDATE_CHANNEL = "beta";
|
|
4
|
+
export declare const InstallMetadataSchema: z.ZodObject<{
|
|
5
|
+
method: z.ZodLiteral<"script">;
|
|
6
|
+
channel: z.ZodDefault<z.ZodString>;
|
|
7
|
+
version: z.ZodOptional<z.ZodString>;
|
|
8
|
+
repo: z.ZodDefault<z.ZodString>;
|
|
9
|
+
bin: z.ZodString;
|
|
10
|
+
installedAt: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
method: "script";
|
|
13
|
+
channel: string;
|
|
14
|
+
version?: string | undefined;
|
|
15
|
+
repo: string;
|
|
16
|
+
bin: string;
|
|
17
|
+
installedAt?: string | undefined;
|
|
18
|
+
}, {
|
|
19
|
+
method: "script";
|
|
20
|
+
channel?: string | undefined;
|
|
21
|
+
version?: string | undefined;
|
|
22
|
+
repo?: string | undefined;
|
|
23
|
+
bin: string;
|
|
24
|
+
installedAt?: string | undefined;
|
|
25
|
+
}>;
|
|
26
|
+
export type InstallMetadata = z.infer<typeof InstallMetadataSchema>;
|
|
27
|
+
export declare const UpdateManifestSchema: z.ZodObject<{
|
|
28
|
+
version: z.ZodString;
|
|
29
|
+
binaryVersion: z.ZodOptional<z.ZodString>;
|
|
30
|
+
channel: z.ZodString;
|
|
31
|
+
repo: z.ZodOptional<z.ZodString>;
|
|
32
|
+
assets: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
|
|
33
|
+
name: z.ZodString;
|
|
34
|
+
sha256: z.ZodOptional<z.ZodString>;
|
|
35
|
+
}, "strip", z.ZodTypeAny, {
|
|
36
|
+
name: string;
|
|
37
|
+
sha256?: string | undefined;
|
|
38
|
+
}, {
|
|
39
|
+
name: string;
|
|
40
|
+
sha256?: string | undefined;
|
|
41
|
+
}>, z.ZodEffects<z.ZodString, {
|
|
42
|
+
name: string;
|
|
43
|
+
}, string>]>>;
|
|
44
|
+
}, "strip", z.ZodTypeAny, {
|
|
45
|
+
version: string;
|
|
46
|
+
binaryVersion?: string | undefined;
|
|
47
|
+
channel: string;
|
|
48
|
+
repo?: string | undefined;
|
|
49
|
+
assets: Record<string, {
|
|
50
|
+
name: string;
|
|
51
|
+
sha256?: string | undefined;
|
|
52
|
+
} | {
|
|
53
|
+
name: string;
|
|
54
|
+
}>;
|
|
55
|
+
}, {
|
|
56
|
+
version: string;
|
|
57
|
+
binaryVersion?: string | undefined;
|
|
58
|
+
channel: string;
|
|
59
|
+
repo?: string | undefined;
|
|
60
|
+
assets: Record<string, string | {
|
|
61
|
+
name: string;
|
|
62
|
+
sha256?: string | undefined;
|
|
63
|
+
}>;
|
|
64
|
+
}>;
|
|
65
|
+
export type UpdateManifest = z.infer<typeof UpdateManifestSchema>;
|
|
66
|
+
export type Platform = {
|
|
67
|
+
os: 'darwin' | 'linux';
|
|
68
|
+
arch: 'arm64' | 'x64';
|
|
69
|
+
};
|
|
70
|
+
export type UpdateRequest = {
|
|
71
|
+
check?: boolean;
|
|
72
|
+
channel?: string;
|
|
73
|
+
version?: string;
|
|
74
|
+
currentVersion: string;
|
|
75
|
+
platform?: Platform;
|
|
76
|
+
installPath?: string;
|
|
77
|
+
execution?: UpdateExecution;
|
|
78
|
+
};
|
|
79
|
+
export type UpdateExecution = {
|
|
80
|
+
kind: 'standalone';
|
|
81
|
+
executable: string;
|
|
82
|
+
} | {
|
|
83
|
+
kind: 'package-managed';
|
|
84
|
+
executable: string;
|
|
85
|
+
};
|
|
86
|
+
declare const admittedScriptInstall: unique symbol;
|
|
87
|
+
export type AdmittedScriptInstall = Readonly<{
|
|
88
|
+
metadata: InstallMetadata;
|
|
89
|
+
executable: string;
|
|
90
|
+
[admittedScriptInstall]: true;
|
|
91
|
+
}>;
|
|
92
|
+
export type UpdateResult = {
|
|
93
|
+
status: 'managed';
|
|
94
|
+
currentVersion: string;
|
|
95
|
+
executable: string;
|
|
96
|
+
} | {
|
|
97
|
+
status: 'up-to-date';
|
|
98
|
+
currentVersion: string;
|
|
99
|
+
latestVersion: string;
|
|
100
|
+
channel: string;
|
|
101
|
+
} | {
|
|
102
|
+
status: 'available';
|
|
103
|
+
currentVersion: string;
|
|
104
|
+
latestVersion: string;
|
|
105
|
+
channel: string;
|
|
106
|
+
} | {
|
|
107
|
+
status: 'updated';
|
|
108
|
+
previousVersion: string;
|
|
109
|
+
currentVersion: string;
|
|
110
|
+
channel: string;
|
|
111
|
+
bin: string;
|
|
112
|
+
};
|
|
113
|
+
export declare function detectPlatform(): Platform;
|
|
114
|
+
export declare function platformKey(platform: Platform): string;
|
|
115
|
+
export declare function classifyUpdateExecution(input: {
|
|
116
|
+
bunVersion?: string;
|
|
117
|
+
executable: string;
|
|
118
|
+
entry?: string;
|
|
119
|
+
}): UpdateExecution;
|
|
120
|
+
export declare function detectUpdateExecution(): UpdateExecution;
|
|
121
|
+
export declare function packageManagedUpdateError(executable: string): AstraleError;
|
|
122
|
+
export declare function readInstallMetadata(path?: string): Promise<InstallMetadata>;
|
|
123
|
+
export declare function admitScriptInstall(meta: InstallMetadata, execution: Extract<UpdateExecution, {
|
|
124
|
+
kind: 'standalone';
|
|
125
|
+
}>): Promise<AdmittedScriptInstall>;
|
|
126
|
+
export declare function writeInstallMetadata(meta: InstallMetadata, path?: string): Promise<void>;
|
|
127
|
+
export declare function releaseBase(meta: InstallMetadata, req: Pick<UpdateRequest, 'channel' | 'version'>): string;
|
|
128
|
+
export declare function fetchManifest(base: string): Promise<UpdateManifest>;
|
|
129
|
+
export declare function shouldUpdate(currentVersion: string, manifestVersion: string): boolean;
|
|
130
|
+
export declare function updateAstrale(req: UpdateRequest): Promise<UpdateResult>;
|
|
131
|
+
export {};
|
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.5",
|
|
4
4
|
"description": "Astrale CLI — connect to existing Astrale kernels",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astrale",
|
|
@@ -62,11 +62,14 @@
|
|
|
62
62
|
"zod": "^3.25.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@astrale-os/kernel-client": ">=0.6.0-beta.
|
|
66
|
-
"@astrale-os/kernel-
|
|
65
|
+
"@astrale-os/kernel-client": ">=0.6.0-beta.8 <1.0.0",
|
|
66
|
+
"@astrale-os/kernel-core": ">=0.9.0-beta.7 <1.0.0",
|
|
67
|
+
"@astrale-os/kernel-dsl": ">=0.2.0-beta.5 <1.0.0",
|
|
68
|
+
"@astrale-os/kernel-protocol": ">=0.5.0-beta.7 <1.0.0",
|
|
69
|
+
"@astrale-os/kernel-server": ">=0.5.0-beta.8 <1.0.0",
|
|
67
70
|
"@astrale-os/ox": ">=0.1.3 <1.0.0",
|
|
68
|
-
"@astrale-os/sdk": ">=0.5.0-beta.
|
|
69
|
-
"@astrale-os/shell": ">=0.3.8-beta.
|
|
71
|
+
"@astrale-os/sdk": ">=0.5.0-beta.14 <1.0.0",
|
|
72
|
+
"@astrale-os/shell": ">=0.3.8-beta.3 <1.0.0",
|
|
70
73
|
"@astrale/commitlint-config": "npm:@jsr/astrale__commitlint-config@~2.0.1",
|
|
71
74
|
"@commitlint/cli": "~20.3.1",
|
|
72
75
|
"@commitlint/config-conventional": "~20.3.1",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ClientSession } from '@astrale-os/kernel-client/session'
|
|
2
|
+
|
|
3
|
+
import { defineDomain } from '@astrale-os/sdk'
|
|
4
|
+
import { issuer } from '@astrale-os/sdk/auth'
|
|
5
|
+
import { createDeployment } from '@astrale-os/sdk/deployment'
|
|
6
|
+
import { defineSchema } from '@astrale-os/sdk/schema/v1'
|
|
7
|
+
import { expect, mock, test } from 'bun:test'
|
|
8
|
+
|
|
9
|
+
import { bindAdmin } from '../binding'
|
|
10
|
+
|
|
11
|
+
const schema = defineSchema('admin.astrale.ai', {})
|
|
12
|
+
const deployment = createDeployment({
|
|
13
|
+
definition: defineDomain({
|
|
14
|
+
schema,
|
|
15
|
+
handlers: { functions: {}, classes: {}, interfaces: {} },
|
|
16
|
+
}),
|
|
17
|
+
issuer: issuer.accept('https://admin.beta.astrale.ai'),
|
|
18
|
+
bundleHref: 'https://admin.beta.astrale.ai/domain.bundle.json',
|
|
19
|
+
bindings: { callables: [] },
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('binds the installed Admin Domain instead of the source Kernel publication', async () => {
|
|
23
|
+
const installation = mock(async () => ({
|
|
24
|
+
state: 'ready' as const,
|
|
25
|
+
target: 'sha256:admin-target' as const,
|
|
26
|
+
source: { kind: 'remote' as const, publication: deployment.publication },
|
|
27
|
+
bundle: deployment.bundle,
|
|
28
|
+
readiness: 'sha256:admin-readiness' as const,
|
|
29
|
+
capabilities: { requested: [], materialized: [] },
|
|
30
|
+
}))
|
|
31
|
+
const session = { installation } as unknown as ClientSession
|
|
32
|
+
|
|
33
|
+
const binding = await bindAdmin(session)
|
|
34
|
+
|
|
35
|
+
expect(installation).toHaveBeenCalledWith('admin.astrale.ai')
|
|
36
|
+
expect(binding.$.origin).toBe('admin.astrale.ai')
|
|
37
|
+
expect(String(binding.$.publication?.identity.issuer)).toBe('https://admin.beta.astrale.ai')
|
|
38
|
+
})
|
package/src/admin/binding.ts
CHANGED
|
@@ -1,168 +1,18 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
ClientSession,
|
|
4
|
-
SessionRequestOptions,
|
|
5
|
-
SessionSnapshot,
|
|
6
|
-
} from '@astrale-os/kernel-client/session'
|
|
7
|
-
import type {
|
|
8
|
-
Definition,
|
|
9
|
-
Domain as RichDomain,
|
|
10
|
-
InstanceMethod,
|
|
11
|
-
StaticMethod,
|
|
12
|
-
} from '@astrale-os/sdk/domain'
|
|
13
|
-
import type { PathLike } from '@astrale-os/sdk/graph/path'
|
|
14
|
-
import type { Key } from '@astrale-os/sdk/schema'
|
|
1
|
+
import type { ClientSession } from '@astrale-os/kernel-client/session'
|
|
2
|
+
import type { DomainBinding } from '@astrale-os/shell'
|
|
15
3
|
|
|
16
|
-
import {
|
|
17
|
-
import * as sessionModule from '@astrale-os/kernel-client/session'
|
|
18
|
-
import { Domain } from '@astrale-os/sdk/domain'
|
|
19
|
-
import { Path } from '@astrale-os/sdk/graph/path'
|
|
4
|
+
import { bindDomain } from '@astrale-os/shell'
|
|
20
5
|
|
|
21
6
|
const ADMIN_ORIGIN = 'admin.astrale.ai'
|
|
22
7
|
|
|
23
|
-
type
|
|
24
|
-
| (Definition<'function'> & { readonly path: NonNullable<Definition<'function'>['path']> })
|
|
25
|
-
| StaticMethod
|
|
26
|
-
| InstanceMethod
|
|
8
|
+
export type AdminBinding = DomainBinding
|
|
27
9
|
|
|
28
|
-
|
|
29
|
-
readonly installation: unknown
|
|
30
|
-
readonly publication: SessionSnapshot['publication']
|
|
31
|
-
invoke(
|
|
32
|
-
callable: DynamicCallable,
|
|
33
|
-
receiverOrInput: PathLike | Input,
|
|
34
|
-
inputOrOptions?: Input | SessionRequestOptions,
|
|
35
|
-
options?: SessionRequestOptions,
|
|
36
|
-
): Promise<unknown>
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export type AdminBinding = RichDomain & {
|
|
40
|
-
readonly $: RichDomain['$'] & AdminBindingOperations
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
interface CompatibleCallableReference {
|
|
44
|
-
readonly origin: typeof ADMIN_ORIGIN
|
|
45
|
-
readonly revision: SessionSnapshot['publication']['schema']['revision']
|
|
46
|
-
readonly callable: Key.Callable
|
|
47
|
-
readonly target: PathLike
|
|
48
|
-
readonly outputMode: DynamicCallable['definition']['output']['mode']
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface CompatibleSession {
|
|
52
|
-
readonly installation?: (origin: typeof ADMIN_ORIGIN) => Promise<unknown>
|
|
53
|
-
readonly installed?: (origin: typeof ADMIN_ORIGIN) => Promise<unknown>
|
|
54
|
-
readonly invoke?: (
|
|
55
|
-
reference: CompatibleCallableReference,
|
|
56
|
-
input: Input,
|
|
57
|
-
options?: SessionRequestOptions,
|
|
58
|
-
) => Promise<unknown>
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
type LegacyDispatchBound = (
|
|
62
|
-
session: ClientSession,
|
|
63
|
-
request: ReturnType<typeof call>,
|
|
64
|
-
expected: {
|
|
65
|
-
readonly schema: { readonly revision: CompatibleCallableReference['revision'] }
|
|
66
|
-
readonly publication: {
|
|
67
|
-
readonly origin: SessionSnapshot['publication']['origin']
|
|
68
|
-
readonly identity: SessionSnapshot['publication']['identity']
|
|
69
|
-
readonly revision: CompatibleCallableReference['revision']
|
|
70
|
-
readonly etag: SessionSnapshot['publication']['etag']
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
options?: SessionRequestOptions,
|
|
74
|
-
) => Promise<Result>
|
|
75
|
-
|
|
76
|
-
/** Bind the Admin revision advertised by this exact Session snapshot. */
|
|
10
|
+
/** Bind the exact Admin revision installed on this source Kernel. */
|
|
77
11
|
export async function bindAdmin(session: ClientSession): Promise<AdminBinding> {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
snapshot.bundle.root.origin !== ADMIN_ORIGIN
|
|
82
|
-
) {
|
|
12
|
+
const installed = await session.installation(ADMIN_ORIGIN)
|
|
13
|
+
const binding = await bindDomain(session, installed.bundle.root)
|
|
14
|
+
if (binding.$.publication?.origin !== ADMIN_ORIGIN || binding.$.origin !== ADMIN_ORIGIN) {
|
|
83
15
|
throw new TypeError('Configured Admin target does not serve the Admin Domain.')
|
|
84
16
|
}
|
|
85
|
-
|
|
86
|
-
const resolveInstallation = compatible.installation ?? compatible.installed
|
|
87
|
-
if (resolveInstallation === undefined) {
|
|
88
|
-
throw new TypeError('Client Session cannot resolve an installed Admin Domain.')
|
|
89
|
-
}
|
|
90
|
-
const installation = await resolveInstallation.call(session, ADMIN_ORIGIN)
|
|
91
|
-
const domain = Domain.fromSchema(snapshot.bundle.root)
|
|
92
|
-
const invoke = ((
|
|
93
|
-
callable: DynamicCallable,
|
|
94
|
-
receiverOrInput: PathLike | Input,
|
|
95
|
-
inputOrOptions?: Input | SessionRequestOptions,
|
|
96
|
-
options?: SessionRequestOptions,
|
|
97
|
-
) => {
|
|
98
|
-
const target =
|
|
99
|
-
'on' in callable ? callable.on(Path.from(receiverOrInput as PathLike)) : callable.path
|
|
100
|
-
const reference: CompatibleCallableReference = Object.freeze({
|
|
101
|
-
origin: ADMIN_ORIGIN,
|
|
102
|
-
revision: snapshot.publication.schema.revision,
|
|
103
|
-
callable: callable.key as Key.Callable,
|
|
104
|
-
target,
|
|
105
|
-
outputMode: callable.definition.output.mode,
|
|
106
|
-
})
|
|
107
|
-
return invokeCompatible(
|
|
108
|
-
session,
|
|
109
|
-
snapshot,
|
|
110
|
-
reference,
|
|
111
|
-
('on' in callable ? inputOrOptions : receiverOrInput) as Input,
|
|
112
|
-
('on' in callable ? options : inputOrOptions) as SessionRequestOptions | undefined,
|
|
113
|
-
)
|
|
114
|
-
}) as AdminBindingOperations['invoke']
|
|
115
|
-
|
|
116
|
-
return Object.freeze({
|
|
117
|
-
...domain,
|
|
118
|
-
$: Object.freeze({
|
|
119
|
-
...domain.$,
|
|
120
|
-
installation,
|
|
121
|
-
publication: snapshot.publication,
|
|
122
|
-
invoke,
|
|
123
|
-
}),
|
|
124
|
-
}) as AdminBinding
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function invokeCompatible(
|
|
128
|
-
session: ClientSession,
|
|
129
|
-
snapshot: SessionSnapshot,
|
|
130
|
-
reference: CompatibleCallableReference,
|
|
131
|
-
input: Input,
|
|
132
|
-
options: SessionRequestOptions | undefined,
|
|
133
|
-
): Promise<unknown> {
|
|
134
|
-
const current = session as unknown as CompatibleSession
|
|
135
|
-
if (current.invoke !== undefined) return current.invoke(reference, input, options)
|
|
136
|
-
|
|
137
|
-
const dispatchBound = (sessionModule as unknown as { dispatchBound?: LegacyDispatchBound })
|
|
138
|
-
.dispatchBound
|
|
139
|
-
if (dispatchBound === undefined) {
|
|
140
|
-
throw new TypeError('Client Session cannot invoke an exact Admin publication.')
|
|
141
|
-
}
|
|
142
|
-
const result = await dispatchBound(
|
|
143
|
-
session,
|
|
144
|
-
call(reference.target, input),
|
|
145
|
-
{
|
|
146
|
-
schema: { revision: reference.revision },
|
|
147
|
-
publication: {
|
|
148
|
-
origin: snapshot.publication.origin,
|
|
149
|
-
identity: snapshot.publication.identity,
|
|
150
|
-
revision: snapshot.publication.schema.revision,
|
|
151
|
-
etag: snapshot.publication.etag,
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
options,
|
|
155
|
-
)
|
|
156
|
-
return requireOutput(reference.outputMode, result)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function requireOutput(
|
|
160
|
-
expected: CompatibleCallableReference['outputMode'],
|
|
161
|
-
result: Result,
|
|
162
|
-
): unknown {
|
|
163
|
-
if (result.kind !== expected) {
|
|
164
|
-
if (result.kind === 'stream') void result.stream.cancel(`Admin callable declared ${expected}.`)
|
|
165
|
-
throw new TypeError(`Admin callable declared ${expected} output but returned ${result.kind}.`)
|
|
166
|
-
}
|
|
167
|
-
return result.kind === 'stream' ? result.stream : result.value
|
|
17
|
+
return binding
|
|
168
18
|
}
|
|
@@ -35,9 +35,11 @@ describe('CLI update staleness', () => {
|
|
|
35
35
|
const result = await cliStale(
|
|
36
36
|
{ channel: 'canary' },
|
|
37
37
|
{
|
|
38
|
-
update: async () => {
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
update: async () => ({
|
|
39
|
+
status: 'managed',
|
|
40
|
+
currentVersion: '1.0.0-beta.0',
|
|
41
|
+
executable: '/opt/homebrew/bin/node',
|
|
42
|
+
}),
|
|
41
43
|
fetchPackageVersion: async ({ channel }) => {
|
|
42
44
|
expect(channel).toBe('canary')
|
|
43
45
|
return '1.0.0-canary.0'
|
|
@@ -52,6 +54,26 @@ describe('CLI update staleness', () => {
|
|
|
52
54
|
})
|
|
53
55
|
})
|
|
54
56
|
|
|
57
|
+
test('does not misclassify a script update failure as package-managed', async () => {
|
|
58
|
+
const fetchPackageVersion = mock(async () => '9.9.9')
|
|
59
|
+
const result = await cliStale(
|
|
60
|
+
{},
|
|
61
|
+
{
|
|
62
|
+
update: async () => {
|
|
63
|
+
throw new Error('release endpoint unavailable')
|
|
64
|
+
},
|
|
65
|
+
fetchPackageVersion,
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
expect(result).toMatchObject({
|
|
70
|
+
stale: false,
|
|
71
|
+
managed: false,
|
|
72
|
+
error: 'release endpoint unavailable',
|
|
73
|
+
})
|
|
74
|
+
expect(fetchPackageVersion).not.toHaveBeenCalled()
|
|
75
|
+
})
|
|
76
|
+
|
|
55
77
|
test('maps the default to beta and the stable channel to npm latest', async () => {
|
|
56
78
|
const originalFetch = globalThis.fetch
|
|
57
79
|
const fetchMock = mock(async (input: RequestInfo | URL) => {
|
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 { DEFAULT_UPDATE_CHANNEL, updateAstrale } from '../lib/update'
|
|
15
|
+
import { DEFAULT_UPDATE_CHANNEL, packageManagedUpdateError, updateAstrale } from '../lib/update'
|
|
16
16
|
|
|
17
17
|
type UpdateOpts = RawOutputOpts & {
|
|
18
18
|
check?: boolean
|
|
@@ -104,14 +104,22 @@ async function refreshSdkDeps(check: boolean, assumeYes = false): Promise<boolea
|
|
|
104
104
|
/**
|
|
105
105
|
* Read-only staleness report for tooling — `astrale update --check --json`.
|
|
106
106
|
* domain-studio polls this on load to drive its "update available" badge. It is
|
|
107
|
-
* unified and NON-THROWING:
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* update`, so a stale CLI or
|
|
107
|
+
* unified and NON-THROWING: an explicit package-managed result uses npm release
|
|
108
|
+
* identity, while script-install failures remain script failures in `error`
|
|
109
|
+
* instead of being recategorized. The SDK axis is already best-effort. Skills are
|
|
110
|
+
* intentionally absent — they ride along with `astrale update`, so a stale CLI or
|
|
111
|
+
* SDK is the only signal worth surfacing.
|
|
111
112
|
*/
|
|
112
113
|
export type StaleReport = {
|
|
113
114
|
stale: boolean
|
|
114
|
-
cli: {
|
|
115
|
+
cli: {
|
|
116
|
+
stale: boolean
|
|
117
|
+
managed: boolean
|
|
118
|
+
current?: string
|
|
119
|
+
latest?: string
|
|
120
|
+
channel?: string
|
|
121
|
+
error?: string
|
|
122
|
+
}
|
|
115
123
|
sdk: { stale: boolean; inProject: boolean; outdated: SdkOutdated[] }
|
|
116
124
|
}
|
|
117
125
|
|
|
@@ -138,6 +146,15 @@ export async function cliStale(
|
|
|
138
146
|
version: target.version,
|
|
139
147
|
currentVersion: running,
|
|
140
148
|
})
|
|
149
|
+
if (r.status === 'managed') {
|
|
150
|
+
const latest = await dependencies.fetchPackageVersion(target).catch(() => undefined)
|
|
151
|
+
return {
|
|
152
|
+
stale: latest !== undefined && latest !== running,
|
|
153
|
+
managed: true,
|
|
154
|
+
current: running,
|
|
155
|
+
...(latest === undefined ? {} : { latest, channel: 'npm' }),
|
|
156
|
+
}
|
|
157
|
+
}
|
|
141
158
|
if (r.status === 'updated') {
|
|
142
159
|
return {
|
|
143
160
|
stale: false,
|
|
@@ -154,13 +171,12 @@ export async function cliStale(
|
|
|
154
171
|
latest: r.latestVersion,
|
|
155
172
|
channel: r.channel,
|
|
156
173
|
}
|
|
157
|
-
} catch {
|
|
158
|
-
const latest = await dependencies.fetchPackageVersion(target).catch(() => undefined)
|
|
174
|
+
} catch (error) {
|
|
159
175
|
return {
|
|
160
|
-
stale:
|
|
161
|
-
managed:
|
|
176
|
+
stale: false,
|
|
177
|
+
managed: false,
|
|
162
178
|
current: running,
|
|
163
|
-
|
|
179
|
+
error: error instanceof Error ? error.message : String(error),
|
|
164
180
|
}
|
|
165
181
|
}
|
|
166
182
|
}
|
|
@@ -294,6 +310,10 @@ Examples:
|
|
|
294
310
|
log.success(`Updated astrale ${result.previousVersion} -> ${result.currentVersion}`)
|
|
295
311
|
log.dim(` channel: ${result.channel}`)
|
|
296
312
|
log.dim(` binary: ${result.bin}`)
|
|
313
|
+
} else if (result?.status === 'managed') {
|
|
314
|
+
const error = packageManagedUpdateError(result.executable)
|
|
315
|
+
if (!opts.yes) throw error
|
|
316
|
+
log.warn(`CLI self-update skipped: ${error.message}`)
|
|
297
317
|
}
|
|
298
318
|
|
|
299
319
|
// Axis B — agent skills. Keep an existing install current with the CLI.
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
DEFAULT_ADMIN_TARGET_NAME,
|
|
7
7
|
DEFAULT_ADMIN_DOMAIN_ISSUER,
|
|
8
8
|
DEFAULT_ADMIN_TARGET_URL,
|
|
9
|
+
defaultAdminTargetForChannel,
|
|
9
10
|
resolveAdminTargetFromStore,
|
|
10
11
|
} from '../admin-target'
|
|
11
12
|
import { DEFAULT_CONFIG, type AstraleConfig } from '../config'
|
|
@@ -32,6 +33,17 @@ const instances: InstanceStore = {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
describe('resolveAdminTargetFromStore', () => {
|
|
36
|
+
test('derives hosted Admin defaults from the release channel', () => {
|
|
37
|
+
expect(defaultAdminTargetForChannel('beta')).toEqual({
|
|
38
|
+
url: 'https://admin.eu.beta.astrale.ai/api',
|
|
39
|
+
domainIssuer: 'https://admin.beta.astrale.ai',
|
|
40
|
+
})
|
|
41
|
+
expect(defaultAdminTargetForChannel('stable')).toEqual({
|
|
42
|
+
url: 'https://admin.eu.astrale.ai/api',
|
|
43
|
+
domainIssuer: 'https://admin.astrale.ai',
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
35
47
|
test('uses hosted admin default without active instance coupling', () => {
|
|
36
48
|
expect(resolveAdminTargetFromStore({}, DEFAULT_CONFIG, instances)).toEqual({
|
|
37
49
|
name: DEFAULT_ADMIN_TARGET_NAME,
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
IdpSessionSchema,
|
|
10
10
|
isSessionExpired,
|
|
11
11
|
issuerFromToken,
|
|
12
|
+
legacyBuiltinWorkosReplacement,
|
|
12
13
|
normalizeTokenResponse,
|
|
13
14
|
OAuthTokenError,
|
|
14
15
|
OidcMetadataSchema,
|
|
@@ -63,8 +64,11 @@ describe('IdP schemas and token helpers', () => {
|
|
|
63
64
|
expect(config?.client.token_request_format).toBe('json')
|
|
64
65
|
})
|
|
65
66
|
|
|
66
|
-
test('
|
|
67
|
-
|
|
67
|
+
test('builds the built-in WorkOS IdP with the public client id by default', () => {
|
|
68
|
+
const config = builtinIdpConfig('workos', undefined, {})
|
|
69
|
+
|
|
70
|
+
expect(config?.entry.builtIn).toBe(true)
|
|
71
|
+
expect(config?.client.client_id).toBe('client_01KC29HET5F3QAQ8GNTPZ7F320')
|
|
68
72
|
})
|
|
69
73
|
|
|
70
74
|
test('reads WorkOS client id from WORKOS_CLIENT_ID before VITE_WORKOS_CLIENT_ID', () => {
|
|
@@ -75,8 +79,34 @@ describe('IdP schemas and token helpers', () => {
|
|
|
75
79
|
}),
|
|
76
80
|
).toBe('client_primary')
|
|
77
81
|
expect(workosClientIdFromEnv({ VITE_WORKOS_CLIENT_ID: 'client_vite' })).toBe('client_vite')
|
|
78
|
-
expect(workosClientIdFromEnv({})).
|
|
79
|
-
expect(workosClientIdFromEnv({ WORKOS_CLIENT_ID: ' ' })).
|
|
82
|
+
expect(workosClientIdFromEnv({})).toBe('client_01KC29HET5F3QAQ8GNTPZ7F320')
|
|
83
|
+
expect(workosClientIdFromEnv({ WORKOS_CLIENT_ID: ' ' })).toBe(
|
|
84
|
+
'client_01KC29HET5F3QAQ8GNTPZ7F320',
|
|
85
|
+
)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('replaces only the legacy persisted built-in WorkOS client', () => {
|
|
89
|
+
const legacy = builtinIdpConfig('workos', 'client_01KC29HEGD7B40TV2C4QZ436BG', {})!
|
|
90
|
+
const replacement = legacyBuiltinWorkosReplacement(legacy, undefined, {})
|
|
91
|
+
|
|
92
|
+
expect(replacement?.client.client_id).toBe('client_01KC29HET5F3QAQ8GNTPZ7F320')
|
|
93
|
+
expect(replacement?.metadata.jwks_uri).toBe(
|
|
94
|
+
'https://api.workos.com/sso/jwks/client_01KC29HET5F3QAQ8GNTPZ7F320',
|
|
95
|
+
)
|
|
96
|
+
expect(
|
|
97
|
+
legacyBuiltinWorkosReplacement(
|
|
98
|
+
{ ...legacy, entry: { ...legacy.entry, builtIn: false } },
|
|
99
|
+
undefined,
|
|
100
|
+
{},
|
|
101
|
+
),
|
|
102
|
+
).toBeUndefined()
|
|
103
|
+
expect(
|
|
104
|
+
legacyBuiltinWorkosReplacement(
|
|
105
|
+
{ ...legacy, client: { ...legacy.client, client_id: 'client_custom' } },
|
|
106
|
+
undefined,
|
|
107
|
+
{},
|
|
108
|
+
),
|
|
109
|
+
).toBeUndefined()
|
|
80
110
|
})
|
|
81
111
|
|
|
82
112
|
test('stores client secret env names, not secret material', () => {
|