@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
|
@@ -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/admin-target.ts
CHANGED
|
@@ -5,11 +5,30 @@ import type { InstanceStore } from './instance'
|
|
|
5
5
|
|
|
6
6
|
import { AstraleError } from '../errors'
|
|
7
7
|
import { readInstances, resolveInstanceKey } from './instance'
|
|
8
|
+
import { DEFAULT_UPDATE_CHANNEL } from './update'
|
|
8
9
|
import { isHttpUrl } from './validation'
|
|
9
10
|
|
|
10
11
|
export const DEFAULT_ADMIN_TARGET_NAME = 'admin'
|
|
11
|
-
|
|
12
|
-
export
|
|
12
|
+
|
|
13
|
+
export function defaultAdminTargetForChannel(channel: string): {
|
|
14
|
+
readonly url: string
|
|
15
|
+
readonly domainIssuer: string
|
|
16
|
+
} {
|
|
17
|
+
return channel === 'stable'
|
|
18
|
+
? {
|
|
19
|
+
url: 'https://admin.eu.astrale.ai/api',
|
|
20
|
+
domainIssuer: 'https://admin.astrale.ai',
|
|
21
|
+
}
|
|
22
|
+
: {
|
|
23
|
+
url: 'https://admin.eu.beta.astrale.ai/api',
|
|
24
|
+
domainIssuer: 'https://admin.beta.astrale.ai',
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DEFAULT_ADMIN_TARGET = defaultAdminTargetForChannel(DEFAULT_UPDATE_CHANNEL)
|
|
29
|
+
|
|
30
|
+
export const DEFAULT_ADMIN_TARGET_URL = DEFAULT_ADMIN_TARGET.url
|
|
31
|
+
export const DEFAULT_ADMIN_DOMAIN_ISSUER = DEFAULT_ADMIN_TARGET.domainIssuer
|
|
13
32
|
|
|
14
33
|
export const DEFAULT_ADMIN_TARGET_CONFIG = {
|
|
15
34
|
name: DEFAULT_ADMIN_TARGET_NAME,
|
package/src/lib/idp.ts
CHANGED
|
@@ -214,6 +214,8 @@ export type IdpConfig = {
|
|
|
214
214
|
|
|
215
215
|
export const BUILTIN_WORKOS_IDP_NAME = 'workos'
|
|
216
216
|
const DEFAULT_WORKOS_API_HOST = 'https://api.workos.com'
|
|
217
|
+
const DEFAULT_WORKOS_CLIENT_ID = 'client_01KC29HET5F3QAQ8GNTPZ7F320'
|
|
218
|
+
const LEGACY_WORKOS_CLIENT_ID = 'client_01KC29HEGD7B40TV2C4QZ436BG'
|
|
217
219
|
const WORKOS_CLIENT_ID_ENV_NAMES = ['WORKOS_CLIENT_ID', 'VITE_WORKOS_CLIENT_ID'] as const
|
|
218
220
|
|
|
219
221
|
export function idpDir(name: string): string {
|
|
@@ -280,7 +282,17 @@ export async function readIdpConfigOrBuiltin(
|
|
|
280
282
|
validateName(name, 'IdP')
|
|
281
283
|
const store = await readIdpStore()
|
|
282
284
|
if (store.idps[name]) {
|
|
283
|
-
|
|
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
|
+
})
|
|
284
296
|
}
|
|
285
297
|
|
|
286
298
|
const builtin = builtinIdpConfig(name, opts.clientId)
|
|
@@ -413,11 +425,30 @@ export function builtinIdpConfig(
|
|
|
413
425
|
}
|
|
414
426
|
|
|
415
427
|
export function workosClientIdFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
416
|
-
return
|
|
417
|
-
(
|
|
428
|
+
return (
|
|
429
|
+
WORKOS_CLIENT_ID_ENV_NAMES.map((name) => env[name]?.trim()).find(
|
|
430
|
+
(value): value is string => typeof value === 'string' && value.length > 0,
|
|
431
|
+
) ?? DEFAULT_WORKOS_CLIENT_ID
|
|
418
432
|
)
|
|
419
433
|
}
|
|
420
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
|
+
|
|
421
452
|
async function fetchOAuthAuthorizationServerMetadata(
|
|
422
453
|
issuer: string,
|
|
423
454
|
): Promise<Partial<OidcMetadata> | undefined> {
|
package/src/lib/update.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
chmod,
|
|
4
|
+
copyFile,
|
|
5
|
+
mkdir,
|
|
6
|
+
mkdtemp,
|
|
7
|
+
readFile,
|
|
8
|
+
realpath,
|
|
9
|
+
rename,
|
|
10
|
+
rm,
|
|
11
|
+
writeFile,
|
|
12
|
+
} from 'node:fs/promises'
|
|
3
13
|
import { tmpdir } from 'node:os'
|
|
4
14
|
import { dirname, join } from 'node:path'
|
|
5
15
|
import { z } from 'zod'
|
|
@@ -61,9 +71,26 @@ export type UpdateRequest = {
|
|
|
61
71
|
currentVersion: string
|
|
62
72
|
platform?: Platform
|
|
63
73
|
installPath?: string
|
|
74
|
+
execution?: UpdateExecution
|
|
64
75
|
}
|
|
65
76
|
|
|
77
|
+
export type UpdateExecution =
|
|
78
|
+
| { kind: 'standalone'; executable: string }
|
|
79
|
+
| { kind: 'package-managed'; executable: string }
|
|
80
|
+
|
|
81
|
+
const admittedScriptInstall = Symbol('admittedScriptInstall')
|
|
82
|
+
export type AdmittedScriptInstall = Readonly<{
|
|
83
|
+
metadata: InstallMetadata
|
|
84
|
+
executable: string
|
|
85
|
+
[admittedScriptInstall]: true
|
|
86
|
+
}>
|
|
87
|
+
|
|
66
88
|
export type UpdateResult =
|
|
89
|
+
| {
|
|
90
|
+
status: 'managed'
|
|
91
|
+
currentVersion: string
|
|
92
|
+
executable: string
|
|
93
|
+
}
|
|
67
94
|
| {
|
|
68
95
|
status: 'up-to-date'
|
|
69
96
|
currentVersion: string
|
|
@@ -106,42 +133,99 @@ export function platformKey(platform: Platform): string {
|
|
|
106
133
|
return `${platform.os}-${platform.arch}`
|
|
107
134
|
}
|
|
108
135
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
136
|
+
export function classifyUpdateExecution(input: {
|
|
137
|
+
bunVersion?: string
|
|
138
|
+
executable: string
|
|
139
|
+
entry?: string
|
|
140
|
+
}): UpdateExecution {
|
|
141
|
+
return input.bunVersion && input.entry?.startsWith('/$bunfs/')
|
|
142
|
+
? { kind: 'standalone', executable: input.executable }
|
|
143
|
+
: { kind: 'package-managed', executable: input.executable }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function detectUpdateExecution(): UpdateExecution {
|
|
147
|
+
return classifyUpdateExecution({
|
|
148
|
+
bunVersion: (process.versions as { bun?: string }).bun,
|
|
149
|
+
executable: process.execPath,
|
|
150
|
+
entry: process.argv[1],
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function packageManagedUpdateError(executable: string): AstraleError {
|
|
155
|
+
return new AstraleError(
|
|
156
|
+
'UPDATE_PACKAGE_MANAGED',
|
|
157
|
+
'This Astrale build is managed by your package manager.',
|
|
158
|
+
`Active binary: ${executable}. Update it with npm, pnpm, or bun; or put the official script installation first on PATH.`,
|
|
159
|
+
)
|
|
116
160
|
}
|
|
117
161
|
|
|
118
162
|
export async function readInstallMetadata(path = INSTALL_PATH): Promise<InstallMetadata> {
|
|
119
163
|
let raw: string
|
|
120
164
|
try {
|
|
121
165
|
raw = await readFile(path, 'utf8')
|
|
122
|
-
} catch {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
: new AstraleError(
|
|
130
|
-
'UPDATE_PACKAGE_MANAGED',
|
|
131
|
-
'This Astrale build is managed by your package manager.',
|
|
132
|
-
'Update with: npm install -g @astrale-os/cli@latest (or pnpm/bun)',
|
|
133
|
-
)
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (!isMissingFile(error)) throw error
|
|
168
|
+
throw new AstraleError(
|
|
169
|
+
'UPDATE_NOT_SCRIPT_INSTALLED',
|
|
170
|
+
'Astrale was not installed by the official install script.',
|
|
171
|
+
'Reinstall with: curl -fsSL https://raw.githubusercontent.com/astrale-os/cli/main/install.sh | sh',
|
|
172
|
+
)
|
|
134
173
|
}
|
|
135
174
|
|
|
136
|
-
|
|
175
|
+
let decoded: unknown
|
|
176
|
+
try {
|
|
177
|
+
decoded = JSON.parse(raw)
|
|
178
|
+
} catch {
|
|
179
|
+
throw badInstallMetadata(path)
|
|
180
|
+
}
|
|
181
|
+
const parsed = InstallMetadataSchema.safeParse(decoded)
|
|
137
182
|
if (!parsed.success) {
|
|
183
|
+
throw badInstallMetadata(path)
|
|
184
|
+
}
|
|
185
|
+
return parsed.data
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function isMissingFile(error: unknown): error is NodeJS.ErrnoException {
|
|
189
|
+
return error instanceof Error && 'code' in error && error.code === 'ENOENT'
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function badInstallMetadata(path: string): AstraleError {
|
|
193
|
+
return new AstraleError(
|
|
194
|
+
'UPDATE_BAD_INSTALL_METADATA',
|
|
195
|
+
`Invalid install metadata at ${path}.`,
|
|
196
|
+
'Reinstall with: curl -fsSL https://raw.githubusercontent.com/astrale-os/cli/main/install.sh | sh',
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function admitScriptInstall(
|
|
201
|
+
meta: InstallMetadata,
|
|
202
|
+
execution: Extract<UpdateExecution, { kind: 'standalone' }>,
|
|
203
|
+
): Promise<AdmittedScriptInstall> {
|
|
204
|
+
const [running, recorded] = await Promise.all([
|
|
205
|
+
realpathIfExists(execution.executable),
|
|
206
|
+
realpathIfExists(meta.bin),
|
|
207
|
+
])
|
|
208
|
+
if (running === undefined || recorded === undefined || running !== recorded) {
|
|
138
209
|
throw new AstraleError(
|
|
139
|
-
'
|
|
140
|
-
|
|
141
|
-
|
|
210
|
+
'UPDATE_INSTALL_MISMATCH',
|
|
211
|
+
'The running Astrale binary does not own the recorded script installation.',
|
|
212
|
+
`Running binary: ${execution.executable}; recorded binary: ${meta.bin}. Refusing to replace a different installation.`,
|
|
142
213
|
)
|
|
143
214
|
}
|
|
144
|
-
return
|
|
215
|
+
return Object.freeze({
|
|
216
|
+
metadata: meta,
|
|
217
|
+
executable: running,
|
|
218
|
+
[admittedScriptInstall]: true as const,
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function realpathIfExists(path: string): Promise<string | undefined> {
|
|
223
|
+
try {
|
|
224
|
+
return await realpath(path)
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if (isMissingFile(error)) return undefined
|
|
227
|
+
throw error
|
|
228
|
+
}
|
|
145
229
|
}
|
|
146
230
|
|
|
147
231
|
export async function writeInstallMetadata(
|
|
@@ -175,7 +259,17 @@ export function shouldUpdate(currentVersion: string, manifestVersion: string): b
|
|
|
175
259
|
}
|
|
176
260
|
|
|
177
261
|
export async function updateAstrale(req: UpdateRequest): Promise<UpdateResult> {
|
|
178
|
-
const
|
|
262
|
+
const execution = req.execution ?? detectUpdateExecution()
|
|
263
|
+
if (execution.kind === 'package-managed') {
|
|
264
|
+
return {
|
|
265
|
+
status: 'managed',
|
|
266
|
+
currentVersion: req.currentVersion,
|
|
267
|
+
executable: execution.executable,
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const install = await admitScriptInstall(await readInstallMetadata(req.installPath), execution)
|
|
272
|
+
const meta = install.metadata
|
|
179
273
|
const currentVersion = meta.version ?? req.currentVersion
|
|
180
274
|
const channel = req.channel ?? meta.channel ?? DEFAULT_UPDATE_CHANNEL
|
|
181
275
|
const platform = req.platform ?? detectPlatform()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{m as L}from"./index-
|
|
1
|
+
import{m as L}from"./index-C-PvAXV4.js";function S(y,b){for(var u=0;u<b.length;u++){const a=b[u];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in y)){const n=Object.getOwnPropertyDescriptor(a,i);n&&Object.defineProperty(y,i,n.get?n:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(y,Symbol.toStringTag,{value:"Module"}))}function w(y){throw new Error('Could not dynamically require "'+y+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}var O={exports:{}},j;function C(){return j||(j=1,(function(y,b){(function(u){y.exports=u()})(function(){return(function(){function u(a,i,n){function d(f,_){if(!i[f]){if(!a[f]){var h=typeof w=="function"&&w;if(!_&&h)return h(f,!0);if(g)return g(f,!0);var o=new Error("Cannot find module '"+f+"'");throw o.code="MODULE_NOT_FOUND",o}var e=i[f]={exports:{}};a[f][0].call(e.exports,function(r){var t=a[f][1][r];return d(t||r)},e,e.exports,u,a,i,n)}return i[f].exports}for(var g=typeof w=="function"&&w,m=0;m<n.length;m++)d(n[m]);return d}return u})()({1:[function(u,a,i){Object.defineProperty(i,"__esModule",{value:!0}),i.default=void 0;function n(o){"@babel/helpers - typeof";return n=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(o)}function d(o,e){if(!(o instanceof e))throw new TypeError("Cannot call a class as a function")}function g(o,e){for(var r=0;r<e.length;r++){var t=e[r];t.enumerable=t.enumerable||!1,t.configurable=!0,"value"in t&&(t.writable=!0),Object.defineProperty(o,f(t.key),t)}}function m(o,e,r){return e&&g(o.prototype,e),Object.defineProperty(o,"prototype",{writable:!1}),o}function f(o){var e=_(o,"string");return n(e)=="symbol"?e:e+""}function _(o,e){if(n(o)!="object"||!o)return o;var r=o[Symbol.toPrimitive];if(r!==void 0){var t=r.call(o,e);if(n(t)!="object")return t;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(o)}i.default=(function(){function o(){var e=this,r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},t=r.defaultLayoutOptions,s=t===void 0?{}:t,l=r.algorithms,v=l===void 0?["layered","stress","mrtree","radial","force","disco","sporeOverlap","sporeCompaction","rectpacking"]:l,c=r.workerFactory,p=r.workerUrl;if(d(this,o),this.defaultLayoutOptions=s,this.initialized=!1,typeof p>"u"&&typeof c>"u")throw new Error("Cannot construct an ELK without both 'workerUrl' and 'workerFactory'.");var k=c;typeof p<"u"&&typeof c>"u"&&(k=function(M){return new Worker(M)});var E=k(p);if(typeof E.postMessage!="function")throw new TypeError("Created worker does not provide the required 'postMessage' function.");this.worker=new h(E),this.worker.postMessage({cmd:"register",algorithms:v}).then(function(P){return e.initialized=!0}).catch(console.err)}return m(o,[{key:"layout",value:function(r){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},s=t.layoutOptions,l=s===void 0?this.defaultLayoutOptions:s,v=t.logging,c=v===void 0?!1:v,p=t.measureExecutionTime,k=p===void 0?!1:p;return r?this.worker.postMessage({cmd:"layout",graph:r,layoutOptions:l,options:{logging:c,measureExecutionTime:k}}):Promise.reject(new Error("Missing mandatory parameter 'graph'."))}},{key:"knownLayoutAlgorithms",value:function(){return this.worker.postMessage({cmd:"algorithms"})}},{key:"knownLayoutOptions",value:function(){return this.worker.postMessage({cmd:"options"})}},{key:"knownLayoutCategories",value:function(){return this.worker.postMessage({cmd:"categories"})}},{key:"terminateWorker",value:function(){this.worker&&this.worker.terminate()}}])})();var h=(function(){function o(e){var r=this;if(d(this,o),e===void 0)throw new Error("Missing mandatory parameter 'worker'.");this.resolvers={},this.worker=e,this.worker.onmessage=function(t){setTimeout(function(){r.receive(r,t)},0)}}return m(o,[{key:"postMessage",value:function(r){var t=this.id||0;this.id=t+1,r.id=t;var s=this;return new Promise(function(l,v){s.resolvers[t]=function(c,p){c?(s.convertGwtStyleError(c),v(c)):l(p)},s.worker.postMessage(r)})}},{key:"receive",value:function(r,t){var s=t.data,l=r.resolvers[s.id];l&&(delete r.resolvers[s.id],s.error?l(s.error):l(null,s.data))}},{key:"terminate",value:function(){this.worker&&this.worker.terminate()}},{key:"convertGwtStyleError",value:function(r){if(r){var t=r.__java$exception;t&&(t.cause&&t.cause.backingJsObject&&(r.cause=t.cause.backingJsObject,this.convertGwtStyleError(r.cause)),delete r.__java$exception)}}}])})()},{}],2:[function(u,a,i){var n=u("./elk-api.js").default;Object.defineProperty(a.exports,"__esModule",{value:!0}),a.exports=n,n.default=n},{"./elk-api.js":1}]},{},[2])(2)})})(O)),O.exports}var x=C();const A=L(x),q=S({__proto__:null,default:A},[x]);export{q as e};
|