@inkeep/create-agents 0.29.9 → 0.29.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.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { cleanupDir, createTempDir, linkLocalPackages, runCommand, runCreateAgentsCLI, verifyDirectoryStructure, verifyFile, waitForServerReady, } from './utils';
|
|
5
|
+
const manageApiUrl = 'http://localhost:3002';
|
|
6
|
+
const runApiUrl = 'http://localhost:3003';
|
|
7
|
+
describe('create-agents quickstart e2e', () => {
|
|
8
|
+
let testDir;
|
|
9
|
+
let projectDir;
|
|
10
|
+
const workspaceName = 'test-project';
|
|
11
|
+
const projectId = 'activities-planner';
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
// Create a temporary directory for each test
|
|
14
|
+
testDir = await createTempDir();
|
|
15
|
+
projectDir = path.join(testDir, workspaceName);
|
|
16
|
+
});
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
await cleanupDir(testDir);
|
|
19
|
+
});
|
|
20
|
+
it('should work e2e', async () => {
|
|
21
|
+
// Run the CLI with all options (non-interactive mode)
|
|
22
|
+
console.log('Running CLI with options:');
|
|
23
|
+
console.log(`Working directory: ${testDir}`);
|
|
24
|
+
const result = await runCreateAgentsCLI([
|
|
25
|
+
workspaceName,
|
|
26
|
+
'--openai-key',
|
|
27
|
+
'test-openai-key',
|
|
28
|
+
'--disable-git', // Skip git init for faster tests
|
|
29
|
+
], testDir);
|
|
30
|
+
// Verify the CLI completed successfully
|
|
31
|
+
expect(result.exitCode).toBe(0);
|
|
32
|
+
console.log('CLI completed successfully');
|
|
33
|
+
// Verify the core directory structure
|
|
34
|
+
console.log('Verifying directory structure...');
|
|
35
|
+
await verifyDirectoryStructure(projectDir, [
|
|
36
|
+
'src',
|
|
37
|
+
'src/inkeep.config.ts',
|
|
38
|
+
`src/projects/${projectId}`,
|
|
39
|
+
'apps/manage-api',
|
|
40
|
+
'apps/run-api',
|
|
41
|
+
'apps/mcp',
|
|
42
|
+
'apps/manage-ui',
|
|
43
|
+
'.env',
|
|
44
|
+
'package.json',
|
|
45
|
+
'drizzle.config.ts',
|
|
46
|
+
]);
|
|
47
|
+
console.log('Directory structure verified');
|
|
48
|
+
// Verify .env file has required variables
|
|
49
|
+
console.log('Verifying .env file...');
|
|
50
|
+
await verifyFile(path.join(projectDir, '.env'), [
|
|
51
|
+
/ENVIRONMENT=development/,
|
|
52
|
+
/OPENAI_API_KEY=test-openai-key/,
|
|
53
|
+
/DB_FILE_NAME=file:.*\/local\.db/,
|
|
54
|
+
/INKEEP_AGENTS_MANAGE_API_URL="http:\/\/localhost:3002"/,
|
|
55
|
+
/INKEEP_AGENTS_RUN_API_URL="http:\/\/localhost:3003"/,
|
|
56
|
+
/INKEEP_AGENTS_JWT_SIGNING_SECRET=\w+/, // Random secret should be generated
|
|
57
|
+
]);
|
|
58
|
+
console.log('.env file verified');
|
|
59
|
+
// Verify inkeep.config.ts was created
|
|
60
|
+
console.log('Verifying inkeep.config.ts...');
|
|
61
|
+
await verifyFile(path.join(projectDir, 'src/inkeep.config.ts'));
|
|
62
|
+
console.log('inkeep.config.ts verified');
|
|
63
|
+
console.log('Starting dev servers');
|
|
64
|
+
// Start dev servers in background with output monitoring
|
|
65
|
+
const devProcess = execa('pnpm', ['dev'], {
|
|
66
|
+
cwd: path.join(projectDir, 'apps/manage-api'),
|
|
67
|
+
env: {
|
|
68
|
+
...process.env,
|
|
69
|
+
FORCE_COLOR: '0',
|
|
70
|
+
NODE_ENV: 'test',
|
|
71
|
+
},
|
|
72
|
+
cleanup: true,
|
|
73
|
+
detached: false,
|
|
74
|
+
stderr: 'pipe',
|
|
75
|
+
});
|
|
76
|
+
// Monitor output for errors and readiness signals
|
|
77
|
+
let serverOutput = '';
|
|
78
|
+
const outputHandler = (data) => {
|
|
79
|
+
const text = data.toString();
|
|
80
|
+
serverOutput += text;
|
|
81
|
+
// Log important messages in CI
|
|
82
|
+
if (process.env.CI) {
|
|
83
|
+
if (text.includes('Error') || text.includes('EADDRINUSE') || text.includes('ready')) {
|
|
84
|
+
console.log('[Server]:', text.trim());
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
if (devProcess.stderr)
|
|
89
|
+
devProcess.stderr.on('data', outputHandler);
|
|
90
|
+
// Handle process crashes during startup
|
|
91
|
+
devProcess.catch((error) => {
|
|
92
|
+
console.error('Dev process crashed during startup:', error.message);
|
|
93
|
+
console.error('Server output:', serverOutput);
|
|
94
|
+
});
|
|
95
|
+
console.log('Waiting for servers to be ready');
|
|
96
|
+
try {
|
|
97
|
+
// Wait for servers to be ready with retries
|
|
98
|
+
await waitForServerReady(`${manageApiUrl}/health`, 120000); // Increased to 2 minutes for CI
|
|
99
|
+
console.log('Manage API is ready');
|
|
100
|
+
console.log('Pushing project');
|
|
101
|
+
const pushResult = await runCommand('pnpm', [
|
|
102
|
+
'inkeep',
|
|
103
|
+
'push',
|
|
104
|
+
'--project',
|
|
105
|
+
`src/projects/${projectId}`,
|
|
106
|
+
'--config',
|
|
107
|
+
'src/inkeep.config.ts',
|
|
108
|
+
], projectDir, 30000);
|
|
109
|
+
expect(pushResult.exitCode).toBe(0);
|
|
110
|
+
console.log('Testing API requests');
|
|
111
|
+
// Test API requests
|
|
112
|
+
const response = await fetch(`${manageApiUrl}/tenants/default/projects/${projectId}`);
|
|
113
|
+
const data = await response.json();
|
|
114
|
+
expect(data.data.tenantId).toBe('default');
|
|
115
|
+
expect(data.data.id).toBe(projectId);
|
|
116
|
+
// Link to local monorepo packages
|
|
117
|
+
const monorepoRoot = path.join(__dirname, '../../../../../'); // Go up to repo root
|
|
118
|
+
await linkLocalPackages(projectDir, monorepoRoot);
|
|
119
|
+
const pushResultLocal = await runCommand('pnpm', [
|
|
120
|
+
'inkeep',
|
|
121
|
+
'push',
|
|
122
|
+
'--project',
|
|
123
|
+
`src/projects/${projectId}`,
|
|
124
|
+
'--config',
|
|
125
|
+
'src/inkeep.config.ts',
|
|
126
|
+
], projectDir, 30000);
|
|
127
|
+
expect(pushResultLocal.exitCode).toBe(0);
|
|
128
|
+
// Test that the project works with local packages
|
|
129
|
+
const responseLocal = await fetch(`${manageApiUrl}/tenants/default/projects/${projectId}`);
|
|
130
|
+
expect(responseLocal.status).toBe(200);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error('Test failed with error:', error);
|
|
134
|
+
// Print server output for debugging
|
|
135
|
+
if (devProcess.stdout) {
|
|
136
|
+
const stdout = await devProcess.stdout;
|
|
137
|
+
console.log('Server stdout:', stdout);
|
|
138
|
+
}
|
|
139
|
+
if (devProcess.stderr) {
|
|
140
|
+
const stderr = await devProcess.stderr;
|
|
141
|
+
console.error('Server stderr:', stderr);
|
|
142
|
+
}
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
console.log('Killing dev process');
|
|
147
|
+
// Kill the process and wait for it to die
|
|
148
|
+
try {
|
|
149
|
+
devProcess.kill('SIGTERM');
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Might already be dead
|
|
153
|
+
}
|
|
154
|
+
// Give it 2 seconds to shut down gracefully, then force kill
|
|
155
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
156
|
+
try {
|
|
157
|
+
devProcess.kill('SIGKILL');
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Already dead or couldn't kill
|
|
161
|
+
}
|
|
162
|
+
// Wait for the process to be fully cleaned up (with timeout)
|
|
163
|
+
await Promise.race([
|
|
164
|
+
devProcess.catch(() => { }), // Wait for process to exit
|
|
165
|
+
new Promise((resolve) => setTimeout(resolve, 5000)), // Or timeout after 5s
|
|
166
|
+
]);
|
|
167
|
+
console.log('Dev process cleanup complete');
|
|
168
|
+
}
|
|
169
|
+
}, 720000); // 12 minute timeout for full flow with network calls (CI can be slow)
|
|
170
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run the create-agents CLI with the given arguments
|
|
3
|
+
*/
|
|
4
|
+
export declare function runCreateAgentsCLI(args: string[], cwd: string, timeout?: number): Promise<{
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
exitCode: number | undefined;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Run a command in the created project directory
|
|
11
|
+
*/
|
|
12
|
+
export declare function runCommand(command: string, args: string[], cwd: string, timeout?: number): Promise<{
|
|
13
|
+
stdout: string;
|
|
14
|
+
stderr: string;
|
|
15
|
+
exitCode: number | undefined;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Create a temporary directory for testing
|
|
19
|
+
*/
|
|
20
|
+
export declare function createTempDir(prefix?: string): Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Clean up a test directory with retries
|
|
23
|
+
*/
|
|
24
|
+
export declare function cleanupDir(dir: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Verify that a file exists and optionally check its contents
|
|
27
|
+
*/
|
|
28
|
+
export declare function verifyFile(filePath: string, expectedContents?: string[] | RegExp[]): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Verify that a directory has the expected structure
|
|
31
|
+
*/
|
|
32
|
+
export declare function verifyDirectoryStructure(baseDir: string, expectedPaths: string[]): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Link local monorepo packages to the created project
|
|
35
|
+
* This replaces published @inkeep packages with local versions for testing
|
|
36
|
+
*/
|
|
37
|
+
export declare function linkLocalPackages(projectDir: string, monorepoRoot: string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Wait for a server to be ready by polling a health endpoint
|
|
40
|
+
*/
|
|
41
|
+
export declare function waitForServerReady(url: string, timeout: number): Promise<void>;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path, { dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
/**
|
|
8
|
+
* Run the create-agents CLI with the given arguments
|
|
9
|
+
*/
|
|
10
|
+
export async function runCreateAgentsCLI(args, cwd, timeout = 300000 // 5 minutes default for full flow
|
|
11
|
+
) {
|
|
12
|
+
const cliPath = path.join(__dirname, '../../../src/index.ts');
|
|
13
|
+
try {
|
|
14
|
+
// Run using tsx to execute TypeScript directly
|
|
15
|
+
const result = await execa('tsx', [cliPath, ...args], {
|
|
16
|
+
cwd,
|
|
17
|
+
timeout,
|
|
18
|
+
env: { ...process.env, FORCE_COLOR: '0' }, // Disable colors for easier assertion
|
|
19
|
+
all: true, // Capture combined stdout + stderr in order
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
stdout: result.stdout,
|
|
23
|
+
stderr: result.stderr,
|
|
24
|
+
exitCode: result.exitCode,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
// execa throws on non-zero exit codes, capture the error info
|
|
29
|
+
return {
|
|
30
|
+
stdout: error.stdout || '',
|
|
31
|
+
stderr: error.stderr || '',
|
|
32
|
+
exitCode: error.exitCode || 1,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Run a command in the created project directory
|
|
38
|
+
*/
|
|
39
|
+
export async function runCommand(command, args, cwd, timeout = 120000 // 2 minutes default
|
|
40
|
+
) {
|
|
41
|
+
try {
|
|
42
|
+
const result = await execa(command, args, {
|
|
43
|
+
cwd,
|
|
44
|
+
timeout,
|
|
45
|
+
env: { ...process.env, FORCE_COLOR: '0' },
|
|
46
|
+
shell: true,
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
stdout: result.stdout,
|
|
50
|
+
stderr: result.stderr,
|
|
51
|
+
exitCode: result.exitCode,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
stdout: error.stdout || '',
|
|
57
|
+
stderr: error.stderr || '',
|
|
58
|
+
exitCode: error.exitCode || 1,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create a temporary directory for testing
|
|
64
|
+
*/
|
|
65
|
+
export async function createTempDir(prefix = 'create-agents-e2e-') {
|
|
66
|
+
return fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Clean up a test directory with retries
|
|
70
|
+
*/
|
|
71
|
+
export async function cleanupDir(dir) {
|
|
72
|
+
if (!(await fs.pathExists(dir))) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
// Try multiple times with delays (common in CI)
|
|
77
|
+
for (let i = 0; i < 3; i++) {
|
|
78
|
+
try {
|
|
79
|
+
await fs.remove(dir);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (i === 2)
|
|
84
|
+
throw error; // Last attempt, throw the error
|
|
85
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1s before retry
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// If still failing, try force removal
|
|
91
|
+
if (error.code === 'ENOTEMPTY' || error.code === 'EBUSY') {
|
|
92
|
+
await execa('rm', ['-rf', dir], { shell: true }).catch(() => {
|
|
93
|
+
console.warn(`Failed to clean up ${dir}`);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Verify that a file exists and optionally check its contents
|
|
100
|
+
*/
|
|
101
|
+
export async function verifyFile(filePath, expectedContents) {
|
|
102
|
+
const exists = await fs.pathExists(filePath);
|
|
103
|
+
if (!exists) {
|
|
104
|
+
throw new Error(`Expected file to exist: ${filePath}`);
|
|
105
|
+
}
|
|
106
|
+
if (expectedContents) {
|
|
107
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
108
|
+
for (const expected of expectedContents) {
|
|
109
|
+
if (typeof expected === 'string') {
|
|
110
|
+
if (!content.includes(expected)) {
|
|
111
|
+
throw new Error(`Expected file ${filePath} to contain: ${expected}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
if (!expected.test(content)) {
|
|
116
|
+
throw new Error(`Expected file ${filePath} to match pattern: ${expected}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Verify that a directory has the expected structure
|
|
124
|
+
*/
|
|
125
|
+
export async function verifyDirectoryStructure(baseDir, expectedPaths) {
|
|
126
|
+
for (const expectedPath of expectedPaths) {
|
|
127
|
+
const fullPath = path.join(baseDir, expectedPath);
|
|
128
|
+
const exists = await fs.pathExists(fullPath);
|
|
129
|
+
if (!exists) {
|
|
130
|
+
throw new Error(`Expected path to exist: ${fullPath}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Link local monorepo packages to the created project
|
|
136
|
+
* This replaces published @inkeep packages with local versions for testing
|
|
137
|
+
*/
|
|
138
|
+
export async function linkLocalPackages(projectDir, monorepoRoot) {
|
|
139
|
+
const packageJsonPaths = [
|
|
140
|
+
path.join(projectDir, 'package.json'),
|
|
141
|
+
path.join(projectDir, 'apps/manage-api/package.json'),
|
|
142
|
+
path.join(projectDir, 'apps/run-api/package.json'),
|
|
143
|
+
];
|
|
144
|
+
const packageJsons = {};
|
|
145
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
146
|
+
packageJsons[packageJsonPath] = await fs.readJson(packageJsonPath);
|
|
147
|
+
}
|
|
148
|
+
// Define local @inkeep packages to link
|
|
149
|
+
const inkeepPackages = {
|
|
150
|
+
'@inkeep/agents-sdk': `link:${path.join(monorepoRoot, 'packages/agents-sdk')}`,
|
|
151
|
+
'@inkeep/agents-core': `link:${path.join(monorepoRoot, 'packages/agents-core')}`,
|
|
152
|
+
'@inkeep/agents-manage-api': `link:${path.join(monorepoRoot, 'agents-manage-api')}`,
|
|
153
|
+
'@inkeep/agents-run-api': `link:${path.join(monorepoRoot, 'agents-run-api')}`,
|
|
154
|
+
'@inkeep/agents-cli': `link:${path.join(monorepoRoot, 'agents-cli')}`,
|
|
155
|
+
};
|
|
156
|
+
// Replace package versions with local links
|
|
157
|
+
for (const [pkg, linkPath] of Object.entries(inkeepPackages)) {
|
|
158
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
159
|
+
if (packageJsons[packageJsonPath].dependencies?.[pkg]) {
|
|
160
|
+
packageJsons[packageJsonPath].dependencies[pkg] = linkPath;
|
|
161
|
+
}
|
|
162
|
+
if (packageJsons[packageJsonPath].devDependencies?.[pkg]) {
|
|
163
|
+
packageJsons[packageJsonPath].devDependencies[pkg] = linkPath;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Write updated package.json
|
|
168
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
169
|
+
await fs.writeJson(packageJsonPath, packageJsons[packageJsonPath], { spaces: 2 });
|
|
170
|
+
}
|
|
171
|
+
// Reinstall to create the symlinks
|
|
172
|
+
await execa('pnpm', ['install', '--no-frozen-lockfile'], {
|
|
173
|
+
cwd: projectDir,
|
|
174
|
+
env: { ...process.env, FORCE_COLOR: '0' },
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Wait for a server to be ready by polling a health endpoint
|
|
179
|
+
*/
|
|
180
|
+
export async function waitForServerReady(url, timeout) {
|
|
181
|
+
const start = Date.now();
|
|
182
|
+
let lastError = null;
|
|
183
|
+
let attempts = 0;
|
|
184
|
+
console.log(`Waiting for server at ${url}...`);
|
|
185
|
+
while (Date.now() - start < timeout) {
|
|
186
|
+
attempts++;
|
|
187
|
+
try {
|
|
188
|
+
const response = await fetch(url, {
|
|
189
|
+
signal: AbortSignal.timeout(5000), // 5 second timeout per request
|
|
190
|
+
});
|
|
191
|
+
if (response.ok) {
|
|
192
|
+
console.log(`✓ Server ready at ${url} after ${attempts} attempts (${Date.now() - start}ms)`);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
196
|
+
// Log status every 10 attempts in CI
|
|
197
|
+
if (process.env.CI && attempts % 10 === 0) {
|
|
198
|
+
console.log(`Still waiting for ${url}... (attempt ${attempts}, ${Math.floor((Date.now() - start) / 1000)}s elapsed)`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
// Server not ready yet or connection refused
|
|
203
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
204
|
+
// Log connection errors periodically in CI
|
|
205
|
+
if (process.env.CI && attempts % 15 === 0) {
|
|
206
|
+
console.log(`Connection attempt ${attempts} failed: ${lastError.message}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Wait before next attempt (exponential backoff up to 5s)
|
|
210
|
+
const waitTime = Math.min(1000 + attempts * 100, 5000);
|
|
211
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
212
|
+
}
|
|
213
|
+
// Timeout reached - provide detailed error
|
|
214
|
+
const elapsed = Date.now() - start;
|
|
215
|
+
const errorDetails = lastError ? `: ${lastError.message}` : '';
|
|
216
|
+
throw new Error(`Server not ready at ${url} after ${elapsed}ms (${attempts} attempts)${errorDetails}`);
|
|
217
|
+
}
|
package/dist/utils.js
CHANGED
|
@@ -316,14 +316,18 @@ INKEEP_AGENTS_JWT_SIGNING_SECRET=${jwtSigningSecret}
|
|
|
316
316
|
}
|
|
317
317
|
async function createInkeepConfig(config) {
|
|
318
318
|
const inkeepConfig = `import { defineConfig } from '@inkeep/agents-cli/config';
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
319
|
+
|
|
320
|
+
const config = defineConfig({
|
|
321
|
+
tenantId: "${config.tenantId}",
|
|
322
|
+
agentsManageApi: {
|
|
323
|
+
url: 'http://localhost:3002',
|
|
324
|
+
},
|
|
325
|
+
agentsRunApi: {
|
|
326
|
+
url: 'http://localhost:3003',
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
export default config;`;
|
|
327
331
|
await fs.writeFile(`src/inkeep.config.ts`, inkeepConfig);
|
|
328
332
|
if (config.customProject) {
|
|
329
333
|
const customIndexContent = `import { project } from '@inkeep/agents-sdk';
|
|
@@ -358,14 +362,16 @@ async function isPortAvailable(port) {
|
|
|
358
362
|
const net = await import('node:net');
|
|
359
363
|
return new Promise((resolve) => {
|
|
360
364
|
const server = net.createServer();
|
|
361
|
-
server.once('error', () => {
|
|
362
|
-
|
|
365
|
+
server.once('error', (err) => {
|
|
366
|
+
// Only treat EADDRINUSE as "port in use", other errors might be transient
|
|
367
|
+
resolve(err.code === 'EADDRINUSE' ? false : true);
|
|
363
368
|
});
|
|
364
369
|
server.once('listening', () => {
|
|
365
|
-
server.close()
|
|
366
|
-
|
|
370
|
+
server.close(() => {
|
|
371
|
+
resolve(true);
|
|
372
|
+
});
|
|
367
373
|
});
|
|
368
|
-
server.listen(port);
|
|
374
|
+
server.listen(port, 'localhost');
|
|
369
375
|
});
|
|
370
376
|
}
|
|
371
377
|
/**
|
|
@@ -399,6 +405,25 @@ async function checkPortsAvailability() {
|
|
|
399
405
|
});
|
|
400
406
|
}
|
|
401
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Wait for a server to be ready by polling a health endpoint
|
|
410
|
+
*/
|
|
411
|
+
async function waitForServerReady(url, timeout) {
|
|
412
|
+
const start = Date.now();
|
|
413
|
+
while (Date.now() - start < timeout) {
|
|
414
|
+
try {
|
|
415
|
+
const response = await fetch(url);
|
|
416
|
+
if (response.ok) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
// Server not ready yet, continue polling
|
|
422
|
+
}
|
|
423
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Check every second
|
|
424
|
+
}
|
|
425
|
+
throw new Error(`Server not ready at ${url} after ${timeout}ms`);
|
|
426
|
+
}
|
|
402
427
|
async function setupProjectInDatabase(config) {
|
|
403
428
|
// Proactively check if ports are available BEFORE starting servers
|
|
404
429
|
await checkPortsAvailability();
|
|
@@ -411,7 +436,6 @@ async function setupProjectInDatabase(config) {
|
|
|
411
436
|
shell: true,
|
|
412
437
|
windowsHide: true,
|
|
413
438
|
});
|
|
414
|
-
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
415
439
|
// Track if port errors occur during startup (as a safety fallback)
|
|
416
440
|
const portErrors = { runApi: false, manageApi: false };
|
|
417
441
|
// Regex patterns for detecting port errors in output
|
|
@@ -430,8 +454,15 @@ async function setupProjectInDatabase(config) {
|
|
|
430
454
|
}
|
|
431
455
|
};
|
|
432
456
|
devProcess.stdout.on('data', checkForPortErrors);
|
|
433
|
-
//
|
|
434
|
-
|
|
457
|
+
// Wait for servers to be ready
|
|
458
|
+
try {
|
|
459
|
+
await waitForServerReady(`http://localhost:${manageApiPort}/health`, 60000);
|
|
460
|
+
await waitForServerReady(`http://localhost:${runApiPort}/health`, 60000);
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
// If servers don't start, we'll still try push but it will likely fail
|
|
464
|
+
console.warn('Warning: Servers may not be fully ready:', error instanceof Error ? error.message : String(error));
|
|
465
|
+
}
|
|
435
466
|
// Check if any port errors occurred during startup
|
|
436
467
|
if (portErrors.runApi || portErrors.manageApi) {
|
|
437
468
|
displayPortConflictError(portErrors);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inkeep/create-agents",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.11",
|
|
4
4
|
"description": "Create an Inkeep Agent Framework project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,15 +31,16 @@
|
|
|
31
31
|
"@clack/prompts": "^0.11.0",
|
|
32
32
|
"commander": "^12.0.0",
|
|
33
33
|
"degit": "^2.8.4",
|
|
34
|
+
"drizzle-kit": "^0.31.5",
|
|
34
35
|
"fs-extra": "^11.0.0",
|
|
35
36
|
"picocolors": "^1.0.0",
|
|
36
|
-
"
|
|
37
|
-
"@inkeep/agents-core": "0.29.9"
|
|
37
|
+
"@inkeep/agents-core": "0.29.11"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/degit": "^2.8.6",
|
|
41
41
|
"@types/fs-extra": "^11.0.0",
|
|
42
42
|
"@types/node": "^20.12.0",
|
|
43
|
+
"execa": "^9.6.0",
|
|
43
44
|
"tsx": "^4.7.0",
|
|
44
45
|
"typescript": "^5.4.0",
|
|
45
46
|
"vitest": "^3.2.4"
|
|
@@ -62,8 +63,12 @@
|
|
|
62
63
|
"lint": "biome lint src",
|
|
63
64
|
"lint:fix": "biome check --write .",
|
|
64
65
|
"format": "biome format --write .",
|
|
65
|
-
"test": "vitest --run --passWithNoTests",
|
|
66
|
-
"test:watch": "vitest",
|
|
66
|
+
"test": "vitest --run src/__tests__ --exclude src/__tests__/e2e/** --passWithNoTests",
|
|
67
|
+
"test:watch": "vitest src/__tests__ --exclude src/__tests__/e2e/**",
|
|
68
|
+
"test:unit": "vitest --run src/__tests__ --exclude src/__tests__/e2e/**",
|
|
69
|
+
"test:e2e": "vitest --run src/__tests__/e2e",
|
|
70
|
+
"test:e2e:watch": "vitest src/__tests__/e2e",
|
|
71
|
+
"test:all": "vitest --run --passWithNoTests",
|
|
67
72
|
"typecheck": "tsc --noEmit"
|
|
68
73
|
}
|
|
69
74
|
}
|