Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

136 changes: 131 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
Expand Down Expand Up @@ -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 <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 <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 <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 <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 <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)', '.')
Expand Down Expand Up @@ -544,20 +623,41 @@ export const parseAndValidate = async (fileName: string): Promise<void> => {
}
};

export const runModule = async (fileName: string): Promise<void> => {
async function resolveAppConfig(fileName: string, configPath?: string): Promise<Config> {
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<void> => {
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,
config.integrations.username,
config.integrations.password,
config.integrations.connections,
);
configureIntegrationClient(config.integrations.host);
}
if (config.openapi) {
await loadOpenApiSpec(config.openapi);
Expand All @@ -573,9 +673,35 @@ export const runModule = async (fileName: string): Promise<void> => {
// 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<void> => {
setRuntimeMode_init_schema();
await runModule(fileName, { ...options, releaseDb: true });
};

export const runMigrationsCommand = async (fileName: string, options?: { config?: string }): Promise<void> => {
setRuntimeMode_migration();
await runModule(fileName, { ...options, releaseDb: true });
};

export const applyMigrationCommand = runMigrationsCommand;

export const undoLastMigrationCommand = async (fileName: string, options?: { config?: string }): Promise<void> => {
setRuntimeMode_undo_migration();
await runModule(fileName, { ...options, releaseDb: true });
};

export const generateMigrationCommand = async (fileName: string, options?: { config?: string }): Promise<void> => {
setRuntimeMode_generate_migration();
await runModule(fileName, { ...options, releaseDb: true });
};

export const generateDoc = async (
fileName: string,
options?: { outputHtml?: boolean; outputPostman?: boolean },
Expand Down
29 changes: 29 additions & 0 deletions src/ui/components/Help.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,35 @@ export default function Help({ version }: HelpProps) {
<Option flag="-c, --config" arg="<file>" desc="Configuration file path" />
</SubOptions>

<Command name="initSchema" args="[file]" description="Initialize database schema" />
<SubOptions>
<Option flag="-c, --config" arg="<file>" desc="Configuration file path" />
</SubOptions>

<Command name="runMigrations" args="[file]" description="Run pending schema migrations" />
<SubOptions>
<Option flag="-c, --config" arg="<file>" desc="Configuration file path" />
</SubOptions>

<Command
name="applyMigration"
args="[file]"
description="Apply pending migrations (alias for runMigrations)"
/>
<SubOptions>
<Option flag="-c, --config" arg="<file>" desc="Configuration file path" />
</SubOptions>

<Command name="undoLastMigration" args="[file]" description="Undo the last schema migration" />
<SubOptions>
<Option flag="-c, --config" arg="<file>" desc="Configuration file path" />
</SubOptions>

<Command name="generateMigration" args="[file]" description="Generate migration script from schema changes" />
<SubOptions>
<Option flag="-c, --config" arg="<file>" desc="Configuration file path" />
</SubOptions>

<Command name="repl" args="[directory]" description="Start interactive REPL environment" />
<SubOptions>
<Option flag="-w, --watch" desc="Watch files and reload automatically" />
Expand Down
Loading