@h-ai/cli 0.1.0-alpha.65 → 0.1.0-alpha.67
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/README.en.md +66 -251
- package/README.ja.md +67 -252
- package/README.md +66 -251
- package/dist/{chunk-47V5OT22.js → chunk-7DSURTJ4.js} +32 -115
- package/dist/chunk-7DSURTJ4.js.map +1 -0
- package/dist/{chunk-IPG3QNYK.js → chunk-AOQ4N657.js} +212 -8
- package/dist/chunk-AOQ4N657.js.map +1 -0
- package/dist/cli-config-templates-6AHNUGBN.js +5 -0
- package/dist/{cli-config-templates-SJ2MBIR7.js.map → cli-config-templates-6AHNUGBN.js.map} +1 -1
- package/dist/{cli-skill-templates-A5VIR5J6.js → cli-skill-templates-FC3QRKDO.js} +3 -3
- package/dist/{cli-skill-templates-A5VIR5J6.js.map → cli-skill-templates-FC3QRKDO.js.map} +1 -1
- package/dist/index.js +644 -391
- package/dist/index.js.map +1 -1
- package/dist/{src-6PPBKQ6Q.js → src-2V6VP4I5.js} +972 -520
- package/dist/src-2V6VP4I5.js.map +1 -0
- package/package.json +8 -8
- package/templates/README.md +48 -145
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +1 -1
- package/templates/apps/api/.dockerignore.hbs +18 -12
- package/templates/apps/api/.gitignore.hbs +9 -1
- package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +28 -7
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -3
- package/templates/apps/api/hai.build.json.hbs +1 -0
- package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
- package/templates/apps/api/package.json.hbs +10 -8
- package/templates/apps/fullstack/.dockerignore.hbs +18 -14
- package/templates/apps/fullstack/.gitignore.hbs +9 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +23 -8
- package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +3 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +25 -10
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +2 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +73 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -10
- package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +3 -3
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +12 -1
- package/templates/apps/fullstack/hai.build.json.hbs +7 -0
- package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
- package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
- package/templates/apps/fullstack/package.json.hbs +7 -12
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +28 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +0 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +33 -12
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +2 -2
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +2 -2
- package/templates/apps/mobile-app/index.html.hbs +1 -0
- package/templates/apps/mobile-app/public/runtime-config.js +1 -0
- package/templates/apps/mobile-app/src/App.svelte.hbs +2 -2
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +2 -1
- package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +3 -2
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
- package/templates/apps/website/src/routes/+layout.svelte.hbs +3 -3
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
- package/templates/base/.dockerignore.hbs +18 -13
- package/templates/base/.gitignore.hbs +9 -1
- package/templates/base/e2e/app.spec.ts.hbs +10 -0
- package/templates/base/hai.build.json.hbs +1 -0
- package/templates/base/images/app.Dockerfile.hbs +64 -0
- package/templates/base/{docker → images/web}/nginx.conf.hbs +6 -0
- package/templates/base/package.json.hbs +20 -19
- package/templates/base/svelte.config.js.hbs +2 -0
- package/templates/base/vite.config.ts.hbs +9 -3
- package/templates/shared/.gitattributes +11 -0
- package/templates/shared/container-runtime-config.sh +30 -0
- package/templates/shared/run-e2e.mjs +65 -0
- package/templates/shared/vite-runtime-config.ts +54 -0
- package/templates/skills/AGENTS.md +3 -3
- package/templates/skills/bridges/admin/AGENTS.md +1 -1
- package/templates/skills/bridges/api/AGENTS.md +1 -1
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +1 -1
- package/templates/skills/bridges/h5/AGENTS.md +1 -1
- package/templates/skills/bridges/mobile-app/AGENTS.md +1 -1
- package/templates/skills/bridges/website/AGENTS.md +1 -1
- package/templates/skills/hai-api-client/SKILL.md +14 -161
- package/templates/skills/hai-api-client/reference.md +166 -0
- package/templates/skills/hai-api-contract/SKILL.md +15 -273
- package/templates/skills/hai-api-contract/reference.md +278 -0
- package/templates/skills/hai-app-create/SKILL.md +2 -2
- package/templates/skills/hai-app-create/development.md +6 -4
- package/templates/skills/hai-app-create/reference.md +5 -4
- package/templates/skills/hai-app-review/SKILL.md +1 -1
- package/templates/skills/hai-app-tests/SKILL.md +4 -3
- package/templates/skills/hai-app-tests/reference.md +2 -2
- package/templates/skills/hai-app-tests/validation.md +31 -0
- package/templates/skills/hai-audit/SKILL.md +17 -142
- package/templates/skills/hai-audit/reference.md +145 -0
- package/templates/skills/hai-build/SKILL.md +24 -3
- package/templates/skills/hai-build/reference.md +10 -9
- package/templates/skills/hai-cache/SKILL.md +15 -182
- package/templates/skills/hai-cache/reference.md +187 -0
- package/templates/skills/hai-capacitor/SKILL.md +4 -3
- package/templates/skills/hai-capacitor/reference.md +6 -4
- package/templates/skills/hai-ci/SKILL.md +2 -0
- package/templates/skills/hai-core/SKILL.md +3 -3
- package/templates/skills/hai-crypto/SKILL.md +16 -252
- package/templates/skills/hai-crypto/reference.md +256 -0
- package/templates/skills/hai-datapipe/SKILL.md +15 -196
- package/templates/skills/hai-datapipe/reference.md +202 -0
- package/templates/skills/hai-deploy/SKILL.md +28 -271
- package/templates/skills/hai-deploy/middleware.md +6 -18
- package/templates/skills/hai-framework-sync/SKILL.md +2 -2
- package/templates/skills/hai-framework-sync/sync.md +1 -1
- package/templates/skills/hai-iam/SKILL.md +1 -3
- package/templates/skills/hai-kit/SKILL.md +12 -167
- package/templates/skills/hai-kit/reference.md +172 -0
- package/templates/skills/hai-payment/SKILL.md +19 -251
- package/templates/skills/hai-payment/reference.md +256 -0
- package/templates/skills/hai-pr-review/SKILL.md +4 -4
- package/templates/skills/hai-reach/SKILL.md +2 -2
- package/templates/skills/hai-reldb/SKILL.md +1 -1
- package/templates/skills/hai-scheduler/SKILL.md +15 -195
- package/templates/skills/hai-scheduler/reference.md +202 -0
- package/templates/skills/hai-serv/SKILL.md +2 -2
- package/templates/skills/hai-spec-implement/SKILL.md +1 -1
- package/templates/skills/hai-spec-implement/workflow.md +3 -3
- package/templates/skills/hai-storage/SKILL.md +17 -238
- package/templates/skills/hai-storage/reference.md +243 -0
- package/templates/skills/hai-ui/SKILL.md +1 -1
- package/templates/skills/hai-ui/reference.md +11 -5
- package/templates/skills/hai-vecdb/SKILL.md +2 -2
- package/dist/chunk-47V5OT22.js.map +0 -1
- package/dist/chunk-IPG3QNYK.js.map +0 -1
- package/dist/cli-config-templates-SJ2MBIR7.js +0 -5
- package/dist/src-6PPBKQ6Q.js.map +0 -1
- package/templates/apps/api/Dockerfile.hbs +0 -48
- package/templates/apps/fullstack/Dockerfile.hbs +0 -69
- package/templates/base/Dockerfile.hbs +0 -79
- /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
- /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
- /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
package/dist/index.js
CHANGED
|
@@ -1,40 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { generateConfigFile, runtimeEnvironmentVariables, renderEnvironment, moduleEnvironmentVariables, parameterizeConfig } from './chunk-
|
|
3
|
-
import { cliM } from './chunk-
|
|
2
|
+
import { generateConfigFile, runtimeEnvironmentVariables, renderEnvironment, moduleEnvironmentVariables, parameterizeConfig } from './chunk-7DSURTJ4.js';
|
|
3
|
+
import { cliM } from './chunk-AOQ4N657.js';
|
|
4
4
|
import './chunk-2H7UOFLK.js';
|
|
5
|
-
import
|
|
5
|
+
import process4 from 'process';
|
|
6
6
|
import { cac } from 'cac';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
|
-
import path2, { resolve, join } from 'path';
|
|
8
|
+
import path2, { resolve, join, dirname, relative } from 'path';
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
10
10
|
import { wrapAnsi } from 'fast-wrap-ansi';
|
|
11
11
|
import fse from 'fs-extra';
|
|
12
12
|
import Handlebars2 from 'handlebars';
|
|
13
|
-
import { core } from '@h-ai/core';
|
|
14
13
|
import ora from 'ora';
|
|
15
14
|
import prompts from 'prompts';
|
|
16
15
|
import { Buffer } from 'buffer';
|
|
17
|
-
import { execSync, execFileSync } from 'child_process';
|
|
18
|
-
import { randomBytes } from 'crypto';
|
|
19
|
-
import { stringify, parseDocument } from 'yaml';
|
|
20
|
-
import { existsSync,
|
|
16
|
+
import { execSync, execFileSync, spawn } from 'child_process';
|
|
17
|
+
import { randomBytes, createHash } from 'crypto';
|
|
18
|
+
import { stringify, parseDocument, isScalar, isMap } from 'yaml';
|
|
19
|
+
import { readFileSync, existsSync, mkdirSync, mkdtempSync, createReadStream, writeFileSync, renameSync, rmSync, readdirSync, copyFileSync } from 'fs';
|
|
21
20
|
import { createRequire } from 'module';
|
|
21
|
+
import { core } from '@h-ai/core';
|
|
22
22
|
|
|
23
23
|
var CAMEL_CASE_SEPARATOR_REGEX = /[-_\s]+(.)?/g;
|
|
24
24
|
var LEADING_CHAR_REGEX = /^./;
|
|
25
25
|
function writeCliMessage(message) {
|
|
26
|
-
|
|
26
|
+
process4.stdout.write(`${message}
|
|
27
27
|
`);
|
|
28
28
|
}
|
|
29
29
|
function writeCliError(message, error) {
|
|
30
|
-
|
|
30
|
+
process4.stderr.write(`${message}
|
|
31
31
|
`);
|
|
32
|
+
if (error && typeof error === "object" && "error" in error)
|
|
33
|
+
error = error.error;
|
|
32
34
|
if (error !== void 0)
|
|
33
|
-
|
|
35
|
+
process4.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
34
36
|
`);
|
|
35
37
|
}
|
|
36
38
|
function formatPromptDescription(description) {
|
|
37
|
-
return wrapAnsi(description, Math.max(1, (
|
|
39
|
+
return wrapAnsi(description, Math.max(1, (process4.stdout.columns || 80) - 32), { hard: true });
|
|
38
40
|
}
|
|
39
41
|
function toCamelCase(str) {
|
|
40
42
|
return str.replace(CAMEL_CASE_SEPARATOR_REGEX, (_, c) => c ? c.toUpperCase() : "").replace(LEADING_CHAR_REGEX, (c) => c.toLowerCase());
|
|
@@ -133,56 +135,56 @@ var CAPACITOR_SKIPPED_SERVER_FILES = /* @__PURE__ */ new Set([
|
|
|
133
135
|
"src/lib/server/init.ts"
|
|
134
136
|
]);
|
|
135
137
|
var VERSIONS = {
|
|
136
|
-
antfuEslintConfig: "
|
|
137
|
-
capacitorSecureStorage: "
|
|
138
|
+
antfuEslintConfig: "9.5.1",
|
|
139
|
+
capacitorSecureStorage: "8.0.1",
|
|
138
140
|
// UI / Icon
|
|
139
|
-
bitsUi: "
|
|
140
|
-
iconifyJsonTabler: "
|
|
141
|
-
iconifyTailwind: "
|
|
141
|
+
bitsUi: "2.19.5",
|
|
142
|
+
iconifyJsonTabler: "1.2.42",
|
|
143
|
+
iconifyTailwind: "1.2.3",
|
|
142
144
|
// Capacitor
|
|
143
|
-
capacitorAndroid: "
|
|
144
|
-
capacitorApp: "
|
|
145
|
-
capacitorCamera: "
|
|
146
|
-
capacitorCli: "
|
|
147
|
-
capacitorCore: "
|
|
148
|
-
capacitorDevice: "
|
|
149
|
-
capacitorIos: "
|
|
150
|
-
capacitorPreferences: "
|
|
151
|
-
capacitorPushNotifications: "
|
|
152
|
-
capacitorStatusBar: "
|
|
145
|
+
capacitorAndroid: "8.5.3",
|
|
146
|
+
capacitorApp: "8.1.2",
|
|
147
|
+
capacitorCamera: "8.2.5",
|
|
148
|
+
capacitorCli: "8.5.3",
|
|
149
|
+
capacitorCore: "8.5.3",
|
|
150
|
+
capacitorDevice: "8.0.3",
|
|
151
|
+
capacitorIos: "8.5.3",
|
|
152
|
+
capacitorPreferences: "8.0.1",
|
|
153
|
+
capacitorPushNotifications: "8.1.3",
|
|
154
|
+
capacitorStatusBar: "8.0.4",
|
|
153
155
|
// Tailwind / DaisyUI
|
|
154
|
-
daisyui: "
|
|
155
|
-
tailwindcss: "
|
|
156
|
-
tailwindcssVite: "
|
|
156
|
+
daisyui: "5.7.47",
|
|
157
|
+
tailwindcss: "4.3.3",
|
|
158
|
+
tailwindcssVite: "4.3.3",
|
|
157
159
|
// i18n
|
|
158
|
-
inlangParaglide: "
|
|
159
|
-
inlangMessageFormat: "
|
|
160
|
+
inlangParaglide: "2.27.0",
|
|
161
|
+
inlangMessageFormat: "4.5.0",
|
|
160
162
|
// Svelte / Vite
|
|
161
|
-
svelte: "
|
|
162
|
-
svelteKit: "
|
|
163
|
-
svelteAdapterNode: "
|
|
164
|
-
svelteCheck: "
|
|
165
|
-
sveltePlugin: "
|
|
163
|
+
svelte: "5.57.2",
|
|
164
|
+
svelteKit: "2.70.3",
|
|
165
|
+
svelteAdapterNode: "5.5.7",
|
|
166
|
+
svelteCheck: "4.7.6",
|
|
167
|
+
sveltePlugin: "7.3.1",
|
|
166
168
|
// Build / Test
|
|
167
|
-
vite: "
|
|
168
|
-
vitest: "
|
|
169
|
-
tsup: "
|
|
170
|
-
rimraf: "
|
|
169
|
+
vite: "8.3.4",
|
|
170
|
+
vitest: "4.1.11",
|
|
171
|
+
tsup: "8.5.1",
|
|
172
|
+
rimraf: "6.1.3",
|
|
171
173
|
// ESLint
|
|
172
|
-
eslint: "
|
|
173
|
-
eslintPluginFormat: "
|
|
174
|
-
eslintPluginSvelte: "
|
|
175
|
-
svelteEslintParser: "
|
|
174
|
+
eslint: "10.12.0",
|
|
175
|
+
eslintPluginFormat: "2.0.1",
|
|
176
|
+
eslintPluginSvelte: "3.23.1",
|
|
177
|
+
svelteEslintParser: "1.9.0",
|
|
176
178
|
// Node / TS
|
|
177
|
-
nodeTypes: "
|
|
179
|
+
nodeTypes: "26.6.5",
|
|
178
180
|
// Svelte / ESLint / tsup 继续使用稳定的 JavaScript 编译 API。
|
|
179
|
-
typescript: "
|
|
181
|
+
typescript: "6.0.3",
|
|
180
182
|
// E2E
|
|
181
|
-
playwright: "
|
|
183
|
+
playwright: "1.64.0",
|
|
182
184
|
// Validation
|
|
183
|
-
zod: "
|
|
185
|
+
zod: "4.6.5",
|
|
184
186
|
// Tauri
|
|
185
|
-
tauriCli: "
|
|
187
|
+
tauriCli: "2.12.1"
|
|
186
188
|
};
|
|
187
189
|
var FRONTEND_PORTS_DEFAULT = {
|
|
188
190
|
web: 4173,
|
|
@@ -208,6 +210,7 @@ var HAI_CATALOG_PACKAGE_NAMES = [
|
|
|
208
210
|
"@h-ai/audit",
|
|
209
211
|
"@h-ai/cache",
|
|
210
212
|
"@h-ai/capacitor",
|
|
213
|
+
"@h-ai/cli",
|
|
211
214
|
"@h-ai/core",
|
|
212
215
|
"@h-ai/crypto",
|
|
213
216
|
"@h-ai/datapipe",
|
|
@@ -388,6 +391,7 @@ function buildFullstackPackageSpecifiers(useCatalogProtocol) {
|
|
|
388
391
|
function buildApiWorkspaceCatalogPackages(featureMap) {
|
|
389
392
|
const packageNames = [
|
|
390
393
|
...API_WORKSPACE_EXTERNAL_CATALOG_PACKAGE_NAMES,
|
|
394
|
+
"@h-ai/cli",
|
|
391
395
|
"@h-ai/api-client",
|
|
392
396
|
"@h-ai/api-contract",
|
|
393
397
|
"@h-ai/core",
|
|
@@ -422,7 +426,7 @@ function buildApiWorkspaceCatalogPackages(featureMap) {
|
|
|
422
426
|
return pickCatalogPackages(packageNames, FULLSTACK_CATALOG_PACKAGES);
|
|
423
427
|
}
|
|
424
428
|
function buildAppHaiCatalogPackages(options) {
|
|
425
|
-
const packageNames = [];
|
|
429
|
+
const packageNames = ["@h-ai/cli"];
|
|
426
430
|
if (options.isSvelteOnlyApp) {
|
|
427
431
|
packageNames.push("@h-ai/api-client", "@h-ai/api-contract", "@h-ai/capacitor", "@h-ai/ui");
|
|
428
432
|
return pickCatalogPackages(packageNames, HAI_CATALOG_PACKAGES);
|
|
@@ -469,6 +473,7 @@ function buildAppHaiCatalogPackages(options) {
|
|
|
469
473
|
function buildFullstackCatalogPackages(frontends, deploy = false) {
|
|
470
474
|
const packageNames = [
|
|
471
475
|
...FULLSTACK_ROOT_CATALOG_PACKAGE_NAMES,
|
|
476
|
+
"@h-ai/cli",
|
|
472
477
|
...FULLSTACK_CONTRACT_CATALOG_PACKAGE_NAMES,
|
|
473
478
|
...FULLSTACK_SERV_CATALOG_PACKAGE_NAMES,
|
|
474
479
|
...FULLSTACK_SHARED_CATALOG_PACKAGE_NAMES
|
|
@@ -577,6 +582,7 @@ function buildTemplateContext(options) {
|
|
|
577
582
|
for (const f of options.features) {
|
|
578
583
|
featureMap[f] = true;
|
|
579
584
|
}
|
|
585
|
+
featureMap.deploy = options.deploy !== false;
|
|
580
586
|
if (options.appType === "fullstack") {
|
|
581
587
|
for (const feature of ["iam", "db", "cache", "crypto", "storage", "audit"]) featureMap[feature] = true;
|
|
582
588
|
}
|
|
@@ -772,6 +778,14 @@ async function generateFromTemplates(projectPath, context) {
|
|
|
772
778
|
}
|
|
773
779
|
}
|
|
774
780
|
await renderDynamicFiles(root, projectPath, context);
|
|
781
|
+
await fse.copy(path2.join(root, "shared", "run-e2e.mjs"), path2.join(projectPath, "scripts", "run-e2e.mjs"));
|
|
782
|
+
await fse.ensureDir(path2.join(projectPath, "images", "dependencies"));
|
|
783
|
+
await fse.writeFile(path2.join(projectPath, "images", "dependencies", ".gitkeep"), "");
|
|
784
|
+
await fse.copy(path2.join(root, "shared", ".gitattributes"), path2.join(projectPath, ".gitattributes"));
|
|
785
|
+
if (context.isSvelteOnlyApp) {
|
|
786
|
+
await fse.copy(path2.join(root, "shared", "vite-runtime-config.ts"), path2.join(projectPath, "scripts", "vite-runtime-config.ts"));
|
|
787
|
+
await fse.copy(path2.join(root, "shared", "container-runtime-config.sh"), path2.join(projectPath, "images", "web", "19-runtime-config.sh"));
|
|
788
|
+
}
|
|
775
789
|
if (context.isSvelteOnlyApp || context.appType === "fullstack") {
|
|
776
790
|
const navigationDir = context.appType === "fullstack" ? path2.join(projectPath, "packages", `${context.projectName}-shared`, "src", "lib", "navigation") : path2.join(projectPath, "src", "lib", "navigation");
|
|
777
791
|
await fse.copy(path2.join(root, "shared", "hash-router.svelte.ts"), path2.join(navigationDir, "hash-router.svelte.ts"));
|
|
@@ -1053,7 +1067,6 @@ var SELECTABLE_FEATURES = [
|
|
|
1053
1067
|
"vecdb",
|
|
1054
1068
|
"datapipe",
|
|
1055
1069
|
"scheduler",
|
|
1056
|
-
"deploy",
|
|
1057
1070
|
"api-client",
|
|
1058
1071
|
"capacitor"
|
|
1059
1072
|
];
|
|
@@ -1126,13 +1139,14 @@ async function createProject(options) {
|
|
|
1126
1139
|
name: resolvedOptions.name,
|
|
1127
1140
|
appType: resolvedOptions.appType,
|
|
1128
1141
|
features: resolvedOptions.features,
|
|
1142
|
+
deploy: resolvedOptions.deploy,
|
|
1129
1143
|
frontends: resolvedOptions.frontends,
|
|
1130
1144
|
moduleConfigs: resolvedOptions.moduleConfigs,
|
|
1131
1145
|
packageManager: resolvedOptions.packageManager
|
|
1132
1146
|
});
|
|
1133
1147
|
await generateFromTemplates(projectPath, context);
|
|
1134
1148
|
{
|
|
1135
|
-
const { generateConfigFile: generateConfigFile2, generateEnvExample } = await import('./cli-config-templates-
|
|
1149
|
+
const { generateConfigFile: generateConfigFile2, generateEnvExample } = await import('./cli-config-templates-6AHNUGBN.js');
|
|
1136
1150
|
const configs = resolvedOptions.moduleConfigs;
|
|
1137
1151
|
const isApiWorkspace = resolvedOptions.appType === "api";
|
|
1138
1152
|
const isFullstack = resolvedOptions.appType === "fullstack";
|
|
@@ -1206,7 +1220,7 @@ HAI_ADMIN_DEFAULT_PASSWORD=${password}
|
|
|
1206
1220
|
await writeFile(path2.join(envDirectory, ".env.local"), "# \u672C\u673A\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u63D0\u4EA4 Git\uFF1B\u8986\u76D6\u516C\u5171\u57FA\u7840\u548C\u73AF\u5883\u914D\u7F6E\u3002\n");
|
|
1207
1221
|
}
|
|
1208
1222
|
}
|
|
1209
|
-
const { generateSkillFiles } = await import('./cli-skill-templates-
|
|
1223
|
+
const { generateSkillFiles } = await import('./cli-skill-templates-FC3QRKDO.js');
|
|
1210
1224
|
await generateSkillFiles(
|
|
1211
1225
|
projectPath,
|
|
1212
1226
|
resolvedOptions.features,
|
|
@@ -1217,7 +1231,7 @@ HAI_ADMIN_DEFAULT_PASSWORD=${password}
|
|
|
1217
1231
|
const readmeContent = resolvedOptions.appType === "fullstack" ? generateFullstackReadme(resolvedOptions.name, resolvedOptions.packageManager, resolvedOptions.frontends) : resolvedOptions.appType === "api" ? generateApiWorkspaceReadme(resolvedOptions.name, resolvedOptions.packageManager) : generateReadme(resolvedOptions.name, appTypeLabel, resolvedOptions.packageManager, resolvedOptions.appType);
|
|
1218
1232
|
await writeFile(
|
|
1219
1233
|
path2.join(projectPath, "README.md"),
|
|
1220
|
-
readmeContent
|
|
1234
|
+
resolvedOptions.deploy === false ? readmeContent.replace(/### 构建与部署[\s\S]*?(?=\n#{2,3} |$)/, "### \u6784\u5EFA\n\npnpm build\npnpm build --target docker\n\n\u672C\u9879\u76EE\u521B\u5EFA\u65F6\u5173\u95ED\u4E86\u90E8\u7F72\u5DE5\u5177\u3002\n") : readmeContent
|
|
1221
1235
|
);
|
|
1222
1236
|
spinner.succeed();
|
|
1223
1237
|
if (resolvedOptions.git) {
|
|
@@ -1253,7 +1267,7 @@ async function resolveOptions(options) {
|
|
|
1253
1267
|
writeCliMessage("");
|
|
1254
1268
|
const onCancel = () => {
|
|
1255
1269
|
writeCliMessage(chalk.red("\n\u5DF2\u53D6\u6D88"));
|
|
1256
|
-
|
|
1270
|
+
process4.exit(1);
|
|
1257
1271
|
};
|
|
1258
1272
|
let projectName = options.name;
|
|
1259
1273
|
if (!projectName && useDefaults) {
|
|
@@ -1319,7 +1333,7 @@ async function resolveOptions(options) {
|
|
|
1319
1333
|
let selectedFeatures = [];
|
|
1320
1334
|
const appTypeDefaults = APP_TYPES[selectedAppType].defaultFeatures;
|
|
1321
1335
|
if (selectedAppType === "fullstack") {
|
|
1322
|
-
selectedFeatures = resolveFeatureDependencies(
|
|
1336
|
+
selectedFeatures = resolveFeatureDependencies(appTypeDefaults);
|
|
1323
1337
|
} else if (selectedTemplate === "custom" && !options.features && useDefaults) {
|
|
1324
1338
|
selectedFeatures = resolveFeatureDependencies(appTypeDefaults);
|
|
1325
1339
|
} else if (selectedTemplate === "custom" && !options.features) {
|
|
@@ -1346,6 +1360,9 @@ async function resolveOptions(options) {
|
|
|
1346
1360
|
const merged = /* @__PURE__ */ new Set([...templateFeatures, ...appTypeDefaults]);
|
|
1347
1361
|
selectedFeatures = resolveFeatureDependencies(Array.from(merged));
|
|
1348
1362
|
}
|
|
1363
|
+
selectedFeatures = selectedFeatures.filter((feature) => feature !== "deploy");
|
|
1364
|
+
if (options.deploy !== false)
|
|
1365
|
+
selectedFeatures.push("deploy");
|
|
1349
1366
|
const moduleConfigs = selectedAppType === "fullstack" ? { ...buildDefaultModuleConfigs(selectedFeatures, projectName), ...options.moduleConfigs } : options.moduleConfigs ?? (useDefaults ? buildDefaultModuleConfigs(selectedFeatures, projectName) : await promptModuleConfigs(selectedFeatures, projectName));
|
|
1350
1367
|
let addExamples = options.examples;
|
|
1351
1368
|
if (addExamples === void 0 && useDefaults) {
|
|
@@ -1427,6 +1444,7 @@ async function resolveOptions(options) {
|
|
|
1427
1444
|
frontends: selectedFrontends,
|
|
1428
1445
|
template: selectedTemplate ?? "default",
|
|
1429
1446
|
features: selectedFeatures,
|
|
1447
|
+
deploy: options.deploy !== false,
|
|
1430
1448
|
moduleConfigs,
|
|
1431
1449
|
examples: addExamples ?? true,
|
|
1432
1450
|
install: install ?? true,
|
|
@@ -1509,7 +1527,7 @@ async function promptModuleConfigs(features, projectName) {
|
|
|
1509
1527
|
const configs = {};
|
|
1510
1528
|
const onCancel = () => {
|
|
1511
1529
|
writeCliMessage(chalk.red("\n\u5DF2\u53D6\u6D88"));
|
|
1512
|
-
|
|
1530
|
+
process4.exit(1);
|
|
1513
1531
|
};
|
|
1514
1532
|
writeCliMessage("");
|
|
1515
1533
|
writeCliMessage(chalk.bold(" \u2699 \u6A21\u5757\u914D\u7F6E"));
|
|
@@ -1723,24 +1741,20 @@ function normalizeChildOutput(output) {
|
|
|
1723
1741
|
return "";
|
|
1724
1742
|
}
|
|
1725
1743
|
function generateAiDevelopmentReadme() {
|
|
1726
|
-
return
|
|
1744
|
+
return `### AI \u8F85\u52A9\u5F00\u53D1
|
|
1727
1745
|
|
|
1728
1746
|
\u4ECE\u672C\u9879\u76EE\u6839\u76EE\u5F55\u6253\u5F00\u6240\u7528\u7F16\u7801\u5DE5\u5177\uFF0C\u5148\u8BFB\u53D6 [AGENTS.md](./AGENTS.md)\u3002Claude Code \u901A\u8FC7 [CLAUDE.md](./CLAUDE.md) \u52A0\u8F7D\u9879\u76EE\u89C4\u5219\uFF1BGitHub Copilot \u4F7F\u7528 [.github/copilot-instructions.md](./.github/copilot-instructions.md)\uFF0COpenCode \u4F7F\u7528 AGENTS.md \u548C [opencode.json](./opencode.json)\u3002
|
|
1729
1747
|
|
|
1730
|
-
\
|
|
1731
|
-
|
|
1732
|
-
Codex\u3001Cursor\u3001Copilot\u3001OpenCode \u4ECE .agents/skills/ \u53D1\u73B0\u5171\u4EAB\u6280\u80FD\uFF1BClaude Code \u901A\u8FC7 CLAUDE.md \u5F15\u7528 AGENTS.md\uFF0C\u5E76\u6309\u9879\u76EE\u6307\u5F15\u8BFB\u53D6 .agents/skills/ \u7684\u540C\u4E00\u4EFD\u6B63\u6587\u3002\u9700\u6C42\u6216\u8BBE\u8BA1\u6587\u6863\u5728\u751F\u6210\u524D\u4F7F\u7528 [\u6587\u6863\u5BA1\u67E5 Skill](./.agents/skills/hai-spec-review/SKILL.md) \u4E25\u683C\u68C0\u67E5\u5B8C\u5907\u6027\u3001\u6B67\u4E49\u3001\u6280\u672F\u6B63\u786E\u6027\u548C\u547D\u540D\u4E00\u81F4\u6027\uFF0C\u95EE\u9898\u89E3\u51B3\u540E\u518D\u751F\u6210\u3002\u666E\u901A\u65B0\u589E\u3001\u4FEE\u6539\u3001\u4FEE\u590D\u4EFB\u52A1\u4F7F\u7528 [\u5E94\u7528\u5F00\u53D1 Skill](./.agents/skills/hai-app-create/SKILL.md) \u548C [\u5F00\u53D1\u89C4\u8303\u4E0E\u6846\u67B6\u80FD\u529B\u8DEF\u7531](./.agents/skills/hai-app-create/development.md)\uFF1B\u5177\u4F53\u6A21\u5757\u518D\u6309\u9700\u8BFB\u53D6\uFF0C\u4E0D\u80FD\u628A Skill \u5DF2\u4EA4\u4ED8\u5F53\u6210\u4F9D\u8D56\u5DF2\u5B89\u88C5\u6216\u914D\u7F6E\u5DF2\u542F\u7528\u3002
|
|
1733
|
-
|
|
1734
|
-
\u6839\u636E\u6587\u6863\u89C4\u5212\u3001\u5B9E\u65BD\u3001\u8FD4\u4FEE\u6216\u6309\u5DF2\u63D0\u4EA4\u6587\u6863\u5DEE\u5F02\u540C\u6B65\u5B9E\u73B0\u65F6\uFF0C\u4F7F\u7528 [\u6587\u6863\u5B9E\u65BD Skill](./.agents/skills/hai-spec-implement/SKILL.md)\uFF1A\u7EF4\u62A4 docs/task-list.md \u4E0E docs/tasks/\uFF0C\u8BB0\u5F55\u5DF2\u9A8C\u6536\u6587\u6863\u548C\u5B9E\u73B0\u7684 Git SHA\uFF0C\u6BD4\u8F83\u56FA\u5B9A\u76EE\u6807\u7248\u672C\u540E\u66F4\u65B0\u539F\u4EFB\u52A1\u6216\u65B0\u5EFA\u4EFB\u52A1\uFF0C\u6BCF\u9879\u72EC\u7ACB\u5206\u652F TDD\u3001\u9A8C\u8BC1\u5E76\u672C\u5730\u5408\u5E76\uFF1B\u5168\u90E8\u5DEE\u5F02\u4E0E\u6574\u4F53\u9A8C\u6536\u901A\u8FC7\u540E\u624D\u63A8\u8FDB\u57FA\u7EBF\u3002\u8FD4\u4FEE\u6CBF\u7528\u539F\u4EFB\u52A1\u4E0E\u5206\u652F\u3002\u4EC5\u89C4\u5212\u4E0D\u5F00\u59CB\u5B9E\u73B0\uFF0C\u6307\u5B9A\u4EFB\u52A1\u5148\u6838\u9A8C\u4F9D\u8D56\u3002
|
|
1748
|
+
\u751F\u6210\u7684\u9875\u9762\u548C\u4E1A\u52A1\u89C4\u5219\u662F\u8D77\u70B9\uFF0C\u53EF\u4EE5\u6309\u9700\u6C42\u8C03\u6574\u3002\u628A\u529F\u80FD\u3001\u6743\u9650\u548C\u9A8C\u6536\u6761\u4EF6\u544A\u8BC9 AI\uFF0C\u518D\u8BA9\u5B83\u8BFB\u53D6\u5BF9\u5E94\u6280\u80FD\u4E0E\u5F53\u524D\u5B89\u88C5\u7248\u672C\u7684 API\u3002
|
|
1735
1749
|
|
|
1736
|
-
\
|
|
1750
|
+
\u666E\u901A\u529F\u80FD\u5F00\u53D1\u4F7F\u7528 [\u5E94\u7528\u5F00\u53D1 Skill](./.agents/skills/hai-app-create/SKILL.md)\u3002\u9700\u6C42\u6587\u6863\u8F83\u591A\u65F6\uFF0C\u5148\u7528 [\u6587\u6863\u5BA1\u67E5 Skill](./.agents/skills/hai-spec-review/SKILL.md) \u627E\u51FA\u7F3A\u5931\u548C\u77DB\u76FE\uFF0C\u518D\u7528 [\u6587\u6863\u5B9E\u65BD Skill](./.agents/skills/hai-spec-implement/SKILL.md) \u7EF4\u62A4 docs/task-list.md \u4E0E docs/tasks/\uFF0C\u9010\u9879\u5B9E\u73B0\u3001\u6D4B\u8BD5\u548C\u4EA4\u4ED8\u3002
|
|
1737
1751
|
|
|
1738
|
-
\u5DF2\u6709
|
|
1752
|
+
\u5DF2\u6709\u9879\u76EE\u53EF\u8FD0\u884C hai init \u8865\u9F50\u7F3A\u5931\u7684 AI \u5165\u53E3\uFF1B\u5B83\u4FDD\u7559\u5DF2\u6709\u81EA\u5B9A\u4E49\u5185\u5BB9\u3002\u5347\u7EA7\u6846\u67B6\u4F7F\u7528 [\u6846\u67B6\u5347\u7EA7 Skill](./.agents/skills/hai-framework-sync/SKILL.md)\uFF0C\u540C\u65F6\u6838\u5BF9\u4F9D\u8D56\u3001\u914D\u7F6E\u3001\u4EE3\u7801\u548C\u6280\u80FD\u3002
|
|
1739
1753
|
|
|
1740
1754
|
`;
|
|
1741
1755
|
}
|
|
1742
1756
|
function generateEnvironmentReadme() {
|
|
1743
|
-
return
|
|
1757
|
+
return `### \u5206\u73AF\u5883\u914D\u7F6E
|
|
1744
1758
|
|
|
1745
1759
|
\u8FD0\u884C\u53C2\u6570\u5728 \`.env.example\` \u4E2D\u9010\u9879\u8BF4\u660E\u7528\u9014\u3001\u679A\u4E3E\u548C\u8303\u56F4\u3002\`.env\` \u4E3A\u516C\u5171\u57FA\u7840\uFF0C\`.env.dev\` / \`.env.test\` / \`.env.prod\` \u4E3A\u73AF\u5883\u8986\u76D6\uFF0C\u5747\u63D0\u4EA4 Git\uFF1B\`.env.local\` \u4E3A\u672C\u673A\u79C1\u6709\u8986\u76D6\uFF0C\u4E0D\u63D0\u4EA4 Git\u3002\u4F18\u5148\u7EA7\u4E3A\u8FDB\u7A0B\u73AF\u5883 > \`.env.local\` > \u73AF\u5883\u6587\u4EF6 > \`.env\`\u3002YAML \u663E\u5F0F\u5F15\u7528 \`HAI_<\u914D\u7F6E\u540D>_<\u8DEF\u5F84>\` \u5E76\u4FDD\u7559\u672C\u5730\u56DE\u9000\uFF0CcamelCase \u4E0D\u62C6\u8BCD\u3002\u5B8C\u6574\u547D\u4EE4\u3001\u52A0\u8F7D\u4F18\u5148\u7EA7\u548C Provider \u5207\u6362\u89C1 [\u73AF\u5883\u914D\u7F6E](.agents/skills/hai-app-create/development.md#\u73AF\u5883\u914D\u7F6E)\u3002
|
|
1746
1760
|
|
|
@@ -1749,40 +1763,63 @@ function generateEnvironmentReadme() {
|
|
|
1749
1763
|
| \u5F00\u53D1 / \u6D4B\u8BD5\u6A21\u5F0F / \u751F\u4EA7\u6A21\u5F0F\u9884\u89C8 | \`pnpm dev\` / \`dev:test\` / \`dev:prod\` |
|
|
1750
1764
|
| \u7C7B\u578B\u68C0\u67E5\uFF08check \u4E3A\u522B\u540D\uFF09 | \`pnpm typecheck\` |
|
|
1751
1765
|
| \u68C0\u67E5 / \u81EA\u52A8\u4FEE\u590D\u683C\u5F0F | \`pnpm lint\` / \`lint:fix\` |
|
|
1752
|
-
| \u6784\u5EFA / \
|
|
1766
|
+
| \u6E90\u7801\u6784\u5EFA / \u955C\u50CF\u6784\u5EFA | \`pnpm build\` / \`pnpm build --target docker\` |
|
|
1753
1767
|
| \u5355\u5143\u6D4B\u8BD5 / \u76D1\u542C / \u6D4F\u89C8\u5668\u9A8C\u6536 | \`pnpm test\` / \`test:watch\` / \`test:e2e\` |
|
|
1754
1768
|
| Node \u670D\u52A1\u6309\u73AF\u5883\u542F\u52A8\uFF08\u6784\u5EFA\u540E\uFF09 | \`pnpm start\` / \`start:test\` / \`start:prod\` |
|
|
1755
1769
|
|
|
1756
1770
|
Vite \u5BA2\u6237\u7AEF\u4F7F\u7528 \`preview\` \u9884\u89C8\u9759\u6001\u4EA7\u7269\uFF0CNode \u670D\u52A1\u4F7F\u7528 \`start\`\uFF1B\u7EAF contract/shared \u5E93\u53EA\u63D0\u4F9B\u9002\u7528\u7684\u6784\u5EFA\u548C\u8D28\u91CF\u547D\u4EE4\u3002API workspace \u7684\u73AF\u5883\u6587\u4EF6\u4F4D\u4E8E \`apps/<name>-service\`\uFF0Cfullstack \u4F4D\u4E8E\u5DE5\u7A0B\u6839\u3002`;
|
|
1757
1771
|
}
|
|
1772
|
+
function generateBuildDeploymentReadme(pm, appDir) {
|
|
1773
|
+
return `### \u6784\u5EFA\u4E0E\u90E8\u7F72
|
|
1774
|
+
|
|
1775
|
+
\u521B\u5EFA\u65F6\u9ED8\u8BA4\u5305\u542B\u90E8\u7F72\u5DE5\u5177\uFF1B\u4E0D\u9700\u8981\u65F6\u4F7F\u7528 hai create --no-deploy\u3002
|
|
1776
|
+
\u6BCF\u4E2A\u955C\u50CF\u5BF9\u5E94 images/ \u4E2D\u4E00\u4E2A Dockerfile\uFF0Chai.build.json \u7528 dockerfile \u6307\u5B9A\u6587\u4EF6\u3002
|
|
1777
|
+
\u4E0B\u8F7D\u3001\u5B89\u88C5\u548C\u6574\u7406\u751F\u4EA7\u4F9D\u8D56\u590D\u7528\u540C\u4E00\u4EFD\u5305\u7F13\u5B58\u4E0E SQLite \u9A71\u52A8\u7F13\u5B58\uFF0C\u907F\u514D\u91CD\u590D\u4E0B\u8F7D\u3002
|
|
1778
|
+
\u9ED8\u8BA4\u5BB9\u5668\u7528\u4E8E CPU \u8FD0\u884C\u6216\u8FDE\u63A5\u8FDC\u7A0B AI \u670D\u52A1\uFF0C\u4E0D\u989D\u5916\u4E0B\u8F7D GPU \u8FD0\u884C\u5E93\uFF1B\u9700\u8981\u4F7F\u7528\u90E8\u7F72\u673A\u5668\u4E0A\u7684 GPU \u65F6\uFF0C\u81EA\u884C\u5B9A\u5236\u955C\u50CF\u548C\u5BB9\u5668\u8FD0\u884C\u53C2\u6570\u3002
|
|
1779
|
+
|
|
1780
|
+
\`\`\`sh
|
|
1781
|
+
${pm} build
|
|
1782
|
+
${pm} build --target docker
|
|
1783
|
+
# \u6784\u5EFA\u548C\u90E8\u7F72\u4E00\u6B21\u5B8C\u6210
|
|
1784
|
+
${pm} exec hai deploy ${appDir} --env test --build
|
|
1785
|
+
# \u590D\u7528\u5DF2\u6709\u955C\u50CF\uFF1B\u53EA\u751F\u6210\u5B8C\u6574\u79BB\u7EBF\u5305\u53CA\u8DE8\u5E73\u53F0\u90E8\u7F72\u811A\u672C
|
|
1786
|
+
${pm} exec hai deploy ${appDir} --env prod --bundle-only
|
|
1787
|
+
# \u53EA\u66F4\u65B0\u524D\u7AEF\uFF08\u5DE5\u7A0B\u5B58\u5728 web \u955C\u50CF\u65F6\uFF09
|
|
1788
|
+
${pm} build --target docker --images web
|
|
1789
|
+
${pm} exec hai deploy ${appDir} --env prod --services web
|
|
1790
|
+
\`\`\`
|
|
1791
|
+
|
|
1792
|
+
\u6784\u5EFA\u4E0D\u8BFB\u53D6\u90E8\u7F72\u73AF\u5883\uFF1B\u4EA7\u7269\u4F4D\u4E8E .hai/build/docker/<buildId>\uFF0C\u7528 --artifact-dir \u7CBE\u786E\u9009\u62E9\u3002
|
|
1793
|
+
\u5E94\u7528\u6839 package.json.version \u9A71\u52A8\u7248\u672C\uFF0C\u540C\u7248\u672C\u5141\u8BB8\u91CD\u65B0\u6784\u5EFA\u548C\u53D1\u5E03\uFF1B\u6E05\u5355\u8BB0\u5F55\u72EC\u7ACB\u6784\u5EFA\u7F16\u53F7\u4E0E\u6821\u9A8C\u548C\u3002
|
|
1794
|
+
\u5B8C\u6574\u79BB\u7EBF\u5305\u81EA\u52A8\u643A\u5E26\u81EA\u5EFA\u4E2D\u95F4\u4EF6\u955C\u50CF\uFF1B\u51C6\u5907\u4E2D\u95F4\u4EF6\u955C\u50CF\u9700\u8981\u6784\u5EFA\u673A\u6709\u53EF\u7528 Podman/Docker\uFF0C\u7F3A\u955C\u50CF\u65F6\u5728\u6253\u5305\u9636\u6BB5\u62C9\u53D6\u3002
|
|
1795
|
+
\u670D\u52A1\u5668\u9700\u8981 Linux\u3001Docker/Podman\u3001Docker Compose v2 \u6216 podman-compose 1.6.0 \u53CA\u4EE5\u4E0A\u3001sh\u3001tar\u3001sha256sum\u3001awk\u3001curl\uFF0C\u542F\u52A8\u9636\u6BB5\u4E0D\u8054\u7F51\u62C9\u53D6\u3002
|
|
1796
|
+
tar \u65C1\u7684\u90E8\u7F72\u76EE\u5F55\u63D0\u4F9B deploy.ps1\uFF08Windows\uFF09\u548C deploy.sh\uFF08Linux/macOS\uFF09\uFF0C\u6309\u5DF2\u4FDD\u5B58\u7684 SSH \u53C2\u6570\u4E0A\u4F20\u3001\u5BFC\u5165\u3001\u542F\u52A8\u5E76\u9A8C\u8BC1 HTTP\u3002
|
|
1797
|
+
\u5B8C\u5168\u9694\u79BB\u65F6\u624B\u5DE5\u642C\u8FD0 tar\uFF0C\u72EC\u7ACB\u76EE\u5F55\u89E3\u5305\u540E\u6267\u884C sh apply.sh\uFF0C\u6216\u4F20\u5165\u90E8\u7F72\u76EE\u5F55\u8986\u76D6\u9ED8\u8BA4\u4F4D\u7F6E\u3002
|
|
1798
|
+
\u90E8\u5206\u66F4\u65B0\u8981\u6C42\u5DF2\u6709\u6210\u529F\u7684\u5B8C\u6574\u90E8\u7F72\uFF0C\u4FDD\u7559\u672A\u9009\u670D\u52A1\u53CA\u6570\u636E\u5377\uFF1B\u5931\u8D25\u8FD4\u56DE\u975E\u96F6\u9000\u51FA\u7801\uFF0C\u5DF2\u6709\u90E8\u7F72\u5C1D\u8BD5\u6062\u590D\u9009\u4E2D\u670D\u52A1\u3002
|
|
1799
|
+
\u4E2D\u95F4\u4EF6\u8FDE\u63A5\u9F50\u5168\u65F6\u4F7F\u7528 --skip-middleware \u8DF3\u8FC7\u914D\u7F6E\u5411\u5BFC\u3002\u5305\u4E2D\u5305\u542B\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u63D0\u4EA4 Git\u3002
|
|
1800
|
+
`;
|
|
1801
|
+
}
|
|
1758
1802
|
function generateReadme(name, appTypeLabel, pm, appType) {
|
|
1759
1803
|
const install = getInstallCommand(pm);
|
|
1760
|
-
const isStaticImage = appType === "mobile-app";
|
|
1761
|
-
const containerPort = isStaticImage ? 8080 : 3e3;
|
|
1762
|
-
const runtimeOptions = isStaticImage ? "" : ` --env-file .env --mount type=bind,source=/absolute/path/to/config,target=/app/config,readonly --mount type=volume,source=${name}-data,target=/app/data`;
|
|
1763
|
-
const embeddedDataBuild = isStaticImage ? "" : `
|
|
1764
|
-
# \u5E26 data/ \u521D\u59CB\u6570\u636E\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
|
|
1765
|
-
${getRunCommand(pm, "docker:build:data")}
|
|
1766
|
-
${getRunCommand(pm, "podman:build:data")}`;
|
|
1767
|
-
const runtimeNotes = isStaticImage ? "- \u8BE5\u6A21\u677F\u751F\u6210\u9759\u6001 Web \u955C\u50CF\uFF0C\u4E0D\u5305\u542B\u53EF\u5199\u6570\u636E\u5E93\u76EE\u5F55\u3002" : `\u4EE5\u4E0A\u4E09\u4E2A\u8FD0\u884C\u65F6\u53C2\u6570\u5747\u53EF\u6309\u9700\u5220\u9664\uFF1A
|
|
1768
|
-
|
|
1769
|
-
| \u53C2\u6570 | \u7528\u9014 | \u53EF\u9009\u6027 |
|
|
1770
|
-
| --- | --- | --- |
|
|
1771
|
-
| \`--env-file .env\` | \u6279\u91CF\u4F20\u5165\u73AF\u5883\u53D8\u91CF\uFF1B\u4E5F\u53EF\u6539\u7528\u591A\u4E2A \`-e NAME=value\`\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u548C YAML \u9ED8\u8BA4\u503C\u3002 |
|
|
1772
|
-
| \`--mount type=bind,...target=/app/config,readonly\` | \u7528\u5BBF\u4E3B\u673A\u914D\u7F6E\u76EE\u5F55\u8986\u76D6\u955C\u50CF\u5185 \`/app/config\`\uFF1B\u5FC5\u987B\u628A \`source\` \u6539\u6210\u771F\u5B9E\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\`readonly\` \u9632\u6B62\u5BB9\u5668\u4FEE\u6539\u914D\u7F6E\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u81EA\u5E26\u914D\u7F6E\u3002 |
|
|
1773
|
-
| \`--mount type=volume,...target=/app/data\` | \u521B\u5EFA\u6216\u590D\u7528 named volume\uFF0C\u6301\u4E45\u5316 SQLite \u548C\u672C\u5730\u5B58\u50A8\u3002 | \u53EF\u9009\u4F46\u751F\u4EA7\u73AF\u5883\u5EFA\u8BAE\u4FDD\u7559\uFF1B\u5220\u9664\u540E\u6570\u636E\u53EA\u5199\u5165\u5BB9\u5668\u5C42\uFF0C\u4F7F\u7528 \`--rm\` \u5220\u9664\u5BB9\u5668\u65F6\u4F1A\u4E22\u5931\u3002 |
|
|
1774
|
-
|
|
1775
|
-
- \u5E26\u6570\u636E\u6784\u5EFA\u4F1A\u628A\u5F53\u524D \`data/\`\uFF08\u4F8B\u5982\u9884\u7F6E SQLite \u6587\u4EF6\uFF09\u56FA\u5316\u4E3A\u955C\u50CF\u521D\u59CB\u5FEB\u7167\uFF1B\u8FD0\u884C\u540E\u4EA7\u751F\u7684\u65B0\u6570\u636E\u4ECD\u5E94\u5199\u5165 \`/app/data\` volume\u3002`;
|
|
1776
|
-
const testPath = isStaticImage ? "/health" : "/";
|
|
1777
1804
|
return `# ${name}
|
|
1778
1805
|
|
|
1806
|
+
## \u7B80\u4ECB
|
|
1807
|
+
|
|
1779
1808
|
\u57FA\u4E8E hai Agent Framework \u6784\u5EFA\u7684${appTypeLabel}\u5E94\u7528\u3002
|
|
1780
1809
|
|
|
1781
|
-
## \
|
|
1810
|
+
## \u6838\u5FC3\u529F\u80FD\u6E05\u5355
|
|
1811
|
+
|
|
1812
|
+
- \u5F00\u53D1\u670D\u52A1\u5668\u3001\u6E90\u7801\u7F16\u8BD1\u4E0E\u6D4F\u89C8\u5668\u6D4B\u8BD5\u3002
|
|
1813
|
+
- \u6309\u6240\u9009\u6A21\u5757\u63D0\u4F9B\u9875\u9762\u548C\u670D\u52A1\u914D\u7F6E\u3002
|
|
1814
|
+
- \u72EC\u7ACB Dockerfile \u4E0E\u955C\u50CF\u5F52\u6863\uFF0C\u4FBF\u4E8E\u4EA4\u4ED8\u3002
|
|
1815
|
+
|
|
1816
|
+
## \u4F7F\u7528\u5165\u95E8
|
|
1817
|
+
|
|
1818
|
+
### \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
|
|
1782
1819
|
|
|
1783
1820
|
\u9875\u9762\u7EDF\u4E00\u7EE7\u627F\u6839\u5E03\u5C40\u7684 header/footer\u3002${appType === "mobile-app" ? "\u9875\u9762\u653E src/lib/pages/**/*.svelte\uFF0Chash \u8DEF\u7531\u81EA\u52A8\u53D1\u73B0\uFF0C\u5237\u65B0\u4E0E\u524D\u8FDB\u540E\u9000\u6062\u590D\u5F53\u524D\u9875\u3002" : "\u4F7F\u7528 SvelteKit \u81EA\u52A8\u6587\u4EF6\u8DEF\u7531\uFF0C\u65B0\u589E +page.svelte \u65E0\u9700\u7EF4\u62A4\u8DEF\u7531\u8868\uFF1B\u5237\u65B0\u5B9A\u4F4D\u4E0E\u670D\u52A1\u7AEF\u9274\u6743\u4FDD\u6301\u4E00\u81F4\u3002"}
|
|
1784
1821
|
|
|
1785
|
-
|
|
1822
|
+
### \u5F00\u53D1
|
|
1786
1823
|
|
|
1787
1824
|
\`\`\`bash
|
|
1788
1825
|
${install}
|
|
@@ -1791,57 +1828,57 @@ ${getRunCommand(pm, "dev")}
|
|
|
1791
1828
|
|
|
1792
1829
|
${generateEnvironmentReadme()}
|
|
1793
1830
|
|
|
1794
|
-
|
|
1831
|
+
### \u6784\u5EFA
|
|
1795
1832
|
|
|
1796
1833
|
\`\`\`bash
|
|
1797
1834
|
${getRunCommand(pm, "build")}
|
|
1798
|
-
${getRunCommand(pm, "
|
|
1835
|
+
${getRunCommand(pm, "paraglide:compile")}
|
|
1799
1836
|
${getRunCommand(pm, "preview")}
|
|
1800
1837
|
\`\`\`
|
|
1801
1838
|
|
|
1802
|
-
|
|
1839
|
+
${generateBuildDeploymentReadme(pm, `.`)}
|
|
1803
1840
|
|
|
1804
|
-
### \
|
|
1841
|
+
${generateAiDevelopmentReadme()}### \u6587\u6863
|
|
1805
1842
|
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
${getRunCommand(pm, "docker:build")}
|
|
1809
|
-
${getRunCommand(pm, "podman:build")}${embeddedDataBuild}
|
|
1810
|
-
\`\`\`
|
|
1811
|
-
|
|
1812
|
-
### \u8FD0\u884C\u955C\u50CF
|
|
1813
|
-
|
|
1814
|
-
\`\`\`bash
|
|
1815
|
-
# Docker\uFF1B\u4F7F\u7528 Podman \u65F6\u5C06 docker \u66FF\u6362\u4E3A podman
|
|
1816
|
-
docker run --rm -d --name ${name} -p ${containerPort}:${containerPort}${runtimeOptions} ${name}:latest
|
|
1817
|
-
\`\`\`
|
|
1843
|
+
- [hai Agent Framework](https://github.com/hai-series/hai-framework)
|
|
1844
|
+
- [Svelte 5](https://svelte.dev/)
|
|
1818
1845
|
|
|
1819
|
-
|
|
1846
|
+
## \u5F00\u53D1\u6D4B\u8BD5
|
|
1820
1847
|
|
|
1821
|
-
\`\`\`
|
|
1822
|
-
|
|
1823
|
-
|
|
1848
|
+
\`\`\`sh
|
|
1849
|
+
${getRunCommand(pm, "typecheck")}
|
|
1850
|
+
${getRunCommand(pm, "lint")}
|
|
1851
|
+
${getRunCommand(pm, "build")}
|
|
1852
|
+
${getRunCommand(pm, "test")}
|
|
1853
|
+
${getRunCommand(pm, "test:e2e")}
|
|
1824
1854
|
\`\`\`
|
|
1825
1855
|
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
${generateAiDevelopmentReadme()}## \u6587\u6863
|
|
1856
|
+
## \u8BB8\u53EF\u8BC1
|
|
1829
1857
|
|
|
1830
|
-
-
|
|
1831
|
-
- [Svelte 5](https://svelte.dev/)
|
|
1858
|
+
Apache-2.0
|
|
1832
1859
|
`;
|
|
1833
1860
|
}
|
|
1834
1861
|
function generateApiWorkspaceReadme(name, pm) {
|
|
1835
1862
|
return `# ${name}
|
|
1836
1863
|
|
|
1837
|
-
|
|
1864
|
+
## \u7B80\u4ECB
|
|
1865
|
+
|
|
1866
|
+
\u540E\u7AEF API \u5DE5\u7A0B\uFF0C\u63A5\u53E3\u5B9A\u4E49\u4E0E\u670D\u52A1\u5B9E\u73B0\u5206\u5305\u7EF4\u62A4\uFF0C\u5BA2\u6237\u7AEF\u53EF\u590D\u7528\u540C\u4E00\u4EFD\u63A5\u53E3\u7C7B\u578B\u3002
|
|
1867
|
+
|
|
1868
|
+
## \u6838\u5FC3\u529F\u80FD\u6E05\u5355
|
|
1838
1869
|
|
|
1839
|
-
|
|
1870
|
+
- \u5171\u4EAB contract\u3001\u8F93\u5165\u8F93\u51FA\u6821\u9A8C\u4E0E\u670D\u52A1 procedures\u3002
|
|
1871
|
+
- \u6A21\u5757\u914D\u7F6E\u3001\u5065\u5EB7\u68C0\u67E5\u548C HTTP \u670D\u52A1\u3002
|
|
1872
|
+
- \u72EC\u7ACB\u955C\u50CF\u6784\u5EFA\u4E0E\u79BB\u7EBF\u4EA4\u4ED8\u3002
|
|
1873
|
+
|
|
1874
|
+
## \u4F7F\u7528\u5165\u95E8
|
|
1875
|
+
|
|
1876
|
+
### \u5DE5\u7A0B\u7ED3\u6784
|
|
1840
1877
|
|
|
1841
1878
|
- \`apps/${name}-contract\`\uFF1A\u5171\u4EAB API contract\u3001Zod schema \u4E0E\u7C7B\u578B
|
|
1842
1879
|
- \`apps/${name}-service\`\uFF1A\u540E\u7AEF API service\uFF0C\u57FA\u4E8E \`@h-ai/serv\` \u66B4\u9732 HTTP \u5E94\u7528
|
|
1843
1880
|
|
|
1844
|
-
|
|
1881
|
+
### \u5F00\u53D1
|
|
1845
1882
|
|
|
1846
1883
|
\`\`\`bash
|
|
1847
1884
|
${pm} install
|
|
@@ -1850,96 +1887,55 @@ ${pm} dev
|
|
|
1850
1887
|
|
|
1851
1888
|
${generateEnvironmentReadme()}
|
|
1852
1889
|
|
|
1853
|
-
## \u8D28\u91CF\u95E8\u7981
|
|
1854
|
-
|
|
1855
|
-
\`\`\`bash
|
|
1856
|
-
${pm} typecheck
|
|
1857
|
-
${pm} lint
|
|
1858
|
-
${pm} build
|
|
1859
|
-
${pm} test
|
|
1860
|
-
${pm} test:e2e
|
|
1861
|
-
\`\`\`
|
|
1862
|
-
|
|
1863
|
-
## \u5BB9\u5668\u8FD0\u884C
|
|
1864
|
-
|
|
1865
|
-
### \u6784\u5EFA\u955C\u50CF
|
|
1866
|
-
|
|
1867
|
-
\`\`\`bash
|
|
1868
|
-
# \u666E\u901A\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
|
|
1869
|
-
${pm} docker:build
|
|
1870
|
-
${pm} podman:build
|
|
1871
|
-
|
|
1872
|
-
# \u5C06 apps/${name}-service/data \u4F5C\u4E3A\u955C\u50CF\u521D\u59CB\u6570\u636E\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
|
|
1873
|
-
${pm} docker:build:data
|
|
1874
|
-
${pm} podman:build:data
|
|
1875
|
-
\`\`\`
|
|
1876
|
-
|
|
1877
|
-
### \u8FD0\u884C\u955C\u50CF
|
|
1878
|
-
|
|
1879
|
-
\`\`\`bash
|
|
1880
|
-
# \u4F7F\u7528 Podman \u65F6\u5C06 docker \u66FF\u6362\u4E3A podman
|
|
1881
|
-
docker run --rm -d --name ${name} -p 3000:3000 --env-file .env \\
|
|
1882
|
-
--mount type=bind,source=/absolute/path/to/config,target=/app/config,readonly \\
|
|
1883
|
-
--mount type=volume,source=${name}-data,target=/app/data \\
|
|
1884
|
-
${name}:latest
|
|
1885
|
-
\`\`\`
|
|
1886
|
-
|
|
1887
|
-
### \u6D4B\u8BD5\u955C\u50CF
|
|
1888
|
-
|
|
1889
|
-
\`\`\`bash
|
|
1890
|
-
curl --fail http://127.0.0.1:3000/health
|
|
1891
|
-
curl --fail http://127.0.0.1:3000/ready
|
|
1892
|
-
curl --fail --request POST http://127.0.0.1:3000/api/v1/app/info \\
|
|
1893
|
-
--header "Content-Type: application/json" --data '{}'
|
|
1894
|
-
docker stop ${name}
|
|
1895
|
-
\`\`\`
|
|
1896
|
-
|
|
1897
|
-
- \u914D\u7F6E\u9ED8\u8BA4\u6765\u81EA \`apps/${name}-service/config\`\uFF1B\u8FD0\u884C\u65F6\u53EF\u53EA\u8BFB\u6302\u8F7D\u5230 \`/app/config\` \u8986\u76D6\uFF0C\u5E76\u7528 YAML \u7684 \`\${ENV_NAME:default}\` \u5360\u4F4D\u7B26\u63A5\u6536\u73AF\u5883\u53D8\u91CF\u3002
|
|
1898
|
-
- \u5E26\u6570\u636E\u6784\u5EFA\u4F1A\u628A \`apps/${name}-service/data\` \u56FA\u5316\u4E3A\u955C\u50CF\u521D\u59CB\u6570\u636E\uFF1B\u751F\u4EA7\u5199\u5165\u4ECD\u5EFA\u8BAE\u4F7F\u7528 \`/app/data\` volume\u3002
|
|
1899
|
-
|
|
1900
|
-
\u4EE5\u4E0A\u4E09\u4E2A\u8FD0\u884C\u65F6\u53C2\u6570\u5747\u53EF\u6309\u9700\u5220\u9664\uFF1A
|
|
1901
1890
|
|
|
1902
|
-
|
|
1903
|
-
| --- | --- | --- |
|
|
1904
|
-
| \`--env-file .env\` | \u6279\u91CF\u4F20\u5165\u73AF\u5883\u53D8\u91CF\uFF1B\u4E5F\u53EF\u6539\u7528\u591A\u4E2A \`-e NAME=value\`\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u548C YAML \u9ED8\u8BA4\u503C\u3002 |
|
|
1905
|
-
| \`--mount type=bind,...target=/app/config,readonly\` | \u7528\u5BBF\u4E3B\u673A\u914D\u7F6E\u76EE\u5F55\u8986\u76D6\u955C\u50CF\u5185 \`/app/config\`\uFF1B\u5FC5\u987B\u628A \`source\` \u6539\u6210\u771F\u5B9E\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\`readonly\` \u9632\u6B62\u5BB9\u5668\u4FEE\u6539\u914D\u7F6E\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u81EA\u5E26\u914D\u7F6E\u3002 |
|
|
1906
|
-
| \`--mount type=volume,...target=/app/data\` | \u521B\u5EFA\u6216\u590D\u7528 named volume\uFF0C\u6301\u4E45\u5316 SQLite \u548C\u672C\u5730\u5B58\u50A8\u3002 | \u53EF\u9009\u4F46\u751F\u4EA7\u73AF\u5883\u5EFA\u8BAE\u4FDD\u7559\uFF1B\u5220\u9664\u540E\u6570\u636E\u53EA\u5199\u5165\u5BB9\u5668\u5C42\uFF0C\u4F7F\u7528 \`--rm\` \u5220\u9664\u5BB9\u5668\u65F6\u4F1A\u4E22\u5931\u3002 |
|
|
1891
|
+
${generateBuildDeploymentReadme(pm, `apps/${name}-service`)}
|
|
1907
1892
|
|
|
1908
|
-
|
|
1893
|
+
### \u6269\u5C55\u5EFA\u8BAE
|
|
1909
1894
|
|
|
1910
1895
|
- \u5728 \`${name}-contract\` \u4E2D\u7EF4\u62A4\u5E94\u7528\u7EA7 contract\uFF0C\u524D\u540E\u7AEF\u5171\u7528\u540C\u4E00\u4EFD\u7C7B\u578B\u4E0E\u8DEF\u5F84\u5B9A\u4E49\u3002
|
|
1911
1896
|
- \u5728 \`${name}-service\` \u4E2D\u6269\u5C55 procedures\u3001\u6A21\u5757\u521D\u59CB\u5316\u4E0E\u914D\u7F6E\u3002
|
|
1912
1897
|
- \u9700\u8981 IAM / storage / ai \u65F6\uFF0C\u4F18\u5148\u590D\u7528 \`@h-ai/api-contract\` \u4E0E \`@h-ai/serv/features/*\`\u3002
|
|
1913
1898
|
|
|
1914
|
-
${generateAiDevelopmentReadme()}
|
|
1899
|
+
${generateAiDevelopmentReadme()}### \u6587\u6863
|
|
1915
1900
|
|
|
1916
1901
|
- [hai Agent Framework](https://github.com/hai-series/hai-framework)
|
|
1917
1902
|
- [@h-ai/serv](./.agents/skills/hai-serv/SKILL.md)
|
|
1918
1903
|
- [@h-ai/api-contract](./.agents/skills/hai-api-contract/SKILL.md)
|
|
1919
1904
|
- [@h-ai/api-client](./.agents/skills/hai-api-client/SKILL.md)
|
|
1905
|
+
|
|
1906
|
+
## \u5F00\u53D1\u6D4B\u8BD5
|
|
1907
|
+
|
|
1908
|
+
\`\`\`bash
|
|
1909
|
+
${pm} typecheck
|
|
1910
|
+
${pm} lint
|
|
1911
|
+
${pm} build
|
|
1912
|
+
${pm} test
|
|
1913
|
+
${pm} test:e2e
|
|
1914
|
+
\`\`\`
|
|
1915
|
+
|
|
1916
|
+
## \u8BB8\u53EF\u8BC1
|
|
1917
|
+
|
|
1918
|
+
Apache-2.0
|
|
1920
1919
|
`;
|
|
1921
1920
|
}
|
|
1922
1921
|
function generateFullstackReadme(name, pm, frontends) {
|
|
1923
1922
|
const enabledFrontends = frontends.map((target) => FRONTEND_TARGETS[target].name).join(" / ");
|
|
1924
|
-
const webBuildCommands = frontends.includes("web") ? `
|
|
1925
|
-
# Web \u9759\u6001\u955C\u50CF\uFF08\u4E8C\u9009\u4E00\uFF09
|
|
1926
|
-
${pm} docker:build:web
|
|
1927
|
-
${pm} podman:build:web` : "";
|
|
1928
|
-
const webRunCommand = frontends.includes("web") ? `
|
|
1929
|
-
|
|
1930
|
-
# Web\uFF1BPUBLIC_API_BASE \u5728\u5BB9\u5668\u542F\u52A8\u65F6\u6CE8\u5165\uFF0C\u4E0D\u9700\u8981\u91CD\u65B0\u6784\u5EFA\u955C\u50CF
|
|
1931
|
-
docker run --rm -d --name ${name}-web -p 8080:8080 \\
|
|
1932
|
-
-e PUBLIC_API_BASE=https://api.example.com \\
|
|
1933
|
-
${name}-web:latest` : "";
|
|
1934
|
-
const webTestCommands = frontends.includes("web") ? `curl --fail http://127.0.0.1:8080/health
|
|
1935
|
-
curl --fail http://127.0.0.1:8080/runtime-config.js
|
|
1936
|
-
` : "";
|
|
1937
|
-
const containerNames = frontends.includes("web") ? `${name} ${name}-web` : name;
|
|
1938
1923
|
return `# ${name}
|
|
1939
1924
|
|
|
1940
|
-
|
|
1925
|
+
## \u7B80\u4ECB
|
|
1941
1926
|
|
|
1942
|
-
|
|
1927
|
+
\u524D\u540E\u7AEF\u5206\u79BB\u5DE5\u7A0B\uFF0CAPI \u4E0E Web\u3001\u79FB\u52A8\u7AEF\u3001\u684C\u9762\u7AEF\u5171\u4EAB\u63A5\u53E3\u7C7B\u578B\u548C\u7BA1\u7406\u754C\u9762\uFF0C\u53EF\u6309\u5B9E\u9645\u4E1A\u52A1\u8C03\u6574\u3002
|
|
1928
|
+
|
|
1929
|
+
## \u6838\u5FC3\u529F\u80FD\u6E05\u5355
|
|
1930
|
+
|
|
1931
|
+
- API \u670D\u52A1\u3001\u5171\u4EAB\u754C\u9762\u4E0E\u6309\u9700\u751F\u6210\u7684\u5BA2\u6237\u7AEF\u3002
|
|
1932
|
+
- \u7528\u6237\u7BA1\u7406\u3001\u89D2\u8272\u3001\u5BA1\u8BA1\u548C\u4E2A\u4EBA\u8BBE\u7F6E\u793A\u4F8B\u3002
|
|
1933
|
+
- \u767B\u5F55\u3001\u4E3B\u9898\u3001\u8BED\u8A00\u3001\u65B0\u624B\u5F15\u5BFC\u548C\u5E2E\u52A9\u4E2D\u5FC3\u3002
|
|
1934
|
+
- API/Web \u72EC\u7ACB Dockerfile \u4E0E\u955C\u50CF\u5F52\u6863\u3002
|
|
1935
|
+
|
|
1936
|
+
## \u4F7F\u7528\u5165\u95E8
|
|
1937
|
+
|
|
1938
|
+
### \u5DE5\u7A0B\u7ED3\u6784
|
|
1943
1939
|
|
|
1944
1940
|
- \`packages/${name}-contract\`\uFF1A\u524D\u540E\u7AEF\u5171\u4EAB API \u5951\u7EA6\u3001Zod Schema \u4E0E\u7C7B\u578B
|
|
1945
1941
|
- \`packages/${name}-serv\`\uFF1A\u540E\u7AEF API Service\uFF08\u901A\u8FC7 \`@h-ai/serv\` \u66B4\u9732\u6700\u5C0F HTTP App \u62BD\u8C61\uFF0C\u4E0D\u76F4\u63A5\u66B4\u9732 Hono\uFF09
|
|
@@ -1951,11 +1947,11 @@ curl --fail http://127.0.0.1:8080/runtime-config.js
|
|
|
1951
1947
|
|
|
1952
1948
|
\u5DF2\u542F\u7528\u524D\u7AEF\uFF1A${enabledFrontends || "Web / App / Desktop"}
|
|
1953
1949
|
|
|
1954
|
-
|
|
1950
|
+
### \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
|
|
1955
1951
|
|
|
1956
1952
|
Web/App/Desktop \u5171\u4EAB AppShell \u7684 header/footer\u3002\u65B0\u589E\u9875\u9762\u653E shared/src/lib/pages/**/*.svelte\uFF0C\u7531 HashRouter \u81EA\u52A8\u53D1\u73B0\u5E76\u6309\u9700\u52A0\u8F7D\uFF1B\u83DC\u5355\u53EA\u7EF4\u62A4\u5165\u53E3\u3002\u5730\u5740\u4F7F\u7528 #/\u9875\u9762\u8DEF\u5F84\uFF0C\u652F\u6301\u5237\u65B0\u3001\u67E5\u8BE2\u4E32\u4E0E\u6D4F\u89C8\u5668\u524D\u8FDB\u540E\u9000\uFF1BAPI \u6743\u9650\u4ECD\u7531\u670D\u52A1\u7AEF\u6821\u9A8C\u3002
|
|
1957
1953
|
|
|
1958
|
-
|
|
1954
|
+
### \u9ED8\u8BA4\u7BA1\u7406\u529F\u80FD
|
|
1959
1955
|
|
|
1960
1956
|
\u4EE5\u4E0B\u63CF\u8FF0\u53EF\u8FD0\u884C\u793A\u4F8B\u7684\u521D\u59CB\u4E1A\u52A1\uFF0C\u53EF\u6309\u5B9E\u9645\u9700\u6C42\u8C03\u6574\u6216\u79FB\u9664\uFF0C\u5E76\u540C\u6B65\u4EE3\u7801\u3001\u6570\u636E\u3001\u9274\u6743\u3001\u754C\u9762\u3001\u6D4B\u8BD5\u548C\u6587\u6863\u3002
|
|
1961
1957
|
|
|
@@ -1969,7 +1965,7 @@ CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env.local\`\uFF0C\u5176\u4E2D
|
|
|
1969
1965
|
|
|
1970
1966
|
\u5E94\u7528\u663E\u793A\u540D\u79F0\u3001\u7248\u672C\u3001\u73AF\u5883\u3001\u9ED8\u8BA4\u8BED\u8A00\u548C\u65E5\u5FD7\u8BBE\u7F6E\u6765\u81EA\u540E\u7AEF \`config/_core.yml\`\uFF0C\u524D\u7AEF\u901A\u8FC7 \`app.info\` \u83B7\u53D6\u5B89\u5168\u5143\u4FE1\u606F\u3002\u9ED8\u8BA4 SQLite\u3001\u672C\u5730\u5B58\u50A8\u4E0E\u5185\u5B58\u7F13\u5B58\u9002\u5408\u5355\u5B9E\u4F8B\uFF0C\u7BA1\u7406\u5199\u5165\u4EC5\u652F\u6301\u5355\u5199\u5B9E\u4F8B\uFF1B\u591A\u5B9E\u4F8B\u8BFB\u53D6\u53EF\u4F7F\u7528\u5171\u4EAB\u6570\u636E\u5E93\u3001Redis cache \u548C S3 \u5B58\u50A8\uFF0C\u4F46\u7BA1\u7406\u5199\u8BF7\u6C42\u987B\u7EDF\u4E00\u8DEF\u7531\u5230\u540C\u4E00\u5199\u5B9E\u4F8B\u3002\u53E6\u9700\u4F1A\u8BDD\u7C98\u6027\u8DEF\u7531\u4F7F\u8BF7\u6C42\u547D\u4E2D\u540C\u4E00\u8FDB\u7A0B\u5185\u4F20\u8F93\u79C1\u94A5\uFF0C\u670D\u52A1\u91CD\u542F\u540E\u5BA2\u6237\u7AEF\u91CD\u65B0\u534F\u5546\u3002\u751F\u4EA7\u9700\u663E\u5F0F\u914D\u7F6E\u5141\u8BB8\u7684 CORS Origin\u3002\u4E1A\u52A1\u63D0\u4EA4\u540E\u7684\u5BA1\u8BA1\u5199\u5165\u91C7\u7528\u6700\u5927\u52AA\u529B\uFF0C\u5931\u8D25\u8BB0\u5F55\u7ED3\u6784\u5316\u9519\u8BEF\u65E5\u5FD7\uFF0C\u4FDD\u7559\u5DF2\u5B8C\u6210\u4E1A\u52A1\u7684\u539F\u59CB\u7ED3\u679C\u3002
|
|
1971
1967
|
|
|
1972
|
-
|
|
1968
|
+
### \u914D\u7F6E\u6269\u5C55
|
|
1973
1969
|
|
|
1974
1970
|
\u670D\u52A1\u7AEF \`packages/${name}-serv/config/\` \u59CB\u7EC8\u4FDD\u7559 core\u3001serv\u3001db\u3001cache\u3001iam\u3001storage\u3001audit\u3001reach\u3001ai\u3001vecdb\u3001payment\u3001scheduler\u3001deploy \u5171 13 \u4EFD YAML\u3002\u5DF2\u7528\u6A21\u5757\u4FDD\u7559\u5DE5\u4F5C\u9ED8\u8BA4\u503C\uFF0C\u672A\u542F\u7528\u6A21\u5757\u6574\u4EFD\u6CE8\u91CA\uFF0C\u53EF\u9009\u5B57\u6BB5\u548C\u66FF\u4EE3 Provider \u6309\u5757\u6CE8\u91CA\uFF1B\u6839 \`.env.example\` \u4E5F\u4FDD\u7559\u672A\u542F\u7528\u6A21\u5757\u7684\u51ED\u636E\u793A\u4F8B\u3002Reach \u9ED8\u8BA4\u4E3A\u5F00\u53D1\u63A7\u5236\u53F0\u901A\u77E5\uFF0C\u751F\u4EA7\u6309 \`_reach.yml\` \u6CE8\u91CA\u66FF\u6362\u4E3A SMTP\u3001\u77ED\u4FE1\u6216 API Provider\uFF0C\u5E76\u4FDD\u6301\u540D\u79F0\u552F\u4E00\u3002
|
|
1975
1971
|
|
|
@@ -1981,79 +1977,23 @@ CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env.local\`\uFF0C\u5176\u4E2D
|
|
|
1981
1977
|
|
|
1982
1978
|
${generateEnvironmentReadme()}
|
|
1983
1979
|
|
|
1984
|
-
|
|
1980
|
+
### \u5F00\u53D1\u4E0E\u6784\u5EFA
|
|
1985
1981
|
|
|
1986
1982
|
\`\`\`bash
|
|
1987
1983
|
${pm} install
|
|
1988
1984
|
${pm} dev
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
${pm}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
${pm} package
|
|
1995
|
-
|
|
1996
|
-
# \u90E8\u7F72\u5165\u53E3\uFF1A\u9ED8\u8BA4\u6267\u884C package\uFF0C\u53EF\u66FF\u6362\u4E3A\u56E2\u961F\u81EA\u5DF1\u7684 CI/CD \u53D1\u5E03\u811A\u672C
|
|
1997
|
-
${pm} deploy
|
|
1998
|
-
\`\`\`
|
|
1999
|
-
|
|
2000
|
-
## \u5BB9\u5668\u8FD0\u884C
|
|
2001
|
-
|
|
2002
|
-
\u9ED8\u8BA4\u955C\u50CF\u76F4\u63A5\u8FD0\u884C API Service\uFF0C\u5916\u90E8\u7AEF\u53E3\u4E3A 3000\u3002
|
|
2003
|
-
|
|
2004
|
-
### \u6784\u5EFA\u955C\u50CF
|
|
2005
|
-
|
|
2006
|
-
\`\`\`bash
|
|
2007
|
-
# Service \u666E\u901A\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
|
|
2008
|
-
${pm} docker:build
|
|
2009
|
-
${pm} podman:build
|
|
2010
|
-
|
|
2011
|
-
# \u5C06 packages/${name}-serv/data \u4F5C\u4E3A\u955C\u50CF\u521D\u59CB\u6570\u636E\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
|
|
2012
|
-
${pm} docker:build:data
|
|
2013
|
-
${pm} podman:build:data
|
|
2014
|
-
${webBuildCommands}
|
|
2015
|
-
\`\`\`
|
|
2016
|
-
|
|
2017
|
-
### \u8FD0\u884C\u955C\u50CF
|
|
2018
|
-
|
|
2019
|
-
\`\`\`bash
|
|
2020
|
-
# Service\uFF1B\u4F7F\u7528 Podman \u65F6\u5C06 docker \u66FF\u6362\u4E3A podman
|
|
2021
|
-
docker run --rm -d --name ${name} -p 3000:3000 --env-file .env --env-file .env.prod --env-file .env.local \\
|
|
2022
|
-
--mount type=bind,source=/absolute/path/to/config,target=/app/config,readonly \\
|
|
2023
|
-
--mount type=volume,source=${name}-data,target=/app/data \\
|
|
2024
|
-
${name}:latest
|
|
2025
|
-
${webRunCommand}
|
|
2026
|
-
\`\`\`
|
|
2027
|
-
|
|
2028
|
-
### \u6D4B\u8BD5\u955C\u50CF
|
|
2029
|
-
|
|
2030
|
-
\`\`\`bash
|
|
2031
|
-
curl --fail http://127.0.0.1:3000/health
|
|
2032
|
-
curl --fail http://127.0.0.1:3000/ready
|
|
2033
|
-
${webTestCommands}docker stop ${containerNames}
|
|
1985
|
+
${pm} typecheck
|
|
1986
|
+
${pm} build
|
|
1987
|
+
${pm} test
|
|
1988
|
+
${pm} test:e2e
|
|
1989
|
+
${frontends.includes("desktop") ? `${pm} build --target desktop --platform windows # Windows \u4E3B\u673A\uFF1BLinux/macOS \u5728\u5BF9\u5E94\u4E3B\u673A\u6784\u5EFA` : ""}
|
|
2034
1990
|
\`\`\`
|
|
2035
1991
|
|
|
2036
|
-
|
|
2037
|
-
- \u914D\u7F6E\u9ED8\u8BA4\u6765\u81EA \`packages/${name}-serv/config\`\uFF0C\u4E5F\u53EF\u6302\u8F7D\u5230 \`/app/config\`\uFF1BYAML \u4E2D\u7684 \`\${ENV_NAME:default}\` \u53EF\u7531 \`--env-file\` \u6216 \`-e\` \u6CE8\u5165\u3002
|
|
2038
|
-
- \u5E26\u6570\u636E\u6784\u5EFA\u4F1A\u628A \`packages/${name}-serv/data\` \u56FA\u5316\u4E3A\u955C\u50CF\u521D\u59CB\u6570\u636E\uFF1B\u6301\u4E45\u5316\u5199\u5165\u4ECD\u5E94\u4F7F\u7528 volume\u3002
|
|
2039
|
-
- Service \u7531 \`@h-ai/serv\` \u76F4\u63A5\u5BF9\u5916\u76D1\u542C\uFF0C\u4E0D\u989D\u5916\u542F\u52A8 Web \u8F6C\u53D1\u5C42\u3002
|
|
2040
|
-
${frontends.includes("web") ? `- Web \u4F7F\u7528\u72EC\u7ACB\u9759\u6001\u955C\u50CF\u5E76\u652F\u6301\u4E00\u6B21\u6784\u5EFA\u3001\u591A\u73AF\u5883\u90E8\u7F72\uFF1B\u6784\u5EFA\u53EA\u9700\u8FD0\u884C \`${pm} docker:build:web\`\uFF0C\u542F\u52A8\u5BB9\u5668\u65F6\u901A\u8FC7 \`-e PUBLIC_API_BASE=https://api.example.com\` \u6307\u5411 Service \u5BF9\u5916\u5730\u5740\u3002\u672A\u8BBE\u7F6E\u65F6\u9ED8\u8BA4\u4F7F\u7528 Web \u9875\u9762\u81EA\u8EAB origin\u3002` : ""}
|
|
2041
|
-
|
|
2042
|
-
\u4EE5\u4E0A\u4E09\u4E2A Service \u8FD0\u884C\u65F6\u53C2\u6570\u5747\u53EF\u6309\u9700\u5220\u9664\uFF1A
|
|
1992
|
+
Tauri \u9700\u8981\u76EE\u6807\u7CFB\u7EDF\u7684 Rust target\u3001SDK \u548C\u6253\u5305\u5DE5\u5177\u3002\u6D4B\u8BD5\u4EA7\u751F\u7684\u4E34\u65F6\u5DE5\u7A0B\u3001\u622A\u56FE\u3001\u62A5\u544A\u3001\u5BB9\u5668\u3001\u955C\u50CF\u4E0E\u8FDB\u7A0B\u5E94\u5728 finally/teardown \u4E2D\u81EA\u52A8\u6E05\u7406\uFF0C\u5931\u8D25\u4E5F\u6267\u884C\u3002
|
|
2043
1993
|
|
|
2044
|
-
|
|
2045
|
-
| --- | --- | --- |
|
|
2046
|
-
| \`--env-file .env\` | \u6279\u91CF\u4F20\u5165\u73AF\u5883\u53D8\u91CF\uFF1B\u4E5F\u53EF\u6539\u7528\u591A\u4E2A \`-e NAME=value\`\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u548C YAML \u9ED8\u8BA4\u503C\u3002 |
|
|
2047
|
-
| \`--mount type=bind,...target=/app/config,readonly\` | \u7528\u5BBF\u4E3B\u673A\u914D\u7F6E\u76EE\u5F55\u8986\u76D6\u955C\u50CF\u5185 \`/app/config\`\uFF1B\u5FC5\u987B\u628A \`source\` \u6539\u6210\u771F\u5B9E\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\`readonly\` \u9632\u6B62\u5BB9\u5668\u4FEE\u6539\u914D\u7F6E\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u81EA\u5E26\u914D\u7F6E\u3002 |
|
|
2048
|
-
| \`--mount type=volume,...target=/app/data\` | \u521B\u5EFA\u6216\u590D\u7528 named volume\uFF0C\u6301\u4E45\u5316 SQLite \u548C\u672C\u5730\u5B58\u50A8\u3002 | \u53EF\u9009\u4F46\u751F\u4EA7\u73AF\u5883\u5EFA\u8BAE\u4FDD\u7559\uFF1B\u5220\u9664\u540E\u6570\u636E\u53EA\u5199\u5165\u5BB9\u5668\u5C42\uFF0C\u4F7F\u7528 \`--rm\` \u5220\u9664\u5BB9\u5668\u65F6\u4F1A\u4E22\u5931\u3002 |
|
|
1994
|
+
${generateBuildDeploymentReadme(pm, `packages/${name}-serv`)}
|
|
2049
1995
|
|
|
2050
|
-
\
|
|
2051
|
-
|
|
2052
|
-
- Web\uFF1A\`${pm} --filter ${name}-web build\` / \`${pm} --filter ${name}-web package\`
|
|
2053
|
-
- App\uFF1A\`${pm} --filter ${name}-app package\`\uFF0CAndroid/iOS \u53EF\u7528 \`cap:build:android:debug\` / \`cap:build:android:release\` / \`cap:build:ios\`
|
|
2054
|
-
- Desktop\uFF1A\`${pm} --filter ${name}-desktop tauri:dev\` / \`${pm} --filter ${name}-desktop package\`
|
|
2055
|
-
|
|
2056
|
-
## i18n \u4E0E shared \u534F\u540C
|
|
1996
|
+
### i18n \u4E0E shared \u534F\u540C
|
|
2057
1997
|
|
|
2058
1998
|
- shared\uFF1A\`packages/${name}-shared/project.inlang\` + \`messages/\`\uFF0C\u53EA\u653E\u8DE8\u7AEF\u5171\u4EAB\u7EC4\u4EF6\u6587\u6848\u3002
|
|
2059
1999
|
- web/app/desktop\uFF1A\u6BCF\u4E2A\u5E94\u7528\u90FD\u6709\u81EA\u5DF1\u7684 \`project.inlang\` + \`messages/\`\uFF0C\u7528\u4E8E\u672C\u7AEF\u9875\u9762\u4E0E\u4EA7\u54C1\u6587\u6848\u3002
|
|
@@ -2061,13 +2001,20 @@ ${frontends.includes("web") ? `- Web \u4F7F\u7528\u72EC\u7ACB\u9759\u6001\u955C\
|
|
|
2061
2001
|
- \u65B0\u589E locale \u65F6\uFF0Cshared \u4E0E\u6240\u6709\u542F\u7528\u524D\u7AEF\u7684 \`locales\` \u5FC5\u987B\u540C\u6B65\uFF1B\u8BED\u8A00\u5207\u6362\u5668\u4F1A\u540C\u65F6\u66F4\u65B0 shared runtime \u4E0E\u5BBF\u4E3B\u5E94\u7528 runtime\u3002
|
|
2062
2002
|
- \`${pm} i18n:compile\` \u4F1A\u7EDF\u4E00\u7F16\u8BD1 shared \u4E0E\u6BCF\u4E2A\u542F\u7528\u524D\u7AEF\u7684 Paraglide \u8F93\u51FA\uFF1B\u5404\u524D\u7AEF\u4E5F\u53EF\u5355\u72EC\u8FD0\u884C \`${pm} --filter ${name}-web paraglide:compile\` \u7B49\u547D\u4EE4\u3002
|
|
2063
2003
|
|
|
2064
|
-
|
|
2004
|
+
### \u81EA\u5B9A\u4E49\u5EFA\u8BAE
|
|
2065
2005
|
|
|
2066
2006
|
- \u6539\u5168\u5C40 Shell/\u4E3B\u9898/\u8BED\u8A00\u5207\u6362\uFF1A\u7F16\u8F91 \`packages/${name}-shared\`\u3002
|
|
2067
|
-
- \u6539\u67D0\u4E2A\u524D\u7AEF\u9875\u9762\u548C\u6587\u6848\uFF1A\u7F16\u8F91\
|
|
2007
|
+
- \u6539\u67D0\u4E2A\u524D\u7AEF\u9875\u9762\u548C\u6587\u6848\uFF1A\u7F16\u8F91\u5171\u4EAB \`packages/${name}-shared/src/lib/pages\`\u3001\u672C\u7AEF\u5165\u53E3\u4E0E \`apps/${name}-*/messages\`\u3002
|
|
2068
2008
|
- \u6539\u4E3B\u9898\u5217\u8868\uFF1A\u8C03\u6574\u811A\u624B\u67B6\u914D\u7F6E\u4E2D\u7684 \`fullstack.theme.themes\`\uFF0C\u6216\u76F4\u63A5\u7F16\u8F91\u5404\u7AEF \`src/app.css\` \u7684 daisyUI \`themes\`\u3002
|
|
2069
2009
|
|
|
2070
|
-
|
|
2010
|
+
|
|
2011
|
+
${generateAiDevelopmentReadme()}### \u6587\u6863
|
|
2012
|
+
|
|
2013
|
+
- [hai Agent Framework](https://github.com/hai-series/hai-framework)
|
|
2014
|
+
- [@h-ai/serv](./.agents/skills/hai-serv/SKILL.md)
|
|
2015
|
+
- [@h-ai/ui](./.agents/skills/hai-ui/SKILL.md)
|
|
2016
|
+
|
|
2017
|
+
## \u5F00\u53D1\u6D4B\u8BD5
|
|
2071
2018
|
|
|
2072
2019
|
\`\`\`bash
|
|
2073
2020
|
${pm} typecheck
|
|
@@ -2077,11 +2024,9 @@ ${pm} test
|
|
|
2077
2024
|
${pm} test:e2e
|
|
2078
2025
|
\`\`\`
|
|
2079
2026
|
|
|
2080
|
-
|
|
2027
|
+
## \u8BB8\u53EF\u8BC1
|
|
2081
2028
|
|
|
2082
|
-
-
|
|
2083
|
-
- [@h-ai/serv](./.agents/skills/hai-serv/SKILL.md)
|
|
2084
|
-
- [@h-ai/ui](./.agents/skills/hai-ui/SKILL.md)
|
|
2029
|
+
Apache-2.0
|
|
2085
2030
|
`;
|
|
2086
2031
|
}
|
|
2087
2032
|
function printCompletionMessage(options) {
|
|
@@ -2165,7 +2110,7 @@ async function addModule(options) {
|
|
|
2165
2110
|
try {
|
|
2166
2111
|
const project = await detectProject(cwd);
|
|
2167
2112
|
if (!project) {
|
|
2168
|
-
|
|
2113
|
+
writeCliError(chalk.red("\u672A\u68C0\u6D4B\u5230\u9879\u76EE\uFF08\u627E\u4E0D\u5230 package.json\uFF09"));
|
|
2169
2114
|
return;
|
|
2170
2115
|
}
|
|
2171
2116
|
let moduleName = options.module;
|
|
@@ -2176,7 +2121,7 @@ async function addModule(options) {
|
|
|
2176
2121
|
value: key
|
|
2177
2122
|
}));
|
|
2178
2123
|
if (choices.length === 0) {
|
|
2179
|
-
|
|
2124
|
+
writeCliMessage(chalk.green("\u6240\u6709\u6A21\u5757\u5DF2\u5B89\u88C5\uFF01"));
|
|
2180
2125
|
return;
|
|
2181
2126
|
}
|
|
2182
2127
|
const { selected } = await prompts({
|
|
@@ -2191,11 +2136,11 @@ async function addModule(options) {
|
|
|
2191
2136
|
}
|
|
2192
2137
|
const moduleDef = MODULE_MAP[moduleName];
|
|
2193
2138
|
if (!moduleDef) {
|
|
2194
|
-
|
|
2195
|
-
|
|
2139
|
+
writeCliError(chalk.red(`\u672A\u77E5\u6A21\u5757: ${moduleName}`));
|
|
2140
|
+
writeCliMessage(`\u53EF\u7528\u6A21\u5757: ${Object.keys(MODULE_MAP).join(", ")}`);
|
|
2196
2141
|
return;
|
|
2197
2142
|
}
|
|
2198
|
-
const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-
|
|
2143
|
+
const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-FC3QRKDO.js');
|
|
2199
2144
|
if (moduleDef.packages.every((p) => project.haiPackages.includes(p))) {
|
|
2200
2145
|
const generatedFiles2 = await generateProjectAiSupport(
|
|
2201
2146
|
cwd,
|
|
@@ -2206,12 +2151,12 @@ async function addModule(options) {
|
|
|
2206
2151
|
}
|
|
2207
2152
|
);
|
|
2208
2153
|
if (generatedFiles2.length > 0) {
|
|
2209
|
-
|
|
2154
|
+
writeCliMessage(chalk.yellow(`\u6A21\u5757 ${moduleName} \u5DF2\u5B89\u88C5\uFF0C\u5DF2\u8865\u5168 AI \u652F\u6301\u6587\u4EF6`));
|
|
2210
2155
|
for (const generatedFile of generatedFiles2) {
|
|
2211
|
-
|
|
2156
|
+
writeCliMessage(chalk.gray(` + ${generatedFile} (AI support)`));
|
|
2212
2157
|
}
|
|
2213
2158
|
} else {
|
|
2214
|
-
|
|
2159
|
+
writeCliMessage(chalk.yellow(`\u6A21\u5757 ${moduleName} \u5DF2\u5B89\u88C5`));
|
|
2215
2160
|
}
|
|
2216
2161
|
return;
|
|
2217
2162
|
}
|
|
@@ -2234,7 +2179,10 @@ async function addModule(options) {
|
|
|
2234
2179
|
const existingHaiSpecifier = Object.entries(pkg.dependencies).find(([pkgName]) => pkgName.startsWith("@h-ai/"))?.[1];
|
|
2235
2180
|
const depVersion = existingHaiSpecifier === "catalog:" ? "catalog:" : getCliVersion();
|
|
2236
2181
|
for (const pkgName of allPackages) {
|
|
2237
|
-
if (
|
|
2182
|
+
if (pkgName === "@h-ai/deploy") {
|
|
2183
|
+
pkg.devDependencies ??= {};
|
|
2184
|
+
pkg.devDependencies[pkgName] ??= depVersion;
|
|
2185
|
+
} else if (!pkg.dependencies[pkgName]) {
|
|
2238
2186
|
pkg.dependencies[pkgName] = depVersion;
|
|
2239
2187
|
}
|
|
2240
2188
|
}
|
|
@@ -2249,7 +2197,7 @@ async function addModule(options) {
|
|
|
2249
2197
|
await writeFile(configPath, content);
|
|
2250
2198
|
spinner.succeed();
|
|
2251
2199
|
} else {
|
|
2252
|
-
|
|
2200
|
+
writeCliMessage(chalk.gray(`\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728: config/_${moduleDef.configKey}.yml`));
|
|
2253
2201
|
}
|
|
2254
2202
|
}
|
|
2255
2203
|
if (moduleDef.deps) {
|
|
@@ -2260,7 +2208,7 @@ async function addModule(options) {
|
|
|
2260
2208
|
if (!await fileExists(depConfigPath)) {
|
|
2261
2209
|
const content = generateConfigFile(depDef.configKey);
|
|
2262
2210
|
await writeFile(depConfigPath, content);
|
|
2263
|
-
|
|
2211
|
+
writeCliMessage(chalk.gray(` + config/_${depDef.configKey}.yml (\u4F9D\u8D56)`));
|
|
2264
2212
|
}
|
|
2265
2213
|
}
|
|
2266
2214
|
}
|
|
@@ -2276,7 +2224,7 @@ async function addModule(options) {
|
|
|
2276
2224
|
);
|
|
2277
2225
|
if (generatedFiles.length > 0) {
|
|
2278
2226
|
for (const generatedFile of generatedFiles) {
|
|
2279
|
-
|
|
2227
|
+
writeCliMessage(chalk.gray(` + ${generatedFile} (AI support)`));
|
|
2280
2228
|
}
|
|
2281
2229
|
}
|
|
2282
2230
|
const doInstall = options.install !== false;
|
|
@@ -2287,20 +2235,217 @@ async function addModule(options) {
|
|
|
2287
2235
|
execSync2(`${pm} install`, { cwd, stdio: "ignore" });
|
|
2288
2236
|
spinner.succeed();
|
|
2289
2237
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2238
|
+
writeCliMessage("");
|
|
2239
|
+
writeCliMessage(chalk.green(`\u2714 \u6A21\u5757 ${moduleName} \u6DFB\u52A0\u6210\u529F\uFF01`));
|
|
2240
|
+
writeCliMessage("");
|
|
2241
|
+
writeCliMessage("\u5DF2\u6DFB\u52A0\u7684\u5305:");
|
|
2294
2242
|
for (const pkg2 of allPackages) {
|
|
2295
|
-
|
|
2243
|
+
writeCliMessage(chalk.cyan(` ${pkg2}`));
|
|
2296
2244
|
}
|
|
2297
|
-
|
|
2245
|
+
writeCliMessage("");
|
|
2298
2246
|
} catch (error) {
|
|
2299
2247
|
spinner.fail();
|
|
2300
|
-
|
|
2248
|
+
writeCliError(chalk.red("\u6DFB\u52A0\u6A21\u5757\u5931\u8D25:"), { error });
|
|
2301
2249
|
throw error;
|
|
2302
2250
|
}
|
|
2303
2251
|
}
|
|
2252
|
+
async function run(command, args, cwd, quiet = false) {
|
|
2253
|
+
await new Promise((resolve3, reject) => {
|
|
2254
|
+
const env = Object.fromEntries(Object.entries(process4.env).filter(([key]) => !/^(?:HAI_|PUBLIC_|VITE_)/.test(key)));
|
|
2255
|
+
env.NODE_ENV = "production";
|
|
2256
|
+
if (env.CI === "1" || env.CI === "0")
|
|
2257
|
+
env.CI = env.CI === "1" ? "true" : "false";
|
|
2258
|
+
const child = spawn(command, args, { cwd, env, windowsHide: true, stdio: quiet ? "ignore" : "inherit", shell: false });
|
|
2259
|
+
child.once("error", reject);
|
|
2260
|
+
child.once("exit", (code) => code === 0 ? resolve3() : reject(new Error(cliM("cli_build_failed", { params: { command, code: String(code) } }))));
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
async function pnpm(args, cwd) {
|
|
2264
|
+
const entry = process4.env.npm_execpath;
|
|
2265
|
+
if (entry && /(?:^|[/\\])pnpm(?:-native)?(?:\.exe|\.[cm]?js)?$/i.test(entry))
|
|
2266
|
+
return /\.[cm]?js$/i.test(entry) ? run(process4.execPath, [entry, ...args], cwd) : run(entry, args, cwd);
|
|
2267
|
+
if (process4.platform === "win32") {
|
|
2268
|
+
const directories = (process4.env.PATH ?? "").split(";");
|
|
2269
|
+
for (const directory of directories) {
|
|
2270
|
+
const executable = join(directory, "pnpm.exe");
|
|
2271
|
+
if (existsSync(executable))
|
|
2272
|
+
return run(executable, args, cwd);
|
|
2273
|
+
for (const relative2 of ["node_modules/pnpm/bin/pnpm.cjs", "pnpm.cjs"]) {
|
|
2274
|
+
const candidate = join(directory, relative2);
|
|
2275
|
+
if (existsSync(candidate))
|
|
2276
|
+
return run(process4.execPath, [candidate, ...args], cwd);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
throw new Error(cliM("cli_build_use_pnpm"));
|
|
2280
|
+
}
|
|
2281
|
+
return run("pnpm", args, cwd);
|
|
2282
|
+
}
|
|
2283
|
+
async function buildCommand(options) {
|
|
2284
|
+
const cwd = resolve(options.cwd ?? process4.cwd());
|
|
2285
|
+
const target = options.target ?? "compile";
|
|
2286
|
+
if (!["compile", "docker", "desktop"].includes(target))
|
|
2287
|
+
throw new Error(cliM("cli_build_target_invalid"));
|
|
2288
|
+
if (target !== "docker" && (options.images !== void 0 || options.runtime !== void 0 || options.network !== void 0 || options.sqlitePrebuildMirror !== void 0 || options.httpProxy === false) || target !== "desktop" && (options.platform !== void 0 || options.arch !== void 0)) {
|
|
2289
|
+
throw new Error(cliM("cli_build_options_invalid"));
|
|
2290
|
+
}
|
|
2291
|
+
if (target === "compile") {
|
|
2292
|
+
await pnpm(["run", "compile"], cwd);
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
const metadata = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
2296
|
+
const appVersion = metadata.version;
|
|
2297
|
+
if (typeof appVersion !== "string" || appVersion.length > 80 || !/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9a-z-]+(?:\.[0-9a-z-]+)*)?(?:\+[0-9a-z-]+(?:\.[0-9a-z-]+)*)?$/i.test(appVersion))
|
|
2298
|
+
throw new Error(cliM("cli_build_version_invalid"));
|
|
2299
|
+
const config = JSON.parse(readFileSync(join(cwd, "hai.build.json"), "utf8"));
|
|
2300
|
+
if (target === "desktop") {
|
|
2301
|
+
const platforms = { win32: "windows", linux: "linux", darwin: "macos" };
|
|
2302
|
+
const host = platforms[process4.platform];
|
|
2303
|
+
const platform = options.platform ?? host;
|
|
2304
|
+
const arch = options.arch ?? process4.arch;
|
|
2305
|
+
if (!platform || platform !== host || !["x64", "arm64"].includes(arch))
|
|
2306
|
+
throw new Error(cliM("cli_build_native_host"));
|
|
2307
|
+
if (!config.desktop?.length)
|
|
2308
|
+
throw new Error(cliM("cli_build_desktop_missing"));
|
|
2309
|
+
const rustTarget = `${arch === "x64" ? "x86_64" : "aarch64"}-${platform === "windows" ? "pc-windows-msvc" : platform === "macos" ? "apple-darwin" : "unknown-linux-gnu"}`;
|
|
2310
|
+
const bundles = platform === "windows" ? "nsis" : platform === "macos" ? "app,dmg" : "deb,appimage";
|
|
2311
|
+
for (const directory of config.desktop)
|
|
2312
|
+
await pnpm(["exec", "tauri", "build", "--target", rustTarget, "--bundles", bundles, "--config", JSON.stringify({ version: appVersion })], resolve(cwd, directory));
|
|
2313
|
+
writeCliMessage(cliM("cli_build_native_ready", { params: { target: rustTarget } }));
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
if (!Array.isArray(config.images) || !config.images.length || config.images.some((image) => !image || typeof image !== "object") || config.images.filter((image) => image.name === "app").length !== 1 || config.images.length > 2 || new Set(config.images.map((image) => image.name)).size !== config.images.length || config.images.some((image) => !["app", "web"].includes(image.name) || typeof image.dockerfile !== "string" || !image.dockerfile || "target" in image || image.containerPort !== void 0 && (!Number.isInteger(image.containerPort) || image.containerPort < 1 || image.containerPort > 65535))) {
|
|
2317
|
+
throw new Error(cliM("cli_build_images_missing"));
|
|
2318
|
+
}
|
|
2319
|
+
const selected = options.images?.split(",").map((name) => name.trim());
|
|
2320
|
+
if (selected && (selected.some((name) => !config.images.some((image) => image.name === name)) || new Set(selected).size !== selected.length))
|
|
2321
|
+
throw new Error(cliM("cli_build_selection_invalid"));
|
|
2322
|
+
const images = config.images.filter((image) => !selected || selected.includes(image.name));
|
|
2323
|
+
for (const image of images) {
|
|
2324
|
+
if (!existsSync(resolve(cwd, image.dockerfile)))
|
|
2325
|
+
throw new Error(cliM("cli_build_dockerfile_missing", { params: { path: image.dockerfile } }));
|
|
2326
|
+
}
|
|
2327
|
+
const candidates = options.runtime && options.runtime !== "auto" ? [options.runtime] : ["podman", "docker"];
|
|
2328
|
+
if (candidates.some((runtime2) => !["podman", "docker"].includes(runtime2)))
|
|
2329
|
+
throw new Error(cliM("cli_build_runtime_missing"));
|
|
2330
|
+
let runtime;
|
|
2331
|
+
for (const candidate of candidates) {
|
|
2332
|
+
try {
|
|
2333
|
+
await run(candidate, ["version"], cwd, true);
|
|
2334
|
+
runtime = candidate;
|
|
2335
|
+
break;
|
|
2336
|
+
} catch {
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
if (!runtime)
|
|
2340
|
+
throw new Error(cliM("cli_build_runtime_missing"));
|
|
2341
|
+
if (options.sqlitePrebuildMirror) {
|
|
2342
|
+
const url = new URL(options.sqlitePrebuildMirror);
|
|
2343
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash)
|
|
2344
|
+
throw new Error(cliM("cli_build_mirror_invalid"));
|
|
2345
|
+
}
|
|
2346
|
+
const buildId = `${appVersion}-${Date.now()}-${randomBytes(3).toString("hex")}`;
|
|
2347
|
+
const artifact = { version: 3, appVersion, buildId, partial: images.length !== config.images.length, hasWeb: config.images.some((image) => image.name === "web"), images: [] };
|
|
2348
|
+
for (const image of config.images) {
|
|
2349
|
+
if (image.containerPort !== void 0)
|
|
2350
|
+
artifact[image.name === "app" ? "appContainerPort" : "webContainerPort"] = image.containerPort;
|
|
2351
|
+
}
|
|
2352
|
+
const parent = join(cwd, ".hai/build/docker");
|
|
2353
|
+
mkdirSync(parent, { recursive: true });
|
|
2354
|
+
const staging = mkdtempSync(join(parent, ".building-"));
|
|
2355
|
+
const built = [];
|
|
2356
|
+
try {
|
|
2357
|
+
for (const context of new Set(images.map((image) => resolve(cwd, image.context ?? "."))))
|
|
2358
|
+
prepareDependencyInputs(context);
|
|
2359
|
+
for (const image of images) {
|
|
2360
|
+
if (!["app", "web"].includes(image.name))
|
|
2361
|
+
throw new Error(cliM("cli_build_images_missing"));
|
|
2362
|
+
const context = resolve(cwd, image.context ?? ".");
|
|
2363
|
+
const imageRef = `localhost/hai-${basenameSafe(cwd)}-${image.name}:${buildId.toLowerCase().replaceAll("+", "_")}`;
|
|
2364
|
+
const args = ["build", "-f", resolve(cwd, image.dockerfile), "-t", imageRef];
|
|
2365
|
+
if (options.network !== void 0)
|
|
2366
|
+
args.push("--network", options.network);
|
|
2367
|
+
const ignore = `${resolve(cwd, image.dockerfile)}.dockerignore`;
|
|
2368
|
+
if (runtime === "podman" && existsSync(ignore))
|
|
2369
|
+
args.push("--ignorefile", ignore);
|
|
2370
|
+
if (runtime === "podman") {
|
|
2371
|
+
args.push("--format", "docker", "--jobs", "1", ...options.httpProxy === false ? ["--http-proxy=false"] : []);
|
|
2372
|
+
if (options.httpProxy !== false) {
|
|
2373
|
+
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "FTP_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "ftp_proxy", "no_proxy"]) {
|
|
2374
|
+
if (process4.env[key])
|
|
2375
|
+
args.push("--build-arg", `${key}=${process4.env[key]}`);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
if (options.sqlitePrebuildMirror)
|
|
2380
|
+
args.push("--build-arg", `SQLITE_PREBUILD_MIRROR=${options.sqlitePrebuildMirror}`);
|
|
2381
|
+
built.push(imageRef);
|
|
2382
|
+
await run(runtime, [...args, context], cwd);
|
|
2383
|
+
const file = `${image.name}-${appVersion}.tar`;
|
|
2384
|
+
await run(runtime, ["save", ...runtime === "podman" ? ["--format", "docker-archive"] : [], "-o", join(staging, file), imageRef], cwd);
|
|
2385
|
+
const hash = createHash("sha256");
|
|
2386
|
+
for await (const chunk of createReadStream(join(staging, file)))
|
|
2387
|
+
hash.update(chunk);
|
|
2388
|
+
artifact.images.push({ name: image.name, file, imageRef, sha256: hash.digest("hex"), ...image.containerPort ? { containerPort: image.containerPort } : {} });
|
|
2389
|
+
}
|
|
2390
|
+
const output = join(parent, buildId);
|
|
2391
|
+
writeFileSync(join(staging, "manifest.json"), `${JSON.stringify(artifact, null, 2)}
|
|
2392
|
+
`);
|
|
2393
|
+
renameSync(staging, output);
|
|
2394
|
+
writeFileSync(join(parent, `latest-${buildId}`), `${buildId}
|
|
2395
|
+
`);
|
|
2396
|
+
renameSync(join(parent, `latest-${buildId}`), join(parent, "latest"));
|
|
2397
|
+
writeCliMessage(cliM("cli_build_ready", { params: { path: output, runtime } }));
|
|
2398
|
+
} catch (error) {
|
|
2399
|
+
for (const imageRef of built) {
|
|
2400
|
+
try {
|
|
2401
|
+
await run(runtime, ["image", "rm", imageRef], cwd, true);
|
|
2402
|
+
} catch {
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
throw error;
|
|
2406
|
+
} finally {
|
|
2407
|
+
rmSync(staging, { recursive: true, force: true });
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
function prepareDependencyInputs(cwd) {
|
|
2411
|
+
const destination = join(cwd, "images", "dependencies");
|
|
2412
|
+
mkdirSync(destination, { recursive: true });
|
|
2413
|
+
const tracking = join(destination, ".managed.json");
|
|
2414
|
+
const previous = existsSync(tracking) ? JSON.parse(readFileSync(tracking, "utf8")) : [];
|
|
2415
|
+
const files = [];
|
|
2416
|
+
const walk = (directory) => {
|
|
2417
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
2418
|
+
const source = join(directory, entry.name);
|
|
2419
|
+
if (source === destination || ["node_modules", ".git", ".hai", ".turbo", ".svelte-kit", ".pnpm-store", ".cache", "dist", "build", "data", "tmp", "test-results", "playwright-report"].includes(entry.name))
|
|
2420
|
+
continue;
|
|
2421
|
+
if (entry.isDirectory()) {
|
|
2422
|
+
walk(source);
|
|
2423
|
+
continue;
|
|
2424
|
+
}
|
|
2425
|
+
if (!entry.isFile() || !(entry.name === "package.json" || entry.name === ".pnpmfile.cjs" || /\.(?:tgz|patch)$/.test(entry.name)))
|
|
2426
|
+
continue;
|
|
2427
|
+
const file = relative(cwd, source);
|
|
2428
|
+
mkdirSync(dirname(join(destination, file)), { recursive: true });
|
|
2429
|
+
const target = join(destination, file);
|
|
2430
|
+
if (!existsSync(target) || !readFileSync(source).equals(readFileSync(target)))
|
|
2431
|
+
copyFileSync(source, target);
|
|
2432
|
+
files.push(file);
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
walk(cwd);
|
|
2436
|
+
for (const file of previous) {
|
|
2437
|
+
const target = resolve(destination, file);
|
|
2438
|
+
if (!files.includes(file) && !relative(destination, target).startsWith(".."))
|
|
2439
|
+
rmSync(target, { force: true });
|
|
2440
|
+
}
|
|
2441
|
+
const content = `${JSON.stringify(files)}
|
|
2442
|
+
`;
|
|
2443
|
+
if (!existsSync(tracking) || readFileSync(tracking, "utf8") !== content)
|
|
2444
|
+
writeFileSync(tracking, content);
|
|
2445
|
+
}
|
|
2446
|
+
function basenameSafe(directory) {
|
|
2447
|
+
return directory.slice(dirname(directory).length + 1).toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+/, "") || "app";
|
|
2448
|
+
}
|
|
2304
2449
|
function displayDeployValue(name, value) {
|
|
2305
2450
|
if (/PASSWORD|SECRET|TOKEN|APIKEY|PRIVATEKEY|ACCESSKEY|AUTHORIZATION/i.test(name.replace(/[^a-z0-9]/gi, "")) || /(?:^|_)PASS$/i.test(name) || value.includes("PRIVATE KEY"))
|
|
2306
2451
|
return cliM("cli_deploy_secret");
|
|
@@ -2421,10 +2566,10 @@ function ensureConfigNode(appDir, requirement, backend) {
|
|
|
2421
2566
|
if (document.errors.length)
|
|
2422
2567
|
throw new Error(cliM("cli_deploy_invalid_value"));
|
|
2423
2568
|
if (requirement.configPath.length && !document.hasIn(requirement.configPath))
|
|
2424
|
-
document.setIn(requirement.configPath, requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service, type: backend } : {});
|
|
2569
|
+
document.setIn(requirement.configPath, document.createNode(requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service, type: backend } : {}));
|
|
2425
2570
|
if (requirement.service === "ldap") {
|
|
2426
2571
|
if (!document.hasIn(["login"]))
|
|
2427
|
-
document.
|
|
2572
|
+
document.set("login", document.createNode({}));
|
|
2428
2573
|
if (!document.hasIn(["login", "ldap"]))
|
|
2429
2574
|
document.setIn(["login", "ldap"], `\${HAI_IAM_LOGIN_LDAP:false}`);
|
|
2430
2575
|
}
|
|
@@ -2447,7 +2592,7 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
|
|
|
2447
2592
|
const current = choices.find((item) => item.backend === requirement.backend);
|
|
2448
2593
|
if (!current)
|
|
2449
2594
|
throw new Error(cliM("cli_deploy_cloud_unsupported", { params: { backend: requirement.backend } }));
|
|
2450
|
-
|
|
2595
|
+
writeCliMessage(cliM("cli_deploy_middleware", { params: { service: requirement.service, backend: current.label, purpose: module.describeMiddlewareBackend(current), source: requirement.source } }));
|
|
2451
2596
|
const canKeep = canKeepMiddleware(requirement, current, environment);
|
|
2452
2597
|
const mode = yes ? canKeep ? "keep" : "default" : await ask({
|
|
2453
2598
|
type: "select",
|
|
@@ -2486,8 +2631,11 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
|
|
|
2486
2631
|
if (deployment.errors.length)
|
|
2487
2632
|
throw new Error(cliM("cli_deploy_invalid_value"));
|
|
2488
2633
|
if (!deployment.hasIn(["container", "s3Engine"])) {
|
|
2489
|
-
|
|
2490
|
-
|
|
2634
|
+
const node = deployment.get("container", true);
|
|
2635
|
+
if (node === void 0 || isScalar(node) && node.value === null)
|
|
2636
|
+
deployment.set("container", deployment.createNode({}));
|
|
2637
|
+
else if (!isMap(node))
|
|
2638
|
+
throw new Error(cliM("cli_deploy_yaml_node", { params: { path: deploymentFile, field: "container" } }));
|
|
2491
2639
|
deployment.setIn(["container", "s3Engine"], `\${HAI_DEPLOY_CONTAINER_S3ENGINE:rustfs}`);
|
|
2492
2640
|
writeFileSync(deploymentFile, deployment.toString());
|
|
2493
2641
|
}
|
|
@@ -2501,9 +2649,10 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
|
|
|
2501
2649
|
for (const name of CLOUD_FIELDS[backend.provisioner]) {
|
|
2502
2650
|
const key = `HAI_DEPLOY_SERVICES_${requirement.service.toUpperCase()}_${name.toUpperCase()}`;
|
|
2503
2651
|
const initial = String(priorConfig[name] ?? environment.values[key] ?? "");
|
|
2504
|
-
const
|
|
2652
|
+
const label = `${backend.label} \u2014 ${cliM(`cli_deploy_cloud_input_${name}`)}`;
|
|
2653
|
+
const value = yes ? initial : String(await ask({ type: /key|token|secret/i.test(name) ? "password" : "text", name: "value", message: label, initial, validate: (input) => Boolean(input) || cliM("cli_deploy_required") }));
|
|
2505
2654
|
if (!value)
|
|
2506
|
-
throw new Error(`${
|
|
2655
|
+
throw new Error(`${label}: ${cliM("cli_deploy_required")}`);
|
|
2507
2656
|
cloud[name] = value;
|
|
2508
2657
|
updates[key] = value;
|
|
2509
2658
|
}
|
|
@@ -2531,7 +2680,7 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
|
|
|
2531
2680
|
if (!saved.success)
|
|
2532
2681
|
throw new Error(saved.error.message);
|
|
2533
2682
|
for (const [name, value] of Object.entries(updates)) {
|
|
2534
|
-
|
|
2683
|
+
writeCliMessage(cliM("cli_deploy_saved", { params: { name, value: displayDeployValue(name, value) } }));
|
|
2535
2684
|
}
|
|
2536
2685
|
}
|
|
2537
2686
|
config.services = Object.keys(services).length ? services : void 0;
|
|
@@ -2542,15 +2691,21 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
|
|
|
2542
2691
|
function configObject(value) {
|
|
2543
2692
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2544
2693
|
}
|
|
2545
|
-
async function configureDeployTarget(module, config, environment, nonInteractive) {
|
|
2694
|
+
async function configureDeployTarget(module, config, environment, nonInteractive, bundleOnly) {
|
|
2695
|
+
if (bundleOnly) {
|
|
2696
|
+
const existing = configObject(config.provider);
|
|
2697
|
+
if (existing.type && existing.type !== "docker-ssh")
|
|
2698
|
+
throw new Error(cliM("cli_deploy_bundle_provider"));
|
|
2699
|
+
config.provider = { type: "docker-ssh", remote: { runtime: "auto" }, expose: { hostPort: 3e3 }, ...existing };
|
|
2700
|
+
}
|
|
2546
2701
|
const current = module.ProviderConfigSchema.safeParse(config.provider);
|
|
2547
|
-
if (current.success) {
|
|
2702
|
+
if (current.success && (bundleOnly || current.data.type !== "docker-ssh" || current.data.ssh)) {
|
|
2548
2703
|
config.provider = current.data;
|
|
2549
|
-
|
|
2704
|
+
writeCliMessage(cliM("cli_deploy_target_reused", { params: { target: current.data.type === "docker-ssh" ? current.data.ssh ? `${current.data.ssh.username}@${current.data.ssh.host}:${current.data.ssh.port}` : cliM("cli_deploy_bundle_local") : "Vercel" } }));
|
|
2550
2705
|
return;
|
|
2551
2706
|
}
|
|
2552
2707
|
if (nonInteractive)
|
|
2553
|
-
throw new Error(cliM("cli_deploy_target_missing", { params: { fields: current.error.issues.map((issue) => `provider.${issue.path.join(".")}`).join(", ") } }));
|
|
2708
|
+
throw new Error(cliM("cli_deploy_target_missing", { params: { fields: current.success ? "provider.ssh" : current.error.issues.map((issue) => `provider.${issue.path.join(".")}`).join(", ") } }));
|
|
2554
2709
|
const original = configObject(config.provider);
|
|
2555
2710
|
const platform = await prompts({
|
|
2556
2711
|
type: "select",
|
|
@@ -2565,9 +2720,9 @@ async function configureDeployTarget(module, config, environment, nonInteractive
|
|
|
2565
2720
|
const answer = await prompts({
|
|
2566
2721
|
type: options.secret ? "password" : "text",
|
|
2567
2722
|
name: "value",
|
|
2568
|
-
message: cliM("
|
|
2723
|
+
message: cliM(`cli_deploy_input${name.split("_").at(-1)}`),
|
|
2569
2724
|
initial: options.secret ? void 0 : initial === void 0 ? "" : String(initial),
|
|
2570
|
-
validate: (value) => options.port ? /^\d+$/.test(value) && Number(value) >= 1 && Number(value) <= 65535 || cliM("
|
|
2725
|
+
validate: (value) => options.port ? /^\d+$/.test(value) && Number(value) >= 1 && Number(value) <= 65535 || cliM("cli_deploy_port_invalid") : options.optional || !!value.trim() || cliM("cli_deploy_invalid_value")
|
|
2571
2726
|
});
|
|
2572
2727
|
if (answer.value === void 0)
|
|
2573
2728
|
throw new Error(cliM("cli_deploy_cancelled"));
|
|
@@ -2575,7 +2730,7 @@ async function configureDeployTarget(module, config, environment, nonInteractive
|
|
|
2575
2730
|
};
|
|
2576
2731
|
let provider;
|
|
2577
2732
|
if (platform.value === "docker-ssh") {
|
|
2578
|
-
|
|
2733
|
+
writeCliMessage(cliM("cli_deploy_target_ssh_hint"));
|
|
2579
2734
|
const ssh = configObject(original.ssh);
|
|
2580
2735
|
const remote = configObject(original.remote);
|
|
2581
2736
|
const expose = configObject(original.expose);
|
|
@@ -2606,38 +2761,59 @@ async function configureDeployTarget(module, config, environment, nonInteractive
|
|
|
2606
2761
|
if (!saved.success)
|
|
2607
2762
|
throw new Error(saved.error.message);
|
|
2608
2763
|
config.provider = validated.data;
|
|
2609
|
-
|
|
2764
|
+
writeCliMessage(cliM("cli_deploy_target_saved"));
|
|
2610
2765
|
}
|
|
2611
2766
|
async function deployCommand(options) {
|
|
2612
2767
|
const spinner = ora();
|
|
2613
|
-
const cwd = options.cwd ??
|
|
2768
|
+
const cwd = options.cwd ?? process4.cwd();
|
|
2614
2769
|
let appDir = resolve(cwd, options.appDir ?? ".");
|
|
2615
2770
|
let closeDeploy = null;
|
|
2616
|
-
|
|
2771
|
+
let restoreProjectLogger;
|
|
2772
|
+
const originalEnv = { ...process4.env };
|
|
2773
|
+
const previousLogLevel = core.logger.getLevel();
|
|
2774
|
+
core.logger.setLevel("fatal");
|
|
2617
2775
|
try {
|
|
2776
|
+
if (options.runtime && !["auto", "podman", "docker"].includes(options.runtime))
|
|
2777
|
+
throw new Error(cliM("cli_build_runtime_missing"));
|
|
2778
|
+
if (options.artifactDir && options.build)
|
|
2779
|
+
throw new Error(cliM("cli_deploy_build_artifact_conflict"));
|
|
2780
|
+
if (!options.build && (options.network !== void 0 || options.sqlitePrebuildMirror !== void 0 || options.httpProxy === false))
|
|
2781
|
+
throw new Error(cliM("cli_deploy_build_options_required"));
|
|
2782
|
+
let selectedServices = options.services?.split(",").map((name) => name.trim());
|
|
2783
|
+
if (selectedServices && (!selectedServices.length || selectedServices.some((name) => !/^[a-z][a-z0-9-]*$/.test(name)) || new Set(selectedServices).size !== selectedServices.length))
|
|
2784
|
+
throw new Error(cliM("cli_build_selection_invalid"));
|
|
2618
2785
|
let environmentName = options.env;
|
|
2619
2786
|
if (!environmentName) {
|
|
2620
|
-
if (!
|
|
2787
|
+
if (!process4.stdin.isTTY)
|
|
2621
2788
|
throw new Error(cliM("cli_deploy_environment_hint"));
|
|
2622
2789
|
const answer = await prompts({ type: "text", name: "environment", message: cliM("cli_deploy_environment"), initial: "prod" });
|
|
2623
2790
|
environmentName = answer.environment;
|
|
2624
2791
|
if (!environmentName)
|
|
2625
2792
|
throw new Error(cliM("cli_deploy_cancelled"));
|
|
2626
2793
|
}
|
|
2627
|
-
if (!options.yes && !
|
|
2794
|
+
if (!options.yes && !process4.stdin.isTTY)
|
|
2628
2795
|
throw new Error(cliM("cli_deploy_environment_hint"));
|
|
2629
2796
|
let deployModule;
|
|
2630
2797
|
try {
|
|
2631
2798
|
const localRequire = createRequire(resolve(appDir, "package.json"));
|
|
2799
|
+
try {
|
|
2800
|
+
const { core: projectCore } = await import(pathToFileURL(localRequire.resolve("@h-ai/core")).href);
|
|
2801
|
+
if (projectCore !== core) {
|
|
2802
|
+
const level = projectCore.logger.getLevel();
|
|
2803
|
+
projectCore.logger.setLevel("fatal");
|
|
2804
|
+
restoreProjectLogger = () => projectCore.logger.setLevel(level);
|
|
2805
|
+
}
|
|
2806
|
+
} catch {
|
|
2807
|
+
}
|
|
2632
2808
|
let localModule;
|
|
2633
2809
|
try {
|
|
2634
2810
|
localModule = localRequire.resolve("@h-ai/deploy");
|
|
2635
2811
|
} catch {
|
|
2636
2812
|
}
|
|
2637
|
-
deployModule = localModule ? await import(pathToFileURL(localModule).href) : await import('./src-
|
|
2813
|
+
deployModule = localModule ? await import(pathToFileURL(localModule).href) : await import('./src-2V6VP4I5.js');
|
|
2638
2814
|
} catch {
|
|
2639
|
-
|
|
2640
|
-
|
|
2815
|
+
writeCliError(chalk.red(cliM("cli_deploy_module_missing")));
|
|
2816
|
+
writeCliMessage(chalk.cyan(" pnpm add -D @h-ai/deploy"));
|
|
2641
2817
|
throw new Error(cliM("cli_deploy_module_missing"));
|
|
2642
2818
|
}
|
|
2643
2819
|
const { deploy } = deployModule;
|
|
@@ -2646,10 +2822,19 @@ async function deployCommand(options) {
|
|
|
2646
2822
|
throw new Error(cliM("cli_deploy_scan_failed", { params: { message: scanResult.error.message } }));
|
|
2647
2823
|
const scan = scanResult.data;
|
|
2648
2824
|
appDir = scan.appDir ?? appDir;
|
|
2825
|
+
let buildRoot = appDir;
|
|
2826
|
+
while (!existsSync(resolve(buildRoot, "hai.build.json")) && dirname(buildRoot) !== buildRoot)
|
|
2827
|
+
buildRoot = dirname(buildRoot);
|
|
2828
|
+
const buildFile = resolve(buildRoot, "hai.build.json");
|
|
2829
|
+
const buildConfig = existsSync(buildFile) ? JSON.parse(readFileSync(buildFile, "utf8")) : {};
|
|
2830
|
+
const names = [...buildConfig.images?.map((image) => image.name) ?? ["app", "web"], ...(scan.middlewares ?? []).filter((item) => !["sqlite", "local", "memory", "disabled", "console", "lancedb"].includes(item.backend)).map((item) => item.service)];
|
|
2831
|
+
const available = [...new Set(names)];
|
|
2832
|
+
if (selectedServices?.some((name) => !available.includes(name)))
|
|
2833
|
+
throw new Error(cliM("cli_deploy_services_invalid", { params: { services: available.join(", ") } }));
|
|
2649
2834
|
const environment = deployModule.loadDeploymentEnvironment({ environment: environmentName, envDir: scan.envDir ?? appDir });
|
|
2650
2835
|
if (!environment.success)
|
|
2651
2836
|
throw new Error(environment.error.message);
|
|
2652
|
-
Object.assign(
|
|
2837
|
+
Object.assign(process4.env, environment.data.values);
|
|
2653
2838
|
spinner.start(cliM("cli_deploy_load_credentials"));
|
|
2654
2839
|
const credResult = deploy.credentials.load();
|
|
2655
2840
|
if (!credResult.success) {
|
|
@@ -2660,17 +2845,59 @@ async function deployCommand(options) {
|
|
|
2660
2845
|
const configPath = resolve(appDir, "config", "_deploy.yml");
|
|
2661
2846
|
if (!existsSync(configPath)) {
|
|
2662
2847
|
spinner.fail(chalk.red(cliM("cli_deploy_config_missing", { params: { path: configPath } })));
|
|
2663
|
-
|
|
2848
|
+
writeCliMessage(chalk.cyan(cliM("cli_deploy_config_hint")));
|
|
2664
2849
|
throw new Error(cliM("cli_deploy_config_missing", { params: { path: configPath } }));
|
|
2665
2850
|
}
|
|
2666
|
-
const loadedConfig = deployModule.loadDeploymentConfig("deploy", configPath,
|
|
2851
|
+
const loadedConfig = deployModule.loadDeploymentConfig("deploy", configPath, process4.env);
|
|
2667
2852
|
if (!loadedConfig.success)
|
|
2668
2853
|
throw new Error(loadedConfig.error.message);
|
|
2669
2854
|
const deployConfig = loadedConfig.data;
|
|
2670
2855
|
spinner.succeed(cliM("cli_deploy_config_loaded"));
|
|
2671
|
-
|
|
2856
|
+
if (!options.build && !selectedServices && configObject(deployConfig.provider).type === "docker-ssh") {
|
|
2857
|
+
let artifactDirectory = options.artifactDir ? resolve(cwd, options.artifactDir) : resolve(buildRoot, ".hai/build/docker");
|
|
2858
|
+
if (existsSync(resolve(artifactDirectory, "latest"))) {
|
|
2859
|
+
const pointer = readFileSync(resolve(artifactDirectory, "latest"), "utf8").trim();
|
|
2860
|
+
if (!/^[a-z0-9][\w.+-]*$/i.test(pointer))
|
|
2861
|
+
throw new Error(cliM("cli_deploy_artifact_invalid"));
|
|
2862
|
+
artifactDirectory = resolve(artifactDirectory, pointer);
|
|
2863
|
+
}
|
|
2864
|
+
if (existsSync(resolve(artifactDirectory, "manifest.json"))) {
|
|
2865
|
+
try {
|
|
2866
|
+
const artifact = await deployModule.readDeploymentArtifact(artifactDirectory);
|
|
2867
|
+
if (artifact.partial)
|
|
2868
|
+
selectedServices = artifact.images.map((image) => image.name);
|
|
2869
|
+
} catch {
|
|
2870
|
+
throw new Error(cliM("cli_deploy_artifact_invalid"));
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
await configureDeployTarget(deployModule, deployConfig, environment.data, options.yes, options.bundleOnly);
|
|
2875
|
+
if (configObject(deployConfig.provider).type !== "docker-ssh" && (selectedServices || options.build))
|
|
2876
|
+
throw new Error(cliM("cli_deploy_bundle_provider"));
|
|
2877
|
+
if (!options.yes && !selectedServices && configObject(deployConfig.provider).type === "docker-ssh") {
|
|
2878
|
+
if (existsSync(buildFile)) {
|
|
2879
|
+
if (available.length > 1) {
|
|
2880
|
+
const choice = await prompts({ type: "multiselect", name: "value", message: cliM("cli_deploy_choose_services"), choices: available.map((name) => ({ title: name, value: name, selected: true })), min: 1 });
|
|
2881
|
+
if (!Array.isArray(choice.value) || choice.value.length === 0)
|
|
2882
|
+
throw new Error(cliM("cli_deploy_cancelled"));
|
|
2883
|
+
if (choice.value.length !== available.length)
|
|
2884
|
+
selectedServices = choice.value;
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2672
2888
|
spinner.succeed(cliM("cli_deploy_scanned", { params: { name: scan.appName, sveltekit: String(scan.isSvelteKit), services: scan.requiredServices.join(", ") || cliM("cli_deploy_none") } }));
|
|
2673
|
-
|
|
2889
|
+
if (options.build) {
|
|
2890
|
+
if (!existsSync(buildFile))
|
|
2891
|
+
throw new Error(cliM("cli_build_images_missing"));
|
|
2892
|
+
const imageNames = selectedServices?.filter((name) => ["app", "web"].includes(name));
|
|
2893
|
+
if (!selectedServices || imageNames?.length) {
|
|
2894
|
+
writeCliMessage(cliM("cli_deploy_build_started"));
|
|
2895
|
+
await buildCommand({ cwd: buildRoot, target: "docker", images: imageNames?.join(","), runtime: options.runtime, network: options.network, sqlitePrebuildMirror: options.sqlitePrebuildMirror, httpProxy: options.httpProxy });
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
if (selectedServices)
|
|
2899
|
+
writeCliMessage(cliM("cli_deploy_services", { params: { services: selectedServices.join(", ") } }));
|
|
2900
|
+
let skipMiddleware = options.skipMiddleware || selectedServices?.every((name) => ["app", "web"].includes(name));
|
|
2674
2901
|
if (!skipMiddleware && !options.yes && scan.requiredServices.length) {
|
|
2675
2902
|
const action = await prompts({
|
|
2676
2903
|
type: "select",
|
|
@@ -2683,23 +2910,23 @@ async function deployCommand(options) {
|
|
|
2683
2910
|
skipMiddleware = action.value === "skip";
|
|
2684
2911
|
}
|
|
2685
2912
|
if (skipMiddleware)
|
|
2686
|
-
|
|
2687
|
-
const serviceModes = skipMiddleware ? Object.fromEntries(scan.requiredServices.map((service) => [service, "keep"])) : await configureDeployMiddleware(deployModule, scan, environment.data, deployConfig, options.yes);
|
|
2913
|
+
writeCliMessage(cliM("cli_deploy_middleware_skipped"));
|
|
2914
|
+
const serviceModes = skipMiddleware ? Object.fromEntries(scan.requiredServices.map((service) => [service, "keep"])) : await configureDeployMiddleware(deployModule, { ...scan, middlewares: scan.middlewares?.filter((item) => !selectedServices || selectedServices.includes(item.service)) }, environment.data, deployConfig, options.yes);
|
|
2688
2915
|
if (skipMiddleware)
|
|
2689
2916
|
deployConfig.services = void 0;
|
|
2690
|
-
for (const key of Object.keys(
|
|
2917
|
+
for (const key of Object.keys(process4.env)) {
|
|
2691
2918
|
if (originalEnv[key] === void 0)
|
|
2692
|
-
delete
|
|
2919
|
+
delete process4.env[key];
|
|
2693
2920
|
}
|
|
2694
|
-
Object.assign(
|
|
2921
|
+
Object.assign(process4.env, originalEnv);
|
|
2695
2922
|
const refreshed = deployModule.loadDeploymentEnvironment({ environment: environment.data.environment, envDir: environment.data.envDir });
|
|
2696
2923
|
if (!refreshed.success)
|
|
2697
2924
|
throw new Error(refreshed.error.message);
|
|
2698
|
-
Object.assign(
|
|
2925
|
+
Object.assign(process4.env, refreshed.data.values);
|
|
2699
2926
|
spinner.start(cliM("cli_deploy_initializing"));
|
|
2700
2927
|
const validatedConfig = deployModule.DeployConfigSchema.safeParse(deployConfig);
|
|
2701
2928
|
if (!validatedConfig.success)
|
|
2702
|
-
throw new Error(cliM("
|
|
2929
|
+
throw new Error(cliM("cli_deploy_config_invalid", { params: { path: configPath, fields: validatedConfig.error.issues.map((issue) => issue.path.join(".")).join(", ") } }));
|
|
2703
2930
|
const initResult = await deploy.init(validatedConfig.data);
|
|
2704
2931
|
if (!initResult.success) {
|
|
2705
2932
|
throw new Error(cliM("cli_deploy_init_failed", { params: { message: initResult.error.message } }));
|
|
@@ -2709,26 +2936,29 @@ async function deployCommand(options) {
|
|
|
2709
2936
|
spinner.start(cliM("cli_deploy_deploying"));
|
|
2710
2937
|
const deployResult = await deploy.deployApp(appDir, {
|
|
2711
2938
|
projectName: options.projectName,
|
|
2712
|
-
|
|
2713
|
-
|
|
2939
|
+
selectedServices,
|
|
2940
|
+
runtime: options.runtime,
|
|
2941
|
+
onProgress: (stage) => writeCliMessage(cliM(`cli_deploy_stage_${stage}`)),
|
|
2942
|
+
skipMiddleware,
|
|
2943
|
+
artifactDir: options.artifactDir ? resolve(cwd, options.artifactDir) : void 0,
|
|
2944
|
+
bundleOnly: options.bundleOnly,
|
|
2945
|
+
output: options.output ? resolve(cwd, options.output) : void 0,
|
|
2714
2946
|
environment: environment.data.environment,
|
|
2715
2947
|
envDir: environment.data.envDir,
|
|
2716
2948
|
serviceModes
|
|
2717
2949
|
});
|
|
2718
2950
|
if (!deployResult.success) {
|
|
2719
2951
|
if (deployResult.error.ext?.recovery) {
|
|
2720
|
-
|
|
2721
|
-
recovery: deployResult.error.ext.recovery
|
|
2722
|
-
});
|
|
2952
|
+
writeCliMessage(deployResult.error.suggestion ?? cliM("cli_deploy_command_failed"));
|
|
2723
2953
|
}
|
|
2724
2954
|
throw new Error(cliM("cli_deploy_failed", { params: { message: deployResult.error.message } }));
|
|
2725
2955
|
}
|
|
2726
|
-
spinner.succeed(chalk.green(cliM("cli_deploy_success")));
|
|
2727
|
-
for (const key of Object.keys(
|
|
2956
|
+
spinner.succeed(chalk.green(cliM(deployResult.data.status === "packaged" ? "cli_deploy_bundle_ready" : "cli_deploy_success")));
|
|
2957
|
+
for (const key of Object.keys(process4.env)) {
|
|
2728
2958
|
if (originalEnv[key] === void 0)
|
|
2729
|
-
delete
|
|
2959
|
+
delete process4.env[key];
|
|
2730
2960
|
}
|
|
2731
|
-
Object.assign(
|
|
2961
|
+
Object.assign(process4.env, originalEnv);
|
|
2732
2962
|
const completedEnvironment = deployModule.loadDeploymentEnvironment({ environment: environment.data.environment, envDir: environment.data.envDir });
|
|
2733
2963
|
if (!completedEnvironment.success)
|
|
2734
2964
|
throw new Error(completedEnvironment.error.message);
|
|
@@ -2737,33 +2967,40 @@ async function deployCommand(options) {
|
|
|
2737
2967
|
continue;
|
|
2738
2968
|
const value = completedEnvironment.data.values[requirement.envPrefix];
|
|
2739
2969
|
if (value !== void 0)
|
|
2740
|
-
|
|
2970
|
+
writeCliMessage(cliM("cli_deploy_saved", { params: { name: requirement.envPrefix, value: displayDeployValue(requirement.envPrefix, value) } }));
|
|
2741
2971
|
}
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2972
|
+
writeCliMessage("");
|
|
2973
|
+
if (deployResult.data.bundlePath)
|
|
2974
|
+
writeCliMessage(cliM("cli_deploy_bundle_path", { params: { path: deployResult.data.bundlePath } }));
|
|
2975
|
+
if (deployResult.data.status === "packaged")
|
|
2976
|
+
writeCliMessage(deployResult.data.instructions ?? "");
|
|
2977
|
+
else
|
|
2978
|
+
writeCliMessage(chalk.cyan(cliM("cli_deploy_url", { params: { url: deployResult.data.url } })));
|
|
2979
|
+
writeCliMessage(chalk.gray(cliM("cli_deploy_id", { params: { id: deployResult.data.deploymentId } })));
|
|
2745
2980
|
if (deployResult.data.envVarsSet.length > 0) {
|
|
2746
|
-
|
|
2981
|
+
writeCliMessage(chalk.gray(cliM("cli_deploy_env", { params: { names: deployResult.data.envVarsSet.join(", ") } })));
|
|
2747
2982
|
}
|
|
2748
|
-
|
|
2983
|
+
writeCliMessage("");
|
|
2749
2984
|
} catch (error) {
|
|
2750
2985
|
spinner.fail(chalk.red(cliM("cli_deploy_command_failed")));
|
|
2751
|
-
|
|
2986
|
+
writeCliError(error instanceof Error ? error.message : String(error));
|
|
2752
2987
|
throw error;
|
|
2753
2988
|
} finally {
|
|
2754
2989
|
if (closeDeploy) {
|
|
2755
2990
|
try {
|
|
2756
2991
|
await closeDeploy();
|
|
2757
2992
|
} catch (error) {
|
|
2758
|
-
|
|
2759
|
-
|
|
2993
|
+
writeCliError("Failed to close deploy module", error);
|
|
2994
|
+
process4.exitCode = 1;
|
|
2760
2995
|
}
|
|
2761
2996
|
}
|
|
2762
|
-
for (const key of Object.keys(
|
|
2997
|
+
for (const key of Object.keys(process4.env)) {
|
|
2763
2998
|
if (originalEnv[key] === void 0)
|
|
2764
|
-
delete
|
|
2999
|
+
delete process4.env[key];
|
|
2765
3000
|
}
|
|
2766
|
-
Object.assign(
|
|
3001
|
+
Object.assign(process4.env, originalEnv);
|
|
3002
|
+
core.logger.setLevel(previousLogLevel);
|
|
3003
|
+
restoreProjectLogger?.();
|
|
2767
3004
|
}
|
|
2768
3005
|
}
|
|
2769
3006
|
var GENERATORS = {
|
|
@@ -2799,7 +3036,7 @@ async function generate(options) {
|
|
|
2799
3036
|
try {
|
|
2800
3037
|
const project = await detectProject(cwd);
|
|
2801
3038
|
if (!project?.isHaiProject) {
|
|
2802
|
-
|
|
3039
|
+
writeCliMessage(chalk.yellow("\u8B66\u544A: \u5F53\u524D\u76EE\u5F55\u4E0D\u662F hai \u9879\u76EE"));
|
|
2803
3040
|
}
|
|
2804
3041
|
const resolvedOptions = await resolveGenerateOptions(options);
|
|
2805
3042
|
const generator = GENERATORS[resolvedOptions.type];
|
|
@@ -2826,8 +3063,8 @@ async function generate(options) {
|
|
|
2826
3063
|
while (!await fse.pathExists(ancestor))
|
|
2827
3064
|
ancestor = path2.dirname(ancestor);
|
|
2828
3065
|
const realAncestor = await fse.realpath(ancestor);
|
|
2829
|
-
const
|
|
2830
|
-
if (
|
|
3066
|
+
const relative2 = path2.relative(realCwd, realAncestor);
|
|
3067
|
+
if (relative2 === ".." || relative2.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative2))
|
|
2831
3068
|
throw new Error(cliM("cli_generateOutsideWorkspace"));
|
|
2832
3069
|
}
|
|
2833
3070
|
for (const file of files) {
|
|
@@ -2835,17 +3072,17 @@ async function generate(options) {
|
|
|
2835
3072
|
await fse.writeFile(file.path, file.content, { encoding: "utf-8", flag: resolvedOptions.force ? "w" : "wx" });
|
|
2836
3073
|
}
|
|
2837
3074
|
spinner.succeed();
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
3075
|
+
writeCliMessage("");
|
|
3076
|
+
writeCliMessage(chalk.green("\u2714 \u751F\u6210\u5B8C\u6210\uFF01"));
|
|
3077
|
+
writeCliMessage("");
|
|
3078
|
+
writeCliMessage("\u521B\u5EFA\u7684\u6587\u4EF6:");
|
|
2842
3079
|
files.forEach((file) => {
|
|
2843
|
-
|
|
3080
|
+
writeCliMessage(chalk.cyan(` ${path2.relative(cwd, file.path)}`));
|
|
2844
3081
|
});
|
|
2845
|
-
|
|
3082
|
+
writeCliMessage("");
|
|
2846
3083
|
} catch (error) {
|
|
2847
3084
|
spinner.fail();
|
|
2848
|
-
|
|
3085
|
+
writeCliError(chalk.red("\u751F\u6210\u5931\u8D25:"), { error });
|
|
2849
3086
|
throw error;
|
|
2850
3087
|
}
|
|
2851
3088
|
}
|
|
@@ -3107,39 +3344,39 @@ async function initProject(options) {
|
|
|
3107
3344
|
const { project, existing, missing } = result;
|
|
3108
3345
|
spinner.succeed(`\u68C0\u6D4B\u5230\u9879\u76EE: ${chalk.cyan(project.name)}`);
|
|
3109
3346
|
if (project.isHaiProject) {
|
|
3110
|
-
const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-
|
|
3347
|
+
const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-FC3QRKDO.js');
|
|
3111
3348
|
const generatedFiles = await generateProjectAiSupport(cwd, resolveSkillNamesForPackages(project.haiPackages), {
|
|
3112
3349
|
overwriteSkills: false,
|
|
3113
3350
|
overwriteBridgeFiles: false
|
|
3114
3351
|
});
|
|
3115
3352
|
for (const generatedFile of generatedFiles) {
|
|
3116
|
-
|
|
3353
|
+
writeCliMessage(chalk.gray(` + ${generatedFile}`));
|
|
3117
3354
|
}
|
|
3118
3355
|
}
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3356
|
+
writeCliMessage("");
|
|
3357
|
+
writeCliMessage(chalk.bold("\u9879\u76EE\u72B6\u6001:"));
|
|
3358
|
+
writeCliMessage(` \u540D\u79F0: ${chalk.cyan(project.name)}`);
|
|
3359
|
+
writeCliMessage(` \u7248\u672C: ${project.version}`);
|
|
3360
|
+
writeCliMessage(` \u5DF2\u5B89\u88C5\u6A21\u5757: ${project.haiPackages.length > 0 ? project.haiPackages.map((p) => chalk.cyan(p)).join(", ") : chalk.gray("\u65E0")}`);
|
|
3361
|
+
writeCliMessage("");
|
|
3125
3362
|
if (existing.length > 0) {
|
|
3126
|
-
|
|
3363
|
+
writeCliMessage(chalk.bold("\u5DF2\u6709\u914D\u7F6E:"));
|
|
3127
3364
|
for (const key of existing) {
|
|
3128
|
-
|
|
3365
|
+
writeCliMessage(chalk.green(` \u2714 config/_${key}.yml`));
|
|
3129
3366
|
}
|
|
3130
3367
|
}
|
|
3131
3368
|
if (missing.length === 0 && !options.force) {
|
|
3132
|
-
|
|
3133
|
-
|
|
3369
|
+
writeCliMessage("");
|
|
3370
|
+
writeCliMessage(chalk.green("\u2714 \u6240\u6709\u914D\u7F6E\u6587\u4EF6\u5DF2\u5C31\u7EEA\uFF01"));
|
|
3134
3371
|
return;
|
|
3135
3372
|
}
|
|
3136
3373
|
if (missing.length > 0) {
|
|
3137
|
-
|
|
3138
|
-
|
|
3374
|
+
writeCliMessage("");
|
|
3375
|
+
writeCliMessage(chalk.bold("\u7F3A\u5931\u914D\u7F6E:"));
|
|
3139
3376
|
for (const item of missing) {
|
|
3140
|
-
|
|
3377
|
+
writeCliMessage(chalk.yellow(` \u2718 config/_${item.configKey}.yml (${item.label})`));
|
|
3141
3378
|
}
|
|
3142
|
-
|
|
3379
|
+
writeCliMessage("");
|
|
3143
3380
|
spinner.start("\u751F\u6210\u7F3A\u5931\u914D\u7F6E\u6587\u4EF6...");
|
|
3144
3381
|
let generated = 0;
|
|
3145
3382
|
for (const item of missing) {
|
|
@@ -3151,7 +3388,7 @@ async function initProject(options) {
|
|
|
3151
3388
|
spinner.succeed(`\u5DF2\u751F\u6210 ${generated} \u4E2A\u914D\u7F6E\u6587\u4EF6`);
|
|
3152
3389
|
}
|
|
3153
3390
|
if (options.force && existing.length > 0) {
|
|
3154
|
-
|
|
3391
|
+
writeCliMessage("");
|
|
3155
3392
|
spinner.start("\u5F3A\u5236\u91CD\u65B0\u751F\u6210\u6240\u6709\u914D\u7F6E\u6587\u4EF6...");
|
|
3156
3393
|
let regenerated = 0;
|
|
3157
3394
|
for (const pkg of project.haiPackages) {
|
|
@@ -3172,16 +3409,16 @@ async function initProject(options) {
|
|
|
3172
3409
|
await writeFile(coreConfigPath, content);
|
|
3173
3410
|
spinner.succeed();
|
|
3174
3411
|
}
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3412
|
+
writeCliMessage("");
|
|
3413
|
+
writeCliMessage(chalk.green("\u2714 \u9879\u76EE\u914D\u7F6E\u521D\u59CB\u5316\u5B8C\u6210\uFF01"));
|
|
3414
|
+
writeCliMessage("");
|
|
3415
|
+
writeCliMessage(chalk.gray("\u4E0B\u4E00\u6B65:"));
|
|
3416
|
+
writeCliMessage(chalk.gray(" 1. \u7F16\u8F91 config/ \u4E2D\u7684\u914D\u7F6E\u6587\u4EF6"));
|
|
3417
|
+
writeCliMessage(chalk.gray(" 2. \u8FD0\u884C pnpm dev \u542F\u52A8\u5F00\u53D1\u670D\u52A1"));
|
|
3418
|
+
writeCliMessage("");
|
|
3182
3419
|
} catch (error) {
|
|
3183
3420
|
spinner.fail();
|
|
3184
|
-
|
|
3421
|
+
writeCliError(chalk.red("\u521D\u59CB\u5316\u5931\u8D25:"), { error });
|
|
3185
3422
|
throw error;
|
|
3186
3423
|
}
|
|
3187
3424
|
}
|
|
@@ -3189,9 +3426,9 @@ async function initProject(options) {
|
|
|
3189
3426
|
// src/cli-main.ts
|
|
3190
3427
|
var VERSION = getCliVersion();
|
|
3191
3428
|
var cli = cac("hai");
|
|
3192
|
-
cli.option("-v, --verbose", "
|
|
3193
|
-
cli.option("-C, --cwd <path>", "
|
|
3194
|
-
cli.command("create [name]", "
|
|
3429
|
+
cli.option("-v, --verbose", cliM("cli_help_verbose"));
|
|
3430
|
+
cli.option("-C, --cwd <path>", cliM("cli_help_cwd"));
|
|
3431
|
+
cli.command("create [name]", cliM("cli_help_create")).option("--type <type>", cliM("cli_help_appType")).option("--frontends <targets>", cliM("cli_help_frontends")).option("-t, --template <template>", cliM("cli_help_template")).option("-f, --features <features>", cliM("cli_help_features")).option("--examples", cliM("cli_help_examples")).option("--no-examples", cliM("cli_help_noExamples")).option("--no-install", cliM("cli_help_noInstall")).option("--no-deploy", cliM("cli_help_noDeploy")).option("-p, --package-manager <pm>", cliM("cli_help_packageManager")).option("--no-git", cliM("cli_help_noGit")).option("--git-remote <url>", cliM("cli_help_gitRemote")).option("-y, --yes", cliM("cli_help_yes")).action(async (name, options) => {
|
|
3195
3432
|
try {
|
|
3196
3433
|
const features = typeof options.features === "string" ? options.features.split(",").map((f) => f.trim()) : void 0;
|
|
3197
3434
|
const frontends = typeof options.frontends === "string" ? options.frontends.split(",").map((f) => f.trim()).filter(Boolean) : void 0;
|
|
@@ -3201,6 +3438,7 @@ cli.command("create [name]", "\u521B\u5EFA\u65B0\u7684 hai \u9879\u76EE").option
|
|
|
3201
3438
|
frontends,
|
|
3202
3439
|
template: options.template,
|
|
3203
3440
|
features,
|
|
3441
|
+
deploy: options.deploy,
|
|
3204
3442
|
examples: options.examples,
|
|
3205
3443
|
install: options.install,
|
|
3206
3444
|
packageManager: options.packageManager,
|
|
@@ -3211,10 +3449,10 @@ cli.command("create [name]", "\u521B\u5EFA\u65B0\u7684 hai \u9879\u76EE").option
|
|
|
3211
3449
|
cwd: options.cwd
|
|
3212
3450
|
});
|
|
3213
3451
|
} catch {
|
|
3214
|
-
|
|
3452
|
+
process4.exitCode = 1;
|
|
3215
3453
|
}
|
|
3216
3454
|
});
|
|
3217
|
-
cli.command("add [module]", "
|
|
3455
|
+
cli.command("add [module]", cliM("cli_help_add")).option("--no-install", cliM("cli_help_noInstall")).action(async (module, options) => {
|
|
3218
3456
|
try {
|
|
3219
3457
|
await addModule({
|
|
3220
3458
|
module,
|
|
@@ -3223,10 +3461,10 @@ cli.command("add [module]", "\u5411\u73B0\u6709\u9879\u76EE\u6DFB\u52A0\u6A21\u5
|
|
|
3223
3461
|
cwd: options.cwd
|
|
3224
3462
|
});
|
|
3225
3463
|
} catch {
|
|
3226
|
-
|
|
3464
|
+
process4.exit(1);
|
|
3227
3465
|
}
|
|
3228
3466
|
});
|
|
3229
|
-
cli.command("init", "
|
|
3467
|
+
cli.command("init", cliM("cli_help_init")).option("-f, --force", cliM("cli_help_forceConfig")).action(async (options) => {
|
|
3230
3468
|
try {
|
|
3231
3469
|
await initProject({
|
|
3232
3470
|
force: options.force,
|
|
@@ -3234,10 +3472,10 @@ cli.command("init", "\u521D\u59CB\u5316/\u6821\u9A8C\u9879\u76EE\u914D\u7F6E").o
|
|
|
3234
3472
|
cwd: options.cwd
|
|
3235
3473
|
});
|
|
3236
3474
|
} catch {
|
|
3237
|
-
|
|
3475
|
+
process4.exit(1);
|
|
3238
3476
|
}
|
|
3239
3477
|
});
|
|
3240
|
-
cli.command("generate [type] [name]", "
|
|
3478
|
+
cli.command("generate [type] [name]", cliM("cli_help_generate")).alias("g").option("-o, --output <path>", cliM("cli_help_output")).option("-f, --force", cliM("cli_help_force")).action(async (type, name, options) => {
|
|
3241
3479
|
try {
|
|
3242
3480
|
await generate({
|
|
3243
3481
|
type,
|
|
@@ -3248,10 +3486,18 @@ cli.command("generate [type] [name]", "\u751F\u6210\u4EE3\u7801").alias("g").opt
|
|
|
3248
3486
|
cwd: options.cwd
|
|
3249
3487
|
});
|
|
3250
3488
|
} catch {
|
|
3251
|
-
|
|
3489
|
+
process4.exit(1);
|
|
3252
3490
|
}
|
|
3253
3491
|
});
|
|
3254
|
-
cli.command("
|
|
3492
|
+
cli.command("build", cliM("cli_help_build")).option("--target <target>", cliM("cli_help_target")).option("--images <names>", cliM("cli_help_images")).option("--runtime <runtime>", cliM("cli_help_buildRuntime")).option("--network <network>", cliM("cli_help_buildNetwork")).option("--platform <platform>", cliM("cli_help_platform")).option("--arch <arch>", cliM("cli_help_arch")).option("--sqlite-prebuild-mirror <url>", cliM("cli_help_sqliteMirror")).option("--no-http-proxy", cliM("cli_help_noHttpProxy")).action(async (options) => {
|
|
3493
|
+
try {
|
|
3494
|
+
await buildCommand(options);
|
|
3495
|
+
} catch (error) {
|
|
3496
|
+
writeCliMessage(error instanceof Error ? error.message : String(error));
|
|
3497
|
+
process4.exitCode = 1;
|
|
3498
|
+
}
|
|
3499
|
+
});
|
|
3500
|
+
cli.command("deploy [appDir]", cliM("cli_help_deploy")).option("--env <environment>", cliM("cli_help_environment")).option("--env-dir <path>", cliM("cli_help_envDir")).option("-y, --yes", cliM("cli_help_deployYes")).option("--project-name <name>", cliM("cli_help_projectName")).option("--skip-middleware", cliM("cli_help_skipMiddleware")).option("--artifact-dir <path>", cliM("cli_help_artifactDir")).option("--services <names>", cliM("cli_help_services")).option("--build", cliM("cli_help_deployBuild")).option("--network <network>", cliM("cli_help_deployNetwork")).option("--sqlite-prebuild-mirror <url>", cliM("cli_help_deploySqliteMirror")).option("--no-http-proxy", cliM("cli_help_deployNoProxy")).option("--runtime <runtime>", cliM("cli_help_deployRuntime")).option("--bundle-only", cliM("cli_help_bundleOnly")).option("--output <path>", cliM("cli_help_bundleOutput")).action(async (appDir, options) => {
|
|
3255
3501
|
try {
|
|
3256
3502
|
await deployCommand({
|
|
3257
3503
|
appDir,
|
|
@@ -3259,29 +3505,36 @@ cli.command("deploy [appDir]", "\u90E8\u7F72\u5E94\u7528\u5230\u4E91\u5E73\u53F0
|
|
|
3259
3505
|
envDir: options.envDir,
|
|
3260
3506
|
yes: options.yes,
|
|
3261
3507
|
projectName: options.projectName,
|
|
3262
|
-
skipProvision: options.skipProvision,
|
|
3263
3508
|
skipMiddleware: options.skipMiddleware,
|
|
3264
|
-
|
|
3509
|
+
artifactDir: options.artifactDir,
|
|
3510
|
+
services: options.services,
|
|
3511
|
+
build: options.build,
|
|
3512
|
+
network: options.network,
|
|
3513
|
+
sqlitePrebuildMirror: options.sqlitePrebuildMirror,
|
|
3514
|
+
httpProxy: options.httpProxy,
|
|
3515
|
+
runtime: options.runtime,
|
|
3516
|
+
bundleOnly: options.bundleOnly,
|
|
3517
|
+
output: options.output,
|
|
3265
3518
|
verbose: options.verbose,
|
|
3266
3519
|
cwd: options.cwd
|
|
3267
3520
|
});
|
|
3268
3521
|
} catch {
|
|
3269
|
-
|
|
3522
|
+
process4.exitCode = 1;
|
|
3270
3523
|
}
|
|
3271
3524
|
});
|
|
3272
|
-
cli.command("g:page <name>", "
|
|
3525
|
+
cli.command("g:page <name>", cliM("cli_help_generatePage")).action(async (name, options) => {
|
|
3273
3526
|
await generate({ type: "page", name, cwd: options.cwd });
|
|
3274
3527
|
});
|
|
3275
|
-
cli.command("g:component <name>", "
|
|
3528
|
+
cli.command("g:component <name>", cliM("cli_help_generateComponent")).action(async (name, options) => {
|
|
3276
3529
|
await generate({ type: "component", name, cwd: options.cwd });
|
|
3277
3530
|
});
|
|
3278
|
-
cli.command("g:api <name>", "
|
|
3531
|
+
cli.command("g:api <name>", cliM("cli_help_generateApi")).action(async (name, options) => {
|
|
3279
3532
|
await generate({ type: "api", name, cwd: options.cwd });
|
|
3280
3533
|
});
|
|
3281
|
-
cli.command("g:model <name>", "
|
|
3534
|
+
cli.command("g:model <name>", cliM("cli_help_generateModel")).action(async (name, options) => {
|
|
3282
3535
|
await generate({ type: "model", name, cwd: options.cwd });
|
|
3283
3536
|
});
|
|
3284
|
-
cli.version(VERSION);
|
|
3537
|
+
cli.version(VERSION, "-V, --version");
|
|
3285
3538
|
cli.help();
|
|
3286
3539
|
var parsed = cli.parse();
|
|
3287
3540
|
if (!cli.matchedCommand && !parsed.options.help && !parsed.options.version) {
|