@astrale-os/cli 1.0.0-beta.4 → 1.0.0-beta.6
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 +55649 -59388
- package/dist/public/connect-core.js +1972 -26831
- package/dist/public/keys/index.js +1754 -40686
- package/dist/public/paths/index.js +859 -39511
- package/dist/types/admin/instance/model.d.ts +3 -4
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/lib/idp.d.ts +1 -0
- package/dist/types/lib/instance-target.d.ts +1 -1
- package/dist/types/lib/log.d.ts +2 -2
- package/dist/types/lib/update.d.ts +30 -0
- package/package.json +4 -6
- package/src/__tests__/fixtures/publication.ts +20 -0
- package/src/admin/__tests__/binding.test.ts +14 -21
- package/src/admin/__tests__/fixture.ts +101 -0
- package/src/admin/binding.ts +86 -6
- package/src/admin/catalog/.spec/api.d.ts +2 -2
- package/src/admin/catalog/__tests__/client.test.ts +20 -49
- package/src/admin/catalog/client.ts +35 -26
- package/src/admin/catalog/model.ts +3 -4
- package/src/admin/graph/.spec/api.d.ts +4 -10
- package/src/admin/graph/nodes.ts +1 -1
- package/src/admin/instance/.spec/api.d.ts +2 -2
- package/src/admin/instance/__tests__/client.test.ts +58 -83
- package/src/admin/instance/client.ts +49 -39
- package/src/admin/instance/model.ts +9 -8
- package/src/commands/__tests__/call-describe.test.ts +46 -33
- package/src/commands/__tests__/domain-install-operation.test.ts +3 -14
- package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
- package/src/commands/__tests__/domain-list.test.ts +5 -25
- package/src/commands/__tests__/install-identity-override.test.ts +3 -14
- package/src/commands/__tests__/read-commands.test.ts +35 -10
- package/src/commands/__tests__/update.test.ts +25 -3
- package/src/commands/call-describe.ts +59 -67
- package/src/commands/call.ts +41 -54
- package/src/commands/domain/install.ts +2 -2
- package/src/commands/domain/uninstall.ts +2 -2
- package/src/commands/get.ts +13 -14
- package/src/commands/identity/__tests__/register.test.ts +3 -3
- package/src/commands/identity/register.ts +5 -4
- package/src/commands/instance/delete.ts +5 -3
- package/src/commands/instance/status.ts +1 -9
- package/src/commands/introspect.ts +4 -11
- package/src/commands/logs.ts +24 -25
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +27 -30
- package/src/commands/token.ts +8 -7
- package/src/commands/update.ts +31 -11
- package/src/commands/view.ts +4 -3
- package/src/connection/.spec/api.d.ts +7 -8
- package/src/connection/.spec/architecture.md +5 -4
- package/src/connection/.spec/flows/session.ts +2 -2
- package/src/connection/.spec/laws/connection.ts +6 -2
- package/src/connection/.spec/layout.ts +1 -0
- package/src/connection/__tests__/credential.test.ts +1 -1
- package/src/connection/__tests__/errors.test.ts +27 -20
- package/src/connection/__tests__/exchange.test.ts +30 -1
- package/src/connection/__tests__/failure-fixtures.ts +39 -0
- package/src/connection/__tests__/failure-safety.test.ts +75 -0
- package/src/connection/__tests__/self.test.ts +31 -11
- package/src/connection/__tests__/session.test.ts +3 -3
- package/src/connection/call.ts +1 -1
- package/src/connection/credential.ts +2 -2
- package/src/connection/errors.ts +1 -355
- package/src/connection/exchange.ts +47 -23
- package/src/connection/failure/classify.ts +110 -0
- package/src/connection/failure/debug.ts +26 -0
- package/src/connection/failure/index.ts +18 -0
- package/src/connection/failure/model.ts +28 -0
- package/src/connection/failure/render.ts +116 -0
- package/src/connection/reasons.ts +16 -0
- package/src/connection/self.ts +39 -21
- package/src/connection/session.ts +8 -8
- package/src/errors.ts +3 -2
- package/src/graph/.spec/api.d.ts +12 -5
- package/src/graph/.spec/layout.ts +9 -1
- package/src/graph/__tests__/mutation.test.ts +2 -2
- package/src/graph/__tests__/query.test.ts +25 -6
- package/src/graph/class.ts +30 -0
- package/src/graph/index.ts +1 -0
- package/src/graph/query.ts +21 -32
- package/src/identity/.spec/api.d.ts +2 -2
- package/src/identity/__tests__/registration.test.ts +1 -1
- package/src/identity/registration.ts +3 -3
- package/src/lib/__tests__/domain-publication.test.ts +4 -15
- package/src/lib/__tests__/idp.test.ts +25 -0
- package/src/lib/__tests__/instance-target.test.ts +6 -39
- package/src/lib/__tests__/log-errors.test.ts +53 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
- 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/command-dx.ts +1 -4
- package/src/lib/domain-publication.ts +1 -1
- package/src/lib/idp.ts +29 -1
- package/src/lib/instance-target.ts +3 -14
- package/src/lib/log.ts +11 -6
- package/src/lib/update.ts +120 -26
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/resolve.ts +1 -1
- package/src/program/__tests__/program.test.ts +1 -1
- package/studio/client/dist/assets/{elk-api-lwhFo7vB.js → elk-api-Di4OXGNH.js} +1 -1
- package/studio/client/dist/assets/index-BLm6J11H.js +81 -0
- package/studio/client/dist/assets/index-BiGR8Oo9.css +1 -0
- package/studio/client/dist/assets/index-DTjvkOUX.js +8 -0
- package/studio/client/dist/index.html +2 -2
- package/studio/package.json +2 -9
- package/studio/server/agent/ask.test.ts +1 -1
- package/studio/server/agent/bridge/grant.test.ts +1 -1
- package/studio/server/agent/bridge/routes.test.ts +1 -1
- package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
- package/studio/server/agent/prompts/anchors.test.ts +32 -36
- package/studio/server/agent/prompts/anchors.ts +20 -49
- package/studio/server/agent/prompts/system.test.ts +3 -2
- package/studio/server/agent/prompts/system.ts +3 -3
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- package/studio/server/api/canvas.ts +0 -1
- package/studio/server/api-agent-loadout.test.ts +1 -1
- package/studio/server/api.test.ts +1 -1
- package/studio/server/cache.test.ts +23 -12
- package/studio/server/cache.ts +12 -15
- package/studio/server/domain.test.ts +50 -68
- package/studio/server/domain.ts +82 -63
- package/studio/server/handoff/copy.ts +1 -1
- package/studio/server/index.ts +1 -1
- package/studio/server/instances/probe.test.ts +1 -1
- package/studio/server/introspect/anatomy/views/routes.ts +46 -37
- package/studio/server/introspect/anatomy/views.ts +9 -14
- package/studio/server/introspect/anatomy-extras.test.ts +26 -42
- package/studio/server/introspect/anatomy.test.ts +22 -14
- package/studio/server/introspect/anatomy.ts +6 -9
- package/studio/server/introspect/bundle.ts +0 -3
- package/studio/server/introspect/canonical-schema.test.ts +94 -389
- package/studio/server/introspect/canonical-schema.ts +227 -457
- package/studio/server/introspect/core-extractor.ts +18 -127
- package/studio/server/introspect/core.ts +1 -2
- package/studio/server/introspect/diff.test.ts +11 -7
- package/studio/server/introspect/diff.ts +18 -55
- package/studio/server/introspect/extractor.ts +22 -117
- package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
- package/studio/server/introspect/overlay.test.ts +26 -54
- package/studio/server/introspect/overlay.ts +10 -37
- package/studio/server/introspect/revision.test.ts +1 -1
- package/studio/server/introspect/revision.ts +1 -1
- package/studio/server/introspect/runtime.test.ts +65 -183
- package/studio/server/introspect/runtime.ts +8 -25
- package/studio/server/introspect/schema-ir-json.test.ts +70 -0
- package/studio/server/introspect/schema-ir-json.ts +38 -68
- package/studio/server/introspect/schema-refs.test.ts +43 -88
- package/studio/server/introspect/schema-refs.ts +14 -49
- package/studio/server/introspect/source-overlay/handlers.ts +116 -636
- package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
- package/studio/server/introspect/source-overlay/spans.ts +15 -45
- package/studio/server/state/baseline.test.ts +14 -9
- package/studio/server/state/baseline.ts +3 -5
- package/studio/server/state/visibility.ts +1 -5
- package/studio/server/views/model.ts +3 -3
- package/studio/server/views/target.test.ts +17 -74
- package/studio/server/views/target.ts +24 -50
- package/studio/server/watch.test.ts +11 -11
- package/studio/server/watch.ts +8 -4
- package/studio/server/workspace-watch.ts +2 -2
- package/studio/shared/contracts/schema.ts +38 -107
- package/studio/shared/contracts/surface.test.ts +13 -15
- package/studio/shared/contracts/workspace.ts +2 -11
- package/studio/shared/schema/identity.test.ts +20 -44
- package/studio/shared/schema/identity.ts +9 -23
- package/viewer/dist/main.js +40 -61
- package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
- package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
- package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
- package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
|
@@ -3,6 +3,7 @@ import { describe, expect, test } from 'bun:test'
|
|
|
3
3
|
import type { AstraleConfig } from '../config'
|
|
4
4
|
import type { InstanceStore } from '../instance'
|
|
5
5
|
|
|
6
|
+
import { AdminInstanceNotFoundError } from '../../admin/instance/model'
|
|
6
7
|
import { AstraleError } from '../../errors'
|
|
7
8
|
import { DEFAULT_CONFIG } from '../config'
|
|
8
9
|
import {
|
|
@@ -124,9 +125,6 @@ describe('resolveInstanceTarget', () => {
|
|
|
124
125
|
})
|
|
125
126
|
|
|
126
127
|
test('unknown slugs surface the original instance-not-found error', async () => {
|
|
127
|
-
const notFound = new Error('Path not found: "/admin/instances/missing"')
|
|
128
|
-
notFound.name = 'NotFoundError'
|
|
129
|
-
|
|
130
128
|
await expect(
|
|
131
129
|
resolveInstanceTarget(
|
|
132
130
|
{ source: 'name', name: 'missing' },
|
|
@@ -134,7 +132,7 @@ describe('resolveInstanceTarget', () => {
|
|
|
134
132
|
config: DEFAULT_CONFIG,
|
|
135
133
|
instances: emptyStore,
|
|
136
134
|
managed: async () => {
|
|
137
|
-
throw
|
|
135
|
+
throw new AdminInstanceNotFoundError('missing')
|
|
138
136
|
},
|
|
139
137
|
},
|
|
140
138
|
),
|
|
@@ -168,14 +166,8 @@ describe('instance target helpers', () => {
|
|
|
168
166
|
expect(couldBeConfiguredAdminInstance('bryan', directAdminConfig)).toBe(false)
|
|
169
167
|
})
|
|
170
168
|
|
|
171
|
-
test('treats
|
|
172
|
-
|
|
173
|
-
data?: unknown
|
|
174
|
-
}
|
|
175
|
-
wrapped.name = 'KernelError'
|
|
176
|
-
wrapped.data = { type: 'NotFoundError' }
|
|
177
|
-
|
|
178
|
-
expect(isManagedInstanceNotFound(wrapped)).toBe(true)
|
|
169
|
+
test('treats the owner-scoped Admin error as managed lookup not-found', () => {
|
|
170
|
+
expect(isManagedInstanceNotFound(new AdminInstanceNotFoundError('admin'))).toBe(true)
|
|
179
171
|
})
|
|
180
172
|
|
|
181
173
|
test('does not hide non-not-found managed lookup failures', () => {
|
|
@@ -194,37 +186,12 @@ describe('instance target helpers', () => {
|
|
|
194
186
|
})
|
|
195
187
|
})
|
|
196
188
|
|
|
197
|
-
describe('isManagedInstanceNotFound:
|
|
198
|
-
test('
|
|
199
|
-
const e = new Error('NOT_FOUND: no instance node for "knowledge"')
|
|
200
|
-
e.name = 'InternalKernelError'
|
|
201
|
-
expect(isManagedInstanceNotFound(e)).toBe(true)
|
|
202
|
-
})
|
|
203
|
-
test('other InternalKernelError messages are not swallowed', () => {
|
|
189
|
+
describe('isManagedInstanceNotFound: unknown errors', () => {
|
|
190
|
+
test('does not infer a miss from an InternalKernelError message', () => {
|
|
204
191
|
const e = new Error('DISPATCH_FAILED: handler crashed')
|
|
205
192
|
e.name = 'InternalKernelError'
|
|
206
193
|
expect(isManagedInstanceNotFound(e)).toBe(false)
|
|
207
194
|
})
|
|
208
|
-
test('resolveNamedInstanceTarget maps the wrap to typed INSTANCE_NOT_FOUND', async () => {
|
|
209
|
-
const managed = async () => {
|
|
210
|
-
const e = new Error('NOT_FOUND: no instance node for "ghost"')
|
|
211
|
-
e.name = 'InternalKernelError'
|
|
212
|
-
throw e
|
|
213
|
-
}
|
|
214
|
-
const opts = {
|
|
215
|
-
config: DEFAULT_CONFIG,
|
|
216
|
-
instances: { instances: {} },
|
|
217
|
-
managed,
|
|
218
|
-
} as unknown as Parameters<typeof resolveInstanceTarget>[1]
|
|
219
|
-
let caught: unknown
|
|
220
|
-
try {
|
|
221
|
-
await resolveInstanceTarget({ source: 'name', name: 'ghost' }, opts)
|
|
222
|
-
} catch (e) {
|
|
223
|
-
caught = e
|
|
224
|
-
}
|
|
225
|
-
expect(caught).toBeInstanceOf(AstraleError)
|
|
226
|
-
expect((caught as AstraleError).code).toBe('INSTANCE_NOT_FOUND')
|
|
227
|
-
})
|
|
228
195
|
|
|
229
196
|
test('maps Admin token-exchange failure to INSTANCE_NOT_FOUND', async () => {
|
|
230
197
|
const managed = async () => {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { AstraleError } from '../../errors'
|
|
4
|
+
import { failInput, fatal } from '../log'
|
|
5
|
+
|
|
6
|
+
class ExitError extends Error {}
|
|
7
|
+
|
|
8
|
+
let stderr = ''
|
|
9
|
+
let exit: typeof process.exit
|
|
10
|
+
let write: typeof process.stderr.write
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
stderr = ''
|
|
14
|
+
exit = process.exit
|
|
15
|
+
write = process.stderr.write
|
|
16
|
+
process.exit = (() => {
|
|
17
|
+
throw new ExitError()
|
|
18
|
+
}) as typeof process.exit
|
|
19
|
+
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
20
|
+
stderr += String(chunk)
|
|
21
|
+
return true
|
|
22
|
+
}) as typeof process.stderr.write
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
process.exit = exit
|
|
27
|
+
process.stderr.write = write
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('terminal error safety', () => {
|
|
31
|
+
test('renders an empty aggregate as an honest non-blank unexpected failure', () => {
|
|
32
|
+
expect(() => fatal(new AggregateError([], ''), { json: true })).toThrow(ExitError)
|
|
33
|
+
expect(JSON.parse(stderr)).toEqual({
|
|
34
|
+
error: 'UNEXPECTED_ERROR',
|
|
35
|
+
message: 'The CLI encountered an unexpected internal failure.',
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('rejects blank admitted CLI diagnostics', () => {
|
|
40
|
+
expect(() => new AstraleError('INVALID_INPUT', ' ')).toThrow(TypeError)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('labels only expected admission failures as invalid input', () => {
|
|
44
|
+
expect(() => failInput(new TypeError('Authored value is invalid.'), { json: true })).toThrow(
|
|
45
|
+
ExitError,
|
|
46
|
+
)
|
|
47
|
+
expect(JSON.parse(stderr)).toMatchObject({ error: 'INVALID_INPUT' })
|
|
48
|
+
|
|
49
|
+
stderr = ''
|
|
50
|
+
expect(() => failInput(new AggregateError([], ''), { json: true })).toThrow(ExitError)
|
|
51
|
+
expect(JSON.parse(stderr)).toMatchObject({ error: 'UNEXPECTED_ERROR' })
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -103,7 +103,7 @@ describe('studio server runtime deps', () => {
|
|
|
103
103
|
describe('studio test gate', () => {
|
|
104
104
|
test('the root and Studio suites include all client, server, and shared tests', () => {
|
|
105
105
|
expect(cliPackage.scripts?.test).toBe(
|
|
106
|
-
'bun test src studio/client/src studio/server studio/shared && node --test scripts/*.test.mjs',
|
|
106
|
+
'node scripts/qualification/source-boundary.mjs --target && bun test src studio/client/src studio/server studio/shared && node --test scripts/*.test.mjs',
|
|
107
107
|
)
|
|
108
108
|
expect(cliPackage.scripts?.['test:studio']).toBe('pnpm --dir studio test')
|
|
109
109
|
expect(studioPackage.scripts?.test).toBe('bun test client/src server shared')
|
|
@@ -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/command-dx.ts
CHANGED
|
@@ -41,12 +41,9 @@ export function renderCommanderError(
|
|
|
41
41
|
|
|
42
42
|
if (error.code === 'commander.missingArgument') {
|
|
43
43
|
const usage = usageFor(matched.path, matched.command)
|
|
44
|
-
const argName = error.message.match(/'([^']+)'/)?.[1]
|
|
45
44
|
return maybeMachine(
|
|
46
45
|
[
|
|
47
|
-
`Missing required argument
|
|
48
|
-
`astrale ${matched.path.join(' ')}`,
|
|
49
|
-
)}`,
|
|
46
|
+
`Missing required argument for ${chalk.bold(`astrale ${matched.path.join(' ')}`)}`,
|
|
50
47
|
'',
|
|
51
48
|
'Usage:',
|
|
52
49
|
` astrale ${usage}`,
|
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> {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { InstanceInfo } from '../admin/instance/model'
|
|
2
1
|
import type { AstraleConfig } from './config'
|
|
3
2
|
|
|
3
|
+
import { AdminInstanceNotFoundError, type InstanceInfo } from '../admin/instance/model'
|
|
4
4
|
import { AstraleError } from '../errors'
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_ADMIN_TARGET_NAME,
|
|
@@ -190,19 +190,8 @@ export function isAdminDiscoveryFailure(error: unknown): boolean {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
export function isManagedInstanceNotFound(error: unknown): boolean {
|
|
193
|
-
if (error instanceof AstraleError && error.code === 'INSTANCE_NOT_FOUND') return true
|
|
194
|
-
if (!(error instanceof Error)) return false
|
|
195
|
-
if (error.name === 'NotFoundError') return true
|
|
196
|
-
// The admin kernel reports a missing instance node as InternalKernelError
|
|
197
|
-
// with a NOT_FOUND-prefixed message. In this lookup that's an instance
|
|
198
|
-
// miss (config problem), not a kernel fault — map it so callers get the
|
|
199
|
-
// typed INSTANCE_NOT_FOUND with remediation instead of a raw kernel error.
|
|
200
|
-
if (error.name === 'InternalKernelError' && /^NOT_FOUND\b/.test(error.message)) return true
|
|
201
|
-
const data = (error as Error & { data?: unknown }).data
|
|
202
193
|
return (
|
|
203
|
-
error
|
|
204
|
-
|
|
205
|
-
typeof data === 'object' &&
|
|
206
|
-
(data as { type?: unknown }).type === 'NotFoundError'
|
|
194
|
+
error instanceof AdminInstanceNotFoundError ||
|
|
195
|
+
(error instanceof AstraleError && error.code === 'INSTANCE_NOT_FOUND')
|
|
207
196
|
)
|
|
208
197
|
}
|
package/src/lib/log.ts
CHANGED
|
@@ -22,24 +22,29 @@ export function fatal(e: unknown, opts?: MachineOpts): never {
|
|
|
22
22
|
// Ctrl-C at an interactive (@inquirer/prompts) prompt — exit quietly with the
|
|
23
23
|
// SIGINT convention, not a red error line.
|
|
24
24
|
if (e instanceof Error && e.name === 'ExitPromptError') process.exit(130)
|
|
25
|
-
const msg =
|
|
26
|
-
|
|
25
|
+
const msg =
|
|
26
|
+
e instanceof AstraleError ? e.message : 'The CLI encountered an unexpected internal failure.'
|
|
27
|
+
const code = e instanceof AstraleError ? e.code : 'UNEXPECTED_ERROR'
|
|
27
28
|
if (isMachine(opts)) {
|
|
28
29
|
const payload: Record<string, unknown> = { error: code, message: msg }
|
|
29
30
|
if (e instanceof AstraleError && e.hint) payload.hint = e.hint
|
|
30
31
|
process.stderr.write(JSON.stringify(payload) + '\n')
|
|
31
32
|
process.exit(1)
|
|
32
33
|
}
|
|
33
|
-
log.error(
|
|
34
|
+
log.error(`${code}: ${msg}`)
|
|
34
35
|
if (e instanceof AstraleError && e.hint) log.dim(` hint: ${e.hint}`)
|
|
35
36
|
process.exit(1)
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
/**
|
|
39
|
-
export function
|
|
39
|
+
/** Project only a caller-authored admission failure into the CLI input family. */
|
|
40
|
+
export function failInput(error: unknown, opts?: MachineOpts): never {
|
|
40
41
|
if (error instanceof AstraleError) fatal(error, opts)
|
|
42
|
+
if (!(error instanceof TypeError)) fatal(error, opts)
|
|
41
43
|
fatal(
|
|
42
|
-
new AstraleError(
|
|
44
|
+
new AstraleError(
|
|
45
|
+
'INVALID_INPUT',
|
|
46
|
+
error.message.trim() ? error.message : 'CLI input is invalid.',
|
|
47
|
+
),
|
|
43
48
|
opts,
|
|
44
49
|
)
|
|
45
50
|
}
|