@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,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/instance.ts
CHANGED
|
@@ -297,22 +297,26 @@ export async function upsertInstance(
|
|
|
297
297
|
* bookmark was repointed to a different kernel so callers can warn.
|
|
298
298
|
*/
|
|
299
299
|
export async function upsertManagedBookmark(
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
300
|
+
input: Readonly<{
|
|
301
|
+
key: string
|
|
302
|
+
slug: string
|
|
303
|
+
url: string
|
|
304
|
+
organizationId?: string
|
|
305
|
+
defaultIdentity?: string
|
|
306
|
+
}>,
|
|
304
307
|
): Promise<{ entry: InstanceEntry; repointedFrom?: string }> {
|
|
305
308
|
const store = await readInstances()
|
|
306
|
-
const previousUrl = store.instances[key]?.url
|
|
307
|
-
const url = normalizeInstanceKernelUrl(
|
|
308
|
-
const { entry } = await upsertInstance(key, {
|
|
309
|
+
const previousUrl = store.instances[input.key]?.url
|
|
310
|
+
const url = normalizeInstanceKernelUrl(input.url)
|
|
311
|
+
const { entry } = await upsertInstance(input.key, {
|
|
309
312
|
url,
|
|
310
313
|
issuer: url,
|
|
311
|
-
slug,
|
|
312
|
-
name: slug,
|
|
314
|
+
slug: input.slug,
|
|
315
|
+
name: input.slug,
|
|
313
316
|
kind: 'bookmark',
|
|
314
317
|
mode: 'remote',
|
|
315
|
-
...(organizationId ? { organizationId } : {}),
|
|
318
|
+
...(input.organizationId ? { organizationId: input.organizationId } : {}),
|
|
319
|
+
...(input.defaultIdentity ? { defaultIdentity: input.defaultIdentity } : {}),
|
|
316
320
|
})
|
|
317
321
|
return {
|
|
318
322
|
entry,
|
|
@@ -362,8 +366,9 @@ export async function getActive(config?: AstraleConfig): Promise<InstanceEntry &
|
|
|
362
366
|
export async function resolveInstance(
|
|
363
367
|
identifier: string,
|
|
364
368
|
config?: AstraleConfig,
|
|
369
|
+
opts: { persist?: boolean } = {},
|
|
365
370
|
): Promise<ResolvedInstance> {
|
|
366
|
-
const store = await readInstances(config)
|
|
371
|
+
const store = await readInstances(config, opts)
|
|
367
372
|
const key = resolveInstanceKey(store, identifier)
|
|
368
373
|
const entry = key ? store.instances[key] : undefined
|
|
369
374
|
if (!key || !entry?.url) {
|
|
@@ -448,7 +453,12 @@ function isRegionRoutedInstanceRoot(url: URL): boolean {
|
|
|
448
453
|
if (url.search || url.hash) return false
|
|
449
454
|
|
|
450
455
|
const labels = url.hostname.toLowerCase().split('.')
|
|
451
|
-
return
|
|
456
|
+
return (
|
|
457
|
+
(labels.length === 4 || labels.length === 5) &&
|
|
458
|
+
/^[a-z]{2}(?:-[a-z0-9]+)*$/u.test(labels[1] ?? '') &&
|
|
459
|
+
labels.at(-2) === 'astrale' &&
|
|
460
|
+
labels.at(-1) === 'ai'
|
|
461
|
+
)
|
|
452
462
|
}
|
|
453
463
|
|
|
454
464
|
function definedEntry(entry: AddInstanceOpts): AddInstanceOpts {
|
package/src/lib/log.ts
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from 'chalk'
|
|
|
2
2
|
import ora, { type Ora } from 'ora'
|
|
3
3
|
|
|
4
4
|
import { AstraleError, NotImplementedError } from '../errors'
|
|
5
|
+
import { printFailureDebug } from './failure-debug'
|
|
5
6
|
import { formatElapsed } from './format'
|
|
6
7
|
import { isMachine, type MachineOpts } from './output'
|
|
7
8
|
|
|
@@ -18,28 +19,34 @@ export const log = {
|
|
|
18
19
|
|
|
19
20
|
/** Report an error with hint (when present) and exit. `--json` / `--ci` / a
|
|
20
21
|
* non-TTY stdout always get one structured JSON line on stderr. */
|
|
21
|
-
export function fatal(e: unknown, opts?: MachineOpts): never {
|
|
22
|
+
export function fatal(e: unknown, opts?: MachineOpts & { readonly debug?: boolean }): never {
|
|
22
23
|
// Ctrl-C at an interactive (@inquirer/prompts) prompt — exit quietly with the
|
|
23
24
|
// SIGINT convention, not a red error line.
|
|
24
25
|
if (e instanceof Error && e.name === 'ExitPromptError') process.exit(130)
|
|
25
|
-
const msg =
|
|
26
|
-
|
|
26
|
+
const msg =
|
|
27
|
+
e instanceof AstraleError ? e.message : 'The CLI encountered an unexpected internal failure.'
|
|
28
|
+
const code = e instanceof AstraleError ? e.code : 'UNEXPECTED_ERROR'
|
|
27
29
|
if (isMachine(opts)) {
|
|
28
30
|
const payload: Record<string, unknown> = { error: code, message: msg }
|
|
29
31
|
if (e instanceof AstraleError && e.hint) payload.hint = e.hint
|
|
30
32
|
process.stderr.write(JSON.stringify(payload) + '\n')
|
|
31
|
-
|
|
33
|
+
} else {
|
|
34
|
+
log.error(`${code}: ${msg}`)
|
|
35
|
+
if (e instanceof AstraleError && e.hint) log.dim(` hint: ${e.hint}`)
|
|
32
36
|
}
|
|
33
|
-
|
|
34
|
-
if (e instanceof AstraleError && e.hint) log.dim(` hint: ${e.hint}`)
|
|
37
|
+
if (opts?.debug) printFailureDebug(e, '')
|
|
35
38
|
process.exit(1)
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
/**
|
|
39
|
-
export function
|
|
41
|
+
/** Project only a caller-authored admission failure into the CLI input family. */
|
|
42
|
+
export function failInput(error: unknown, opts?: MachineOpts): never {
|
|
40
43
|
if (error instanceof AstraleError) fatal(error, opts)
|
|
44
|
+
if (!(error instanceof TypeError)) fatal(error, opts)
|
|
41
45
|
fatal(
|
|
42
|
-
new AstraleError(
|
|
46
|
+
new AstraleError(
|
|
47
|
+
'INVALID_INPUT',
|
|
48
|
+
error.message.trim() ? error.message : 'CLI input is invalid.',
|
|
49
|
+
),
|
|
43
50
|
opts,
|
|
44
51
|
)
|
|
45
52
|
}
|
package/src/lib/login-flow.ts
CHANGED
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
normalizeTokenResponse,
|
|
8
8
|
pollDeviceToken,
|
|
9
9
|
readIdpConfigOrBuiltin,
|
|
10
|
+
readIdpSession,
|
|
10
11
|
readIdpStore,
|
|
12
|
+
requireClientId,
|
|
11
13
|
requestClientCredentials,
|
|
12
14
|
requestDeviceAuthorization,
|
|
13
15
|
saveIdpSession,
|
|
@@ -71,10 +73,17 @@ export type LoginResult = {
|
|
|
71
73
|
*/
|
|
72
74
|
export async function loginViaIdp(opts: LoginFlowOpts): Promise<LoginResult> {
|
|
73
75
|
const idpName = await resolveIdpName(opts.idp)
|
|
74
|
-
const
|
|
76
|
+
const previous = opts.name && !opts.clientId ? await readIdpSession(opts.name) : null
|
|
77
|
+
const savedClientId = previous?.idp === idpName ? previous.clientId : undefined
|
|
78
|
+
const requestedClientId = opts.clientId ?? savedClientId
|
|
79
|
+
const idp = await readIdpConfigOrBuiltin(idpName, {
|
|
80
|
+
clientId: requestedClientId,
|
|
81
|
+
persist: true,
|
|
82
|
+
})
|
|
83
|
+
const clientId = requireClientId(idp, requestedClientId)
|
|
75
84
|
const scope = opts.scope ?? idp.client.scope ?? 'openid profile email offline_access'
|
|
76
85
|
|
|
77
|
-
const token = normalizeTokenResponse(await obtainToken(idp, opts, scope))
|
|
86
|
+
const token = normalizeTokenResponse(await obtainToken(idp, { ...opts, clientId }, scope))
|
|
78
87
|
if (!token.access_token) throw new Error('IdP response did not include access_token')
|
|
79
88
|
if (opts.audience && !tokenAudienceMatches(token.access_token, opts.audience)) {
|
|
80
89
|
throw new Error(
|
|
@@ -83,7 +92,7 @@ export async function loginViaIdp(opts: LoginFlowOpts): Promise<LoginResult> {
|
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
const claims = decodeTokenClaims(token.id_token ?? token.access_token)
|
|
86
|
-
const subject = subjectFromToken(token,
|
|
95
|
+
const subject = subjectFromToken(token, clientId)
|
|
87
96
|
const issuer = issuerFromToken(token, idp.metadata.issuer)
|
|
88
97
|
const identityName = opts.name ?? identityNameFromClaims(claims, idpName)
|
|
89
98
|
const session: IdpSession = {
|
|
@@ -91,6 +100,7 @@ export async function loginViaIdp(opts: LoginFlowOpts): Promise<LoginResult> {
|
|
|
91
100
|
idp: idpName,
|
|
92
101
|
issuer,
|
|
93
102
|
subject,
|
|
103
|
+
clientId,
|
|
94
104
|
audience: opts.audience,
|
|
95
105
|
access_token: token.access_token,
|
|
96
106
|
id_token: token.id_token,
|
package/src/lib/meta.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* `jwks_uri`) and JWKS at `/.well-known/jwks.json`. These helpers probe
|
|
7
7
|
* both to verify the issuer is alive and publishes at least one key.
|
|
8
8
|
*
|
|
9
|
-
* Domain
|
|
10
|
-
* outside this
|
|
9
|
+
* Domain deployment readiness uses the canonical Domain Publication; that is
|
|
10
|
+
* outside this issuer-connectivity helper.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { IssuerUnreachableError } from '../errors'
|
|
@@ -9,12 +9,10 @@ import { createOwnedInstance } from './admin-instance'
|
|
|
9
9
|
import { setActive, upsertManagedBookmark } from './instance'
|
|
10
10
|
import { withSpinner } from './log'
|
|
11
11
|
import { isMachine } from './output'
|
|
12
|
-
import { promptSelect } from './prompt'
|
|
13
12
|
import { validateSlug } from './validation'
|
|
14
13
|
|
|
15
14
|
export type ProvisionOpts = KernelCommandOpts &
|
|
16
15
|
AdminTargetCommandOpts & {
|
|
17
|
-
hostId?: string
|
|
18
16
|
// Global flags (program.ts) that force non-interactive — mirrors `instance use`.
|
|
19
17
|
ci?: boolean
|
|
20
18
|
noPrompt?: boolean
|
|
@@ -35,10 +33,10 @@ export type ProvisionResult = {
|
|
|
35
33
|
const SAGA_TIMEOUT_MS = '240000'
|
|
36
34
|
|
|
37
35
|
/**
|
|
38
|
-
* Provision an
|
|
36
|
+
* Provision an instance through the admin kernel, then bookmark it and
|
|
39
37
|
* make it the active target. Extracted from `instance create` so `astrale
|
|
40
|
-
* setup` provisions through the exact same saga (auth assertion →
|
|
41
|
-
* bookmark → set-active)
|
|
38
|
+
* setup` provisions through the exact same saga (auth assertion → create →
|
|
39
|
+
* bookmark → set-active).
|
|
42
40
|
*
|
|
43
41
|
* Presentation is deliberately minimal here (a spinner + a one-line success);
|
|
44
42
|
* the caller renders anything richer — `setup` follows this with a hero panel.
|
|
@@ -48,15 +46,10 @@ export async function provisionInstance(
|
|
|
48
46
|
opts: ProvisionOpts,
|
|
49
47
|
): Promise<ProvisionResult> {
|
|
50
48
|
validateSlug(slug)
|
|
51
|
-
await
|
|
52
|
-
|
|
53
|
-
// identity pinned on the admin bookmark (that mismatch silently made a fresh
|
|
54
|
-
// user's instance unusable). `--as` still wins.
|
|
55
|
-
if (!opts.as) opts = { ...opts, as: (await readIdentities()).default }
|
|
49
|
+
const authentication = await instanceCreateAuthentication(opts)
|
|
50
|
+
opts = authentication.opts
|
|
56
51
|
|
|
57
52
|
const machine = isMachine(opts)
|
|
58
|
-
const interactive = !!process.stdin.isTTY && !(opts.ci || opts.noPrompt || process.env.CI)
|
|
59
|
-
|
|
60
53
|
let repointedFrom: string | undefined
|
|
61
54
|
let selectionError: unknown = null
|
|
62
55
|
// The global 30s default doesn't just fail the CLIENT: the disconnect kills
|
|
@@ -65,21 +58,24 @@ export async function provisionInstance(
|
|
|
65
58
|
// timeout; an explicit --timeout still wins.
|
|
66
59
|
const createOpts = { ...opts, timeout: opts.timeout ?? SAGA_TIMEOUT_MS }
|
|
67
60
|
|
|
68
|
-
const runProvision = (
|
|
61
|
+
const runProvision = () =>
|
|
69
62
|
withSpinner(
|
|
70
63
|
`Provisioning instance ${slug}`,
|
|
71
64
|
!machine,
|
|
72
65
|
async () => {
|
|
73
|
-
const created = await createOwnedInstance(createOpts, slug
|
|
66
|
+
const created = await createOwnedInstance(createOpts, slug)
|
|
74
67
|
try {
|
|
75
68
|
// Org id from the create response — authoritative for token scoping
|
|
76
69
|
// (the router's /auth/org is eventually consistent).
|
|
77
|
-
const bookmarked = await upsertManagedBookmark(
|
|
78
|
-
slug,
|
|
70
|
+
const bookmarked = await upsertManagedBookmark({
|
|
71
|
+
key: slug,
|
|
79
72
|
slug,
|
|
80
|
-
created.url,
|
|
81
|
-
created.organizationId,
|
|
82
|
-
|
|
73
|
+
url: created.url,
|
|
74
|
+
...(created.organizationId ? { organizationId: created.organizationId } : {}),
|
|
75
|
+
...(authentication.defaultIdentity
|
|
76
|
+
? { defaultIdentity: authentication.defaultIdentity }
|
|
77
|
+
: {}),
|
|
78
|
+
})
|
|
83
79
|
repointedFrom = bookmarked.repointedFrom
|
|
84
80
|
await setActive(slug)
|
|
85
81
|
} catch (e) {
|
|
@@ -93,23 +89,7 @@ export async function provisionInstance(
|
|
|
93
89
|
},
|
|
94
90
|
)
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
// hosts). We recover ONLY from its multi-host ambiguity: pop a picker and
|
|
98
|
-
// retry with the chosen host_id — the ambiguity error throws before any
|
|
99
|
-
// provisioning side effect, so the retry is clean. No host / permission /
|
|
100
|
-
// capacity / a non-interactive run all reject through to the caller's `fatal`,
|
|
101
|
-
// surfacing the server's own message plus the listed ids. An explicit
|
|
102
|
-
// `--host-id` skips all of this.
|
|
103
|
-
const created = await runProvision(opts.hostId).catch(async (e: unknown) => {
|
|
104
|
-
const hostIds = !opts.hostId && interactive ? parseEligibleHostIds(e) : null
|
|
105
|
-
if (!hostIds) throw e
|
|
106
|
-
const chosen = await promptSelect(
|
|
107
|
-
`${hostIds.length} hosts available — pick one to provision on`,
|
|
108
|
-
hostIds.map((hid) => ({ name: hid, value: hid })),
|
|
109
|
-
)
|
|
110
|
-
if (!chosen) throw e
|
|
111
|
-
return runProvision(chosen)
|
|
112
|
-
})
|
|
92
|
+
const created = await runProvision()
|
|
113
93
|
|
|
114
94
|
// Warnings go to stderr so machine-readable stdout stays clean.
|
|
115
95
|
const warn = (msg: string) => console.error(chalk.yellow('⚠'), msg)
|
|
@@ -124,31 +104,28 @@ export async function provisionInstance(
|
|
|
124
104
|
return { created, slug, repointedFrom, ...(selectionError ? { selectionError } : {}) }
|
|
125
105
|
}
|
|
126
106
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (!match) return null
|
|
139
|
-
const ids = match[1]!
|
|
140
|
-
.split(',')
|
|
141
|
-
.map((s) => s.trim())
|
|
142
|
-
.filter(Boolean)
|
|
143
|
-
return ids.length > 1 ? ids : null
|
|
107
|
+
async function instanceCreateAuthentication(opts: ProvisionOpts): Promise<{
|
|
108
|
+
readonly opts: ProvisionOpts
|
|
109
|
+
readonly defaultIdentity?: string
|
|
110
|
+
}> {
|
|
111
|
+
if (opts.creds) return { opts }
|
|
112
|
+
const store = await readIdentities()
|
|
113
|
+
const defaultIdentity = selectInstanceCreateIdentity(store, opts)
|
|
114
|
+
return {
|
|
115
|
+
opts: opts.as ? opts : { ...opts, as: defaultIdentity },
|
|
116
|
+
defaultIdentity,
|
|
117
|
+
}
|
|
144
118
|
}
|
|
145
119
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
120
|
+
export function selectInstanceCreateIdentity(
|
|
121
|
+
store: IdentityStore,
|
|
122
|
+
opts: Pick<ProvisionOpts, 'as'> = {},
|
|
123
|
+
): string {
|
|
124
|
+
assertInstanceCreateIdentity(store, opts)
|
|
125
|
+
return opts.as ?? store.default
|
|
149
126
|
}
|
|
150
127
|
|
|
151
|
-
export function
|
|
128
|
+
export function assertInstanceCreateIdentity(
|
|
152
129
|
store: IdentityStore,
|
|
153
130
|
opts: Pick<ProvisionOpts, 'as'> = {},
|
|
154
131
|
): void {
|
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'
|
|
@@ -9,11 +19,11 @@ import { INSTALL_PATH } from '../state/index'
|
|
|
9
19
|
import { run } from './proc'
|
|
10
20
|
|
|
11
21
|
const DEFAULT_REPO = 'astrale-os/cli'
|
|
12
|
-
const
|
|
22
|
+
export const DEFAULT_UPDATE_CHANNEL = 'beta'
|
|
13
23
|
|
|
14
24
|
export const InstallMetadataSchema = z.object({
|
|
15
25
|
method: z.literal('script'),
|
|
16
|
-
channel: z.string().min(1).default(
|
|
26
|
+
channel: z.string().min(1).default(DEFAULT_UPDATE_CHANNEL),
|
|
17
27
|
version: z.string().min(1).optional(),
|
|
18
28
|
repo: z.string().min(1).default(DEFAULT_REPO),
|
|
19
29
|
bin: z.string().min(1),
|
|
@@ -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(
|
|
@@ -162,7 +246,7 @@ export function releaseBase(
|
|
|
162
246
|
const version = req.version.replace(/^cli\/v/, '').replace(/^v/, '')
|
|
163
247
|
return `https://github.com/${repo}/releases/download/cli/v${version}`
|
|
164
248
|
}
|
|
165
|
-
return `https://github.com/${repo}/releases/download/${req.channel ?? meta.channel ??
|
|
249
|
+
return `https://github.com/${repo}/releases/download/${req.channel ?? meta.channel ?? DEFAULT_UPDATE_CHANNEL}`
|
|
166
250
|
}
|
|
167
251
|
|
|
168
252
|
export async function fetchManifest(base: string): Promise<UpdateManifest> {
|
|
@@ -175,9 +259,19 @@ 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
|
-
const channel = req.channel ?? meta.channel ??
|
|
274
|
+
const channel = req.channel ?? meta.channel ?? DEFAULT_UPDATE_CHANNEL
|
|
181
275
|
const platform = req.platform ?? detectPlatform()
|
|
182
276
|
const key = platformKey(platform)
|
|
183
277
|
const base = releaseBase(meta, { channel, version: req.version })
|
package/src/lib/validation.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isDnsLabel } from '@astrale-os/sdk/
|
|
1
|
+
import { isDnsLabel } from '@astrale-os/sdk/value'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
|
|
4
4
|
import { ReservedSlugError } from '../errors'
|
|
@@ -21,7 +21,7 @@ export function validateName(name: string, entity: string): void {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export function validateSlug(slug: string): void {
|
|
24
|
-
// Canonical DNS-label rule
|
|
24
|
+
// Canonical Core DNS-label rule: a slug becomes a
|
|
25
25
|
// hostname label, so reject anything not DNS-safe (§4.7).
|
|
26
26
|
if (!slug || !isDnsLabel(slug)) {
|
|
27
27
|
throw new Error(
|
package/src/lib/view/resolve.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ResolvedView as SessionResolvedView } from '@astrale-os/
|
|
1
|
+
import type { ResolvedView as SessionResolvedView } from '@astrale-os/sdk/client/session'
|
|
2
2
|
import type { ResolvedView } from '@astrale-os/shell'
|
|
3
3
|
|
|
4
4
|
import { Path } from '@astrale-os/sdk/graph/path'
|
package/src/lib/view/server.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AuthApi } from '@astrale-os/sdk/auth'
|
|
2
|
+
import type { IssuerId } from '@astrale-os/sdk/auth'
|
|
1
3
|
import type { ReadableStream as WebReadableStream } from 'node:stream/web'
|
|
2
4
|
|
|
3
5
|
import { readFile } from 'node:fs/promises'
|
|
@@ -23,8 +25,9 @@ export { ensureViewerAssets, viewerDistDir } from './assets'
|
|
|
23
25
|
* heartbeat keeps a live session alive.
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
+
const VIEW_TOKEN_TTL_SECONDS = 4 * 60
|
|
29
|
+
const TOKEN_REFRESH_MARGIN_MS = 60_000
|
|
30
|
+
const FALLBACK_TOKEN_TTL_MS = VIEW_TOKEN_TTL_SECONDS * 1_000
|
|
28
31
|
const IDLE_SWEEP_MS = 60_000
|
|
29
32
|
|
|
30
33
|
export type PageStatus = { state: string; error?: string; at: string }
|
|
@@ -44,7 +47,7 @@ export function startViewServer(config: ViewServeConfig): Server {
|
|
|
44
47
|
async function freshGrant(): Promise<TokenGrant> {
|
|
45
48
|
if (grant && grant.expiresAt - Date.now() > TOKEN_REFRESH_MARGIN_MS) return grant
|
|
46
49
|
grant = await withClientSession(config.kernel, async ({ auth, target }) => {
|
|
47
|
-
const token = await auth
|
|
50
|
+
const token = await mintViewCredential(auth, target.kernelIssuer)
|
|
48
51
|
return {
|
|
49
52
|
token,
|
|
50
53
|
expiresAt: jwtExpiry(token) ?? Date.now() + FALLBACK_TOKEN_TTL_MS,
|
|
@@ -182,6 +185,17 @@ export function startViewServer(config: ViewServeConfig): Server {
|
|
|
182
185
|
return server
|
|
183
186
|
}
|
|
184
187
|
|
|
188
|
+
/** Mint a short-lived credential from the selected CLI identity proof. */
|
|
189
|
+
export async function mintViewCredential(
|
|
190
|
+
auth: Pick<AuthApi, 'mint'>,
|
|
191
|
+
audience: IssuerId,
|
|
192
|
+
): Promise<string> {
|
|
193
|
+
return auth.mint({
|
|
194
|
+
audience,
|
|
195
|
+
ttlSeconds: VIEW_TOKEN_TTL_SECONDS,
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
|
|
185
199
|
function jwtExpiry(token: string): number | null {
|
|
186
200
|
try {
|
|
187
201
|
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString('utf8')) as {
|