feat: fix cli and publishing flow

pull/715/head
Travis Fischer 2025-06-24 08:17:45 -05:00
rodzic cf4a404fd0
commit 6a00a49de0
19 zmienionych plików z 437 dodań i 312 usunięć

Wyświetl plik

@ -19,7 +19,10 @@ export const deploymentIdParamsSchema = z.object({
})
export const createDeploymentQuerySchema = z.object({
publish: z.boolean().default(false).optional()
publish: z
.union([z.literal('true'), z.literal('false')])
.default('false')
.transform((p) => p === 'true')
})
export const filterDeploymentSchema = z.object({

Wyświetl plik

@ -1,10 +1,9 @@
import type { DefaultHonoEnv } from '@agentic/platform-hono'
import { OpenAPIHono } from '@hono/zod-openapi'
import { fromError } from 'zod-validation-error'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import * as middleware from '@/lib/middleware'
import { registerOpenAPIErrorResponses } from '@/lib/openapi-utils'
import { defaultHook, registerOpenAPIErrorResponses } from '@/lib/openapi-utils'
import { registerV1GitHubOAuthCallback } from './auth/github-callback'
import { registerV1GitHubOAuthExchange } from './auth/github-exchange'
@ -55,20 +54,7 @@ import { registerV1StripeWebhook } from './webhooks/stripe-webhook'
// Note that the order of some of these routes is important because of
// wildcards, so be careful when updating them or adding new routes.
export const apiV1 = new OpenAPIHono<DefaultHonoEnv>({
defaultHook: (result, ctx) => {
if (!result.success) {
const requestId = ctx.get('requestId')
return ctx.json(
{
error: fromError(result.error).toString(),
requestId
},
400
)
}
}
})
export const apiV1 = new OpenAPIHono<DefaultHonoEnv>({ defaultHook })
apiV1.openAPIRegistry.registerComponent('securitySchemes', 'Bearer', {
type: 'http',
@ -79,10 +65,10 @@ apiV1.openAPIRegistry.registerComponent('securitySchemes', 'Bearer', {
registerOpenAPIErrorResponses(apiV1)
// Public routes
const publicRouter = new OpenAPIHono<DefaultHonoEnv>()
const publicRouter = new OpenAPIHono<DefaultHonoEnv>({ defaultHook })
// Private, authenticated routes
const privateRouter = new OpenAPIHono<AuthenticatedHonoEnv>()
const privateRouter = new OpenAPIHono<AuthenticatedHonoEnv>({ defaultHook })
registerHealthCheck(publicRouter)

Wyświetl plik

@ -1,3 +1,5 @@
import { fromError } from 'zod-validation-error'
import type { HonoApp } from './types'
export const openapiErrorResponses = {
@ -85,3 +87,17 @@ export function registerOpenAPIErrorResponses(app: HonoApp) {
content: openapiErrorContent
})
}
export function defaultHook(result: any, ctx: any) {
if (!result.success) {
const requestId = ctx.get('requestId')
return ctx.json(
{
error: fromError(result.error).toString(),
requestId
},
400
)
}
}

Wyświetl plik

@ -31,8 +31,10 @@
"@hono/node-server": "catalog:",
"commander": "catalog:",
"conf": "catalog:",
"exit-hook": "catalog:",
"get-port": "catalog:",
"hono": "catalog:",
"ky": "catalog:",
"open": "catalog:",
"ora": "catalog:",
"restore-cursor": "catalog:",

Wyświetl plik

@ -1,7 +1,7 @@
import { AgenticApiClient } from '@agentic/platform-api-client'
import { Command } from 'commander'
import restoreCursor from 'restore-cursor'
import type { Context } from './types'
import { registerDebugCommand } from './commands/debug'
import { registerDeployCommand } from './commands/deploy'
import { registerGetDeploymentCommand } from './commands/get'
@ -12,9 +12,11 @@ import { registerSignoutCommand } from './commands/signout'
import { registerSignupCommand } from './commands/signup'
import { registerWhoAmICommand } from './commands/whoami'
import { AuthStore } from './lib/auth-store'
import { initExitHooks } from './lib/exit-hooks'
import { createErrorHandler } from './lib/handle-error'
async function main() {
restoreCursor()
initExitHooks()
// Initialize the API client
const client = new AgenticApiClient({
@ -64,12 +66,18 @@ async function main() {
}
}
const ctx = {
const partialCtx = {
client,
program,
logger
}
const errorHandler = createErrorHandler(partialCtx)
const ctx: Context = {
...partialCtx,
handleError: errorHandler
}
// Register all commands
registerSigninCommand(ctx)
registerSignupCommand(ctx)

Wyświetl plik

@ -1,10 +1,15 @@
import { loadAgenticConfig } from '@agentic/platform'
import { Command } from 'commander'
import { gracefulExit } from 'exit-hook'
import { oraPromise } from 'ora'
import type { Context } from '../types'
export function registerDebugCommand({ program, logger }: Context) {
export function registerDebugCommand({
program,
logger,
handleError
}: Context) {
const command = new Command('debug')
.description('Prints config for a local project.')
.option(
@ -12,27 +17,32 @@ export function registerDebugCommand({ program, logger }: Context) {
'The directory to load the Agentic project config from (defaults to cwd). This directory must contain an "agentic.config.{ts,js,json}" project file.'
)
.action(async (opts) => {
// Load the Agentic project config, parse, and validate it. This will also
// validate any origin adapter config such as OpenAPI or MCP specs and
// embed them if they point to local files or URLs. Note that the server
// also performs validation; this is just a client-side convenience for
// failing fast and sharing 99% of the validation code between server and
// client.
const config = await oraPromise(
loadAgenticConfig({
cwd: opts.cwd
}),
{
text: `Loading Agentic config from ${opts.cwd}`,
successText: `Agentic config loaded successfully.`,
failText: 'Failed to load Agentic config.'
}
)
try {
// Load the Agentic project config, parse, and validate it. This will also
// validate any origin adapter config such as OpenAPI or MCP specs and
// embed them if they point to local files or URLs. Note that the server
// also performs validation; this is just a client-side convenience for
// failing fast and sharing 99% of the validation code between server and
// client.
const config = await oraPromise(
loadAgenticConfig({
cwd: opts.cwd
}),
{
text: `Loading Agentic config from ${opts.cwd}`,
successText: `Agentic config loaded successfully.`,
failText: 'Failed to load Agentic config.'
}
)
// TODO: we may want to resolve the resulting agentic config so we see
// the inferred `tools` (and `toolToOperationMap` for mcp servers)
// TODO: we may want to resolve the resulting agentic config so we see
// the inferred `tools` (and `toolToOperationMap` for mcp servers)
logger.log(config)
logger.log(config)
gracefulExit(0)
} catch (err) {
handleError(err)
}
})
program.addCommand(command)

Wyświetl plik

@ -1,11 +1,17 @@
import { loadAgenticConfig } from '@agentic/platform'
import { Command } from 'commander'
import { gracefulExit } from 'exit-hook'
import { oraPromise } from 'ora'
import type { Context } from '../types'
import { AuthStore } from '../lib/auth-store'
export function registerDeployCommand({ client, program, logger }: Context) {
export function registerDeployCommand({
client,
program,
logger,
handleError
}: Context) {
const command = new Command('deploy')
.description('Creates a new deployment.')
.option(
@ -17,42 +23,48 @@ export function registerDeployCommand({ client, program, logger }: Context) {
.action(async (opts) => {
AuthStore.requireAuth()
// Load the Agentic project config, parse, and validate it. This will also
// validate any origin adapter config such as OpenAPI or MCP specs and
// embed them if they point to local files or URLs. Note that the server
// also performs validation; this is just a client-side convenience for
// failing fast and sharing 99% of the validation code between server and
// client.
const config = await oraPromise(
loadAgenticConfig({
cwd: opts.cwd
}),
{
text: `Loading Agentic config from ${opts.cwd}`,
successText: `Agentic config loaded successfully.`,
failText: 'Failed to load Agentic config.'
}
)
try {
// Load the Agentic project config, parse, and validate it. This will also
// validate any origin adapter config such as OpenAPI or MCP specs and
// embed them if they point to local files or URLs. Note that the server
// also performs validation; this is just a client-side convenience for
// failing fast and sharing 99% of the validation code between server and
// client.
const config = await oraPromise(
loadAgenticConfig({
cwd: opts.cwd
}),
{
text: `Loading Agentic config from ${opts.cwd}`,
successText: `Agentic config loaded successfully.`,
failText: 'Failed to load Agentic config.'
}
)
if (opts.debug) {
logger.log(config)
return
if (opts.debug) {
logger.log(config)
gracefulExit(0)
return
}
// Create the deployment on the backend, validate it, and optionally
// publish it.
const deployment = await oraPromise(
client.createDeployment(config, {
publish: !!opts.publish
}),
{
text: `Creating deployment for project "${config.name}"`,
successText: `Deployment created successfully`,
failText: 'Failed to create deployment'
}
)
logger.log(deployment)
gracefulExit(0)
} catch (err) {
handleError(err)
}
// Create the deployment on the backend, validate it, and optionally
// publish it.
const deployment = await oraPromise(
client.createDeployment(config, {
publish: !!opts.publish
}),
{
text: `Creating deployment for project "${config.name}"`,
successText: `Deployment created successfully`,
failText: 'Failed to create deployment'
}
)
logger.log(deployment)
})
program.addCommand(command)

Wyświetl plik

@ -1,4 +1,5 @@
import { Command } from 'commander'
import { gracefulExit } from 'exit-hook'
import { oraPromise } from 'ora'
import type { Context } from '../types'
@ -7,7 +8,8 @@ import { AuthStore } from '../lib/auth-store'
export function registerGetDeploymentCommand({
client,
program,
logger
logger,
handleError
}: Context) {
const command = new Command('get')
.description('Gets details for a specific deployment.')
@ -15,18 +17,23 @@ export function registerGetDeploymentCommand({
.action(async (deploymentIdentifier) => {
AuthStore.requireAuth()
const deployment = await oraPromise(
client.getDeploymentByIdentifier({
deploymentIdentifier
}),
{
text: 'Resolving deployment...',
successText: 'Resolved deployment',
failText: 'Failed to resolve deployment'
}
)
try {
const deployment = await oraPromise(
client.getDeploymentByIdentifier({
deploymentIdentifier
}),
{
text: 'Resolving deployment...',
successText: 'Resolved deployment',
failText: 'Failed to resolve deployment'
}
)
logger.log(deployment)
logger.log(deployment)
gracefulExit(0)
} catch (err) {
handleError(err)
}
})
program.addCommand(command)

Wyświetl plik

@ -1,6 +1,7 @@
import type { Deployment } from '@agentic/platform-types'
import { parseDeploymentIdentifier } from '@agentic/platform-validators'
import { Command } from 'commander'
import { gracefulExit } from 'exit-hook'
import { oraPromise } from 'ora'
import type { Context } from '../types'
@ -10,7 +11,8 @@ import { pruneDeployment } from '../lib/utils'
export function registerListDeploymentsCommand({
client,
program,
logger
logger,
handleError
}: Context) {
const command = new Command('list')
.alias('ls')
@ -23,74 +25,82 @@ export function registerListDeploymentsCommand({
.action(async (identifier, opts) => {
AuthStore.requireAuth()
const query: Parameters<typeof client.listDeployments>[0] = {}
let label = 'Fetching all projects and deployments'
try {
const query: Parameters<typeof client.listDeployments>[0] = {}
let label = 'Fetching all projects and deployments'
if (identifier) {
const parsedDeploymentIdentifier = parseDeploymentIdentifier(
identifier,
{
strict: false
if (identifier) {
const parsedDeploymentIdentifier = parseDeploymentIdentifier(
identifier,
{
strict: false
}
)
query.projectIdentifier = parsedDeploymentIdentifier.projectIdentifier
label = `Fetching deployments for project "${query.projectIdentifier}"`
// TODO: this logic needs tweaking.
if (
parsedDeploymentIdentifier.deploymentVersion !== 'latest' ||
identifier.includes('@latest')
) {
query.deploymentIdentifier =
parsedDeploymentIdentifier.deploymentIdentifier
label = `Fetching deployment "${query.deploymentIdentifier}"`
}
}
const deployments = await oraPromise(
client.listDeployments(query),
label
)
query.projectIdentifier = parsedDeploymentIdentifier.projectIdentifier
label = `Fetching deployments for project "${query.projectIdentifier}"`
const projectIdToDeploymentsMap: Record<string, Deployment[]> = {}
const sortedProjects: {
projectId: string
deployments: Deployment[]
}[] = []
// TODO: this logic needs tweaking.
if (
parsedDeploymentIdentifier.deploymentVersion !== 'latest' ||
identifier.includes('@latest')
) {
query.deploymentIdentifier =
parsedDeploymentIdentifier.deploymentIdentifier
label = `Fetching deployment "${query.deploymentIdentifier}"`
}
}
// Aggregate deployments by project
for (const deployment of deployments) {
const prunedDeployment = pruneDeployment(deployment, opts)
const deployments = await oraPromise(client.listDeployments(query), label)
const { projectId } = deployment
if (!projectIdToDeploymentsMap[projectId]) {
projectIdToDeploymentsMap[projectId] = []
}
const projectIdToDeploymentsMap: Record<string, Deployment[]> = {}
const sortedProjects: {
projectId: string
deployments: Deployment[]
}[] = []
// Aggregate deployments by project
for (const deployment of deployments) {
const prunedDeployment = pruneDeployment(deployment, opts)
const { projectId } = deployment
if (!projectIdToDeploymentsMap[projectId]) {
projectIdToDeploymentsMap[projectId] = []
projectIdToDeploymentsMap[projectId].push(prunedDeployment)
}
projectIdToDeploymentsMap[projectId].push(prunedDeployment)
}
// Sort deployments within each project with recently created first
for (const projectId of Object.keys(projectIdToDeploymentsMap)) {
const deployments = projectIdToDeploymentsMap[projectId]!
deployments.sort(
(a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
)
// Sort deployments within each project with recently created first
for (const projectId of Object.keys(projectIdToDeploymentsMap)) {
const deployments = projectIdToDeploymentsMap[projectId]!
deployments.sort(
sortedProjects.push({
projectId,
deployments
})
}
// Sort projects with most recently created first
sortedProjects.sort(
(a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
new Date(b.deployments[0]!.createdAt).getTime() -
new Date(a.deployments[0]!.createdAt).getTime()
)
sortedProjects.push({
projectId,
deployments
})
// TODO: better output formatting
logger.log(sortedProjects)
gracefulExit(0)
} catch (err) {
handleError(err)
}
// Sort projects with most recently created first
sortedProjects.sort(
(a, b) =>
new Date(b.deployments[0]!.createdAt).getTime() -
new Date(a.deployments[0]!.createdAt).getTime()
)
// TODO: better output formatting
logger.log(sortedProjects)
})
program.addCommand(command)

Wyświetl plik

@ -1,5 +1,6 @@
import { select } from '@clack/prompts'
import { Command } from 'commander'
import { gracefulExit } from 'exit-hook'
import { oraPromise } from 'ora'
import semver from 'semver'
@ -7,7 +8,12 @@ import type { Context } from '../types'
import { AuthStore } from '../lib/auth-store'
import { resolveDeployment } from '../lib/resolve-deployment'
export function registerPublishCommand({ client, program, logger }: Context) {
export function registerPublishCommand({
client,
program,
logger,
handleError
}: Context) {
const command = new Command('publish')
.description(
'Publishes a deployment. Defaults to the most recent deployment for the project in the target directory. If a deployment identifier is provided, it will be used instead.'
@ -24,93 +30,98 @@ export function registerPublishCommand({ client, program, logger }: Context) {
// TODO: parseToolIdentifier
}
const deployment = await oraPromise(
resolveDeployment({
client,
deploymentIdentifier,
fuzzyDeploymentIdentifierVersion: 'dev',
cwd: opts.cwd,
populate: ['project']
}),
{
text: 'Resolving deployment...',
successText: 'Resolved deployment',
failText: 'Failed to resolve deployment'
}
)
const { project } = deployment
if (deployment.published) {
logger.error(
deploymentIdentifier
? `Deployment "${deploymentIdentifier}" is already published`
: `Latest deployment "${deployment.identifier}" is already published`
try {
const deployment = await oraPromise(
resolveDeployment({
client,
deploymentIdentifier,
fuzzyDeploymentIdentifierVersion: 'dev',
cwd: opts.cwd,
populate: ['project']
}),
{
text: 'Resolving deployment...',
successText: 'Resolved deployment',
failText: 'Failed to resolve deployment'
}
)
return
}
const { project } = deployment
if (!project) {
logger.error(
deploymentIdentifier
? `Deployment "${deploymentIdentifier}" failed to fetch project "${deployment.projectId}"`
: `Latest deployment "${deployment.identifier}" failed to fetch project "${deployment.projectId}"`
)
return
}
const initialVersion = deployment.version
const baseVersion =
initialVersion || project.lastPublishedDeploymentVersion || '0.0.0'
const options = [
initialVersion
? { value: initialVersion, label: initialVersion }
: null,
{
value: semver.inc(baseVersion, 'patch'),
label: `${semver.inc(baseVersion, 'patch')} (patch)`
},
{
value: semver.inc(baseVersion, 'minor'),
label: `${semver.inc(baseVersion, 'minor')} (minor)`
},
{
value: semver.inc(baseVersion, 'major'),
label: `${semver.inc(baseVersion, 'major')} (major)`
if (deployment.published) {
logger.error(
deploymentIdentifier
? `Deployment "${deploymentIdentifier}" is already published`
: `Latest deployment "${deployment.identifier}" is already published`
)
return gracefulExit(1)
}
].filter(Boolean)
if (project.lastPublishedDeploymentVersion) {
if (!project) {
logger.error(
deploymentIdentifier
? `Deployment "${deploymentIdentifier}" failed to fetch project "${deployment.projectId}"`
: `Latest deployment "${deployment.identifier}" failed to fetch project "${deployment.projectId}"`
)
return gracefulExit(1)
}
const initialVersion = deployment.version
const baseVersion =
initialVersion || project.lastPublishedDeploymentVersion || '0.0.0'
const options = [
initialVersion
? { value: initialVersion, label: initialVersion }
: null,
{
value: semver.inc(baseVersion, 'patch'),
label: `${semver.inc(baseVersion, 'patch')} (patch)`
},
{
value: semver.inc(baseVersion, 'minor'),
label: `${semver.inc(baseVersion, 'minor')} (minor)`
},
{
value: semver.inc(baseVersion, 'major'),
label: `${semver.inc(baseVersion, 'major')} (major)`
}
].filter(Boolean)
if (project.lastPublishedDeploymentVersion) {
logger.info(
`Project "${project.identifier}" latest published version is "${project.lastPublishedDeploymentVersion}".\n`
)
} else {
logger.info(`Project "${project.identifier}" is not published yet.\n`)
}
const version = await select({
message: `Select version of deployment "${deployment.identifier}" to publish:`,
options
})
if (!version || typeof version !== 'string') {
logger.error('No version selected')
return gracefulExit(1)
}
const publishedDeployment = await client.publishDeployment(
{
version
},
{
deploymentId: deployment.id
}
)
logger.info(
`Project "${project.identifier}" latest published version is "${project.lastPublishedDeploymentVersion}".\n`
`Deployment "${publishedDeployment.identifier}" published with version "${publishedDeployment.version}"`
)
} else {
logger.info(`Project "${project.identifier}" is not published yet.\n`)
logger.log(publishedDeployment)
gracefulExit(0)
} catch (err) {
handleError(err)
}
const version = await select({
message: `Select version of deployment "${deployment.identifier}" to publish:`,
options
})
if (!version || typeof version !== 'string') {
logger.error('No version selected')
return
}
const publishedDeployment = await client.publishDeployment(
{
version
},
{
deploymentId: deployment.id
}
)
logger.info(
`Deployment "${publishedDeployment.identifier}" published with version "${publishedDeployment.version}"`
)
logger.log(publishedDeployment)
})
program.addCommand(command)

Wyświetl plik

@ -1,9 +1,15 @@
import { Command } from 'commander'
import { gracefulExit } from 'exit-hook'
import type { Context } from '../types'
import { auth } from '../lib/auth'
export function registerSigninCommand({ client, program, logger }: Context) {
export function registerSigninCommand({
client,
program,
logger,
handleError
}: Context) {
const command = new Command('login')
.alias('signin')
.description(
@ -17,20 +23,25 @@ export function registerSigninCommand({ client, program, logger }: Context) {
'either pass email and password or neither (which will use github auth)'
)
program.outputHelp()
return
return gracefulExit(1)
}
if (opts.email && opts.password) {
await client.signInWithPassword({
email: opts.email,
password: opts.password
})
} else {
await auth({ client, provider: 'github' })
}
try {
if (opts.email && opts.password) {
await client.signInWithPassword({
email: opts.email,
password: opts.password
})
} else {
await auth({ client, provider: 'github' })
}
const user = await client.getMe()
logger.log(user)
const user = await client.getMe()
logger.log(user)
gracefulExit(0)
} catch (err) {
handleError(err)
}
})
program.addCommand(command)

Wyświetl plik

@ -1,21 +1,33 @@
import { Command } from 'commander'
import { gracefulExit } from 'exit-hook'
import type { Context } from '../types'
import { AuthStore } from '../lib/auth-store'
export function registerSignoutCommand({ client, program, logger }: Context) {
export function registerSignoutCommand({
client,
program,
logger,
handleError
}: Context) {
const command = new Command('logout')
.alias('signout')
.description('Signs the current user out.')
.action(async () => {
if (!client.isAuthenticated) {
return
logger.log('You are already signed out')
return gracefulExit(0)
}
await client.logout()
AuthStore.clearAuth()
try {
await client.logout()
AuthStore.clearAuth()
logger.log('Signed out')
logger.log('Signed out')
gracefulExit(0)
} catch (err) {
handleError(err)
}
})
program.addCommand(command)

Wyświetl plik

@ -1,9 +1,15 @@
import { Command } from 'commander'
import { gracefulExit } from 'exit-hook'
import type { Context } from '../types'
import { auth } from '../lib/auth'
export function registerSignupCommand({ client, program, logger }: Context) {
export function registerSignupCommand({
client,
program,
logger,
handleError
}: Context) {
const command = new Command('signup')
.description(
'Creates a new account for Agentic. If no credentials are provided, uses GitHub auth.'
@ -20,21 +26,26 @@ export function registerSignupCommand({ client, program, logger }: Context) {
'either pass email, username, and password or none of them (which will use github auth)'
)
program.outputHelp()
return
return gracefulExit(1)
}
if (opts.email && opts.username && opts.password) {
await client.signUpWithPassword({
email: opts.email,
username: opts.username,
password: opts.password
})
} else {
await auth({ client, provider: 'github' })
}
try {
if (opts.email && opts.username && opts.password) {
await client.signUpWithPassword({
email: opts.email,
username: opts.username,
password: opts.password
})
} else {
await auth({ client, provider: 'github' })
}
const user = await client.getMe()
logger.log(user)
const user = await client.getMe()
logger.log(user)
gracefulExit(0)
} catch (err) {
handleError(err)
}
})
program.addCommand(command)

Wyświetl plik

@ -1,16 +1,27 @@
import { Command } from 'commander'
import { gracefulExit } from 'exit-hook'
import type { Context } from '../types'
import { AuthStore } from '../lib/auth-store'
export function registerWhoAmICommand({ client, program, logger }: Context) {
export function registerWhoAmICommand({
client,
program,
logger,
handleError
}: Context) {
const command = new Command('whoami')
.description('Displays info about the current user.')
.action(async () => {
AuthStore.requireAuth()
const res = await client.getMe()
logger.log(res)
try {
const res = await client.getMe()
logger.log(res)
gracefulExit(0)
} catch (err) {
handleError(err)
}
})
program.addCommand(command)

Wyświetl plik

@ -0,0 +1,14 @@
import restoreCursor from 'restore-cursor'
export function initExitHooks() {
// Gracefully restore the cursor if run from a TTY
restoreCursor()
process.on('SIGINT', () => {
process.exit(0)
})
process.on('SIGTERM', () => {
process.exit(0)
})
}

Wyświetl plik

@ -0,0 +1,44 @@
import { gracefulExit } from 'exit-hook'
import { HTTPError } from 'ky'
import type { Context } from '../types'
export function createErrorHandler(ctx: Omit<Context, 'handleError'>) {
return async function handleError(error: any) {
let message: string | undefined
let details: Error | undefined
if (typeof error === 'string') {
message = error
} else if (error instanceof Error) {
details = error
message = error.message
if (error instanceof HTTPError) {
if (error.response) {
try {
message = error.response.statusText
const body = await error.response.json()
if (body.error && typeof body.error === 'string') {
message = JSON.stringify(
{
...body,
details: error.toString()
},
null,
2
)
details = undefined
}
} catch {
// TODO
}
}
}
}
ctx.logger.error([message, details].filter(Boolean).join('\n'))
gracefulExit(1)
}
}

Wyświetl plik

@ -9,4 +9,5 @@ export type Context = {
info: (...args: any[]) => void
error: (...args: any[]) => void
}
handleError: (error: any) => void
}

Wyświetl plik

@ -124,7 +124,7 @@ catalogs:
specifier: ^2.0.1
version: 2.0.1
exit-hook:
specifier: ^4.0.0
specifier: 4.0.0
version: 4.0.0
fast-content-type-parse:
specifier: ^3.0.0
@ -242,7 +242,7 @@ catalogs:
version: 3.2.4
wrangler:
specifier: ^4.21.0
version: 4.20.3
version: 4.21.0
zod:
specifier: ^3.25.67
version: 3.25.67
@ -508,7 +508,7 @@ importers:
version: 2.46.0
wrangler:
specifier: 'catalog:'
version: 4.20.3(@cloudflare/workers-types@4.20250620.0)
version: 4.21.0(@cloudflare/workers-types@4.20250620.0)
apps/web:
dependencies:
@ -751,12 +751,18 @@ importers:
conf:
specifier: 'catalog:'
version: 14.0.0
exit-hook:
specifier: 'catalog:'
version: 4.0.0
get-port:
specifier: 'catalog:'
version: 7.1.0
hono:
specifier: 'catalog:'
version: 4.8.1
ky:
specifier: 'catalog:'
version: 1.8.1
open:
specifier: 'catalog:'
version: 10.1.2
@ -5934,11 +5940,6 @@ packages:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
miniflare@4.20250617.1:
resolution: {integrity: sha512-NjwKVzPGCAUgkCOJxHBwgV2Obu3g4/wAJE0JaA9whcYip4VAJwQ1fU9TMtoKLY91jD9ANR221+CqLyqxennjqg==}
engines: {node: '>=18.0.0'}
hasBin: true
miniflare@4.20250617.3:
resolution: {integrity: sha512-j+LZycT11UdlVeNdaqD0XdNnYnqAL+wXmboz+tNPFgTq6zhD489Ujj3BfSDyEHDCA9UFBLbkc5ByGWBh+pYZ5Q==}
engines: {node: '>=18.0.0'}
@ -7631,16 +7632,6 @@ packages:
engines: {node: '>=16'}
hasBin: true
wrangler@4.20.3:
resolution: {integrity: sha512-ugvmi43CFPbjeQFfhU7EqE1V0ek6ZFv80jzwHcPk/7jPFmOA4ahT5uUU1ga5ZP6vz6lUuG2bLnyl1T5qJah0cg==}
engines: {node: '>=18.0.0'}
hasBin: true
peerDependencies:
'@cloudflare/workers-types': ^4.20250617.0
peerDependenciesMeta:
'@cloudflare/workers-types':
optional: true
wrangler@4.21.0:
resolution: {integrity: sha512-37xm0CG2qMvsJUNZYQKje6HbCsJFYuE8dQSnu7981iDRT4DLrEIL1DAUnZJG9HiXteKPvrSj96AkZyomi5sYHw==}
engines: {node: '>=18.0.0'}
@ -12687,24 +12678,6 @@ snapshots:
min-indent@1.0.1: {}
miniflare@4.20250617.1:
dependencies:
'@cspotcode/source-map-support': 0.8.1
acorn: 8.14.0
acorn-walk: 8.3.2
exit-hook: 2.2.1
glob-to-regexp: 0.4.1
sharp: 0.33.5
stoppable: 1.1.0
undici: 5.29.0
workerd: 1.20250617.0
ws: 8.18.0
youch: 3.3.4
zod: 3.22.3
transitivePeerDependencies:
- bufferutil
- utf-8-validate
miniflare@4.20250617.3:
dependencies:
'@cspotcode/source-map-support': 0.8.1
@ -14591,23 +14564,6 @@ snapshots:
'@cloudflare/workerd-linux-arm64': 1.20250617.0
'@cloudflare/workerd-windows-64': 1.20250617.0
wrangler@4.20.3(@cloudflare/workers-types@4.20250620.0):
dependencies:
'@cloudflare/kv-asset-handler': 0.4.0
'@cloudflare/unenv-preset': 2.3.3(unenv@2.0.0-rc.17)(workerd@1.20250617.0)
blake3-wasm: 2.1.5
esbuild: 0.25.4
miniflare: 4.20250617.1
path-to-regexp: 6.3.0
unenv: 2.0.0-rc.17
workerd: 1.20250617.0
optionalDependencies:
'@cloudflare/workers-types': 4.20250620.0
fsevents: 2.3.3
transitivePeerDependencies:
- bufferutil
- utf-8-validate
wrangler@4.21.0(@cloudflare/workers-types@4.20250620.0):
dependencies:
'@cloudflare/kv-asset-handler': 0.4.0

Wyświetl plik

@ -47,7 +47,7 @@ catalog:
eslint: ^9.29.0
eslint-plugin-drizzle: ^0.2.3
eventid: ^2.0.1
exit-hook: ^4.0.0
exit-hook: 4.0.0
fast-content-type-parse: ^3.0.0
fast-uri: ^3.0.6
fastmcp: ^3.4.0