@hexabot-ai/cli 3.0.0-alpha.3
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/.prettierrc +5 -0
- package/AGENTS.md +64 -0
- package/README.md +192 -0
- package/dist/cli.js +31 -0
- package/dist/commands/__tests__/check.test.js +97 -0
- package/dist/commands/__tests__/config.test.js +80 -0
- package/dist/commands/__tests__/dev.test.js +105 -0
- package/dist/commands/__tests__/docker.test.js +132 -0
- package/dist/commands/__tests__/env.test.js +72 -0
- package/dist/commands/__tests__/migrate.test.js +42 -0
- package/dist/commands/__tests__/start.test.js +120 -0
- package/dist/commands/check.js +73 -0
- package/dist/commands/config.js +76 -0
- package/dist/commands/create.js +131 -0
- package/dist/commands/dev.js +72 -0
- package/dist/commands/docker.js +119 -0
- package/dist/commands/env.js +44 -0
- package/dist/commands/migrate.js +22 -0
- package/dist/commands/start.js +76 -0
- package/dist/core/__tests__/config.test.js +88 -0
- package/dist/core/__tests__/docker.test.js +43 -0
- package/dist/core/__tests__/env.test.js +71 -0
- package/dist/core/__tests__/package-manager.test.js +95 -0
- package/dist/core/__tests__/project.test.js +49 -0
- package/dist/core/config.js +78 -0
- package/dist/core/docker.js +66 -0
- package/dist/core/env.js +50 -0
- package/dist/core/package-manager.js +87 -0
- package/dist/core/prerequisites.js +80 -0
- package/dist/core/project.js +58 -0
- package/dist/index.js +16 -0
- package/dist/services/templates.js +27 -0
- package/dist/ui/banner.js +14 -0
- package/dist/utils/__tests__/services.test.js +18 -0
- package/dist/utils/__tests__/validation.test.js +17 -0
- package/dist/utils/__tests__/version.test.js +27 -0
- package/dist/utils/services.js +11 -0
- package/dist/utils/validation.js +9 -0
- package/dist/utils/version.js +22 -0
- package/eslint.config-staged.cjs +10 -0
- package/eslint.config.cjs +104 -0
- package/jest.config.ts +24 -0
- package/package.json +63 -0
- package/src/cli.ts +37 -0
- package/src/commands/__tests__/check.test.ts +116 -0
- package/src/commands/__tests__/config.test.ts +97 -0
- package/src/commands/__tests__/dev.test.ts +151 -0
- package/src/commands/__tests__/docker.test.ts +168 -0
- package/src/commands/__tests__/env.test.ts +95 -0
- package/src/commands/__tests__/migrate.test.ts +64 -0
- package/src/commands/__tests__/start.test.ts +166 -0
- package/src/commands/check.ts +102 -0
- package/src/commands/config.ts +90 -0
- package/src/commands/create.ts +201 -0
- package/src/commands/dev.ts +122 -0
- package/src/commands/docker.ts +190 -0
- package/src/commands/env.ts +62 -0
- package/src/commands/migrate.ts +27 -0
- package/src/commands/start.ts +126 -0
- package/src/core/__tests__/config.test.ts +114 -0
- package/src/core/__tests__/docker.test.ts +59 -0
- package/src/core/__tests__/env.test.ts +97 -0
- package/src/core/__tests__/package-manager.test.ts +121 -0
- package/src/core/__tests__/project.test.ts +68 -0
- package/src/core/config.ts +127 -0
- package/src/core/docker.ts +91 -0
- package/src/core/env.ts +90 -0
- package/src/core/package-manager.ts +126 -0
- package/src/core/prerequisites.ts +117 -0
- package/src/core/project.ts +97 -0
- package/src/index.ts +21 -0
- package/src/services/templates.ts +33 -0
- package/src/ui/banner.ts +18 -0
- package/src/utils/__tests__/services.test.ts +21 -0
- package/src/utils/__tests__/validation.test.ts +21 -0
- package/src/utils/__tests__/version.test.ts +35 -0
- package/src/utils/services.ts +12 -0
- package/src/utils/validation.ts +11 -0
- package/src/utils/version.ts +28 -0
- package/test/__mocks__/chalk.ts +13 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { Command } from 'commander';
|
|
12
|
+
|
|
13
|
+
import { ensureProjectConfig, loadProjectConfig } from '../core/config.js';
|
|
14
|
+
import { bootstrapEnvFile } from '../core/env.js';
|
|
15
|
+
import {
|
|
16
|
+
detectPackageManager,
|
|
17
|
+
installDependencies,
|
|
18
|
+
normalizePackageManager,
|
|
19
|
+
} from '../core/package-manager.js';
|
|
20
|
+
import { downloadAndExtractTemplate } from '../services/templates.js';
|
|
21
|
+
import { validateProjectName } from '../utils/validation.js';
|
|
22
|
+
|
|
23
|
+
import { runDev } from './dev.js';
|
|
24
|
+
|
|
25
|
+
const DEFAULT_TEMPLATE_REPO = 'hexastack/hexabot-template-starter';
|
|
26
|
+
|
|
27
|
+
interface CreateCommandOptions {
|
|
28
|
+
template?: string;
|
|
29
|
+
pm?: string;
|
|
30
|
+
noInstall?: boolean;
|
|
31
|
+
dev?: boolean;
|
|
32
|
+
docker?: boolean;
|
|
33
|
+
force?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const registerCreateCommand = (program: Command) => {
|
|
37
|
+
program
|
|
38
|
+
.command('create <projectName>')
|
|
39
|
+
.description('Create a new Hexabot project from the starter')
|
|
40
|
+
.option(
|
|
41
|
+
'-t, --template <name>',
|
|
42
|
+
'Project template to use (default: starter)',
|
|
43
|
+
)
|
|
44
|
+
.option('--pm <npm|pnpm|yarn|bun>', 'Preferred package manager')
|
|
45
|
+
.option('--no-install', 'Skip installing dependencies')
|
|
46
|
+
.option('--dev', 'Run hexabot dev after creation')
|
|
47
|
+
.option('--docker', 'Bootstrap Docker env files during creation')
|
|
48
|
+
.option('--force', 'Allow scaffolding into a non-empty directory')
|
|
49
|
+
.action(async (projectName: string, options: CreateCommandOptions) => {
|
|
50
|
+
await createProject(projectName, options);
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const createProject = async (
|
|
55
|
+
projectName: string,
|
|
56
|
+
options: CreateCommandOptions,
|
|
57
|
+
) => {
|
|
58
|
+
if (!validateProjectName(projectName)) {
|
|
59
|
+
console.error(
|
|
60
|
+
chalk.red(
|
|
61
|
+
'Invalid project name. Use lowercase letters, numbers, and dashes.',
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
68
|
+
ensureTargetDirectory(projectPath, options.force);
|
|
69
|
+
|
|
70
|
+
const templateRepo = resolveTemplateRepo(options.template);
|
|
71
|
+
console.log(chalk.blue(`Using template ${templateRepo}`));
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const latestTag = await fetchLatestReleaseTag(templateRepo);
|
|
75
|
+
const templateUrl = `https://github.com/${templateRepo}/archive/refs/tags/${latestTag}.zip`;
|
|
76
|
+
await downloadAndExtractTemplate(templateUrl, projectPath);
|
|
77
|
+
|
|
78
|
+
const pmPreference = normalizePackageManager(options.pm);
|
|
79
|
+
const detectedPm = detectPackageManager(projectPath);
|
|
80
|
+
const packageManager = pmPreference || detectedPm;
|
|
81
|
+
const configOverrides: Partial<ReturnType<typeof loadProjectConfig>> = {
|
|
82
|
+
packageManager,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
ensureProjectConfig(projectPath, configOverrides);
|
|
86
|
+
const config = loadProjectConfig(projectPath);
|
|
87
|
+
|
|
88
|
+
bootstrapEnvFile(projectPath, config.env.localExample, config.env.local, {
|
|
89
|
+
quiet: true,
|
|
90
|
+
});
|
|
91
|
+
if (options.docker) {
|
|
92
|
+
bootstrapEnvFile(
|
|
93
|
+
projectPath,
|
|
94
|
+
config.env.dockerExample,
|
|
95
|
+
config.env.docker,
|
|
96
|
+
{ quiet: true },
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (options.noInstall) {
|
|
101
|
+
console.log(
|
|
102
|
+
chalk.yellow('Skipping dependency installation (--no-install).'),
|
|
103
|
+
);
|
|
104
|
+
} else {
|
|
105
|
+
console.log(
|
|
106
|
+
chalk.blue(`Installing dependencies with ${packageManager}...`),
|
|
107
|
+
);
|
|
108
|
+
installDependencies(packageManager, projectPath);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
logSuccessMessage(projectName, { docker: options.docker });
|
|
112
|
+
|
|
113
|
+
if (options.dev) {
|
|
114
|
+
if (options.noInstall) {
|
|
115
|
+
console.log(
|
|
116
|
+
chalk.yellow(
|
|
117
|
+
'Dependencies were not installed. Run `npm install` before `--dev`.',
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
} else {
|
|
121
|
+
console.log(chalk.blue('Starting dev server...'));
|
|
122
|
+
await runDev({
|
|
123
|
+
cwd: projectPath,
|
|
124
|
+
docker: options.docker,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error(chalk.red('Error creating project.'), error);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const resolveTemplateRepo = (template?: string) => {
|
|
134
|
+
if (!template) {
|
|
135
|
+
return DEFAULT_TEMPLATE_REPO;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (template.includes('/')) {
|
|
139
|
+
return template;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return `hexastack/hexabot-template-${template}`;
|
|
143
|
+
};
|
|
144
|
+
const ensureTargetDirectory = (projectPath: string, force?: boolean) => {
|
|
145
|
+
if (fs.existsSync(projectPath)) {
|
|
146
|
+
const isEmpty = fs.readdirSync(projectPath).length === 0;
|
|
147
|
+
if (!isEmpty && !force) {
|
|
148
|
+
console.error(
|
|
149
|
+
chalk.red(
|
|
150
|
+
`Directory ${projectPath} is not empty. Use --force to scaffold anyway.`,
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const fetchLatestReleaseTag = async (templateRepo: string) => {
|
|
160
|
+
const response = await fetch(
|
|
161
|
+
`https://api.github.com/repos/${templateRepo}/releases/latest`,
|
|
162
|
+
);
|
|
163
|
+
const data = await response.json();
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Failed to fetch the latest release information: ${data.message}`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return data.tag_name;
|
|
171
|
+
};
|
|
172
|
+
const logSuccessMessage = (
|
|
173
|
+
projectName: string,
|
|
174
|
+
options: { docker?: boolean },
|
|
175
|
+
) => {
|
|
176
|
+
console.log('\n');
|
|
177
|
+
console.log(chalk.green(`🎉 Project ${projectName} created successfully.`));
|
|
178
|
+
console.log('\n');
|
|
179
|
+
console.log(chalk.bgYellow.black(`Next steps:`));
|
|
180
|
+
console.log(chalk.gray(`1. Navigate to the project folder:`));
|
|
181
|
+
console.log(chalk.yellow(` cd ${projectName}`));
|
|
182
|
+
if (options.docker) {
|
|
183
|
+
console.log(
|
|
184
|
+
chalk.gray(
|
|
185
|
+
`2. Run dev mode with Docker (or omit --docker for local sqlite):`,
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
console.log(chalk.yellow(` hexabot dev --docker`));
|
|
189
|
+
} else {
|
|
190
|
+
console.log(chalk.gray(`2. Start local dev server (SQLite by default):`));
|
|
191
|
+
console.log(chalk.yellow(` hexabot dev`));
|
|
192
|
+
}
|
|
193
|
+
console.log(chalk.gray(`3. Explore docker helpers if needed:`));
|
|
194
|
+
console.log(chalk.yellow(` hexabot docker up --services postgres`));
|
|
195
|
+
console.log(
|
|
196
|
+
chalk.gray(
|
|
197
|
+
`Need env files? Run ${chalk.white('hexabot env init --docker')}`,
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
console.log('\n');
|
|
201
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
|
|
12
|
+
import { loadProjectConfig, updateProjectConfig } from '../core/config.js';
|
|
13
|
+
import {
|
|
14
|
+
dockerCompose,
|
|
15
|
+
generateComposeFiles,
|
|
16
|
+
resolveComposeFile,
|
|
17
|
+
} from '../core/docker.js';
|
|
18
|
+
import { bootstrapEnvFile, resolveEnvExample } from '../core/env.js';
|
|
19
|
+
import {
|
|
20
|
+
detectPackageManager,
|
|
21
|
+
normalizePackageManager,
|
|
22
|
+
runPackageScript,
|
|
23
|
+
} from '../core/package-manager.js';
|
|
24
|
+
import { checkDocker } from '../core/prerequisites.js';
|
|
25
|
+
import { assertHexabotProject, ensureDockerFolder } from '../core/project.js';
|
|
26
|
+
import { parseServices } from '../utils/services.js';
|
|
27
|
+
|
|
28
|
+
export interface DevOptions {
|
|
29
|
+
docker?: boolean;
|
|
30
|
+
services?: string;
|
|
31
|
+
detach?: boolean;
|
|
32
|
+
env?: string;
|
|
33
|
+
envBootstrap?: boolean;
|
|
34
|
+
pm?: string;
|
|
35
|
+
cwd?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const registerDevCommand = (program: Command) => {
|
|
39
|
+
program
|
|
40
|
+
.command('dev')
|
|
41
|
+
.description('Run the current Hexabot project in development mode')
|
|
42
|
+
.option('--docker', 'Run using Docker')
|
|
43
|
+
.option(
|
|
44
|
+
'--services <list>',
|
|
45
|
+
'Comma-separated services or profiles to enable',
|
|
46
|
+
)
|
|
47
|
+
.option('-d, --detach', 'Detach Docker containers (Docker mode only)')
|
|
48
|
+
.option('--env <file>', 'Env file to use for local dev (default: .env)')
|
|
49
|
+
.option('--no-env-bootstrap', 'Skip env bootstrapping')
|
|
50
|
+
.option('--pm <npm|pnpm|yarn|bun>', 'Override package manager')
|
|
51
|
+
.action(async (options: DevOptions) => {
|
|
52
|
+
await runDev(options);
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const runDev = async (options: DevOptions = {}) => {
|
|
57
|
+
const projectRoot = path.resolve(options.cwd || process.cwd());
|
|
58
|
+
assertHexabotProject(projectRoot);
|
|
59
|
+
const config = loadProjectConfig(projectRoot);
|
|
60
|
+
const normalizedPm = normalizePackageManager(options.pm);
|
|
61
|
+
const pm =
|
|
62
|
+
normalizedPm || config.packageManager || detectPackageManager(projectRoot);
|
|
63
|
+
|
|
64
|
+
if (config.packageManager !== pm) {
|
|
65
|
+
updateProjectConfig(projectRoot, { packageManager: pm });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const shouldBootstrap = options.envBootstrap !== false;
|
|
69
|
+
if (shouldBootstrap) {
|
|
70
|
+
if (options.docker) {
|
|
71
|
+
bootstrapEnvFile(
|
|
72
|
+
projectRoot,
|
|
73
|
+
config.env.dockerExample,
|
|
74
|
+
config.env.docker,
|
|
75
|
+
);
|
|
76
|
+
} else {
|
|
77
|
+
const envFile = options.env || config.env.local;
|
|
78
|
+
const envExample = resolveEnvExample(
|
|
79
|
+
projectRoot,
|
|
80
|
+
envFile,
|
|
81
|
+
config.env.localExample,
|
|
82
|
+
);
|
|
83
|
+
bootstrapEnvFile(projectRoot, envExample, envFile);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (options.docker) {
|
|
88
|
+
await runDockerDev(projectRoot, options, config);
|
|
89
|
+
} else {
|
|
90
|
+
runPackageScript(pm, config.devScript, projectRoot);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const runDockerDev = async (
|
|
95
|
+
projectRoot: string,
|
|
96
|
+
options: DevOptions,
|
|
97
|
+
config: ReturnType<typeof loadProjectConfig>,
|
|
98
|
+
) => {
|
|
99
|
+
checkDocker({ silent: true });
|
|
100
|
+
ensureDockerFolder(projectRoot);
|
|
101
|
+
const servicesInput = parseServices(options.services || '');
|
|
102
|
+
const services = servicesInput.length
|
|
103
|
+
? servicesInput
|
|
104
|
+
: config.docker.defaultServices;
|
|
105
|
+
const composeFile = resolveComposeFile(
|
|
106
|
+
projectRoot,
|
|
107
|
+
config.docker.composeFile,
|
|
108
|
+
);
|
|
109
|
+
const composeArgs = generateComposeFiles(composeFile, services, 'dev');
|
|
110
|
+
const upArgs = ['up', '--build'];
|
|
111
|
+
if (options.detach) {
|
|
112
|
+
upArgs.push('-d');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const composeCommand = `${composeArgs} ${upArgs.join(' ')}`.trim();
|
|
116
|
+
console.log(
|
|
117
|
+
chalk.blue(
|
|
118
|
+
`Starting Docker services${services.length ? ` (${services.join(', ')})` : ''}`,
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
dockerCompose(composeCommand);
|
|
122
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
|
|
11
|
+
import { loadProjectConfig } from '../core/config.js';
|
|
12
|
+
import {
|
|
13
|
+
dockerCompose,
|
|
14
|
+
generateComposeFiles,
|
|
15
|
+
resolveComposeFile,
|
|
16
|
+
} from '../core/docker.js';
|
|
17
|
+
import { bootstrapEnvFile } from '../core/env.js';
|
|
18
|
+
import { checkDocker } from '../core/prerequisites.js';
|
|
19
|
+
import { assertHexabotProject, ensureDockerFolder } from '../core/project.js';
|
|
20
|
+
import { parseServices } from '../utils/services.js';
|
|
21
|
+
|
|
22
|
+
import { runStart } from './start.js';
|
|
23
|
+
|
|
24
|
+
export const registerDockerCommand = (program: Command) => {
|
|
25
|
+
const dockerCommand = program.command('docker').description('Docker helpers');
|
|
26
|
+
|
|
27
|
+
dockerCommand
|
|
28
|
+
.command('up')
|
|
29
|
+
.description('Start Docker services')
|
|
30
|
+
.option('--services <list>', 'Comma-separated services/profiles')
|
|
31
|
+
.option('-d, --detach', 'Run containers in the background')
|
|
32
|
+
.option('--build', 'Build images before starting')
|
|
33
|
+
.action(
|
|
34
|
+
(options: { services?: string; detach?: boolean; build?: boolean }) =>
|
|
35
|
+
runDockerCommand('up', options),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
dockerCommand
|
|
39
|
+
.command('down')
|
|
40
|
+
.description('Stop Docker services')
|
|
41
|
+
.option('--services <list>', 'Comma-separated services/profiles')
|
|
42
|
+
.option('--volumes', 'Remove volumes')
|
|
43
|
+
.action((options: { services?: string; volumes?: boolean }) =>
|
|
44
|
+
runDockerCommand('down', options),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
dockerCommand
|
|
48
|
+
.command('logs [service]')
|
|
49
|
+
.description('View Docker logs')
|
|
50
|
+
.option('-f, --follow', 'Follow logs')
|
|
51
|
+
.option('--since <time>', 'Only show logs since time (e.g. 1h)')
|
|
52
|
+
.action(
|
|
53
|
+
(
|
|
54
|
+
service: string | undefined,
|
|
55
|
+
options: { follow?: boolean; since?: string },
|
|
56
|
+
) => runDockerLogs(service, options),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
dockerCommand
|
|
60
|
+
.command('ps')
|
|
61
|
+
.description('List running services')
|
|
62
|
+
.action(() => runDockerPs());
|
|
63
|
+
|
|
64
|
+
dockerCommand
|
|
65
|
+
.command('start')
|
|
66
|
+
.description('Start Docker services in production mode')
|
|
67
|
+
.option('--services <list>', 'Comma-separated services/profiles')
|
|
68
|
+
.option('-d, --detach', 'Run containers in the background')
|
|
69
|
+
.option('--build', 'Build images before starting')
|
|
70
|
+
.option('--env-bootstrap', 'Generate env files from *.example if missing')
|
|
71
|
+
.action(
|
|
72
|
+
async (options: {
|
|
73
|
+
services?: string;
|
|
74
|
+
detach?: boolean;
|
|
75
|
+
build?: boolean;
|
|
76
|
+
envBootstrap?: boolean;
|
|
77
|
+
}) => {
|
|
78
|
+
await runStart({
|
|
79
|
+
docker: true,
|
|
80
|
+
services: options.services,
|
|
81
|
+
detach: options.detach,
|
|
82
|
+
build: options.build,
|
|
83
|
+
envBootstrap: options.envBootstrap,
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const runDockerCommand = (
|
|
90
|
+
lifecycle: 'up' | 'down',
|
|
91
|
+
options: {
|
|
92
|
+
services?: string;
|
|
93
|
+
detach?: boolean;
|
|
94
|
+
build?: boolean;
|
|
95
|
+
volumes?: boolean;
|
|
96
|
+
},
|
|
97
|
+
) => {
|
|
98
|
+
const projectRoot = path.resolve(process.cwd());
|
|
99
|
+
assertHexabotProject(projectRoot);
|
|
100
|
+
checkDocker({ silent: true });
|
|
101
|
+
const config = loadProjectConfig(projectRoot);
|
|
102
|
+
ensureDockerFolder(projectRoot);
|
|
103
|
+
const services = resolveServices(options.services, config);
|
|
104
|
+
const composeFile = resolveComposeFile(
|
|
105
|
+
projectRoot,
|
|
106
|
+
config.docker.composeFile,
|
|
107
|
+
);
|
|
108
|
+
const composeArgs = generateComposeFiles(
|
|
109
|
+
composeFile,
|
|
110
|
+
services,
|
|
111
|
+
lifecycle === 'up' ? 'dev' : undefined,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (lifecycle === 'up') {
|
|
115
|
+
bootstrapEnvFile(projectRoot, config.env.dockerExample, config.env.docker, {
|
|
116
|
+
quiet: true,
|
|
117
|
+
});
|
|
118
|
+
const commandArgs = ['up'];
|
|
119
|
+
if (options.build) {
|
|
120
|
+
commandArgs.push('--build');
|
|
121
|
+
}
|
|
122
|
+
if (options.detach) {
|
|
123
|
+
commandArgs.push('-d');
|
|
124
|
+
}
|
|
125
|
+
dockerCompose(`${composeArgs} ${commandArgs.join(' ')}`.trim());
|
|
126
|
+
} else {
|
|
127
|
+
const commandArgs = ['down'];
|
|
128
|
+
if (options.volumes) {
|
|
129
|
+
commandArgs.push('-v');
|
|
130
|
+
}
|
|
131
|
+
dockerCompose(`${composeArgs} ${commandArgs.join(' ')}`.trim());
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const runDockerLogs = (
|
|
135
|
+
service: string | undefined,
|
|
136
|
+
options: { follow?: boolean; since?: string },
|
|
137
|
+
) => {
|
|
138
|
+
const projectRoot = path.resolve(process.cwd());
|
|
139
|
+
assertHexabotProject(projectRoot);
|
|
140
|
+
checkDocker({ silent: true });
|
|
141
|
+
const config = loadProjectConfig(projectRoot);
|
|
142
|
+
ensureDockerFolder(projectRoot);
|
|
143
|
+
const composeFile = resolveComposeFile(
|
|
144
|
+
projectRoot,
|
|
145
|
+
config.docker.composeFile,
|
|
146
|
+
);
|
|
147
|
+
const composeArgs = generateComposeFiles(
|
|
148
|
+
composeFile,
|
|
149
|
+
config.docker.defaultServices,
|
|
150
|
+
'dev',
|
|
151
|
+
);
|
|
152
|
+
const args = ['logs'];
|
|
153
|
+
if (options.follow) {
|
|
154
|
+
args.push('-f');
|
|
155
|
+
}
|
|
156
|
+
if (options.since) {
|
|
157
|
+
args.push(`--since ${options.since}`);
|
|
158
|
+
}
|
|
159
|
+
if (service) {
|
|
160
|
+
args.push(service);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
dockerCompose(`${composeArgs} ${args.join(' ')}`.trim());
|
|
164
|
+
};
|
|
165
|
+
const runDockerPs = () => {
|
|
166
|
+
const projectRoot = path.resolve(process.cwd());
|
|
167
|
+
assertHexabotProject(projectRoot);
|
|
168
|
+
checkDocker({ silent: true });
|
|
169
|
+
const config = loadProjectConfig(projectRoot);
|
|
170
|
+
ensureDockerFolder(projectRoot);
|
|
171
|
+
const composeFile = resolveComposeFile(
|
|
172
|
+
projectRoot,
|
|
173
|
+
config.docker.composeFile,
|
|
174
|
+
);
|
|
175
|
+
const composeArgs = generateComposeFiles(
|
|
176
|
+
composeFile,
|
|
177
|
+
config.docker.defaultServices,
|
|
178
|
+
'dev',
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
dockerCompose(`${composeArgs} ps`);
|
|
182
|
+
};
|
|
183
|
+
const resolveServices = (
|
|
184
|
+
servicesInput: string | undefined,
|
|
185
|
+
config: ReturnType<typeof loadProjectConfig>,
|
|
186
|
+
) => {
|
|
187
|
+
const provided = parseServices(servicesInput || '');
|
|
188
|
+
|
|
189
|
+
return provided.length ? provided : config.docker.defaultServices;
|
|
190
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
|
|
12
|
+
import { loadProjectConfig } from '../core/config.js';
|
|
13
|
+
import { bootstrapEnvFile, listEnvStatus } from '../core/env.js';
|
|
14
|
+
import { assertHexabotProject } from '../core/project.js';
|
|
15
|
+
|
|
16
|
+
export const registerEnvCommand = (program: Command) => {
|
|
17
|
+
const envCommand = program.command('env').description('Manage env files');
|
|
18
|
+
|
|
19
|
+
envCommand
|
|
20
|
+
.command('init')
|
|
21
|
+
.description('Copy *.example env files')
|
|
22
|
+
.option('--docker', 'Initialize Docker env file')
|
|
23
|
+
.option('--force', 'Overwrite existing env files')
|
|
24
|
+
.action((options: { docker?: boolean; force?: boolean }) => {
|
|
25
|
+
const projectRoot = path.resolve(process.cwd());
|
|
26
|
+
assertHexabotProject(projectRoot);
|
|
27
|
+
const config = loadProjectConfig(projectRoot);
|
|
28
|
+
|
|
29
|
+
if (options.docker) {
|
|
30
|
+
bootstrapEnvFile(
|
|
31
|
+
projectRoot,
|
|
32
|
+
config.env.dockerExample,
|
|
33
|
+
config.env.docker,
|
|
34
|
+
{ force: options.force },
|
|
35
|
+
);
|
|
36
|
+
} else {
|
|
37
|
+
bootstrapEnvFile(
|
|
38
|
+
projectRoot,
|
|
39
|
+
config.env.localExample,
|
|
40
|
+
config.env.local,
|
|
41
|
+
{
|
|
42
|
+
force: options.force,
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
envCommand
|
|
49
|
+
.command('list')
|
|
50
|
+
.description('Show env file status')
|
|
51
|
+
.action(() => {
|
|
52
|
+
const projectRoot = path.resolve(process.cwd());
|
|
53
|
+
assertHexabotProject(projectRoot);
|
|
54
|
+
const config = loadProjectConfig(projectRoot);
|
|
55
|
+
const statuses = listEnvStatus(projectRoot, config);
|
|
56
|
+
|
|
57
|
+
statuses.forEach((status) => {
|
|
58
|
+
const symbol = status.exists ? chalk.green('✓') : chalk.red('✗');
|
|
59
|
+
console.log(`${symbol} ${status.file}`);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
|
|
9
|
+
import { dockerExec } from '../core/docker.js';
|
|
10
|
+
import { checkDocker } from '../core/prerequisites.js';
|
|
11
|
+
import { ensureDockerFolder } from '../core/project.js';
|
|
12
|
+
|
|
13
|
+
export const registerMigrateCommand = (program: Command) => {
|
|
14
|
+
program
|
|
15
|
+
.command('migrate [args...]')
|
|
16
|
+
.description('Run database migrations')
|
|
17
|
+
.action((args: string[] = []) => {
|
|
18
|
+
checkDocker({ silent: true });
|
|
19
|
+
ensureDockerFolder();
|
|
20
|
+
const migrateArgs = args.join(' ').trim();
|
|
21
|
+
const command = migrateArgs
|
|
22
|
+
? `npm run migrate ${migrateArgs}`
|
|
23
|
+
: 'npm run migrate';
|
|
24
|
+
|
|
25
|
+
dockerExec('api', command, '--user $(id -u):$(id -g)');
|
|
26
|
+
});
|
|
27
|
+
};
|