diff --git a/package.json b/package.json index 1aec600..fb4f684 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@anthropic-ai/sdk": "^0.71.2", "@asteasolutions/zod-to-openapi": "8.4.0", "@redocly/cli": "^2.15.0", - "agentlang": "^0.10.6", + "agentlang": "^0.10.7", "better-sqlite3": "^12.6.2", "chokidar": "^5.0.0", "commander": "^14.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a1dc8b..486de6a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,8 +27,8 @@ importers: specifier: ^2.15.0 version: 2.16.0(@opentelemetry/api@1.9.0)(core-js@3.48.0) agentlang: - specifier: ^0.10.6 - version: 0.10.6(@cfworker/json-schema@4.1.1)(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(axios@1.13.4)(js-yaml@4.1.1)(openai@6.18.0(ws@8.19.0)(zod@4.3.6))(ts-node@10.9.2(@types/node@25.2.1)(typescript@5.9.3))(ws@8.19.0) + specifier: ^0.10.7 + version: 0.10.7(@cfworker/json-schema@4.1.1)(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(axios@1.13.4)(js-yaml@4.1.1)(openai@6.18.0(ws@8.19.0)(zod@4.3.6))(ts-node@10.9.2(@types/node@25.2.1)(typescript@5.9.3))(ws@8.19.0) better-sqlite3: specifier: ^12.6.2 version: 12.6.2 @@ -1455,8 +1455,8 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} - agentlang@0.10.6: - resolution: {integrity: sha512-3cToUaoaT7YHlEbWmIJsxSpGOuxkoTgz2DGa/PjbFyP5pAvLmUAXwek6xgcbKUZNTUPLgz7G4OkyDuw5sAQ1Ug==} + agentlang@0.10.7: + resolution: {integrity: sha512-XwyV//sed15dDeNiG+y6GM4sV+J5IVnIUPe4BflwQeWVff9zz8edfR4zqDAurzk6eR8+oyPNS+EBmMYsBAp++Q==} engines: {node: '>=20.0.0', vscode: ^1.67.0} hasBin: true @@ -5869,7 +5869,7 @@ snapshots: agent-base@7.1.4: {} - agentlang@0.10.6(@cfworker/json-schema@4.1.1)(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(axios@1.13.4)(js-yaml@4.1.1)(openai@6.18.0(ws@8.19.0)(zod@4.3.6))(ts-node@10.9.2(@types/node@25.2.1)(typescript@5.9.3))(ws@8.19.0): + agentlang@0.10.7(@cfworker/json-schema@4.1.1)(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(axios@1.13.4)(js-yaml@4.1.1)(openai@6.18.0(ws@8.19.0)(zod@4.3.6))(ts-node@10.9.2(@types/node@25.2.1)(typescript@5.9.3))(ws@8.19.0): dependencies: '@aws-sdk/client-cognito-identity': 3.984.0 '@aws-sdk/client-cognito-identity-provider': 3.984.0 diff --git a/src/main.ts b/src/main.ts index 2c48bdb..0c7c2d7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -52,15 +52,35 @@ const modCreateAgentlangServices: typeof import('agentlang/out/language/agentlan ); const { createAgentlangServices } = modCreateAgentlangServices; const modLoader: typeof import('agentlang/out/runtime/loader.js') = await import(`${agPath}/out/runtime/loader.js`); -const { internModule, load, loadAppConfig, extractDocument } = modLoader; +const { internModule, load, loadAppConfig, loadRawConfig, extractDocument } = modLoader; import type { ApplicationSpec } from 'agentlang/out/runtime/loader.js'; const modLogger: typeof import('agentlang/out/runtime/logger.js') = await import(`${agPath}/out/runtime/logger.js`); -const { logger } = modLogger; +const { logger, updateLoggerFromConfig } = modLogger; +const modState: typeof import('agentlang/out/runtime/state.js') = await import(`${agPath}/out/runtime/state.js`); +const { setAppConfig } = modState; import type { Config } from 'agentlang/out/runtime/state.js'; const modIntegrations: typeof import('agentlang/out/runtime/integrations.js') = await import( `${agPath}/out/runtime/integrations.js` ); const { prepareIntegrations } = modIntegrations; +const modIntegrationClient: typeof import('agentlang/out/runtime/integration-client.js') = await import( + `${agPath}/out/runtime/integration-client.js` +); +const { configureIntegrationClient } = modIntegrationClient; +const modDatabase: typeof import('agentlang/out/runtime/resolvers/sqldb/database.js') = await import( + `${agPath}/out/runtime/resolvers/sqldb/database.js` +); +const { resetDefaultDatabase } = modDatabase; +const modDefs: typeof import('agentlang/out/runtime/defs.js') = await import(`${agPath}/out/runtime/defs.js`); +const { + isRuntimeMode_dev, + setRuntimeMode_prod, + setRuntimeMode_test, + setRuntimeMode_init_schema, + setRuntimeMode_migration, + setRuntimeMode_undo_migration, + setRuntimeMode_generate_migration, +} = modDefs; const modRuntime: typeof import('agentlang/out/utils/runtime.js') = await import(`${agPath}/out/utils/runtime.js`); const { isNodeEnv } = modRuntime; const modOpenApi: typeof import('agentlang/out/runtime/openapi.js') = await import(`${agPath}/out/runtime/openapi.js`); @@ -285,6 +305,65 @@ ${ui.format.boldWhite('EXAMPLES')} ) .action(runModule); + const migrationHelp = (cmd: string, oneLiner: string) => ` +${ui.format.boldWhite('DESCRIPTION')} + ${oneLiner} + +${ui.format.boldWhite('EXAMPLES')} + ${ui.format.dim('$')} ${ui.format.cyan(`agent ${cmd}`)} + ${ui.format.dim('$')} ${ui.format.cyan(`agent ${cmd} ./src/core.al`)} + ${ui.format.dim('$')} ${ui.format.cyan(`agent ${cmd} . -c app.config.json`)} +`; + + program + .command('initSchema') + .argument('[file]', `Agentlang source file (${fileExtensions})`, '.') + .option('-c, --config ', 'Path to configuration file') + .description('Initialize database schema') + .addHelpText('after', migrationHelp('initSchema', 'Initializes the database schema from your Agentlang module.')) + .action(initSchemaCommand); + + program + .command('runMigrations') + .argument('[file]', `Agentlang source file (${fileExtensions})`, '.') + .option('-c, --config ', 'Path to configuration file') + .description('Run pending schema migrations') + .addHelpText( + 'after', + migrationHelp('runMigrations', 'Applies pending migrations to bring the database schema up to date.'), + ) + .action(runMigrationsCommand); + + program + .command('applyMigration') + .argument('[file]', `Agentlang source file (${fileExtensions})`, '.') + .option('-c, --config ', 'Path to configuration file') + .description('Apply pending migrations (alias for runMigrations)') + .addHelpText( + 'after', + migrationHelp('applyMigration', 'Same behavior as runMigrations: applies pending schema migrations.'), + ) + .action(applyMigrationCommand); + + program + .command('undoLastMigration') + .argument('[file]', `Agentlang source file (${fileExtensions})`, '.') + .option('-c, --config ', 'Path to configuration file') + .description('Undo the last schema migration') + .addHelpText('after', migrationHelp('undoLastMigration', 'Reverts the most recently applied migration.')) + .action(undoLastMigrationCommand); + + program + .command('generateMigration') + .argument('[file]', `Agentlang source file (${fileExtensions})`, '.') + .option('-c, --config ', 'Path to configuration file') + .description('Generate migration script from schema changes') + .addHelpText( + 'after', + migrationHelp('generateMigration', 'Generates a migration script for pending schema changes.'), + ) + .action(generateMigrationCommand); + program .command('repl') .argument('[directory]', 'Application directory (defaults to current)', '.') @@ -544,13 +623,33 @@ export const parseAndValidate = async (fileName: string): Promise => { } }; -export const runModule = async (fileName: string): Promise => { +async function resolveAppConfig(fileName: string, configPath?: string): Promise { + if (configPath) { + const abs = path.resolve(process.cwd(), configPath); + const raw = await loadRawConfig(abs); + return setAppConfig(raw as Config); + } + const configDir = path.dirname(fileName) === '.' ? process.cwd() : path.resolve(process.cwd(), fileName); + return loadAppConfig(configDir); +} + +export const runModule = async ( + fileName: string, + options?: { config?: string; releaseDb?: boolean }, +): Promise => { + if (isRuntimeMode_dev()) { + if (process.env.NODE_ENV === 'production') { + setRuntimeMode_prod(); + } else if (process.env.NODE_ENV === 'test') { + setRuntimeMode_test(); + } + } const r: boolean = await runPreInitTasks(); if (!r) { throw new Error('Failed to initialize runtime'); } - const configDir = path.dirname(fileName) === '.' ? process.cwd() : path.resolve(process.cwd(), fileName); - const config: Config = await loadAppConfig(configDir); + const config: Config = await resolveAppConfig(fileName, options?.config); + updateLoggerFromConfig(); if (config.integrations) { await prepareIntegrations( config.integrations.host, @@ -558,6 +657,7 @@ export const runModule = async (fileName: string): Promise => { config.integrations.password, config.integrations.connections, ); + configureIntegrationClient(config.integrations.host); } if (config.openapi) { await loadOpenApiSpec(config.openapi); @@ -573,9 +673,35 @@ export const runModule = async (fileName: string): Promise => { // eslint-disable-next-line no-console console.error(String(err)); } + } finally { + if (options?.releaseDb) { + await resetDefaultDatabase(); + } } }; +export const initSchemaCommand = async (fileName: string, options?: { config?: string }): Promise => { + setRuntimeMode_init_schema(); + await runModule(fileName, { ...options, releaseDb: true }); +}; + +export const runMigrationsCommand = async (fileName: string, options?: { config?: string }): Promise => { + setRuntimeMode_migration(); + await runModule(fileName, { ...options, releaseDb: true }); +}; + +export const applyMigrationCommand = runMigrationsCommand; + +export const undoLastMigrationCommand = async (fileName: string, options?: { config?: string }): Promise => { + setRuntimeMode_undo_migration(); + await runModule(fileName, { ...options, releaseDb: true }); +}; + +export const generateMigrationCommand = async (fileName: string, options?: { config?: string }): Promise => { + setRuntimeMode_generate_migration(); + await runModule(fileName, { ...options, releaseDb: true }); +}; + export const generateDoc = async ( fileName: string, options?: { outputHtml?: boolean; outputPostman?: boolean }, diff --git a/src/ui/components/Help.tsx b/src/ui/components/Help.tsx index 9c6f0d0..7f78761 100644 --- a/src/ui/components/Help.tsx +++ b/src/ui/components/Help.tsx @@ -147,6 +147,35 @@ export default function Help({ version }: HelpProps) {