@astrale-os/cli 1.0.0-beta.1 → 1.0.0-beta.11
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 +23 -12
- package/dist/astrale.js +48048 -56798
- package/dist/public/connect-core.js +2011 -30765
- package/dist/public/keys/index.js +884 -29483
- package/dist/public/paths/index.js +13101 -41459
- package/dist/types/admin/binding.d.ts +19 -0
- package/dist/types/admin/instance/client.d.ts +22 -0
- package/dist/types/admin/instance/index.d.ts +2 -0
- package/dist/types/admin/instance/model.d.ts +5 -12
- package/dist/types/connection/auth.d.ts +3 -0
- package/dist/types/connection/call.d.ts +3 -0
- package/dist/types/connection/command.d.ts +27 -0
- package/dist/types/connection/credential.d.ts +14 -0
- package/dist/types/connection/errors.d.ts +1 -0
- package/dist/types/connection/exchange.d.ts +9 -0
- package/dist/types/connection/failure/classify.d.ts +2 -0
- package/dist/types/connection/failure/index.d.ts +5 -0
- package/dist/types/connection/failure/model.d.ts +25 -0
- package/dist/types/connection/failure/render.d.ts +3 -0
- package/dist/types/connection/index.d.ts +8 -0
- package/dist/types/connection/reasons.d.ts +37 -0
- package/dist/types/connection/self.d.ts +37 -0
- package/dist/types/connection/session.d.ts +31 -0
- package/dist/types/connection/target.d.ts +38 -0
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/lib/admin-instance.d.ts +17 -0
- package/dist/types/lib/admin-target.d.ts +11 -36
- package/dist/types/lib/config.d.ts +7 -59
- package/dist/types/lib/failure-debug.d.ts +1 -0
- package/dist/types/lib/idp.d.ts +26 -168
- package/dist/types/lib/instance-target.d.ts +1 -1
- package/dist/types/lib/instance.d.ts +30 -88
- package/dist/types/lib/log.d.ts +5 -3
- package/dist/types/lib/meta.d.ts +2 -2
- package/dist/types/lib/proc.d.ts +43 -0
- package/dist/types/lib/self.d.ts +9 -0
- package/dist/types/lib/update.d.ts +91 -0
- package/dist/types/lib/validation.d.ts +4 -1
- package/package.json +16 -19
- package/src/__tests__/fixtures/publication.ts +20 -0
- package/src/admin/__tests__/binding.test.ts +31 -0
- package/src/admin/__tests__/fixture.ts +101 -0
- package/src/admin/binding.ts +98 -0
- 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 +36 -32
- 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 +39 -164
- package/src/admin/instance/client.ts +35 -124
- package/src/admin/instance/index.ts +1 -2
- package/src/admin/instance/model.ts +11 -20
- package/src/commands/__tests__/admin-instance.test.ts +11 -8
- package/src/commands/__tests__/auth-login.test.ts +54 -1
- package/src/commands/__tests__/call-describe.test.ts +46 -33
- package/src/commands/__tests__/call.test.ts +5 -1
- package/src/commands/__tests__/domain-install-operation.test.ts +12 -7
- package/src/commands/__tests__/domain-install-owned.test.ts +6 -2
- package/src/commands/__tests__/domain-list.test.ts +63 -4
- package/src/commands/__tests__/install-identity-override.test.ts +19 -19
- package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
- package/src/commands/__tests__/instance-status.test.ts +139 -0
- package/src/commands/__tests__/read-commands.test.ts +35 -10
- package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
- package/src/commands/__tests__/update.test.ts +48 -19
- package/src/commands/call-describe.ts +60 -68
- package/src/commands/call.ts +43 -56
- package/src/commands/domain/install.ts +13 -17
- package/src/commands/domain/list.ts +18 -13
- package/src/commands/domain/publish.ts +14 -11
- package/src/commands/domain/uninstall.ts +2 -2
- package/src/commands/get.ts +14 -15
- package/src/commands/identity/__tests__/register.test.ts +3 -3
- package/src/commands/identity/register.ts +5 -4
- package/src/commands/instance/create.ts +8 -16
- package/src/commands/instance/delete.ts +12 -9
- package/src/commands/instance/list.ts +2 -2
- package/src/commands/instance/status.ts +66 -20
- package/src/commands/instance/use.ts +16 -12
- package/src/commands/introspect.ts +9 -16
- package/src/commands/logs.ts +24 -25
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +27 -30
- package/src/commands/studio.ts +20 -0
- package/src/commands/token.ts +8 -7
- package/src/commands/update.ts +38 -14
- package/src/commands/view.ts +7 -6
- 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 +20 -1
- package/src/connection/__tests__/errors.test.ts +43 -32
- package/src/connection/__tests__/exchange.test.ts +36 -6
- 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/__tests__/target.test.ts +1 -0
- package/src/connection/auth.ts +13 -0
- 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 +52 -27
- package/src/connection/failure/classify.ts +110 -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 +19 -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/keys/__tests__/keys.test.ts +4 -0
- package/src/keys/credential.ts +1 -0
- package/src/lib/__tests__/admin-target.test.ts +12 -0
- package/src/lib/__tests__/command-dx.test.ts +1 -1
- package/src/lib/__tests__/domain-publication.test.ts +138 -0
- package/src/lib/__tests__/idp-session.test.ts +22 -0
- package/src/lib/__tests__/idp.test.ts +36 -4
- package/src/lib/__tests__/instance-candidates.test.ts +1 -3
- package/src/lib/__tests__/instance-target.test.ts +7 -39
- package/src/lib/__tests__/instance.test.ts +6 -0
- package/src/lib/__tests__/log-errors.test.ts +64 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
- package/src/lib/__tests__/update.test.ts +113 -8
- package/src/lib/__tests__/view-open-intent.test.ts +0 -2
- package/src/lib/__tests__/view-server.test.ts +18 -3
- package/src/lib/__tests__/view-session.test.ts +1 -1
- package/src/lib/admin-instance.ts +14 -8
- package/src/lib/admin-target.ts +21 -2
- package/src/lib/command-dx.ts +9 -9
- package/src/lib/domain-publication.ts +104 -0
- package/src/lib/failure-debug.ts +26 -0
- package/src/lib/idp.ts +39 -4
- package/src/lib/instance-candidates.ts +2 -2
- package/src/lib/instance-target.ts +3 -14
- package/src/lib/instance.ts +22 -12
- package/src/lib/log.ts +16 -9
- package/src/lib/login-flow.ts +13 -3
- package/src/lib/meta.ts +2 -2
- package/src/lib/provision-instance.ts +34 -57
- package/src/lib/update.ts +124 -30
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/resolve.ts +1 -1
- package/src/lib/view/server.ts +17 -3
- package/src/program/__tests__/program.test.ts +20 -4
- package/src/program/build.ts +2 -2
- package/src/setup/__tests__/instance-step.test.ts +27 -10
- package/src/setup/steps/instance.ts +22 -15
- package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
- package/studio/client/dist/assets/index-7YHdeCxp.js +81 -0
- package/studio/client/dist/assets/index-C0FAnwNw.css +2 -0
- package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
- package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +1 -0
- package/studio/client/dist/assets/schema-studio-CABpTfvH.js +8 -0
- package/studio/client/dist/index.html +3 -2
- package/studio/package.json +10 -14
- 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/bridge/stdio.ts +8 -1
- package/studio/server/agent/conversation.test.ts +10 -5
- package/studio/server/agent/conversation.ts +56 -8
- package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
- package/studio/server/agent/harness/codex/loadout.ts +18 -21
- package/studio/server/agent/harness/gateway/config.ts +20 -18
- package/studio/server/agent/harness/selection.ts +9 -1
- 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 +6 -5
- package/studio/server/agent/prompts/turn.ts +13 -4
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/completion.ts +2 -2
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- package/studio/server/agent/run/preparation.ts +6 -5
- package/studio/server/agent/run/transcript.test.ts +35 -1
- package/studio/server/agent/run/transcript.ts +169 -3
- package/studio/server/agent/run/usage.test.ts +24 -0
- package/studio/server/agent/run/usage.ts +26 -1
- package/studio/server/api/canvas.ts +43 -0
- package/studio/server/api/comments.ts +85 -0
- package/studio/server/api/context.ts +61 -0
- package/studio/server/api/deployment.ts +39 -0
- package/studio/server/api/documents.ts +55 -0
- package/studio/server/api/domain.ts +59 -0
- package/studio/server/api/http.ts +45 -0
- package/studio/server/api/project.ts +57 -0
- package/studio/server/api/schema.ts +12 -0
- package/studio/server/api/views.ts +57 -0
- package/studio/server/api/workspace.ts +60 -0
- package/studio/server/api-agent-loadout.test.ts +1 -1
- package/studio/server/api.test.ts +147 -0
- package/studio/server/api.ts +20 -422
- package/studio/server/cache.test.ts +122 -0
- package/studio/server/cache.ts +162 -11
- package/studio/server/cli-consumers.test.ts +494 -0
- package/studio/server/cli.test.ts +88 -0
- package/studio/server/cli.ts +240 -0
- package/studio/server/client-package.ts +25 -1
- package/studio/server/domain.test.ts +50 -40
- package/studio/server/domain.ts +83 -52
- package/studio/server/environment/dotenv-preview.test.ts +39 -0
- package/studio/server/environment/dotenv-preview.ts +27 -0
- package/studio/server/environment/files.test.ts +53 -0
- package/studio/server/{state/env.ts → environment/files.ts} +7 -48
- package/studio/server/{state → handoff}/copy.ts +21 -6
- package/studio/server/handoff/service.test.ts +22 -0
- package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
- package/studio/server/index.ts +1 -1
- package/studio/server/instances/active.ts +72 -0
- package/studio/server/instances/deploy-record.test.ts +31 -0
- package/studio/server/instances/deploy-record.ts +37 -0
- package/studio/server/instances/deploy.ts +56 -0
- package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
- package/studio/server/instances/probe.ts +67 -0
- package/studio/server/instances/status.test.ts +12 -0
- package/studio/server/instances/status.ts +49 -0
- package/studio/server/introspect/anatomy/client-tree.ts +57 -0
- package/studio/server/introspect/anatomy/env-fields.ts +66 -0
- package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
- package/studio/server/introspect/anatomy/source.ts +181 -0
- package/studio/server/introspect/anatomy/views/routes.ts +114 -0
- package/studio/server/introspect/anatomy/views.ts +53 -0
- package/studio/server/introspect/anatomy-extras.test.ts +106 -35
- package/studio/server/introspect/anatomy-extras.ts +7 -726
- package/studio/server/introspect/anatomy.test.ts +22 -14
- package/studio/server/introspect/anatomy.ts +52 -28
- package/studio/server/introspect/bundle.ts +15 -9
- package/studio/server/introspect/canonical-schema.test.ts +93 -309
- package/studio/server/introspect/canonical-schema.ts +257 -425
- package/studio/server/introspect/config-preview.test.ts +32 -0
- package/studio/server/introspect/config-preview.ts +119 -0
- package/studio/server/introspect/core-extractor.ts +18 -127
- package/studio/server/introspect/core.ts +1 -2
- package/studio/server/introspect/diff.test.ts +18 -18
- package/studio/server/introspect/diff.ts +44 -90
- package/studio/server/introspect/extractor.ts +23 -116
- package/studio/server/introspect/hash.ts +5 -2
- package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
- package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
- package/studio/server/introspect/overlay.test.ts +26 -54
- package/studio/server/introspect/overlay.ts +10 -37
- package/studio/server/introspect/revision.test.ts +54 -0
- package/studio/server/introspect/revision.ts +49 -0
- package/studio/server/introspect/runtime.test.ts +83 -143
- package/studio/server/introspect/runtime.ts +40 -11
- package/studio/server/introspect/schema-ir-json.test.ts +70 -0
- package/studio/server/introspect/schema-ir-json.ts +151 -0
- 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/annotations.ts +14 -0
- package/studio/server/introspect/source-overlay/handlers.ts +161 -0
- package/studio/server/introspect/source-overlay/kernel-calls.ts +46 -0
- package/studio/server/introspect/source-overlay/project.ts +248 -0
- package/studio/server/introspect/source-overlay/spans.ts +330 -0
- package/studio/server/json.ts +46 -0
- package/studio/server/lifecycle.ts +5 -1
- package/studio/server/sse.test.ts +26 -0
- package/studio/server/sse.ts +4 -1
- package/studio/server/state/baseline.test.ts +223 -7
- package/studio/server/state/baseline.ts +148 -34
- package/studio/server/state/comments.test.ts +66 -1
- package/studio/server/state/comments.ts +173 -10
- package/studio/server/state/context.ts +34 -2
- package/studio/server/state/documents.ts +42 -1
- package/studio/server/state/integrations.ts +27 -1
- package/studio/server/state/layout.test.ts +34 -0
- package/studio/server/state/layout.ts +44 -17
- package/studio/server/state/settings.ts +29 -22
- package/studio/server/state/store.test.ts +122 -0
- package/studio/server/state/store.ts +66 -17
- package/studio/server/state/visibility.ts +22 -11
- package/studio/server/views/model.test.ts +57 -0
- package/studio/server/views/model.ts +66 -0
- package/studio/server/views/runtime.ts +40 -0
- package/studio/server/views/selection-repository.test.ts +31 -0
- package/studio/server/views/selection-repository.ts +60 -0
- package/studio/server/views/session.test.ts +73 -0
- package/studio/server/views/session.ts +166 -0
- package/studio/server/views/target.test.ts +78 -0
- package/studio/server/views/target.ts +222 -0
- package/studio/server/watch.test.ts +11 -11
- package/studio/server/watch.ts +9 -5
- package/studio/server/workspace/catalog.test.ts +17 -0
- package/studio/server/workspace/catalog.ts +43 -0
- package/studio/server/{state → workspace}/create.test.ts +1 -1
- package/studio/server/{state → workspace}/create.ts +1 -1
- package/studio/server/{state → workspace}/git.ts +1 -1
- package/studio/server/workspace/updates.ts +83 -0
- package/studio/server/workspace-state.ts +1 -1
- package/studio/server/workspace-watch.ts +2 -2
- package/studio/shared/contracts/README.md +16 -0
- package/studio/shared/contracts/agent.ts +270 -0
- package/studio/shared/contracts/runtime.ts +50 -0
- package/studio/shared/contracts/schema.ts +393 -0
- package/studio/shared/contracts/surface.test.ts +317 -0
- package/studio/shared/contracts/workspace.ts +265 -0
- package/studio/shared/layout.test.ts +116 -0
- package/studio/shared/schema/identity.test.ts +34 -0
- package/studio/shared/schema/identity.ts +68 -0
- package/studio/shared/types.ts +11 -1099
- package/studio/tsconfig.json +1 -1
- package/viewer/dist/main.js +40 -61
- package/.check-workspace.cjs +0 -40
- package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
- package/studio/client/dist/assets/elk-api-D2xgMJvi.js +0 -1
- package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
- package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
- package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
- package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
- package/studio/server/state/catalog.ts +0 -117
- package/studio/server/state/instance.ts +0 -280
- package/studio/server/state/updates.ts +0 -63
- package/studio/server/state/views.test.ts +0 -286
- package/studio/server/state/views.ts +0 -535
|
@@ -1,14 +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,
|
|
9
|
+
DEFAULT_UPDATE_CHANNEL,
|
|
10
|
+
InstallMetadataSchema,
|
|
11
|
+
readInstallMetadata,
|
|
7
12
|
releaseBase,
|
|
8
13
|
shouldUpdate,
|
|
9
14
|
updateAstrale,
|
|
10
15
|
writeInstallMetadata,
|
|
11
16
|
type InstallMetadata,
|
|
17
|
+
type UpdateExecution,
|
|
12
18
|
} from '../update'
|
|
13
19
|
|
|
14
20
|
async function makeFakeRelease(
|
|
@@ -63,7 +69,7 @@ async function makeFakeRelease(
|
|
|
63
69
|
async function makeInstall(
|
|
64
70
|
root: string,
|
|
65
71
|
version: string,
|
|
66
|
-
): Promise<{ meta: InstallMetadata; path: string }> {
|
|
72
|
+
): Promise<{ meta: InstallMetadata; path: string; execution: UpdateExecution }> {
|
|
67
73
|
const bin = join(root, 'bin', 'astrale')
|
|
68
74
|
await mkdir(join(root, 'bin'), { recursive: true })
|
|
69
75
|
await writeFile(
|
|
@@ -80,10 +86,43 @@ async function makeInstall(
|
|
|
80
86
|
bin,
|
|
81
87
|
}
|
|
82
88
|
await writeInstallMetadata(meta, path)
|
|
83
|
-
return { meta, path }
|
|
89
|
+
return { meta, path, execution: { kind: 'standalone', executable: bin } }
|
|
84
90
|
}
|
|
85
91
|
|
|
86
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
|
+
|
|
116
|
+
test('defaults missing install metadata to the beta channel', () => {
|
|
117
|
+
expect(DEFAULT_UPDATE_CHANNEL).toBe('beta')
|
|
118
|
+
expect(
|
|
119
|
+
InstallMetadataSchema.parse({
|
|
120
|
+
method: 'script',
|
|
121
|
+
bin: '/tmp/astrale',
|
|
122
|
+
}).channel,
|
|
123
|
+
).toBe('beta')
|
|
124
|
+
})
|
|
125
|
+
|
|
87
126
|
test('releaseBase resolves exact versions and channels', () => {
|
|
88
127
|
const meta: InstallMetadata = {
|
|
89
128
|
method: 'script',
|
|
@@ -106,10 +145,70 @@ describe('update helpers', () => {
|
|
|
106
145
|
})
|
|
107
146
|
})
|
|
108
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
|
+
|
|
109
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
|
+
|
|
110
209
|
test('check compares the installed release identity from metadata', async () => {
|
|
111
210
|
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
112
|
-
const { path } = await makeInstall(root, 'main-abc123')
|
|
211
|
+
const { path, execution } = await makeInstall(root, 'main-abc123')
|
|
113
212
|
const release = await makeFakeRelease(root, 'main-abc123', { binaryVersion: '1.0.0' })
|
|
114
213
|
process.env.ASTRALE_UPDATE_BASE = `file://${release}`
|
|
115
214
|
try {
|
|
@@ -118,6 +217,7 @@ describe('updateAstrale', () => {
|
|
|
118
217
|
currentVersion: '1.0.0',
|
|
119
218
|
platform: { os: 'darwin', arch: 'arm64' },
|
|
120
219
|
installPath: path,
|
|
220
|
+
execution,
|
|
121
221
|
})
|
|
122
222
|
|
|
123
223
|
expect(result).toMatchObject({
|
|
@@ -132,7 +232,7 @@ describe('updateAstrale', () => {
|
|
|
132
232
|
|
|
133
233
|
test('check reports available update without replacing the binary', async () => {
|
|
134
234
|
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
135
|
-
const { path, meta } = await makeInstall(root, '1.0.0')
|
|
235
|
+
const { path, meta, execution } = await makeInstall(root, '1.0.0')
|
|
136
236
|
const release = await makeFakeRelease(root, '1.1.0')
|
|
137
237
|
process.env.ASTRALE_UPDATE_BASE = `file://${release}`
|
|
138
238
|
try {
|
|
@@ -141,6 +241,7 @@ describe('updateAstrale', () => {
|
|
|
141
241
|
currentVersion: '1.0.0',
|
|
142
242
|
platform: { os: 'darwin', arch: 'arm64' },
|
|
143
243
|
installPath: path,
|
|
244
|
+
execution,
|
|
144
245
|
})
|
|
145
246
|
|
|
146
247
|
expect(result).toMatchObject({
|
|
@@ -156,7 +257,7 @@ describe('updateAstrale', () => {
|
|
|
156
257
|
|
|
157
258
|
test('supports legacy string asset manifests', async () => {
|
|
158
259
|
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
159
|
-
const { path, meta } = await makeInstall(root, '1.0.0')
|
|
260
|
+
const { path, meta, execution } = await makeInstall(root, '1.0.0')
|
|
160
261
|
const release = await makeFakeRelease(root, '1.1.0', { legacyManifest: true })
|
|
161
262
|
process.env.ASTRALE_UPDATE_BASE = `file://${release}`
|
|
162
263
|
try {
|
|
@@ -165,6 +266,7 @@ describe('updateAstrale', () => {
|
|
|
165
266
|
currentVersion: '1.0.0',
|
|
166
267
|
platform: { os: 'darwin', arch: 'arm64' },
|
|
167
268
|
installPath: path,
|
|
269
|
+
execution,
|
|
168
270
|
})
|
|
169
271
|
|
|
170
272
|
expect(check).toMatchObject({
|
|
@@ -176,6 +278,7 @@ describe('updateAstrale', () => {
|
|
|
176
278
|
currentVersion: '1.0.0',
|
|
177
279
|
platform: { os: 'darwin', arch: 'arm64' },
|
|
178
280
|
installPath: path,
|
|
281
|
+
execution,
|
|
179
282
|
})
|
|
180
283
|
|
|
181
284
|
expect(result).toMatchObject({
|
|
@@ -192,7 +295,7 @@ describe('updateAstrale', () => {
|
|
|
192
295
|
|
|
193
296
|
test('updates the binary and install metadata', async () => {
|
|
194
297
|
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
195
|
-
const { path, meta } = await makeInstall(root, '1.0.0')
|
|
298
|
+
const { path, meta, execution } = await makeInstall(root, '1.0.0')
|
|
196
299
|
const release = await makeFakeRelease(root, '1.1.0')
|
|
197
300
|
process.env.ASTRALE_UPDATE_BASE = `file://${release}`
|
|
198
301
|
try {
|
|
@@ -200,6 +303,7 @@ describe('updateAstrale', () => {
|
|
|
200
303
|
currentVersion: '1.0.0',
|
|
201
304
|
platform: { os: 'darwin', arch: 'arm64' },
|
|
202
305
|
installPath: path,
|
|
306
|
+
execution,
|
|
203
307
|
})
|
|
204
308
|
|
|
205
309
|
expect(result).toMatchObject({
|
|
@@ -219,7 +323,7 @@ describe('updateAstrale', () => {
|
|
|
219
323
|
|
|
220
324
|
test('updates canary-style releases whose binary reports the package version', async () => {
|
|
221
325
|
const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
|
|
222
|
-
const { path, meta } = await makeInstall(root, 'main-old123')
|
|
326
|
+
const { path, meta, execution } = await makeInstall(root, 'main-old123')
|
|
223
327
|
const release = await makeFakeRelease(root, 'main-new456', { binaryVersion: '1.0.0' })
|
|
224
328
|
process.env.ASTRALE_UPDATE_BASE = `file://${release}`
|
|
225
329
|
try {
|
|
@@ -227,6 +331,7 @@ describe('updateAstrale', () => {
|
|
|
227
331
|
currentVersion: '1.0.0',
|
|
228
332
|
platform: { os: 'darwin', arch: 'arm64' },
|
|
229
333
|
installPath: path,
|
|
334
|
+
execution,
|
|
230
335
|
})
|
|
231
336
|
|
|
232
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',
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { AuthApi } from '@astrale-os/sdk/auth'
|
|
2
|
+
|
|
3
|
+
import { describe, expect, mock, test } from 'bun:test'
|
|
2
4
|
import { once } from 'node:events'
|
|
3
5
|
|
|
4
6
|
import type { ViewServeConfig } from '../view/session'
|
|
5
7
|
|
|
6
8
|
import { findFreePort } from '../port'
|
|
7
|
-
import { startViewServer } from '../view/server'
|
|
9
|
+
import { mintViewCredential, startViewServer } from '../view/server'
|
|
8
10
|
|
|
9
11
|
const digest = (character: string) => `sha256:${character.repeat(64)}` as const
|
|
10
12
|
const target = (value: string) => value as ViewServeConfig['session']['view']['target']
|
|
@@ -13,6 +15,19 @@ const revision = (character: string) =>
|
|
|
13
15
|
digest(character) as ViewServeConfig['session']['view']['route']['revision']
|
|
14
16
|
|
|
15
17
|
describe('view session server credentials', () => {
|
|
18
|
+
test('mints a proof-bounded credential for the Kernel audience', async () => {
|
|
19
|
+
const mint = mock(async () => 'minted-credential')
|
|
20
|
+
const auth = { mint } as unknown as Pick<AuthApi, 'mint'>
|
|
21
|
+
|
|
22
|
+
await expect(mintViewCredential(auth, issuer('https://kernel.test'))).resolves.toBe(
|
|
23
|
+
'minted-credential',
|
|
24
|
+
)
|
|
25
|
+
expect(mint).toHaveBeenCalledWith({
|
|
26
|
+
audience: 'https://kernel.test',
|
|
27
|
+
ttlSeconds: 240,
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
16
31
|
/** @evidence TEST-CLI-PLAIN-VIEW-RECEIVES-NO-CREDENTIAL */
|
|
17
32
|
test('refuses to mint a token for a handshake-none View', async () => {
|
|
18
33
|
const nonce = 'plain-view'
|
|
@@ -29,7 +44,7 @@ describe('view session server credentials', () => {
|
|
|
29
44
|
target: target('/:example.test'),
|
|
30
45
|
route: {
|
|
31
46
|
key: 'example.test:view.public',
|
|
32
|
-
declaration: { target: { kind: 'domain' }
|
|
47
|
+
declaration: { target: { kind: 'domain' } },
|
|
33
48
|
href: 'https://example.test/ui/public',
|
|
34
49
|
handshake: 'none',
|
|
35
50
|
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',
|
|
@@ -6,7 +6,7 @@ import { withAdminClientSession } from '../connection'
|
|
|
6
6
|
export {
|
|
7
7
|
AdminInstanceNotFoundError,
|
|
8
8
|
findOwnedInstance,
|
|
9
|
-
|
|
9
|
+
formatInstanceState,
|
|
10
10
|
type InstanceInfo,
|
|
11
11
|
type InstanceState,
|
|
12
12
|
type OwnedInstanceInfo,
|
|
@@ -19,19 +19,25 @@ export function listOwnedInstances(options: AdminConnectionOptions) {
|
|
|
19
19
|
)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** Read caller-visible inventory together with the exact local identity used for the Admin call. */
|
|
23
|
+
export function listOwnedInstancesWithIdentity(options: AdminConnectionOptions) {
|
|
24
|
+
return withAdminClientSession(options, async (context) =>
|
|
25
|
+
Object.freeze({
|
|
26
|
+
instances: await (await connectAdminInstances(context)).list(),
|
|
27
|
+
...(context.identity === undefined ? {} : { identity: context.identity }),
|
|
28
|
+
}),
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
/** Reuse an already-open Admin session for a caller-visible Instance inventory. */
|
|
23
33
|
export async function listOwnedInstancesInContext(context: ConnectionContext) {
|
|
24
34
|
return (await connectAdminInstances(context)).list()
|
|
25
35
|
}
|
|
26
36
|
|
|
27
|
-
/**
|
|
28
|
-
export function createOwnedInstance(
|
|
29
|
-
options: AdminConnectionOptions,
|
|
30
|
-
slug: string,
|
|
31
|
-
hostId?: string,
|
|
32
|
-
) {
|
|
37
|
+
/** Request one managed Instance; Admin owns infrastructure placement. */
|
|
38
|
+
export function createOwnedInstance(options: AdminConnectionOptions, slug: string) {
|
|
33
39
|
return withAdminClientSession(options, async (context) =>
|
|
34
|
-
(await connectAdminInstances(context)).create(slug
|
|
40
|
+
(await connectAdminInstances(context)).create(slug),
|
|
35
41
|
)
|
|
36
42
|
}
|
|
37
43
|
|
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/command-dx.ts
CHANGED
|
@@ -2,8 +2,6 @@ import type { Command, CommanderError } from 'commander'
|
|
|
2
2
|
|
|
3
3
|
import chalk from 'chalk'
|
|
4
4
|
|
|
5
|
-
import { isMachine } from './output'
|
|
6
|
-
|
|
7
5
|
export type CommandCatalogEntry = {
|
|
8
6
|
path: string[]
|
|
9
7
|
usage: string
|
|
@@ -30,27 +28,27 @@ export function renderCommanderError(
|
|
|
30
28
|
program: Command,
|
|
31
29
|
error: CommanderError,
|
|
32
30
|
argv = process.argv.slice(2),
|
|
31
|
+
machine = argv.some((token) => token === '--ci' || token === '--json' || token === '--raw') ||
|
|
32
|
+
!(process.stdout.isTTY ?? false),
|
|
33
33
|
): string {
|
|
34
34
|
const tokens = stripOptions(argv)
|
|
35
35
|
const catalog = collectCommandCatalog(program)
|
|
36
36
|
const matched = matchRegisteredPrefix(program, tokens)
|
|
37
37
|
|
|
38
38
|
if (matched.path.length === 0 && tokens.length > 0) {
|
|
39
|
-
return maybeMachine(renderUnknownCommand(tokens, catalog))
|
|
39
|
+
return maybeMachine(renderUnknownCommand(tokens, catalog), machine)
|
|
40
40
|
}
|
|
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}`,
|
|
53
50
|
].join('\n'),
|
|
51
|
+
machine,
|
|
54
52
|
)
|
|
55
53
|
}
|
|
56
54
|
|
|
@@ -66,6 +64,7 @@ export function renderCommanderError(
|
|
|
66
64
|
'Usage:',
|
|
67
65
|
` astrale ${usage}`,
|
|
68
66
|
].join('\n'),
|
|
67
|
+
machine,
|
|
69
68
|
)
|
|
70
69
|
}
|
|
71
70
|
|
|
@@ -77,6 +76,7 @@ export function renderCommanderError(
|
|
|
77
76
|
? ['', 'Did you mean:', ...suggestions.map((s) => ` astrale ${s}`)]
|
|
78
77
|
: []),
|
|
79
78
|
].join('\n'),
|
|
79
|
+
machine,
|
|
80
80
|
)
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -143,8 +143,8 @@ function usageFor(path: string[], command: Command): string {
|
|
|
143
143
|
|
|
144
144
|
const ANSI_RE = new RegExp(String.fromCharCode(27) + '\\[[0-9;]*m', 'g')
|
|
145
145
|
|
|
146
|
-
function maybeMachine(text: string): string {
|
|
147
|
-
if (!
|
|
146
|
+
function maybeMachine(text: string, machine: boolean): string {
|
|
147
|
+
if (!machine) return text
|
|
148
148
|
const plain = text.replace(ANSI_RE, '')
|
|
149
149
|
const first = plain.split('\n').find((line) => line.trim().length > 0) ?? plain
|
|
150
150
|
return JSON.stringify({ error: 'USAGE_ERROR', message: first, detail: plain })
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { publication } from '@astrale-os/sdk/publication'
|
|
2
|
+
|
|
3
|
+
const MAXIMUM_PUBLICATION_BYTES = 1024 * 1024
|
|
4
|
+
type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
|
|
5
|
+
|
|
6
|
+
/** Fetch and admit one canonical Domain Publication without following its bundle. */
|
|
7
|
+
export async function fetchDomainPublication(
|
|
8
|
+
input: string,
|
|
9
|
+
signal?: AbortSignal,
|
|
10
|
+
fetchImpl: FetchLike = globalThis.fetch,
|
|
11
|
+
): Promise<publication.Publication> {
|
|
12
|
+
const url = domainPublicationUrl(input)
|
|
13
|
+
const response = await fetchImpl(url, {
|
|
14
|
+
redirect: 'error',
|
|
15
|
+
...(signal === undefined ? {} : { signal }),
|
|
16
|
+
})
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
await cancel(response.body)
|
|
19
|
+
throw new Error(`GET ${url.href} → ${response.status}`)
|
|
20
|
+
}
|
|
21
|
+
const bytes = await readBounded(response, url)
|
|
22
|
+
try {
|
|
23
|
+
const source = new TextDecoder('utf-8', { fatal: true }).decode(bytes)
|
|
24
|
+
return publication.accept(JSON.parse(source) as unknown)
|
|
25
|
+
} catch (cause) {
|
|
26
|
+
throw new Error(`GET ${url.href} returned an invalid Domain Publication`, { cause })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function readBounded(response: Response, url: URL): Promise<Uint8Array> {
|
|
31
|
+
const declared = response.headers.get('content-length')
|
|
32
|
+
if (
|
|
33
|
+
declared !== null &&
|
|
34
|
+
(!/^\d+$/u.test(declared) ||
|
|
35
|
+
!Number.isSafeInteger(Number(declared)) ||
|
|
36
|
+
Number(declared) > MAXIMUM_PUBLICATION_BYTES)
|
|
37
|
+
) {
|
|
38
|
+
await cancel(response.body)
|
|
39
|
+
throw sizeError(url)
|
|
40
|
+
}
|
|
41
|
+
if (response.body === null) return new Uint8Array()
|
|
42
|
+
|
|
43
|
+
const reader = response.body.getReader()
|
|
44
|
+
const chunks: Uint8Array[] = []
|
|
45
|
+
let size = 0
|
|
46
|
+
let complete = false
|
|
47
|
+
try {
|
|
48
|
+
for (;;) {
|
|
49
|
+
const next = await reader.read()
|
|
50
|
+
if (next.done) {
|
|
51
|
+
complete = true
|
|
52
|
+
break
|
|
53
|
+
}
|
|
54
|
+
if (next.value.byteLength === 0) continue
|
|
55
|
+
if (next.value.byteLength > MAXIMUM_PUBLICATION_BYTES - size) {
|
|
56
|
+
throw sizeError(url)
|
|
57
|
+
}
|
|
58
|
+
chunks.push(next.value)
|
|
59
|
+
size += next.value.byteLength
|
|
60
|
+
}
|
|
61
|
+
} finally {
|
|
62
|
+
if (!complete) await reader.cancel().catch(() => undefined)
|
|
63
|
+
reader.releaseLock()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const bytes = new Uint8Array(size)
|
|
67
|
+
let offset = 0
|
|
68
|
+
for (const chunk of chunks) {
|
|
69
|
+
bytes.set(chunk, offset)
|
|
70
|
+
offset += chunk.byteLength
|
|
71
|
+
}
|
|
72
|
+
return bytes
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function sizeError(url: URL): Error {
|
|
76
|
+
return new Error(`GET ${url.href} exceeded ${MAXIMUM_PUBLICATION_BYTES} bytes`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function cancel(body: ReadableStream<Uint8Array> | null): Promise<void> {
|
|
80
|
+
if (body !== null) await body.cancel().catch(() => undefined)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Canonical discovery document for a Domain origin or exact Publication URL. */
|
|
84
|
+
export function domainPublicationUrl(input: string): URL {
|
|
85
|
+
let url: URL
|
|
86
|
+
try {
|
|
87
|
+
url = new URL(input)
|
|
88
|
+
} catch {
|
|
89
|
+
throw new TypeError(`Domain deployment URL is invalid: ${input}`)
|
|
90
|
+
}
|
|
91
|
+
if (
|
|
92
|
+
!['http:', 'https:'].includes(url.protocol) ||
|
|
93
|
+
url.username !== '' ||
|
|
94
|
+
url.password !== '' ||
|
|
95
|
+
url.search !== '' ||
|
|
96
|
+
url.hash !== '' ||
|
|
97
|
+
!['', '/', publication.PATH].includes(url.pathname)
|
|
98
|
+
) {
|
|
99
|
+
throw new TypeError(
|
|
100
|
+
`Domain deployment URL must be an HTTP(S) origin or canonical Publication URL: ${input}`,
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
return new URL(publication.PATH, url.origin)
|
|
104
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
const MAXIMUM_DEBUG_FAILURES = 32
|
|
4
|
+
|
|
5
|
+
export function printFailureDebug(error: unknown, url: string): void {
|
|
6
|
+
process.stderr.write(`\n${chalk.dim('── debug ─────────────────')}\n`)
|
|
7
|
+
if (url) process.stderr.write(`${chalk.dim(`url: ${url}`)}\n`)
|
|
8
|
+
const seen = new Set<unknown>()
|
|
9
|
+
const pending: Array<{ value: unknown; relation: string }> = [{ value: error, relation: 'error' }]
|
|
10
|
+
while (pending.length > 0 && seen.size < MAXIMUM_DEBUG_FAILURES) {
|
|
11
|
+
const { value, relation } = pending.shift()!
|
|
12
|
+
if (seen.has(value)) continue
|
|
13
|
+
seen.add(value)
|
|
14
|
+
if (!(value instanceof Error)) {
|
|
15
|
+
process.stderr.write(`${chalk.dim(`${relation}: ${String(value)}`)}\n`)
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
process.stderr.write(
|
|
19
|
+
`${chalk.dim(`${relation}: ${value.constructor.name}: ${value.message}`)}\n`,
|
|
20
|
+
)
|
|
21
|
+
if (value.stack) process.stderr.write(`${chalk.dim(value.stack)}\n`)
|
|
22
|
+
if (value.cause !== undefined) pending.push({ value: value.cause, relation: 'caused by' })
|
|
23
|
+
if (value instanceof AggregateError)
|
|
24
|
+
for (const child of value.errors) pending.push({ value: child, relation: 'aggregate' })
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/lib/idp.ts
CHANGED
|
@@ -145,6 +145,8 @@ export const IdpSessionSchema = z
|
|
|
145
145
|
idp: z.string(),
|
|
146
146
|
issuer: z.string().url(),
|
|
147
147
|
subject: z.string(),
|
|
148
|
+
/** OAuth client that issued this session's refresh token. */
|
|
149
|
+
clientId: z.string().optional(),
|
|
148
150
|
audience: z.string().optional(),
|
|
149
151
|
organizationId: z.string().optional(),
|
|
150
152
|
access_token: z.string(),
|
|
@@ -212,6 +214,8 @@ export type IdpConfig = {
|
|
|
212
214
|
|
|
213
215
|
export const BUILTIN_WORKOS_IDP_NAME = 'workos'
|
|
214
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'
|
|
215
219
|
const WORKOS_CLIENT_ID_ENV_NAMES = ['WORKOS_CLIENT_ID', 'VITE_WORKOS_CLIENT_ID'] as const
|
|
216
220
|
|
|
217
221
|
export function idpDir(name: string): string {
|
|
@@ -278,7 +282,17 @@ export async function readIdpConfigOrBuiltin(
|
|
|
278
282
|
validateName(name, 'IdP')
|
|
279
283
|
const store = await readIdpStore()
|
|
280
284
|
if (store.idps[name]) {
|
|
281
|
-
|
|
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
|
+
})
|
|
282
296
|
}
|
|
283
297
|
|
|
284
298
|
const builtin = builtinIdpConfig(name, opts.clientId)
|
|
@@ -411,11 +425,30 @@ export function builtinIdpConfig(
|
|
|
411
425
|
}
|
|
412
426
|
|
|
413
427
|
export function workosClientIdFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
414
|
-
return
|
|
415
|
-
(
|
|
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
|
|
416
432
|
)
|
|
417
433
|
}
|
|
418
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
|
+
|
|
419
452
|
async function fetchOAuthAuthorizationServerMetadata(
|
|
420
453
|
issuer: string,
|
|
421
454
|
): Promise<Partial<OidcMetadata> | undefined> {
|
|
@@ -644,10 +677,11 @@ export async function refreshSession(
|
|
|
644
677
|
if (!session.refresh_token)
|
|
645
678
|
throw new Error(`IdP session for "${identityName}" has no refresh token`)
|
|
646
679
|
const idp = await readIdpConfig(session.idp)
|
|
680
|
+
const clientId = requireClientId(idp, session.clientId)
|
|
647
681
|
const params = new URLSearchParams({
|
|
648
682
|
grant_type: 'refresh_token',
|
|
649
683
|
refresh_token: session.refresh_token,
|
|
650
|
-
client_id:
|
|
684
|
+
client_id: clientId,
|
|
651
685
|
})
|
|
652
686
|
const audience = opts.audience ?? session.audience
|
|
653
687
|
if (audience) params.set('audience', audience)
|
|
@@ -674,6 +708,7 @@ export async function refreshSession(
|
|
|
674
708
|
const expiresAt = tokenExpiresAt(token)
|
|
675
709
|
const next: IdpSession = {
|
|
676
710
|
...session,
|
|
711
|
+
clientId,
|
|
677
712
|
access_token: token.access_token,
|
|
678
713
|
id_token: token.id_token ?? session.id_token,
|
|
679
714
|
refresh_token: token.refresh_token ?? session.refresh_token,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { InstanceEntry, InstanceStore } from './instance'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { formatInstanceState, type OwnedInstanceInfo } from './admin-instance'
|
|
4
4
|
import { normalizeInstanceKernelUrl, resolveInstanceKey } from './instance'
|
|
5
5
|
|
|
6
6
|
export type InstanceCandidate =
|
|
@@ -44,6 +44,6 @@ export function describeInstanceCandidate(candidate: InstanceCandidate): string
|
|
|
44
44
|
if (candidate.source === 'bookmark') {
|
|
45
45
|
return `${candidate.key} (bookmark) ${candidate.url}`
|
|
46
46
|
}
|
|
47
|
-
const extra =
|
|
47
|
+
const extra = formatInstanceState(candidate.info)
|
|
48
48
|
return `${candidate.key} (managed) ${candidate.url}${extra ? ` — ${extra}` : ''}`
|
|
49
49
|
}
|