@astrale-os/cli 1.0.0-beta.4 → 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/dist/astrale.js +29682 -35214
- package/dist/public/connect-core.js +24198 -30085
- package/dist/public/keys/index.js +24086 -30014
- package/dist/public/paths/index.js +23680 -29365
- package/dist/types/lib/idp.d.ts +1 -0
- package/dist/types/lib/update.d.ts +30 -0
- package/package.json +8 -5
- package/src/admin/__tests__/binding.test.ts +3 -3
- package/src/admin/binding.ts +4 -4
- 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__/idp.test.ts +25 -0
- 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/idp.ts +29 -1
- 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
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;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { AstraleError } from '../errors';
|
|
2
3
|
export declare const DEFAULT_UPDATE_CHANNEL = "beta";
|
|
3
4
|
export declare const InstallMetadataSchema: z.ZodObject<{
|
|
4
5
|
method: z.ZodLiteral<"script">;
|
|
@@ -73,8 +74,26 @@ export type UpdateRequest = {
|
|
|
73
74
|
currentVersion: string;
|
|
74
75
|
platform?: Platform;
|
|
75
76
|
installPath?: string;
|
|
77
|
+
execution?: UpdateExecution;
|
|
76
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
|
+
}>;
|
|
77
92
|
export type UpdateResult = {
|
|
93
|
+
status: 'managed';
|
|
94
|
+
currentVersion: string;
|
|
95
|
+
executable: string;
|
|
96
|
+
} | {
|
|
78
97
|
status: 'up-to-date';
|
|
79
98
|
currentVersion: string;
|
|
80
99
|
latestVersion: string;
|
|
@@ -93,9 +112,20 @@ export type UpdateResult = {
|
|
|
93
112
|
};
|
|
94
113
|
export declare function detectPlatform(): Platform;
|
|
95
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;
|
|
96
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>;
|
|
97
126
|
export declare function writeInstallMetadata(meta: InstallMetadata, path?: string): Promise<void>;
|
|
98
127
|
export declare function releaseBase(meta: InstallMetadata, req: Pick<UpdateRequest, 'channel' | 'version'>): string;
|
|
99
128
|
export declare function fetchManifest(base: string): Promise<UpdateManifest>;
|
|
100
129
|
export declare function shouldUpdate(currentVersion: string, manifestVersion: string): boolean;
|
|
101
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",
|
|
@@ -20,7 +20,7 @@ const deployment = createDeployment({
|
|
|
20
20
|
})
|
|
21
21
|
|
|
22
22
|
test('binds the installed Admin Domain instead of the source Kernel publication', async () => {
|
|
23
|
-
const
|
|
23
|
+
const installation = mock(async () => ({
|
|
24
24
|
state: 'ready' as const,
|
|
25
25
|
target: 'sha256:admin-target' as const,
|
|
26
26
|
source: { kind: 'remote' as const, publication: deployment.publication },
|
|
@@ -28,11 +28,11 @@ test('binds the installed Admin Domain instead of the source Kernel publication'
|
|
|
28
28
|
readiness: 'sha256:admin-readiness' as const,
|
|
29
29
|
capabilities: { requested: [], materialized: [] },
|
|
30
30
|
}))
|
|
31
|
-
const session = {
|
|
31
|
+
const session = { installation } as unknown as ClientSession
|
|
32
32
|
|
|
33
33
|
const binding = await bindAdmin(session)
|
|
34
34
|
|
|
35
|
-
expect(
|
|
35
|
+
expect(installation).toHaveBeenCalledWith('admin.astrale.ai')
|
|
36
36
|
expect(binding.$.origin).toBe('admin.astrale.ai')
|
|
37
37
|
expect(String(binding.$.publication?.identity.issuer)).toBe('https://admin.beta.astrale.ai')
|
|
38
38
|
})
|
package/src/admin/binding.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { DomainBinding } from '@astrale-os/kernel-client/domain'
|
|
2
1
|
import type { ClientSession } from '@astrale-os/kernel-client/session'
|
|
2
|
+
import type { DomainBinding } from '@astrale-os/shell'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { bindDomain } from '@astrale-os/shell'
|
|
5
5
|
|
|
6
6
|
const ADMIN_ORIGIN = 'admin.astrale.ai'
|
|
7
7
|
|
|
@@ -9,8 +9,8 @@ export type AdminBinding = DomainBinding
|
|
|
9
9
|
|
|
10
10
|
/** Bind the exact Admin revision installed on this source Kernel. */
|
|
11
11
|
export async function bindAdmin(session: ClientSession): Promise<AdminBinding> {
|
|
12
|
-
const installed = await session.
|
|
13
|
-
const binding =
|
|
12
|
+
const installed = await session.installation(ADMIN_ORIGIN)
|
|
13
|
+
const binding = await bindDomain(session, installed.bundle.root)
|
|
14
14
|
if (binding.$.publication?.origin !== ADMIN_ORIGIN || binding.$.origin !== ADMIN_ORIGIN) {
|
|
15
15
|
throw new TypeError('Configured Admin target does not serve the Admin Domain.')
|
|
16
16
|
}
|
|
@@ -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.
|
|
@@ -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,
|
|
@@ -84,6 +85,30 @@ describe('IdP schemas and token helpers', () => {
|
|
|
84
85
|
)
|
|
85
86
|
})
|
|
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()
|
|
110
|
+
})
|
|
111
|
+
|
|
87
112
|
test('stores client secret env names, not secret material', () => {
|
|
88
113
|
const client = IdpClientConfigSchema.parse({
|
|
89
114
|
client_id: 'client_123',
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import { chmod, mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { chmod, mkdir, mkdtemp, readFile, symlink, writeFile } from 'node:fs/promises'
|
|
3
3
|
import { tmpdir } from 'node:os'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
admitScriptInstall,
|
|
8
|
+
classifyUpdateExecution,
|
|
7
9
|
DEFAULT_UPDATE_CHANNEL,
|
|
8
10
|
InstallMetadataSchema,
|
|
11
|
+
readInstallMetadata,
|
|
9
12
|
releaseBase,
|
|
10
13
|
shouldUpdate,
|
|
11
14
|
updateAstrale,
|
|
12
15
|
writeInstallMetadata,
|
|
13
16
|
type InstallMetadata,
|
|
17
|
+
type UpdateExecution,
|
|
14
18
|
} from '../update'
|
|
15
19
|
|
|
16
20
|
async function makeFakeRelease(
|
|
@@ -65,7 +69,7 @@ async function makeFakeRelease(
|
|
|
65
69
|
async function makeInstall(
|
|
66
70
|
root: string,
|
|
67
71
|
version: string,
|
|
68
|
-
): Promise<{ meta: InstallMetadata; path: string }> {
|
|
72
|
+
): Promise<{ meta: InstallMetadata; path: string; execution: UpdateExecution }> {
|
|
69
73
|
const bin = join(root, 'bin', 'astrale')
|
|
70
74
|
await mkdir(join(root, 'bin'), { recursive: true })
|
|
71
75
|
await writeFile(
|
|
@@ -82,10 +86,33 @@ async function makeInstall(
|
|
|
82
86
|
bin,
|
|
83
87
|
}
|
|
84
88
|
await writeInstallMetadata(meta, path)
|
|
85
|
-
return { meta, path }
|
|
89
|
+
return { meta, path, execution: { kind: 'standalone', executable: bin } }
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
describe('update helpers', () => {
|
|
93
|
+
test('classifies only a Bun-compiled executable as standalone', () => {
|
|
94
|
+
expect(
|
|
95
|
+
classifyUpdateExecution({
|
|
96
|
+
bunVersion: '1.3.14',
|
|
97
|
+
executable: '/tmp/astrale',
|
|
98
|
+
entry: '/$bunfs/root/astrale',
|
|
99
|
+
}),
|
|
100
|
+
).toEqual({ kind: 'standalone', executable: '/tmp/astrale' })
|
|
101
|
+
expect(
|
|
102
|
+
classifyUpdateExecution({
|
|
103
|
+
bunVersion: '1.3.14',
|
|
104
|
+
executable: '/opt/homebrew/bin/bun',
|
|
105
|
+
entry: '/tmp/astrale.ts',
|
|
106
|
+
}),
|
|
107
|
+
).toEqual({ kind: 'package-managed', executable: '/opt/homebrew/bin/bun' })
|
|
108
|
+
expect(
|
|
109
|
+
classifyUpdateExecution({
|
|
110
|
+
executable: '/opt/homebrew/bin/node',
|
|
111
|
+
entry: '/tmp/astrale.js',
|
|
112
|
+
}),
|
|
113
|
+
).toEqual({ kind: 'package-managed', executable: '/opt/homebrew/bin/node' })
|
|
114
|
+
})
|
|
115
|
+
|
|
89
116
|
test('defaults missing install metadata to the beta channel', () => {
|
|
90
117
|
expect(DEFAULT_UPDATE_CHANNEL).toBe('beta')
|
|
91
118
|
expect(
|
|
@@ -118,10 +145,70 @@ describe('update helpers', () => {
|
|
|
118
145
|
})
|
|
119
146
|
})
|
|
120
147
|
|
|
148
|
+
describe('script install admission', () => {
|
|
149
|
+
test('rejects malformed JSON with the stable metadata error', async () => {
|
|
150
|
+
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
151
|
+
const path = join(root, 'install.json')
|
|
152
|
+
await writeFile(path, '{')
|
|
153
|
+
|
|
154
|
+
await expect(readInstallMetadata(path)).rejects.toMatchObject({
|
|
155
|
+
code: 'UPDATE_BAD_INSTALL_METADATA',
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test('admits a symlink only when it resolves to the recorded binary', async () => {
|
|
160
|
+
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
161
|
+
const { meta } = await makeInstall(root, '1.0.0')
|
|
162
|
+
const alias = join(root, 'astrale-alias')
|
|
163
|
+
await symlink(meta.bin, alias)
|
|
164
|
+
|
|
165
|
+
const admitted = await admitScriptInstall(meta, {
|
|
166
|
+
kind: 'standalone',
|
|
167
|
+
executable: alias,
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
expect(admitted.metadata).toEqual(meta)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('rejects a standalone binary that does not own the recorded target', async () => {
|
|
174
|
+
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
175
|
+
const { meta } = await makeInstall(root, '1.0.0')
|
|
176
|
+
const other = join(root, 'other-astrale')
|
|
177
|
+
await writeFile(other, '#!/bin/sh\n')
|
|
178
|
+
|
|
179
|
+
await expect(
|
|
180
|
+
admitScriptInstall(meta, { kind: 'standalone', executable: other }),
|
|
181
|
+
).rejects.toMatchObject({ code: 'UPDATE_INSTALL_MISMATCH' })
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
121
185
|
describe('updateAstrale', () => {
|
|
186
|
+
test('a package-managed process never consults or mutates a coexisting script install', async () => {
|
|
187
|
+
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
188
|
+
const { path, meta } = await makeInstall(root, '1.0.0')
|
|
189
|
+
const before = await readFile(meta.bin, 'utf8')
|
|
190
|
+
process.env.ASTRALE_UPDATE_BASE = 'file:///definitely-not-a-release'
|
|
191
|
+
try {
|
|
192
|
+
const result = await updateAstrale({
|
|
193
|
+
currentVersion: '2.0.0',
|
|
194
|
+
installPath: path,
|
|
195
|
+
execution: { kind: 'package-managed', executable: '/opt/homebrew/bin/node' },
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
expect(result).toEqual({
|
|
199
|
+
status: 'managed',
|
|
200
|
+
currentVersion: '2.0.0',
|
|
201
|
+
executable: '/opt/homebrew/bin/node',
|
|
202
|
+
})
|
|
203
|
+
expect(await readFile(meta.bin, 'utf8')).toBe(before)
|
|
204
|
+
} finally {
|
|
205
|
+
delete process.env.ASTRALE_UPDATE_BASE
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
122
209
|
test('check compares the installed release identity from metadata', async () => {
|
|
123
210
|
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
124
|
-
const { path } = await makeInstall(root, 'main-abc123')
|
|
211
|
+
const { path, execution } = await makeInstall(root, 'main-abc123')
|
|
125
212
|
const release = await makeFakeRelease(root, 'main-abc123', { binaryVersion: '1.0.0' })
|
|
126
213
|
process.env.ASTRALE_UPDATE_BASE = `file://${release}`
|
|
127
214
|
try {
|
|
@@ -130,6 +217,7 @@ describe('updateAstrale', () => {
|
|
|
130
217
|
currentVersion: '1.0.0',
|
|
131
218
|
platform: { os: 'darwin', arch: 'arm64' },
|
|
132
219
|
installPath: path,
|
|
220
|
+
execution,
|
|
133
221
|
})
|
|
134
222
|
|
|
135
223
|
expect(result).toMatchObject({
|
|
@@ -144,7 +232,7 @@ describe('updateAstrale', () => {
|
|
|
144
232
|
|
|
145
233
|
test('check reports available update without replacing the binary', async () => {
|
|
146
234
|
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
147
|
-
const { path, meta } = await makeInstall(root, '1.0.0')
|
|
235
|
+
const { path, meta, execution } = await makeInstall(root, '1.0.0')
|
|
148
236
|
const release = await makeFakeRelease(root, '1.1.0')
|
|
149
237
|
process.env.ASTRALE_UPDATE_BASE = `file://${release}`
|
|
150
238
|
try {
|
|
@@ -153,6 +241,7 @@ describe('updateAstrale', () => {
|
|
|
153
241
|
currentVersion: '1.0.0',
|
|
154
242
|
platform: { os: 'darwin', arch: 'arm64' },
|
|
155
243
|
installPath: path,
|
|
244
|
+
execution,
|
|
156
245
|
})
|
|
157
246
|
|
|
158
247
|
expect(result).toMatchObject({
|
|
@@ -168,7 +257,7 @@ describe('updateAstrale', () => {
|
|
|
168
257
|
|
|
169
258
|
test('supports legacy string asset manifests', async () => {
|
|
170
259
|
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
171
|
-
const { path, meta } = await makeInstall(root, '1.0.0')
|
|
260
|
+
const { path, meta, execution } = await makeInstall(root, '1.0.0')
|
|
172
261
|
const release = await makeFakeRelease(root, '1.1.0', { legacyManifest: true })
|
|
173
262
|
process.env.ASTRALE_UPDATE_BASE = `file://${release}`
|
|
174
263
|
try {
|
|
@@ -177,6 +266,7 @@ describe('updateAstrale', () => {
|
|
|
177
266
|
currentVersion: '1.0.0',
|
|
178
267
|
platform: { os: 'darwin', arch: 'arm64' },
|
|
179
268
|
installPath: path,
|
|
269
|
+
execution,
|
|
180
270
|
})
|
|
181
271
|
|
|
182
272
|
expect(check).toMatchObject({
|
|
@@ -188,6 +278,7 @@ describe('updateAstrale', () => {
|
|
|
188
278
|
currentVersion: '1.0.0',
|
|
189
279
|
platform: { os: 'darwin', arch: 'arm64' },
|
|
190
280
|
installPath: path,
|
|
281
|
+
execution,
|
|
191
282
|
})
|
|
192
283
|
|
|
193
284
|
expect(result).toMatchObject({
|
|
@@ -204,7 +295,7 @@ describe('updateAstrale', () => {
|
|
|
204
295
|
|
|
205
296
|
test('updates the binary and install metadata', async () => {
|
|
206
297
|
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
207
|
-
const { path, meta } = await makeInstall(root, '1.0.0')
|
|
298
|
+
const { path, meta, execution } = await makeInstall(root, '1.0.0')
|
|
208
299
|
const release = await makeFakeRelease(root, '1.1.0')
|
|
209
300
|
process.env.ASTRALE_UPDATE_BASE = `file://${release}`
|
|
210
301
|
try {
|
|
@@ -212,6 +303,7 @@ describe('updateAstrale', () => {
|
|
|
212
303
|
currentVersion: '1.0.0',
|
|
213
304
|
platform: { os: 'darwin', arch: 'arm64' },
|
|
214
305
|
installPath: path,
|
|
306
|
+
execution,
|
|
215
307
|
})
|
|
216
308
|
|
|
217
309
|
expect(result).toMatchObject({
|
|
@@ -231,7 +323,7 @@ describe('updateAstrale', () => {
|
|
|
231
323
|
|
|
232
324
|
test('updates canary-style releases whose binary reports the package version', async () => {
|
|
233
325
|
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
234
|
-
const { path, meta } = await makeInstall(root, 'main-old123')
|
|
326
|
+
const { path, meta, execution } = await makeInstall(root, 'main-old123')
|
|
235
327
|
const release = await makeFakeRelease(root, 'main-new456', { binaryVersion: '1.0.0' })
|
|
236
328
|
process.env.ASTRALE_UPDATE_BASE = `file://${release}`
|
|
237
329
|
try {
|
|
@@ -239,6 +331,7 @@ describe('updateAstrale', () => {
|
|
|
239
331
|
currentVersion: '1.0.0',
|
|
240
332
|
platform: { os: 'darwin', arch: 'arm64' },
|
|
241
333
|
installPath: path,
|
|
334
|
+
execution,
|
|
242
335
|
})
|
|
243
336
|
|
|
244
337
|
expect(result).toMatchObject({
|
|
@@ -23,7 +23,6 @@ const profile: ResolvedView = {
|
|
|
23
23
|
kind: 'definition',
|
|
24
24
|
definitions: [{ origin: 'shell.test', kind: 'class', name: 'Person' }],
|
|
25
25
|
},
|
|
26
|
-
auth: 'required',
|
|
27
26
|
},
|
|
28
27
|
href: 'https://shell.test/profile',
|
|
29
28
|
handshake: 'shell',
|
|
@@ -41,7 +40,6 @@ const card: ResolvedView = {
|
|
|
41
40
|
kind: 'definition',
|
|
42
41
|
definitions: [{ origin: 'shell.test', kind: 'class', name: 'Person' }],
|
|
43
42
|
},
|
|
44
|
-
auth: 'public',
|
|
45
43
|
},
|
|
46
44
|
href: 'https://shell.test/card',
|
|
47
45
|
handshake: 'none',
|
|
@@ -29,7 +29,7 @@ describe('view session server credentials', () => {
|
|
|
29
29
|
target: target('/:example.test'),
|
|
30
30
|
route: {
|
|
31
31
|
key: 'example.test:view.public',
|
|
32
|
-
declaration: { target: { kind: 'domain' }
|
|
32
|
+
declaration: { target: { kind: 'domain' } },
|
|
33
33
|
href: 'https://example.test/ui/public',
|
|
34
34
|
handshake: 'none',
|
|
35
35
|
issuer: issuer('https://example.test'),
|
|
@@ -58,7 +58,7 @@ describe('view session private state', () => {
|
|
|
58
58
|
issuer: issuer('https://example.test'),
|
|
59
59
|
etag: `sha256:${'a'.repeat(64)}`,
|
|
60
60
|
revision: revision('b'),
|
|
61
|
-
declaration: { target: { kind: 'domain' }
|
|
61
|
+
declaration: { target: { kind: 'domain' } },
|
|
62
62
|
},
|
|
63
63
|
},
|
|
64
64
|
createdAt: '2026-08-12T00:00:00.000Z',
|
package/src/lib/idp.ts
CHANGED
|
@@ -215,6 +215,7 @@ export type IdpConfig = {
|
|
|
215
215
|
export const BUILTIN_WORKOS_IDP_NAME = 'workos'
|
|
216
216
|
const DEFAULT_WORKOS_API_HOST = 'https://api.workos.com'
|
|
217
217
|
const DEFAULT_WORKOS_CLIENT_ID = 'client_01KC29HET5F3QAQ8GNTPZ7F320'
|
|
218
|
+
const LEGACY_WORKOS_CLIENT_ID = 'client_01KC29HEGD7B40TV2C4QZ436BG'
|
|
218
219
|
const WORKOS_CLIENT_ID_ENV_NAMES = ['WORKOS_CLIENT_ID', 'VITE_WORKOS_CLIENT_ID'] as const
|
|
219
220
|
|
|
220
221
|
export function idpDir(name: string): string {
|
|
@@ -281,7 +282,17 @@ export async function readIdpConfigOrBuiltin(
|
|
|
281
282
|
validateName(name, 'IdP')
|
|
282
283
|
const store = await readIdpStore()
|
|
283
284
|
if (store.idps[name]) {
|
|
284
|
-
|
|
285
|
+
const existing = await readIdpConfig(name)
|
|
286
|
+
const replacement = opts.persist
|
|
287
|
+
? legacyBuiltinWorkosReplacement(existing, opts.clientId)
|
|
288
|
+
: undefined
|
|
289
|
+
if (!replacement) return existing
|
|
290
|
+
return upsertIdpConfig({
|
|
291
|
+
name: replacement.name,
|
|
292
|
+
metadata: replacement.metadata,
|
|
293
|
+
client: replacement.client,
|
|
294
|
+
builtIn: true,
|
|
295
|
+
})
|
|
285
296
|
}
|
|
286
297
|
|
|
287
298
|
const builtin = builtinIdpConfig(name, opts.clientId)
|
|
@@ -421,6 +432,23 @@ export function workosClientIdFromEnv(env: NodeJS.ProcessEnv = process.env): str
|
|
|
421
432
|
)
|
|
422
433
|
}
|
|
423
434
|
|
|
435
|
+
export function legacyBuiltinWorkosReplacement(
|
|
436
|
+
existing: IdpConfig,
|
|
437
|
+
clientIdOverride?: string,
|
|
438
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
439
|
+
): IdpConfig | undefined {
|
|
440
|
+
if (
|
|
441
|
+
existing.name !== BUILTIN_WORKOS_IDP_NAME ||
|
|
442
|
+
!existing.entry.builtIn ||
|
|
443
|
+
existing.client.client_id !== LEGACY_WORKOS_CLIENT_ID
|
|
444
|
+
) {
|
|
445
|
+
return undefined
|
|
446
|
+
}
|
|
447
|
+
const replacement = builtinIdpConfig(existing.name, clientIdOverride, env)
|
|
448
|
+
if (!replacement || replacement.client.client_id === LEGACY_WORKOS_CLIENT_ID) return undefined
|
|
449
|
+
return replacement
|
|
450
|
+
}
|
|
451
|
+
|
|
424
452
|
async function fetchOAuthAuthorizationServerMetadata(
|
|
425
453
|
issuer: string,
|
|
426
454
|
): Promise<Partial<OidcMetadata> | undefined> {
|