@h-ai/cli 0.1.0-alpha.66 → 0.1.0-alpha.68
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 +86 -241
- package/README.ja.md +86 -241
- package/README.md +86 -241
- package/dist/{src-RDQ2EIBT.js → chunk-FI56GONY.js} +32 -2669
- package/dist/chunk-FI56GONY.js.map +1 -0
- package/dist/chunk-MBPB37FR.js +1348 -0
- package/dist/chunk-MBPB37FR.js.map +1 -0
- package/dist/{chunk-QYX6V7H2.js → chunk-ZFNBUF77.js} +347 -276
- package/dist/chunk-ZFNBUF77.js.map +1 -0
- package/dist/cli-config-templates-W2LLPMB6.js +5 -0
- package/dist/{cli-config-templates-3XSFIXBP.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
- package/dist/{cli-skill-templates-TSYEQTCY.js → cli-skill-templates-US5CCAJ4.js} +3 -3
- package/dist/{cli-skill-templates-TSYEQTCY.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
- package/dist/index.js +1304 -532
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -0
- package/dist/src-DSJJXM2F.js +3849 -0
- package/dist/src-DSJJXM2F.js.map +1 -0
- package/package.json +9 -8
- package/templates/README.md +48 -145
- package/templates/apps/admin/messages/ja-JP.json +38 -0
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
- package/templates/apps/api/.dockerignore.hbs +14 -7
- package/templates/apps/api/.gitignore.hbs +7 -0
- package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +33 -8
- package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
- package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -2
- package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
- package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
- package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
- package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
- package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
- package/templates/apps/api/hai.build.json.hbs +1 -1
- package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
- package/templates/apps/api/package.json.hbs +6 -1
- package/templates/apps/api/playwright.config.ts.hbs +15 -7
- package/templates/apps/fullstack/.dockerignore.hbs +14 -9
- package/templates/apps/fullstack/.gitignore.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -6
- package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +23 -5
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -6
- package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +30 -9
- package/templates/apps/fullstack/hai.build.json.hbs +2 -2
- 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 +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +32 -8
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +40 -14
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +6 -6
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +4 -4
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +13 -14
- package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
- package/templates/apps/h5/messages/ja-JP.json +40 -0
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +16 -5
- package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
- package/templates/apps/mobile-app/src/App.svelte.hbs +15 -5
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
- package/templates/apps/website/messages/ja-JP.json +32 -0
- package/templates/apps/website/src/routes/+layout.svelte.hbs +13 -5
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
- package/templates/base/.dockerignore.hbs +14 -8
- package/templates/base/.gitignore.hbs +7 -0
- package/templates/base/e2e/app.spec.ts.hbs +23 -8
- package/templates/base/hai.build.json.hbs +1 -1
- package/templates/base/{Dockerfile.hbs → images/app.Dockerfile.hbs} +19 -17
- package/templates/base/package.json.hbs +11 -3
- package/templates/base/playwright.config.ts +15 -4
- package/templates/base/src/hooks.server.ts.hbs +15 -6
- package/templates/base/src/lib/server/init.ts.hbs +139 -30
- package/templates/base/vite.config.ts.hbs +5 -3
- package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
- package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
- package/templates/i18n/project.inlang/settings.json +1 -1
- package/templates/shared/.gitattributes +6 -0
- package/templates/shared/e2e-env.ts +98 -0
- package/templates/shared/run-e2e.mjs +51 -12
- package/templates/skills/AGENTS.md +7 -3
- package/templates/skills/bridges/admin/AGENTS.md +6 -2
- package/templates/skills/bridges/api/AGENTS.md +3 -1
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +5 -1
- package/templates/skills/bridges/h5/AGENTS.md +6 -2
- package/templates/skills/bridges/mobile-app/AGENTS.md +6 -2
- package/templates/skills/bridges/website/AGENTS.md +6 -2
- package/templates/skills/hai-ai/SKILL.md +8 -2
- package/templates/skills/hai-ai/guide.md +134 -60
- package/templates/skills/hai-ai/reference.md +51 -11
- package/templates/skills/hai-api-client/SKILL.md +15 -160
- package/templates/skills/hai-api-client/reference.md +178 -0
- package/templates/skills/hai-api-contract/SKILL.md +16 -273
- package/templates/skills/hai-api-contract/reference.md +280 -0
- package/templates/skills/hai-app-create/SKILL.md +6 -2
- package/templates/skills/hai-app-create/development.md +24 -8
- package/templates/skills/hai-app-create/reference.md +7 -5
- package/templates/skills/hai-app-review/SKILL.md +7 -3
- package/templates/skills/hai-app-review/differential.md +34 -0
- package/templates/skills/hai-app-tests/SKILL.md +6 -3
- package/templates/skills/hai-app-tests/reference.md +5 -5
- package/templates/skills/hai-app-tests/validation.md +38 -0
- package/templates/skills/hai-audit/SKILL.md +24 -147
- package/templates/skills/hai-audit/reference.md +148 -0
- package/templates/skills/hai-build/SKILL.md +14 -8
- package/templates/skills/hai-build/reference.md +16 -13
- package/templates/skills/hai-cache/SKILL.md +20 -181
- package/templates/skills/hai-cache/reference.md +229 -0
- package/templates/skills/hai-capacitor/SKILL.md +7 -4
- package/templates/skills/hai-capacitor/reference.md +15 -9
- package/templates/skills/hai-ci/SKILL.md +2 -0
- package/templates/skills/hai-core/SKILL.md +7 -3
- package/templates/skills/hai-core/reference.md +25 -5
- package/templates/skills/hai-crypto/SKILL.md +22 -250
- package/templates/skills/hai-crypto/reference.md +310 -0
- package/templates/skills/hai-datapipe/SKILL.md +16 -195
- package/templates/skills/hai-datapipe/reference.md +204 -0
- package/templates/skills/hai-deploy/SKILL.md +24 -54
- package/templates/skills/hai-deploy/middleware.md +51 -43
- 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 +25 -11
- package/templates/skills/hai-iam/reference.md +181 -154
- package/templates/skills/hai-kit/SKILL.md +35 -174
- package/templates/skills/hai-kit/reference.md +190 -0
- package/templates/skills/hai-payment/SKILL.md +33 -263
- package/templates/skills/hai-payment/reference.md +295 -0
- package/templates/skills/hai-pr-review/SKILL.md +21 -88
- package/templates/skills/hai-reach/SKILL.md +6 -3
- package/templates/skills/hai-reach/reference.md +83 -22
- package/templates/skills/hai-reldb/SKILL.md +6 -3
- package/templates/skills/hai-reldb/reference.md +68 -25
- package/templates/skills/hai-scheduler/SKILL.md +18 -195
- package/templates/skills/hai-scheduler/reference.md +216 -0
- package/templates/skills/hai-serv/SKILL.md +25 -10
- package/templates/skills/hai-serv/reference.md +123 -87
- package/templates/skills/hai-spec-implement/SKILL.md +5 -1
- package/templates/skills/hai-spec-implement/evaluation.md +18 -0
- package/templates/skills/hai-spec-implement/handoff.md +47 -0
- package/templates/skills/hai-spec-implement/templates.md +5 -0
- package/templates/skills/hai-spec-implement/workflow.md +5 -3
- package/templates/skills/hai-spec-review/SKILL.md +4 -0
- package/templates/skills/hai-storage/SKILL.md +23 -238
- package/templates/skills/hai-storage/reference.md +282 -0
- package/templates/skills/hai-ui/SKILL.md +17 -3
- package/templates/skills/hai-ui/guide.md +3 -3
- package/templates/skills/hai-ui/reference.md +26 -11
- package/templates/skills/hai-ui-design/SKILL.md +31 -0
- package/templates/skills/hai-ui-design/patterns.md +26 -0
- package/templates/skills/hai-ui-review/SKILL.md +30 -0
- package/templates/skills/hai-ui-review/checklist.md +22 -0
- package/templates/skills/hai-vecdb/SKILL.md +14 -10
- package/templates/skills/hai-vecdb/reference.md +33 -3
- package/dist/chunk-FWN5B54L.js +0 -468
- package/dist/chunk-FWN5B54L.js.map +0 -1
- package/dist/chunk-QYX6V7H2.js.map +0 -1
- package/dist/cli-config-templates-3XSFIXBP.js +0 -5
- package/dist/src-RDQ2EIBT.js.map +0 -1
- package/templates/apps/api/Dockerfile.hbs +0 -35
- package/templates/apps/fullstack/Dockerfile.hbs +0 -57
- /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/templates/base/{docker → images/web}/nginx.conf.hbs +0 -0
package/dist/index.js
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
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, generateProductionEnvironment, moduleEnvironmentVariables, parameterizeConfig } from './chunk-ZFNBUF77.js';
|
|
3
|
+
import { cliM } from './chunk-MBPB37FR.js';
|
|
4
|
+
import { external_exports } from './chunk-FI56GONY.js';
|
|
4
5
|
import './chunk-2H7UOFLK.js';
|
|
5
6
|
import process4 from 'process';
|
|
6
7
|
import { cac } from 'cac';
|
|
7
8
|
import chalk from 'chalk';
|
|
8
|
-
import path2, { resolve, join, dirname } from 'path';
|
|
9
|
+
import path2, { resolve, join, dirname, relative } from 'path';
|
|
9
10
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
10
11
|
import { wrapAnsi } from 'fast-wrap-ansi';
|
|
11
12
|
import fse from 'fs-extra';
|
|
12
13
|
import Handlebars2 from 'handlebars';
|
|
13
|
-
import {
|
|
14
|
+
import { parseEnv } from 'util';
|
|
14
15
|
import ora from 'ora';
|
|
15
16
|
import prompts from 'prompts';
|
|
16
17
|
import { Buffer } from 'buffer';
|
|
17
18
|
import { execSync, execFileSync, spawn } from 'child_process';
|
|
18
|
-
import { randomBytes, createHash } from 'crypto';
|
|
19
|
-
import { stringify, parseDocument } from 'yaml';
|
|
20
|
-
import { readFileSync, mkdirSync, mkdtempSync,
|
|
19
|
+
import { randomBytes, createECDH, createHash, randomUUID } from 'crypto';
|
|
20
|
+
import { stringify, parse, parseDocument, isScalar, isMap } from 'yaml';
|
|
21
|
+
import { readFileSync, existsSync, mkdirSync, mkdtempSync, createReadStream, writeFileSync, renameSync, rmSync, readdirSync, copyFileSync } from 'fs';
|
|
21
22
|
import { createRequire } from 'module';
|
|
23
|
+
import { core } from '@h-ai/core';
|
|
24
|
+
import { glob } from 'fs/promises';
|
|
22
25
|
|
|
23
26
|
var CAMEL_CASE_SEPARATOR_REGEX = /[-_\s]+(.)?/g;
|
|
24
27
|
var LEADING_CHAR_REGEX = /^./;
|
|
@@ -29,6 +32,8 @@ function writeCliMessage(message) {
|
|
|
29
32
|
function writeCliError(message, error) {
|
|
30
33
|
process4.stderr.write(`${message}
|
|
31
34
|
`);
|
|
35
|
+
if (error && typeof error === "object" && "error" in error)
|
|
36
|
+
error = error.error;
|
|
32
37
|
if (error !== void 0)
|
|
33
38
|
process4.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
34
39
|
`);
|
|
@@ -133,56 +138,56 @@ var CAPACITOR_SKIPPED_SERVER_FILES = /* @__PURE__ */ new Set([
|
|
|
133
138
|
"src/lib/server/init.ts"
|
|
134
139
|
]);
|
|
135
140
|
var VERSIONS = {
|
|
136
|
-
antfuEslintConfig: "
|
|
137
|
-
capacitorSecureStorage: "
|
|
141
|
+
antfuEslintConfig: "9.5.1",
|
|
142
|
+
capacitorSecureStorage: "8.0.1",
|
|
138
143
|
// UI / Icon
|
|
139
|
-
bitsUi: "
|
|
140
|
-
iconifyJsonTabler: "
|
|
141
|
-
iconifyTailwind: "
|
|
144
|
+
bitsUi: "2.19.5",
|
|
145
|
+
iconifyJsonTabler: "1.2.42",
|
|
146
|
+
iconifyTailwind: "1.2.3",
|
|
142
147
|
// Capacitor
|
|
143
|
-
capacitorAndroid: "
|
|
144
|
-
capacitorApp: "
|
|
145
|
-
capacitorCamera: "
|
|
146
|
-
capacitorCli: "
|
|
147
|
-
capacitorCore: "
|
|
148
|
-
capacitorDevice: "
|
|
149
|
-
capacitorIos: "
|
|
150
|
-
capacitorPreferences: "
|
|
151
|
-
capacitorPushNotifications: "
|
|
152
|
-
capacitorStatusBar: "
|
|
148
|
+
capacitorAndroid: "8.5.3",
|
|
149
|
+
capacitorApp: "8.1.2",
|
|
150
|
+
capacitorCamera: "8.2.5",
|
|
151
|
+
capacitorCli: "8.5.3",
|
|
152
|
+
capacitorCore: "8.5.3",
|
|
153
|
+
capacitorDevice: "8.0.3",
|
|
154
|
+
capacitorIos: "8.5.3",
|
|
155
|
+
capacitorPreferences: "8.0.1",
|
|
156
|
+
capacitorPushNotifications: "8.1.3",
|
|
157
|
+
capacitorStatusBar: "8.0.4",
|
|
153
158
|
// Tailwind / DaisyUI
|
|
154
|
-
daisyui: "
|
|
155
|
-
tailwindcss: "
|
|
156
|
-
tailwindcssVite: "
|
|
159
|
+
daisyui: "5.7.47",
|
|
160
|
+
tailwindcss: "4.3.3",
|
|
161
|
+
tailwindcssVite: "4.3.3",
|
|
157
162
|
// i18n
|
|
158
|
-
inlangParaglide: "
|
|
159
|
-
inlangMessageFormat: "
|
|
163
|
+
inlangParaglide: "2.27.0",
|
|
164
|
+
inlangMessageFormat: "4.5.0",
|
|
160
165
|
// Svelte / Vite
|
|
161
|
-
svelte: "
|
|
162
|
-
svelteKit: "
|
|
163
|
-
svelteAdapterNode: "
|
|
164
|
-
svelteCheck: "
|
|
165
|
-
sveltePlugin: "
|
|
166
|
+
svelte: "5.57.2",
|
|
167
|
+
svelteKit: "2.70.3",
|
|
168
|
+
svelteAdapterNode: "5.5.7",
|
|
169
|
+
svelteCheck: "4.7.6",
|
|
170
|
+
sveltePlugin: "7.3.1",
|
|
166
171
|
// Build / Test
|
|
167
|
-
vite: "
|
|
168
|
-
vitest: "
|
|
169
|
-
tsup: "
|
|
170
|
-
rimraf: "
|
|
172
|
+
vite: "8.3.4",
|
|
173
|
+
vitest: "4.1.11",
|
|
174
|
+
tsup: "8.5.1",
|
|
175
|
+
rimraf: "6.1.3",
|
|
171
176
|
// ESLint
|
|
172
|
-
eslint: "
|
|
173
|
-
eslintPluginFormat: "
|
|
174
|
-
eslintPluginSvelte: "
|
|
175
|
-
svelteEslintParser: "
|
|
177
|
+
eslint: "10.12.0",
|
|
178
|
+
eslintPluginFormat: "2.0.1",
|
|
179
|
+
eslintPluginSvelte: "3.23.1",
|
|
180
|
+
svelteEslintParser: "1.9.0",
|
|
176
181
|
// Node / TS
|
|
177
|
-
nodeTypes: "
|
|
182
|
+
nodeTypes: "26.6.5",
|
|
178
183
|
// Svelte / ESLint / tsup 继续使用稳定的 JavaScript 编译 API。
|
|
179
|
-
typescript: "
|
|
184
|
+
typescript: "6.0.3",
|
|
180
185
|
// E2E
|
|
181
|
-
playwright: "
|
|
186
|
+
playwright: "1.64.0",
|
|
182
187
|
// Validation
|
|
183
|
-
zod: "
|
|
188
|
+
zod: "4.6.5",
|
|
184
189
|
// Tauri
|
|
185
|
-
tauriCli: "
|
|
190
|
+
tauriCli: "2.12.1"
|
|
186
191
|
};
|
|
187
192
|
var FRONTEND_PORTS_DEFAULT = {
|
|
188
193
|
web: 4173,
|
|
@@ -427,6 +432,8 @@ function buildAppHaiCatalogPackages(options) {
|
|
|
427
432
|
const packageNames = ["@h-ai/cli"];
|
|
428
433
|
if (options.isSvelteOnlyApp) {
|
|
429
434
|
packageNames.push("@h-ai/api-client", "@h-ai/api-contract", "@h-ai/capacitor", "@h-ai/ui");
|
|
435
|
+
if (options.featureMap.crypto)
|
|
436
|
+
packageNames.push("@h-ai/crypto");
|
|
430
437
|
return pickCatalogPackages(packageNames, HAI_CATALOG_PACKAGES);
|
|
431
438
|
}
|
|
432
439
|
packageNames.push("@h-ai/core", "@h-ai/kit");
|
|
@@ -468,7 +475,7 @@ function buildAppHaiCatalogPackages(options) {
|
|
|
468
475
|
packageNames.push("@h-ai/capacitor");
|
|
469
476
|
return pickCatalogPackages(packageNames, HAI_CATALOG_PACKAGES);
|
|
470
477
|
}
|
|
471
|
-
function buildFullstackCatalogPackages(frontends, deploy = false) {
|
|
478
|
+
function buildFullstackCatalogPackages(frontends, deploy = false, features = {}) {
|
|
472
479
|
const packageNames = [
|
|
473
480
|
...FULLSTACK_ROOT_CATALOG_PACKAGE_NAMES,
|
|
474
481
|
"@h-ai/cli",
|
|
@@ -484,6 +491,10 @@ function buildFullstackCatalogPackages(frontends, deploy = false) {
|
|
|
484
491
|
packageNames.push(...FULLSTACK_DESKTOP_CATALOG_PACKAGE_NAMES);
|
|
485
492
|
if (deploy)
|
|
486
493
|
packageNames.push("@h-ai/deploy");
|
|
494
|
+
if (features.ai)
|
|
495
|
+
packageNames.push("@h-ai/ai");
|
|
496
|
+
if (features.vecdb)
|
|
497
|
+
packageNames.push("@h-ai/vecdb");
|
|
487
498
|
return pickCatalogPackages(packageNames, FULLSTACK_CATALOG_PACKAGES);
|
|
488
499
|
}
|
|
489
500
|
var FEATURE_ROUTE_DIRS = {
|
|
@@ -580,6 +591,7 @@ function buildTemplateContext(options) {
|
|
|
580
591
|
for (const f of options.features) {
|
|
581
592
|
featureMap[f] = true;
|
|
582
593
|
}
|
|
594
|
+
featureMap.deploy = options.deploy !== false;
|
|
583
595
|
if (options.appType === "fullstack") {
|
|
584
596
|
for (const feature of ["iam", "db", "cache", "crypto", "storage", "audit"]) featureMap[feature] = true;
|
|
585
597
|
}
|
|
@@ -588,8 +600,12 @@ function buildTemplateContext(options) {
|
|
|
588
600
|
featureMap.cache = true;
|
|
589
601
|
featureMap.crypto = true;
|
|
590
602
|
}
|
|
591
|
-
if (featureMap.audit || featureMap.scheduler || featureMap.payment) {
|
|
603
|
+
if (featureMap.audit || featureMap.scheduler || featureMap.payment || featureMap.crypto) {
|
|
604
|
+
featureMap.db = true;
|
|
605
|
+
}
|
|
606
|
+
if (featureMap.ai) {
|
|
592
607
|
featureMap.db = true;
|
|
608
|
+
featureMap.vecdb = true;
|
|
593
609
|
}
|
|
594
610
|
const isApi = options.appType === "api";
|
|
595
611
|
const isFullstack = options.appType === "fullstack";
|
|
@@ -600,7 +616,7 @@ function buildTemplateContext(options) {
|
|
|
600
616
|
const selectedFrontends = options.frontends ?? DEFAULT_FULLSTACK_FRONTENDS;
|
|
601
617
|
let haiCatalogPackages;
|
|
602
618
|
if (isFullstack) {
|
|
603
|
-
haiCatalogPackages = buildFullstackCatalogPackages(selectedFrontends, featureMap.deploy);
|
|
619
|
+
haiCatalogPackages = buildFullstackCatalogPackages(selectedFrontends, featureMap.deploy, featureMap);
|
|
604
620
|
} else if (isApi) {
|
|
605
621
|
haiCatalogPackages = buildApiWorkspaceCatalogPackages(featureMap);
|
|
606
622
|
} else {
|
|
@@ -692,9 +708,8 @@ function buildFullstackTemplateContext(projectName, frontendsInput, moduleConfig
|
|
|
692
708
|
function resolveI18nContext(moduleConfigs) {
|
|
693
709
|
const coreLocale = moduleConfigs?.core?.defaultLocale ?? "zh-CN";
|
|
694
710
|
const baseLocale = moduleConfigs?.fullstack?.i18n?.baseLocale ?? coreLocale;
|
|
695
|
-
const fallbackLocale = baseLocale === "en-US" ? "zh-CN" : "en-US";
|
|
696
711
|
const requested = moduleConfigs?.fullstack?.i18n?.locales;
|
|
697
|
-
const locales = requested && requested.length > 0 ? [...new Set(requested)] : [baseLocale,
|
|
712
|
+
const locales = requested && requested.length > 0 ? [...new Set(requested)] : [.../* @__PURE__ */ new Set([baseLocale, "zh-CN", "en-US", "ja-JP"])];
|
|
698
713
|
if (!locales.includes(baseLocale)) {
|
|
699
714
|
locales.unshift(baseLocale);
|
|
700
715
|
}
|
|
@@ -776,10 +791,13 @@ async function generateFromTemplates(projectPath, context) {
|
|
|
776
791
|
}
|
|
777
792
|
await renderDynamicFiles(root, projectPath, context);
|
|
778
793
|
await fse.copy(path2.join(root, "shared", "run-e2e.mjs"), path2.join(projectPath, "scripts", "run-e2e.mjs"));
|
|
794
|
+
await fse.copy(path2.join(root, "shared", "e2e-env.ts"), path2.join(projectPath, "scripts", "e2e-env.ts"));
|
|
795
|
+
await fse.ensureDir(path2.join(projectPath, "images", "dependencies"));
|
|
796
|
+
await fse.writeFile(path2.join(projectPath, "images", "dependencies", ".gitkeep"), "");
|
|
779
797
|
await fse.copy(path2.join(root, "shared", ".gitattributes"), path2.join(projectPath, ".gitattributes"));
|
|
780
798
|
if (context.isSvelteOnlyApp) {
|
|
781
799
|
await fse.copy(path2.join(root, "shared", "vite-runtime-config.ts"), path2.join(projectPath, "scripts", "vite-runtime-config.ts"));
|
|
782
|
-
await fse.copy(path2.join(root, "shared", "container-runtime-config.sh"), path2.join(projectPath, "
|
|
800
|
+
await fse.copy(path2.join(root, "shared", "container-runtime-config.sh"), path2.join(projectPath, "images", "web", "19-runtime-config.sh"));
|
|
783
801
|
}
|
|
784
802
|
if (context.isSvelteOnlyApp || context.appType === "fullstack") {
|
|
785
803
|
const navigationDir = context.appType === "fullstack" ? path2.join(projectPath, "packages", `${context.projectName}-shared`, "src", "lib", "navigation") : path2.join(projectPath, "src", "lib", "navigation");
|
|
@@ -789,6 +807,9 @@ async function generateFromTemplates(projectPath, context) {
|
|
|
789
807
|
const i18nDir = path2.join(root, "i18n");
|
|
790
808
|
if (await fse.pathExists(i18nDir)) {
|
|
791
809
|
await fse.copy(i18nDir, projectPath, { overwrite: true });
|
|
810
|
+
const settingsPath = path2.join(projectPath, "project.inlang", "settings.json");
|
|
811
|
+
const settings = await fse.readJson(settingsPath);
|
|
812
|
+
await fse.writeJson(settingsPath, { ...settings, baseLocale: context.defaultLocale }, { spaces: 2 });
|
|
792
813
|
}
|
|
793
814
|
}
|
|
794
815
|
if (includeBaseTemplate) {
|
|
@@ -916,136 +937,242 @@ var RE_HTTPS_URL = /^https:\/\/.+/;
|
|
|
916
937
|
var RE_GIT_SSH_URL = /^git@.+:.+/;
|
|
917
938
|
var APP_TYPES = {
|
|
918
939
|
"admin": {
|
|
919
|
-
name
|
|
920
|
-
|
|
940
|
+
get name() {
|
|
941
|
+
return cliM("cli_appAdminName");
|
|
942
|
+
},
|
|
943
|
+
get description() {
|
|
944
|
+
return cliM("cli_appAdminDescription");
|
|
945
|
+
},
|
|
921
946
|
defaultFeatures: ["iam", "db", "cache", "crypto"]
|
|
922
947
|
},
|
|
923
948
|
"website": {
|
|
924
|
-
name
|
|
925
|
-
|
|
949
|
+
get name() {
|
|
950
|
+
return cliM("cli_appWebsiteName");
|
|
951
|
+
},
|
|
952
|
+
get description() {
|
|
953
|
+
return cliM("cli_appWebsiteDescription");
|
|
954
|
+
},
|
|
926
955
|
defaultFeatures: ["db", "cache"]
|
|
927
956
|
},
|
|
928
957
|
"h5": {
|
|
929
|
-
name
|
|
930
|
-
|
|
958
|
+
get name() {
|
|
959
|
+
return cliM("cli_appH5Name");
|
|
960
|
+
},
|
|
961
|
+
get description() {
|
|
962
|
+
return cliM("cli_appH5Description");
|
|
963
|
+
},
|
|
931
964
|
defaultFeatures: ["db", "cache"]
|
|
932
965
|
},
|
|
933
966
|
"api": {
|
|
934
|
-
name
|
|
935
|
-
|
|
967
|
+
get name() {
|
|
968
|
+
return cliM("cli_appApiName");
|
|
969
|
+
},
|
|
970
|
+
get description() {
|
|
971
|
+
return cliM("cli_appApiDescription");
|
|
972
|
+
},
|
|
936
973
|
defaultFeatures: ["db", "cache"]
|
|
937
974
|
},
|
|
938
975
|
"mobile-app": {
|
|
939
|
-
name
|
|
940
|
-
|
|
976
|
+
get name() {
|
|
977
|
+
return cliM("cli_appMobileName");
|
|
978
|
+
},
|
|
979
|
+
get description() {
|
|
980
|
+
return cliM("cli_appMobileDescription");
|
|
981
|
+
},
|
|
941
982
|
defaultFeatures: ["api-client", "capacitor"]
|
|
942
983
|
},
|
|
943
984
|
"fullstack": {
|
|
944
|
-
name
|
|
945
|
-
|
|
985
|
+
get name() {
|
|
986
|
+
return cliM("cli_appFullstackName");
|
|
987
|
+
},
|
|
988
|
+
get description() {
|
|
989
|
+
return cliM("cli_appFullstackDescription");
|
|
990
|
+
},
|
|
946
991
|
defaultFeatures: ["iam", "db", "cache", "crypto", "storage", "audit"]
|
|
947
992
|
}
|
|
948
993
|
};
|
|
949
994
|
var FRONTEND_TARGETS = {
|
|
950
|
-
web: { name: "Web", description
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
995
|
+
web: { name: "Web", get description() {
|
|
996
|
+
return cliM("cli_frontendWebDescription");
|
|
997
|
+
}, implemented: true },
|
|
998
|
+
app: { name: "App", get description() {
|
|
999
|
+
return cliM("cli_frontendAppDescription");
|
|
1000
|
+
}, implemented: true },
|
|
1001
|
+
miniapp: {
|
|
1002
|
+
get name() {
|
|
1003
|
+
return cliM("cli_frontendMiniappName");
|
|
1004
|
+
},
|
|
1005
|
+
get description() {
|
|
1006
|
+
return cliM("cli_frontendMiniappDescription");
|
|
1007
|
+
},
|
|
1008
|
+
implemented: false
|
|
1009
|
+
},
|
|
1010
|
+
desktop: {
|
|
1011
|
+
get name() {
|
|
1012
|
+
return cliM("cli_frontendDesktopName");
|
|
1013
|
+
},
|
|
1014
|
+
get description() {
|
|
1015
|
+
return cliM("cli_frontendDesktopDescription");
|
|
1016
|
+
},
|
|
1017
|
+
implemented: true
|
|
1018
|
+
}
|
|
954
1019
|
};
|
|
955
1020
|
var DEFAULT_FULLSTACK_FRONTENDS2 = ["web", "app", "desktop"];
|
|
956
1021
|
var FEATURES = {
|
|
957
1022
|
"iam": {
|
|
958
1023
|
id: "iam",
|
|
959
|
-
name
|
|
960
|
-
|
|
1024
|
+
get name() {
|
|
1025
|
+
return cliM("cli_featureIamName");
|
|
1026
|
+
},
|
|
1027
|
+
get description() {
|
|
1028
|
+
return cliM("cli_featureIamDescription");
|
|
1029
|
+
},
|
|
961
1030
|
packages: ["@h-ai/iam"],
|
|
962
1031
|
dependencies: ["crypto", "db", "cache"]
|
|
963
1032
|
},
|
|
964
1033
|
"db": {
|
|
965
1034
|
id: "db",
|
|
966
|
-
name
|
|
967
|
-
|
|
1035
|
+
get name() {
|
|
1036
|
+
return cliM("cli_featureDbName");
|
|
1037
|
+
},
|
|
1038
|
+
get description() {
|
|
1039
|
+
return cliM("cli_featureDbDescription");
|
|
1040
|
+
},
|
|
968
1041
|
packages: ["@h-ai/reldb"]
|
|
969
1042
|
},
|
|
970
1043
|
"cache": {
|
|
971
1044
|
id: "cache",
|
|
972
|
-
name
|
|
973
|
-
|
|
1045
|
+
get name() {
|
|
1046
|
+
return cliM("cli_featureCacheName");
|
|
1047
|
+
},
|
|
1048
|
+
get description() {
|
|
1049
|
+
return cliM("cli_featureCacheDescription");
|
|
1050
|
+
},
|
|
974
1051
|
packages: ["@h-ai/cache"]
|
|
975
1052
|
},
|
|
976
1053
|
"ai": {
|
|
977
1054
|
id: "ai",
|
|
978
|
-
name
|
|
979
|
-
|
|
980
|
-
|
|
1055
|
+
get name() {
|
|
1056
|
+
return cliM("cli_featureAiName");
|
|
1057
|
+
},
|
|
1058
|
+
get description() {
|
|
1059
|
+
return cliM("cli_featureAiDescription");
|
|
1060
|
+
},
|
|
1061
|
+
packages: ["@h-ai/ai"],
|
|
1062
|
+
dependencies: ["db", "vecdb"]
|
|
981
1063
|
},
|
|
982
1064
|
"storage": {
|
|
983
1065
|
id: "storage",
|
|
984
|
-
name
|
|
985
|
-
|
|
1066
|
+
get name() {
|
|
1067
|
+
return cliM("cli_featureStorageName");
|
|
1068
|
+
},
|
|
1069
|
+
get description() {
|
|
1070
|
+
return cliM("cli_featureStorageDescription");
|
|
1071
|
+
},
|
|
986
1072
|
packages: ["@h-ai/storage"]
|
|
987
1073
|
},
|
|
988
1074
|
"crypto": {
|
|
989
1075
|
id: "crypto",
|
|
990
|
-
name
|
|
991
|
-
|
|
992
|
-
|
|
1076
|
+
get name() {
|
|
1077
|
+
return cliM("cli_featureCryptoName");
|
|
1078
|
+
},
|
|
1079
|
+
get description() {
|
|
1080
|
+
return cliM("cli_featureCryptoDescription");
|
|
1081
|
+
},
|
|
1082
|
+
packages: ["@h-ai/crypto"],
|
|
1083
|
+
dependencies: ["db"]
|
|
993
1084
|
},
|
|
994
1085
|
"audit": {
|
|
995
1086
|
id: "audit",
|
|
996
|
-
name
|
|
997
|
-
|
|
1087
|
+
get name() {
|
|
1088
|
+
return cliM("cli_featureAuditName");
|
|
1089
|
+
},
|
|
1090
|
+
get description() {
|
|
1091
|
+
return cliM("cli_featureAuditDescription");
|
|
1092
|
+
},
|
|
998
1093
|
packages: ["@h-ai/audit"],
|
|
999
1094
|
dependencies: ["db"]
|
|
1000
1095
|
},
|
|
1001
1096
|
"reach": {
|
|
1002
1097
|
id: "reach",
|
|
1003
|
-
name
|
|
1004
|
-
|
|
1098
|
+
get name() {
|
|
1099
|
+
return cliM("cli_featureReachName");
|
|
1100
|
+
},
|
|
1101
|
+
get description() {
|
|
1102
|
+
return cliM("cli_featureReachDescription");
|
|
1103
|
+
},
|
|
1005
1104
|
packages: ["@h-ai/reach"]
|
|
1006
1105
|
},
|
|
1007
1106
|
"payment": {
|
|
1008
1107
|
id: "payment",
|
|
1009
|
-
name
|
|
1010
|
-
|
|
1108
|
+
get name() {
|
|
1109
|
+
return cliM("cli_featurePaymentName");
|
|
1110
|
+
},
|
|
1111
|
+
get description() {
|
|
1112
|
+
return cliM("cli_featurePaymentDescription");
|
|
1113
|
+
},
|
|
1011
1114
|
packages: ["@h-ai/payment"],
|
|
1012
1115
|
dependencies: ["db"]
|
|
1013
1116
|
},
|
|
1014
1117
|
"vecdb": {
|
|
1015
1118
|
id: "vecdb",
|
|
1016
|
-
name
|
|
1017
|
-
|
|
1119
|
+
get name() {
|
|
1120
|
+
return cliM("cli_featureVecdbName");
|
|
1121
|
+
},
|
|
1122
|
+
get description() {
|
|
1123
|
+
return cliM("cli_featureVecdbDescription");
|
|
1124
|
+
},
|
|
1018
1125
|
packages: ["@h-ai/vecdb"]
|
|
1019
1126
|
},
|
|
1020
1127
|
"datapipe": {
|
|
1021
1128
|
id: "datapipe",
|
|
1022
|
-
name
|
|
1023
|
-
|
|
1129
|
+
get name() {
|
|
1130
|
+
return cliM("cli_featureDatapipeName");
|
|
1131
|
+
},
|
|
1132
|
+
get description() {
|
|
1133
|
+
return cliM("cli_featureDatapipeDescription");
|
|
1134
|
+
},
|
|
1024
1135
|
packages: ["@h-ai/datapipe"]
|
|
1025
1136
|
},
|
|
1026
1137
|
"scheduler": {
|
|
1027
1138
|
id: "scheduler",
|
|
1028
|
-
name
|
|
1029
|
-
|
|
1139
|
+
get name() {
|
|
1140
|
+
return cliM("cli_featureSchedulerName");
|
|
1141
|
+
},
|
|
1142
|
+
get description() {
|
|
1143
|
+
return cliM("cli_featureSchedulerDescription");
|
|
1144
|
+
},
|
|
1030
1145
|
packages: ["@h-ai/scheduler"],
|
|
1031
1146
|
dependencies: ["db"]
|
|
1032
1147
|
},
|
|
1033
1148
|
"deploy": {
|
|
1034
1149
|
id: "deploy",
|
|
1035
|
-
name
|
|
1036
|
-
|
|
1150
|
+
get name() {
|
|
1151
|
+
return cliM("cli_featureDeployName");
|
|
1152
|
+
},
|
|
1153
|
+
get description() {
|
|
1154
|
+
return cliM("cli_featureDeployDescription");
|
|
1155
|
+
},
|
|
1037
1156
|
packages: ["@h-ai/deploy"]
|
|
1038
1157
|
},
|
|
1039
1158
|
"api-client": {
|
|
1040
1159
|
id: "api-client",
|
|
1041
|
-
name
|
|
1042
|
-
|
|
1160
|
+
get name() {
|
|
1161
|
+
return cliM("cli_featureApiClientName");
|
|
1162
|
+
},
|
|
1163
|
+
get description() {
|
|
1164
|
+
return cliM("cli_featureApiClientDescription");
|
|
1165
|
+
},
|
|
1043
1166
|
packages: ["@h-ai/api-client"]
|
|
1044
1167
|
},
|
|
1045
1168
|
"capacitor": {
|
|
1046
1169
|
id: "capacitor",
|
|
1047
|
-
name
|
|
1048
|
-
|
|
1170
|
+
get name() {
|
|
1171
|
+
return cliM("cli_featureCapacitorName");
|
|
1172
|
+
},
|
|
1173
|
+
get description() {
|
|
1174
|
+
return cliM("cli_featureCapacitorDescription");
|
|
1175
|
+
},
|
|
1049
1176
|
packages: ["@h-ai/capacitor"]
|
|
1050
1177
|
}
|
|
1051
1178
|
};
|
|
@@ -1062,7 +1189,6 @@ var SELECTABLE_FEATURES = [
|
|
|
1062
1189
|
"vecdb",
|
|
1063
1190
|
"datapipe",
|
|
1064
1191
|
"scheduler",
|
|
1065
|
-
"deploy",
|
|
1066
1192
|
"api-client",
|
|
1067
1193
|
"capacitor"
|
|
1068
1194
|
];
|
|
@@ -1070,17 +1196,23 @@ var PROJECT_NAME_REGEX = /^[a-z0-9-]+$/;
|
|
|
1070
1196
|
var PROJECT_TEMPLATES = {
|
|
1071
1197
|
minimal: {
|
|
1072
1198
|
name: "minimal",
|
|
1073
|
-
description
|
|
1199
|
+
get description() {
|
|
1200
|
+
return cliM("cli_templateMinimalDescription");
|
|
1201
|
+
},
|
|
1074
1202
|
features: []
|
|
1075
1203
|
},
|
|
1076
1204
|
default: {
|
|
1077
1205
|
name: "default",
|
|
1078
|
-
description
|
|
1206
|
+
get description() {
|
|
1207
|
+
return cliM("cli_templateDefaultDescription");
|
|
1208
|
+
},
|
|
1079
1209
|
features: ["iam", "db", "cache", "crypto"]
|
|
1080
1210
|
},
|
|
1081
1211
|
full: {
|
|
1082
1212
|
name: "full",
|
|
1083
|
-
description
|
|
1213
|
+
get description() {
|
|
1214
|
+
return cliM("cli_templateFullDescription");
|
|
1215
|
+
},
|
|
1084
1216
|
features: [
|
|
1085
1217
|
"iam",
|
|
1086
1218
|
"db",
|
|
@@ -1101,7 +1233,9 @@ var PROJECT_TEMPLATES = {
|
|
|
1101
1233
|
},
|
|
1102
1234
|
custom: {
|
|
1103
1235
|
name: "custom",
|
|
1104
|
-
description
|
|
1236
|
+
get description() {
|
|
1237
|
+
return cliM("cli_templateCustomDescription");
|
|
1238
|
+
},
|
|
1105
1239
|
features: []
|
|
1106
1240
|
}
|
|
1107
1241
|
};
|
|
@@ -1109,39 +1243,40 @@ async function createProject(options) {
|
|
|
1109
1243
|
const spinner = ora();
|
|
1110
1244
|
try {
|
|
1111
1245
|
const resolvedOptions = await resolveOptions(options);
|
|
1112
|
-
if (!PROJECT_NAME_REGEX.test(resolvedOptions.name))
|
|
1113
|
-
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1246
|
+
if (!PROJECT_NAME_REGEX.test(resolvedOptions.name))
|
|
1247
|
+
throw new Error(cliM("cli_createInvalidName", { params: { name: resolvedOptions.name } }));
|
|
1116
1248
|
const projectPath = path2.resolve(resolvedOptions.cwd ?? ".", resolvedOptions.name);
|
|
1117
1249
|
if (await fileExists(projectPath)) {
|
|
1118
1250
|
const { overwrite } = await prompts({
|
|
1119
1251
|
type: "confirm",
|
|
1252
|
+
active: cliM("cli_yes"),
|
|
1253
|
+
inactive: cliM("cli_no"),
|
|
1120
1254
|
name: "overwrite",
|
|
1121
|
-
message:
|
|
1255
|
+
message: cliM("cli_createOverwrite", { params: { name: resolvedOptions.name } }),
|
|
1122
1256
|
initial: false
|
|
1123
1257
|
});
|
|
1124
1258
|
if (!overwrite) {
|
|
1125
|
-
writeCliMessage(chalk.yellow("
|
|
1259
|
+
writeCliMessage(chalk.yellow(cliM("cli_cancelled")));
|
|
1126
1260
|
return;
|
|
1127
1261
|
}
|
|
1128
1262
|
await fse.remove(projectPath);
|
|
1129
1263
|
}
|
|
1130
|
-
spinner.start("
|
|
1264
|
+
spinner.start(cliM("cli_createDirectory"));
|
|
1131
1265
|
await fse.ensureDir(projectPath);
|
|
1132
1266
|
spinner.succeed();
|
|
1133
|
-
spinner.start("
|
|
1267
|
+
spinner.start(cliM("cli_createFiles"));
|
|
1134
1268
|
const context = buildTemplateContext({
|
|
1135
1269
|
name: resolvedOptions.name,
|
|
1136
1270
|
appType: resolvedOptions.appType,
|
|
1137
1271
|
features: resolvedOptions.features,
|
|
1272
|
+
deploy: resolvedOptions.deploy,
|
|
1138
1273
|
frontends: resolvedOptions.frontends,
|
|
1139
1274
|
moduleConfigs: resolvedOptions.moduleConfigs,
|
|
1140
1275
|
packageManager: resolvedOptions.packageManager
|
|
1141
1276
|
});
|
|
1142
1277
|
await generateFromTemplates(projectPath, context);
|
|
1143
1278
|
{
|
|
1144
|
-
const { generateConfigFile: generateConfigFile2, generateEnvExample } = await import('./cli-config-templates-
|
|
1279
|
+
const { generateConfigFile: generateConfigFile2, generateEnvExample, generateProductionEnvironment: generateProductionEnvironment2 } = await import('./cli-config-templates-W2LLPMB6.js');
|
|
1145
1280
|
const configs = resolvedOptions.moduleConfigs;
|
|
1146
1281
|
const isApiWorkspace = resolvedOptions.appType === "api";
|
|
1147
1282
|
const isFullstack = resolvedOptions.appType === "fullstack";
|
|
@@ -1167,7 +1302,7 @@ async function createProject(options) {
|
|
|
1167
1302
|
if (/^_.*\.yml$/.test(file))
|
|
1168
1303
|
configContents[file.slice(1, -4)] = await fse.readFile(path2.join(configDir, file), "utf8");
|
|
1169
1304
|
}
|
|
1170
|
-
const envFeatures = resolvedOptions.features;
|
|
1305
|
+
const envFeatures = isFullstack ? [.../* @__PURE__ */ new Set([...resolvedOptions.features, "reach"])] : resolvedOptions.features;
|
|
1171
1306
|
const envContents = generateEnvExample(envFeatures, configs, isFullstack, configContents);
|
|
1172
1307
|
let envExample = isApiWorkspace ? envContents : isFullstack ? `${envContents}
|
|
1173
1308
|
# First-start administrator password; must satisfy the IAM password policy.
|
|
@@ -1200,50 +1335,57 @@ ${runtimeExample.split("\n").map((line) => line && !line.startsWith("#") ? `# ${
|
|
|
1200
1335
|
${sharedVariables.join("\n")}
|
|
1201
1336
|
`);
|
|
1202
1337
|
for (const [mode, environment] of [["dev", "development"], ["test", "test"], ["prod", "production"]]) {
|
|
1203
|
-
|
|
1338
|
+
const values = mode === "prod" ? generateProductionEnvironment2(envFeatures, configs) : `# \u516C\u5171 ${mode} \u73AF\u5883\u914D\u7F6E\uFF1B\u672C\u673A\u79C1\u6709\u503C\u653E .env.local\uFF0C\u8FDB\u7A0B\u73AF\u5883\u53D8\u91CF\u4F18\u5148\u3002
|
|
1204
1339
|
HAI_CORE_ENV=${environment}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
`);
|
|
1340
|
+
HAI_CORE_LOGGING_LEVEL=${mode === "test" ? "warn" : "debug"}
|
|
1341
|
+
`;
|
|
1342
|
+
await writeFile(path2.join(envDirectory, `.env.${mode}`), values);
|
|
1208
1343
|
}
|
|
1344
|
+
const privateEntries = [];
|
|
1209
1345
|
if (isFullstack) {
|
|
1210
1346
|
const password = `Aa1!${randomBytes(24).toString("base64url")}`;
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1347
|
+
privateEntries.push(`HAI_ADMIN_DEFAULT_PASSWORD=${password}`);
|
|
1348
|
+
}
|
|
1349
|
+
if (isFullstack || isApiWorkspace && resolvedOptions.features.includes("crypto")) {
|
|
1350
|
+
const key = createECDH("SM2");
|
|
1351
|
+
key.generateKeys();
|
|
1352
|
+
const id = `bootstrap-${randomBytes(6).toString("hex")}`;
|
|
1353
|
+
const ring = { activeKeyId: id, keys: { [id]: { publicKey: key.getPublicKey("hex"), privateKey: key.getPrivateKey("hex").padStart(64, "0") } } };
|
|
1354
|
+
privateEntries.push(`HAI_CRYPTO_TRANSPORT_SERVERKEYRING='${JSON.stringify(ring)}'`);
|
|
1216
1355
|
}
|
|
1356
|
+
await fse.writeFile(path2.join(envDirectory, ".env.local"), `# \u672C\u673A\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u63D0\u4EA4 Git\uFF1B\u90E8\u7F72\u51ED\u636E\u7531\u76EE\u6807\u73AF\u5883\u5355\u72EC\u63D0\u4F9B\u3002
|
|
1357
|
+
${privateEntries.join("\n")}
|
|
1358
|
+
`, { mode: 384 });
|
|
1217
1359
|
}
|
|
1218
|
-
const { generateSkillFiles } = await import('./cli-skill-templates-
|
|
1360
|
+
const { generateSkillFiles } = await import('./cli-skill-templates-US5CCAJ4.js');
|
|
1219
1361
|
await generateSkillFiles(
|
|
1220
1362
|
projectPath,
|
|
1221
1363
|
resolvedOptions.features,
|
|
1222
1364
|
resolvedOptions.appType,
|
|
1223
1365
|
resolvedOptions.name
|
|
1224
1366
|
);
|
|
1225
|
-
const appTypeLabel =
|
|
1367
|
+
const appTypeLabel = cliM(`cli_app${{ "admin": "Admin", "website": "Website", "h5": "H5", "api": "Api", "mobile-app": "Mobile", "fullstack": "Fullstack" }[resolvedOptions.appType]}Name`, { locale: "zh-CN" });
|
|
1226
1368
|
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);
|
|
1227
1369
|
await writeFile(
|
|
1228
1370
|
path2.join(projectPath, "README.md"),
|
|
1229
|
-
readmeContent
|
|
1371
|
+
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
|
|
1230
1372
|
);
|
|
1231
1373
|
spinner.succeed();
|
|
1232
1374
|
if (resolvedOptions.git) {
|
|
1233
|
-
spinner.start("
|
|
1375
|
+
spinner.start(cliM("cli_createGit"));
|
|
1234
1376
|
execSync("git init", { cwd: projectPath, stdio: "ignore" });
|
|
1235
1377
|
if (resolvedOptions.gitRemote) {
|
|
1236
1378
|
const gitRemote = resolvedOptions.gitRemote;
|
|
1237
1379
|
if (!RE_HTTPS_URL.test(gitRemote) && !RE_GIT_SSH_URL.test(gitRemote)) {
|
|
1238
1380
|
spinner.fail();
|
|
1239
|
-
throw new Error(
|
|
1381
|
+
throw new Error(cliM("cli_createRemoteFailure", { params: { remote: gitRemote } }));
|
|
1240
1382
|
}
|
|
1241
1383
|
execFileSync("git", ["remote", "add", "origin", gitRemote], { cwd: projectPath, stdio: "ignore" });
|
|
1242
1384
|
}
|
|
1243
1385
|
spinner.succeed();
|
|
1244
1386
|
}
|
|
1245
1387
|
if (resolvedOptions.install) {
|
|
1246
|
-
spinner.start(
|
|
1388
|
+
spinner.start(cliM("cli_createInstall", { params: { manager: resolvedOptions.packageManager } }));
|
|
1247
1389
|
installDependencies(projectPath, resolvedOptions.packageManager);
|
|
1248
1390
|
spinner.succeed();
|
|
1249
1391
|
}
|
|
@@ -1258,10 +1400,10 @@ async function resolveOptions(options) {
|
|
|
1258
1400
|
const useDefaults = options.yes ?? false;
|
|
1259
1401
|
writeCliMessage("");
|
|
1260
1402
|
writeCliMessage(chalk.bold.cyan(" \u{1F680} hai Agent Framework"));
|
|
1261
|
-
writeCliMessage(chalk.gray("
|
|
1403
|
+
writeCliMessage(chalk.gray(cliM("cli_createTagline")));
|
|
1262
1404
|
writeCliMessage("");
|
|
1263
1405
|
const onCancel = () => {
|
|
1264
|
-
writeCliMessage(chalk.red("
|
|
1406
|
+
writeCliMessage(chalk.red(cliM("cli_cancelledLine")));
|
|
1265
1407
|
process4.exit(1);
|
|
1266
1408
|
};
|
|
1267
1409
|
let projectName = options.name;
|
|
@@ -1271,13 +1413,13 @@ async function resolveOptions(options) {
|
|
|
1271
1413
|
const { name } = await prompts({
|
|
1272
1414
|
type: "text",
|
|
1273
1415
|
name: "name",
|
|
1274
|
-
message: "
|
|
1416
|
+
message: cliM("cli_createNamePrompt"),
|
|
1275
1417
|
initial: "my-app",
|
|
1276
1418
|
validate: (value) => {
|
|
1277
1419
|
if (!value.trim())
|
|
1278
|
-
return "
|
|
1420
|
+
return cliM("cli_createNameRequired");
|
|
1279
1421
|
if (!PROJECT_NAME_REGEX.test(value))
|
|
1280
|
-
return "
|
|
1422
|
+
return cliM("cli_createNamePattern");
|
|
1281
1423
|
return true;
|
|
1282
1424
|
}
|
|
1283
1425
|
}, { onCancel });
|
|
@@ -1290,8 +1432,9 @@ async function resolveOptions(options) {
|
|
|
1290
1432
|
const { appType } = await prompts({
|
|
1291
1433
|
type: "select",
|
|
1292
1434
|
name: "appType",
|
|
1293
|
-
message: "
|
|
1294
|
-
hint:
|
|
1435
|
+
message: cliM("cli_createTypePrompt"),
|
|
1436
|
+
hint: `
|
|
1437
|
+
${formatPromptDescription(cliM("cli_selectHint"))}`,
|
|
1295
1438
|
choices: Object.entries(APP_TYPES).map(([key, t]) => ({
|
|
1296
1439
|
title: t.name,
|
|
1297
1440
|
description: formatPromptDescription(t.description),
|
|
@@ -1313,8 +1456,9 @@ async function resolveOptions(options) {
|
|
|
1313
1456
|
const { template } = await prompts({
|
|
1314
1457
|
type: "select",
|
|
1315
1458
|
name: "template",
|
|
1316
|
-
message: "
|
|
1317
|
-
hint:
|
|
1459
|
+
message: cliM("cli_createTemplatePrompt"),
|
|
1460
|
+
hint: `
|
|
1461
|
+
${formatPromptDescription(cliM("cli_selectHint"))}`,
|
|
1318
1462
|
choices: Object.values(PROJECT_TEMPLATES).map((t) => ({
|
|
1319
1463
|
title: t.name,
|
|
1320
1464
|
description: formatPromptDescription(t.description),
|
|
@@ -1328,7 +1472,9 @@ async function resolveOptions(options) {
|
|
|
1328
1472
|
let selectedFeatures = [];
|
|
1329
1473
|
const appTypeDefaults = APP_TYPES[selectedAppType].defaultFeatures;
|
|
1330
1474
|
if (selectedAppType === "fullstack") {
|
|
1331
|
-
selectedFeatures = resolveFeatureDependencies([...appTypeDefaults, ...options.features
|
|
1475
|
+
selectedFeatures = resolveFeatureDependencies([...appTypeDefaults, ...options.features ?? []]);
|
|
1476
|
+
} else if (options.features) {
|
|
1477
|
+
selectedFeatures = resolveFeatureDependencies(options.features);
|
|
1332
1478
|
} else if (selectedTemplate === "custom" && !options.features && useDefaults) {
|
|
1333
1479
|
selectedFeatures = resolveFeatureDependencies(appTypeDefaults);
|
|
1334
1480
|
} else if (selectedTemplate === "custom" && !options.features) {
|
|
@@ -1343,18 +1489,20 @@ async function resolveOptions(options) {
|
|
|
1343
1489
|
name: "features",
|
|
1344
1490
|
message: cliM("cli_featuresPrompt"),
|
|
1345
1491
|
choices: featureChoices,
|
|
1346
|
-
hint:
|
|
1492
|
+
hint: `
|
|
1493
|
+
${formatPromptDescription(cliM("cli_multiselectHint"))}`,
|
|
1347
1494
|
instructions: false
|
|
1348
1495
|
}, { onCancel });
|
|
1349
1496
|
selectedFeatures = features || [];
|
|
1350
1497
|
selectedFeatures = resolveFeatureDependencies(selectedFeatures);
|
|
1351
|
-
} else if (selectedTemplate === "custom" && options.features) {
|
|
1352
|
-
selectedFeatures = resolveFeatureDependencies(options.features);
|
|
1353
1498
|
} else {
|
|
1354
1499
|
const templateFeatures = PROJECT_TEMPLATES[selectedTemplate]?.features || [];
|
|
1355
1500
|
const merged = /* @__PURE__ */ new Set([...templateFeatures, ...appTypeDefaults]);
|
|
1356
1501
|
selectedFeatures = resolveFeatureDependencies(Array.from(merged));
|
|
1357
1502
|
}
|
|
1503
|
+
selectedFeatures = selectedFeatures.filter((feature) => feature !== "deploy");
|
|
1504
|
+
if (options.deploy !== false)
|
|
1505
|
+
selectedFeatures.push("deploy");
|
|
1358
1506
|
const moduleConfigs = selectedAppType === "fullstack" ? { ...buildDefaultModuleConfigs(selectedFeatures, projectName), ...options.moduleConfigs } : options.moduleConfigs ?? (useDefaults ? buildDefaultModuleConfigs(selectedFeatures, projectName) : await promptModuleConfigs(selectedFeatures, projectName));
|
|
1359
1507
|
let addExamples = options.examples;
|
|
1360
1508
|
if (addExamples === void 0 && useDefaults) {
|
|
@@ -1362,8 +1510,10 @@ async function resolveOptions(options) {
|
|
|
1362
1510
|
} else if (addExamples === void 0) {
|
|
1363
1511
|
const { examples } = await prompts({
|
|
1364
1512
|
type: "confirm",
|
|
1513
|
+
active: cliM("cli_yes"),
|
|
1514
|
+
inactive: cliM("cli_no"),
|
|
1365
1515
|
name: "examples",
|
|
1366
|
-
message: "
|
|
1516
|
+
message: cliM("cli_createExamplesPrompt"),
|
|
1367
1517
|
initial: true
|
|
1368
1518
|
}, { onCancel });
|
|
1369
1519
|
addExamples = examples;
|
|
@@ -1376,10 +1526,12 @@ async function resolveOptions(options) {
|
|
|
1376
1526
|
if (!packageManager) {
|
|
1377
1527
|
const { pm } = await prompts({
|
|
1378
1528
|
type: "select",
|
|
1529
|
+
hint: `
|
|
1530
|
+
${formatPromptDescription(cliM("cli_selectHint"))}`,
|
|
1379
1531
|
name: "pm",
|
|
1380
|
-
message: "
|
|
1532
|
+
message: cliM("cli_createManagerPrompt"),
|
|
1381
1533
|
choices: [
|
|
1382
|
-
{ title: "
|
|
1534
|
+
{ title: cliM("cli_createPnpmRecommended"), value: "pnpm" },
|
|
1383
1535
|
{ title: "npm", value: "npm" },
|
|
1384
1536
|
{ title: "yarn", value: "yarn" }
|
|
1385
1537
|
],
|
|
@@ -1393,8 +1545,10 @@ async function resolveOptions(options) {
|
|
|
1393
1545
|
} else if (install === void 0) {
|
|
1394
1546
|
const { doInstall } = await prompts({
|
|
1395
1547
|
type: "confirm",
|
|
1548
|
+
active: cliM("cli_yes"),
|
|
1549
|
+
inactive: cliM("cli_no"),
|
|
1396
1550
|
name: "doInstall",
|
|
1397
|
-
message: "
|
|
1551
|
+
message: cliM("cli_createInstallPrompt"),
|
|
1398
1552
|
initial: true
|
|
1399
1553
|
}, { onCancel });
|
|
1400
1554
|
install = doInstall;
|
|
@@ -1405,8 +1559,10 @@ async function resolveOptions(options) {
|
|
|
1405
1559
|
} else if (git === void 0) {
|
|
1406
1560
|
const { initGit } = await prompts({
|
|
1407
1561
|
type: "confirm",
|
|
1562
|
+
active: cliM("cli_yes"),
|
|
1563
|
+
inactive: cliM("cli_no"),
|
|
1408
1564
|
name: "initGit",
|
|
1409
|
-
message: "
|
|
1565
|
+
message: cliM("cli_createGitPrompt"),
|
|
1410
1566
|
initial: true
|
|
1411
1567
|
}, { onCancel });
|
|
1412
1568
|
git = initGit;
|
|
@@ -1418,14 +1574,14 @@ async function resolveOptions(options) {
|
|
|
1418
1574
|
const { remote } = await prompts({
|
|
1419
1575
|
type: "text",
|
|
1420
1576
|
name: "remote",
|
|
1421
|
-
message: "
|
|
1577
|
+
message: cliM("cli_createRemotePrompt"),
|
|
1422
1578
|
initial: "",
|
|
1423
1579
|
validate: (value) => {
|
|
1424
1580
|
if (!value.trim())
|
|
1425
1581
|
return true;
|
|
1426
1582
|
if (RE_HTTPS_URL.test(value) || RE_GIT_SSH_URL.test(value))
|
|
1427
1583
|
return true;
|
|
1428
|
-
return "
|
|
1584
|
+
return cliM("cli_createRemoteInvalid");
|
|
1429
1585
|
}
|
|
1430
1586
|
}, { onCancel });
|
|
1431
1587
|
gitRemote = remote?.trim() || "";
|
|
@@ -1436,6 +1592,7 @@ async function resolveOptions(options) {
|
|
|
1436
1592
|
frontends: selectedFrontends,
|
|
1437
1593
|
template: selectedTemplate ?? "default",
|
|
1438
1594
|
features: selectedFeatures,
|
|
1595
|
+
deploy: options.deploy !== false,
|
|
1439
1596
|
moduleConfigs,
|
|
1440
1597
|
examples: addExamples ?? true,
|
|
1441
1598
|
install: install ?? true,
|
|
@@ -1485,7 +1642,9 @@ async function resolveFrontendTargets(frontends, useDefaults, onCancel) {
|
|
|
1485
1642
|
value: id,
|
|
1486
1643
|
selected: DEFAULT_FULLSTACK_FRONTENDS2.includes(id)
|
|
1487
1644
|
})),
|
|
1488
|
-
|
|
1645
|
+
// 独立行显示帮助,避免宽字符让 prompts 按字符数清除时遗留折行。
|
|
1646
|
+
hint: `
|
|
1647
|
+
${formatPromptDescription(cliM("cli_multiselectHint"))}`,
|
|
1489
1648
|
instructions: false
|
|
1490
1649
|
}, { onCancel });
|
|
1491
1650
|
return normalizeFrontendTargets(selectedFrontends ?? DEFAULT_FULLSTACK_FRONTENDS2);
|
|
@@ -1517,12 +1676,12 @@ function resolveFeatureDependencies(features) {
|
|
|
1517
1676
|
async function promptModuleConfigs(features, projectName) {
|
|
1518
1677
|
const configs = {};
|
|
1519
1678
|
const onCancel = () => {
|
|
1520
|
-
writeCliMessage(chalk.red("
|
|
1679
|
+
writeCliMessage(chalk.red(cliM("cli_cancelledLine")));
|
|
1521
1680
|
process4.exit(1);
|
|
1522
1681
|
};
|
|
1523
1682
|
writeCliMessage("");
|
|
1524
|
-
writeCliMessage(chalk.bold("
|
|
1525
|
-
writeCliMessage(chalk.gray("
|
|
1683
|
+
writeCliMessage(chalk.bold(cliM("cli_createModuleConfig")));
|
|
1684
|
+
writeCliMessage(chalk.gray(cliM("cli_createModuleConfigHint")));
|
|
1526
1685
|
writeCliMessage("");
|
|
1527
1686
|
configs.core = await promptCoreConfig(projectName, onCancel);
|
|
1528
1687
|
if (features.includes("db")) {
|
|
@@ -1543,27 +1702,32 @@ async function promptModuleConfigs(features, projectName) {
|
|
|
1543
1702
|
return configs;
|
|
1544
1703
|
}
|
|
1545
1704
|
async function promptCoreConfig(projectName, onCancel) {
|
|
1546
|
-
writeCliMessage(chalk.cyan("
|
|
1705
|
+
writeCliMessage(chalk.cyan(cliM("cli_createCoreConfig")));
|
|
1547
1706
|
const { defaultLocale } = await prompts({
|
|
1548
1707
|
type: "select",
|
|
1708
|
+
hint: `
|
|
1709
|
+
${formatPromptDescription(cliM("cli_selectHint"))}`,
|
|
1549
1710
|
name: "defaultLocale",
|
|
1550
|
-
message: "
|
|
1711
|
+
message: cliM("cli_createLocalePrompt"),
|
|
1551
1712
|
choices: [
|
|
1552
|
-
{ title: "
|
|
1553
|
-
{ title: "
|
|
1713
|
+
{ title: cliM("cli_createChineseLocale"), value: "zh-CN" },
|
|
1714
|
+
{ title: cliM("cli_createEnglishLocale"), value: "en-US" },
|
|
1715
|
+
{ title: cliM("cli_createJapaneseLocale"), value: "ja-JP" }
|
|
1554
1716
|
],
|
|
1555
1717
|
initial: 0
|
|
1556
1718
|
}, { onCancel });
|
|
1557
1719
|
return { name: projectName, defaultLocale };
|
|
1558
1720
|
}
|
|
1559
1721
|
async function promptDbConfig(projectName, onCancel) {
|
|
1560
|
-
writeCliMessage(chalk.cyan("
|
|
1722
|
+
writeCliMessage(chalk.cyan(cliM("cli_createDbConfig")));
|
|
1561
1723
|
const { type } = await prompts({
|
|
1562
1724
|
type: "select",
|
|
1725
|
+
hint: `
|
|
1726
|
+
${formatPromptDescription(cliM("cli_selectHint"))}`,
|
|
1563
1727
|
name: "type",
|
|
1564
|
-
message: "
|
|
1728
|
+
message: cliM("cli_createDbTypePrompt"),
|
|
1565
1729
|
choices: [
|
|
1566
|
-
{ title: "
|
|
1730
|
+
{ title: cliM("cli_createSqliteOption"), value: "sqlite" },
|
|
1567
1731
|
{ title: "PostgreSQL", value: "postgresql" },
|
|
1568
1732
|
{ title: "MySQL", value: "mysql" }
|
|
1569
1733
|
],
|
|
@@ -1573,26 +1737,28 @@ async function promptDbConfig(projectName, onCancel) {
|
|
|
1573
1737
|
const { database } = await prompts({
|
|
1574
1738
|
type: "text",
|
|
1575
1739
|
name: "database",
|
|
1576
|
-
message: "
|
|
1740
|
+
message: cliM("cli_createSqlitePathPrompt"),
|
|
1577
1741
|
initial: "./data/app.db"
|
|
1578
1742
|
}, { onCancel });
|
|
1579
1743
|
return { type, database };
|
|
1580
1744
|
}
|
|
1581
1745
|
const answers = await prompts([
|
|
1582
|
-
{ type: "text", name: "host", message:
|
|
1583
|
-
{ type: "number", name: "port", message: "
|
|
1584
|
-
{ type: "text", name: "database", message: "
|
|
1746
|
+
{ type: "text", name: "host", message: cliM("cli_createDbHostPrompt", { params: { backend: type === "postgresql" ? "PostgreSQL" : "MySQL" } }), initial: "localhost" },
|
|
1747
|
+
{ type: "number", name: "port", message: cliM("cli_createPortPrompt"), initial: type === "postgresql" ? 5432 : 3306 },
|
|
1748
|
+
{ type: "text", name: "database", message: cliM("cli_createDatabasePrompt"), initial: projectName }
|
|
1585
1749
|
], { onCancel });
|
|
1586
1750
|
return { type, host: answers.host, port: answers.port, database: answers.database };
|
|
1587
1751
|
}
|
|
1588
1752
|
async function promptCacheConfig(onCancel) {
|
|
1589
|
-
writeCliMessage(chalk.cyan("
|
|
1753
|
+
writeCliMessage(chalk.cyan(cliM("cli_createCacheConfig")));
|
|
1590
1754
|
const { type } = await prompts({
|
|
1591
1755
|
type: "select",
|
|
1756
|
+
hint: `
|
|
1757
|
+
${formatPromptDescription(cliM("cli_selectHint"))}`,
|
|
1592
1758
|
name: "type",
|
|
1593
|
-
message: "
|
|
1759
|
+
message: cliM("cli_createCacheTypePrompt"),
|
|
1594
1760
|
choices: [
|
|
1595
|
-
{ title: "
|
|
1761
|
+
{ title: cliM("cli_createMemoryOption"), value: "memory" },
|
|
1596
1762
|
{ title: "Redis", value: "redis" }
|
|
1597
1763
|
],
|
|
1598
1764
|
initial: 0
|
|
@@ -1600,27 +1766,29 @@ async function promptCacheConfig(onCancel) {
|
|
|
1600
1766
|
if (type === "memory")
|
|
1601
1767
|
return { type };
|
|
1602
1768
|
const answers = await prompts([
|
|
1603
|
-
{ type: "text", name: "host", message: "
|
|
1604
|
-
{ type: "number", name: "port", message: "
|
|
1769
|
+
{ type: "text", name: "host", message: cliM("cli_createRedisHostPrompt"), initial: "localhost" },
|
|
1770
|
+
{ type: "number", name: "port", message: cliM("cli_createRedisPortPrompt"), initial: 6379 }
|
|
1605
1771
|
], { onCancel });
|
|
1606
1772
|
return { type, host: answers.host, port: answers.port };
|
|
1607
1773
|
}
|
|
1608
1774
|
async function promptIamConfig(onCancel) {
|
|
1609
|
-
writeCliMessage(chalk.cyan("
|
|
1775
|
+
writeCliMessage(chalk.cyan(cliM("cli_createIamConfig")));
|
|
1610
1776
|
const answers = await prompts([
|
|
1611
|
-
{ type: "confirm", name: "loginPassword", message: "
|
|
1612
|
-
{ type: "confirm", name: "loginOtp", message: "
|
|
1777
|
+
{ type: "confirm", active: cliM("cli_yes"), inactive: cliM("cli_no"), name: "loginPassword", message: cliM("cli_createPasswordPrompt"), initial: true },
|
|
1778
|
+
{ type: "confirm", active: cliM("cli_yes"), inactive: cliM("cli_no"), name: "loginOtp", message: cliM("cli_createOtpPrompt"), initial: false }
|
|
1613
1779
|
], { onCancel });
|
|
1614
1780
|
return { loginPassword: answers.loginPassword, loginOtp: answers.loginOtp };
|
|
1615
1781
|
}
|
|
1616
1782
|
async function promptStorageConfig(onCancel) {
|
|
1617
|
-
writeCliMessage(chalk.cyan("
|
|
1783
|
+
writeCliMessage(chalk.cyan(cliM("cli_createStorageConfig")));
|
|
1618
1784
|
const { type } = await prompts({
|
|
1619
1785
|
type: "select",
|
|
1786
|
+
hint: `
|
|
1787
|
+
${formatPromptDescription(cliM("cli_selectHint"))}`,
|
|
1620
1788
|
name: "type",
|
|
1621
|
-
message: "
|
|
1789
|
+
message: cliM("cli_createStorageTypePrompt"),
|
|
1622
1790
|
choices: [
|
|
1623
|
-
{ title: "
|
|
1791
|
+
{ title: cliM("cli_createLocalStorageOption"), value: "local" },
|
|
1624
1792
|
{ title: "S3 / MinIO / OSS", value: "s3" }
|
|
1625
1793
|
],
|
|
1626
1794
|
initial: 0
|
|
@@ -1629,7 +1797,7 @@ async function promptStorageConfig(onCancel) {
|
|
|
1629
1797
|
const { localPath } = await prompts({
|
|
1630
1798
|
type: "text",
|
|
1631
1799
|
name: "localPath",
|
|
1632
|
-
message: "
|
|
1800
|
+
message: cliM("cli_createStoragePathPrompt"),
|
|
1633
1801
|
initial: "./data/uploads"
|
|
1634
1802
|
}, { onCancel });
|
|
1635
1803
|
return { type, localPath };
|
|
@@ -1637,15 +1805,17 @@ async function promptStorageConfig(onCancel) {
|
|
|
1637
1805
|
return { type };
|
|
1638
1806
|
}
|
|
1639
1807
|
async function promptAiConfig(onCancel) {
|
|
1640
|
-
writeCliMessage(chalk.cyan("
|
|
1808
|
+
writeCliMessage(chalk.cyan(cliM("cli_createAiConfig")));
|
|
1641
1809
|
const { defaultProvider } = await prompts({
|
|
1642
1810
|
type: "select",
|
|
1811
|
+
hint: `
|
|
1812
|
+
${formatPromptDescription(cliM("cli_selectHint"))}`,
|
|
1643
1813
|
name: "defaultProvider",
|
|
1644
|
-
message: "
|
|
1814
|
+
message: cliM("cli_createProviderPrompt"),
|
|
1645
1815
|
choices: [
|
|
1646
1816
|
{ title: "OpenAI", value: "openai" },
|
|
1647
1817
|
{ title: "Anthropic (Claude)", value: "anthropic" },
|
|
1648
|
-
{ title: "
|
|
1818
|
+
{ title: cliM("cli_createCompatibleAiOption"), value: "custom" }
|
|
1649
1819
|
],
|
|
1650
1820
|
initial: 0
|
|
1651
1821
|
}, { onCancel });
|
|
@@ -1653,7 +1823,7 @@ async function promptAiConfig(onCancel) {
|
|
|
1653
1823
|
const { model } = await prompts({
|
|
1654
1824
|
type: "text",
|
|
1655
1825
|
name: "model",
|
|
1656
|
-
message: "
|
|
1826
|
+
message: cliM("cli_createModelPrompt"),
|
|
1657
1827
|
initial: defaultModel
|
|
1658
1828
|
}, { onCancel });
|
|
1659
1829
|
return { defaultProvider, model };
|
|
@@ -1732,27 +1902,25 @@ function normalizeChildOutput(output) {
|
|
|
1732
1902
|
return "";
|
|
1733
1903
|
}
|
|
1734
1904
|
function generateAiDevelopmentReadme() {
|
|
1735
|
-
return
|
|
1905
|
+
return `### AI \u8F85\u52A9\u5F00\u53D1
|
|
1736
1906
|
|
|
1737
1907
|
\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
|
|
1738
1908
|
|
|
1739
|
-
\
|
|
1740
|
-
|
|
1741
|
-
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
|
|
1909
|
+
\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
|
|
1742
1910
|
|
|
1743
|
-
\
|
|
1911
|
+
\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
|
|
1744
1912
|
|
|
1745
|
-
\
|
|
1746
|
-
|
|
1747
|
-
\u5DF2\u6709 hai \u9879\u76EE\u53EF\u8FD0\u884C hai init \u8865\u9F50\u7F3A\u5931\u7684 AI \u652F\u6301\u6587\u4EF6\u4E0E\u53C2\u8003\u8D44\u6E90\uFF1B\u5DF2\u6709 AGENTS.md\u3001Skill \u548C\u81EA\u5B9A\u4E49\u89C4\u5219\u4F1A\u4FDD\u7559\uFF0C\u5347\u7EA7\u65F6\u9700\u5C06\u4E0A\u8FF0\u9700\u6C42\u4F18\u5148\u89C4\u5219\u3001\u666E\u901A\u5F00\u53D1\u5165\u53E3\u4E0E\u89C4\u8303\u663E\u5F0F\u5408\u5E76\u5230\u65E7\u6307\u5F15\uFF0C\u5E76\u4FEE\u6B63\u5DF2\u88AB\u9700\u6C42\u66FF\u4EE3\u7684\u6A21\u677F\u7EA6\u675F\u3002
|
|
1913
|
+
\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
|
|
1748
1914
|
|
|
1749
1915
|
`;
|
|
1750
1916
|
}
|
|
1751
1917
|
function generateEnvironmentReadme() {
|
|
1752
|
-
return
|
|
1918
|
+
return `### \u5206\u73AF\u5883\u914D\u7F6E
|
|
1753
1919
|
|
|
1754
1920
|
\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
|
|
1755
1921
|
|
|
1922
|
+
\u751F\u4EA7 .env.prod \u6309\u5DF2\u542F\u7528\u6A21\u5757\u9009\u62E9\u5171\u4EAB PostgreSQL\uFF08\u660E\u786E\u9009\u62E9 MySQL \u65F6\u4FDD\u7559\uFF09\u3001Redis\u3001S3\u3001Qdrant\uFF0C\u9ED8\u8BA4\u4F7F\u7528 Compose \u5185\u90E8\u670D\u52A1\u540D db/cache/storage/vecdb\u3002\u5BC6\u7801\u3001S3 \u51ED\u636E\u3001\u6A21\u578B Key \u4E0E\u53D7\u4FDD\u62A4 Qdrant \u7684 Key \u53EA\u7531\u79C1\u6709\u73AF\u5883\u6CE8\u5165\uFF1B\u5BB9\u5668\u5916\u8FD0\u884C\u8986\u76D6\u5185\u90E8\u8FDE\u63A5\u5730\u5740\u3002\u751F\u4EA7 Reach \u5FC5\u987B\u6CE8\u5165\u771F\u5B9E HAI_REACH_PROVIDERS JSON\uFF0C\u516C\u5171\u7A7A\u6570\u7EC4\u4F1A\u62D2\u7EDD\u542F\u52A8\u3002AI \u81EA\u52A8\u8865\u5168 db+vecdb \u4F9D\u8D56\uFF0C\u5E76\u5728\u4E8C\u8005\u5C31\u7EEA\u540E\u521D\u59CB\u5316\uFF0C\u5931\u8D25\u9006\u5E8F\u6E05\u7406\u3002
|
|
1923
|
+
|
|
1756
1924
|
| \u7528\u9014 | \u547D\u4EE4 |
|
|
1757
1925
|
| --- | --- |
|
|
1758
1926
|
| \u5F00\u53D1 / \u6D4B\u8BD5\u6A21\u5F0F / \u751F\u4EA7\u6A21\u5F0F\u9884\u89C8 | \`pnpm dev\` / \`dev:test\` / \`dev:prod\` |
|
|
@@ -1764,17 +1932,58 @@ function generateEnvironmentReadme() {
|
|
|
1764
1932
|
|
|
1765
1933
|
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`;
|
|
1766
1934
|
}
|
|
1935
|
+
function generateBuildDeploymentReadme(pm, appDir) {
|
|
1936
|
+
return `### \u6784\u5EFA\u4E0E\u90E8\u7F72
|
|
1937
|
+
|
|
1938
|
+
\u521B\u5EFA\u65F6\u9ED8\u8BA4\u5305\u542B\u90E8\u7F72\u5DE5\u5177\uFF1B\u4E0D\u9700\u8981\u65F6\u4F7F\u7528 hai create --no-deploy\u3002
|
|
1939
|
+
\u6BCF\u4E2A\u955C\u50CF\u5BF9\u5E94 images/ \u4E2D\u4E00\u4E2A Dockerfile\uFF0Chai.build.json \u7528 dockerfile \u6307\u5B9A\u6587\u4EF6\u3002
|
|
1940
|
+
\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
|
|
1941
|
+
\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
|
|
1942
|
+
|
|
1943
|
+
\`\`\`sh
|
|
1944
|
+
${pm} build
|
|
1945
|
+
${pm} build --target docker
|
|
1946
|
+
# \u6784\u5EFA\u548C\u90E8\u7F72\u4E00\u6B21\u5B8C\u6210
|
|
1947
|
+
${pm} exec hai deploy ${appDir} --env test --build
|
|
1948
|
+
# \u590D\u7528\u5DF2\u6709\u955C\u50CF\uFF1B\u53EA\u751F\u6210\u5B8C\u6574\u79BB\u7EBF\u5305\u53CA\u8DE8\u5E73\u53F0\u90E8\u7F72\u811A\u672C
|
|
1949
|
+
${pm} exec hai deploy ${appDir} --env prod --bundle-only
|
|
1950
|
+
# \u53EA\u66F4\u65B0\u524D\u7AEF\uFF08\u5DE5\u7A0B\u5B58\u5728 web \u955C\u50CF\u65F6\uFF09
|
|
1951
|
+
${pm} build --target docker --images web
|
|
1952
|
+
${pm} exec hai deploy ${appDir} --env prod --services web
|
|
1953
|
+
\`\`\`
|
|
1954
|
+
|
|
1955
|
+
\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
|
|
1956
|
+
\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
|
|
1957
|
+
\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
|
|
1958
|
+
\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\u3001util-linux flock \u548C\u652F\u6301 flock \u7684\u6587\u4EF6\u7CFB\u7EDF\uFF0C\u542F\u52A8\u9636\u6BB5\u4E0D\u8054\u7F51\u62C9\u53D6\u3002
|
|
1959
|
+
\u56FA\u5B9A .deploy.lock \u5185\u6838\u9501\u8986\u76D6\u90E8\u7F72\u53CA\u6062\u590D\uFF1B\u7981\u6B62\u5220\u9664\u6216\u66FF\u6362\uFF0C\u6240\u6709\u6301\u6709\u8FDB\u7A0B\u9000\u51FA\u6216\u91CD\u542F\u540E\u81EA\u52A8\u91CA\u653E\u3002\u5F3A\u6740\u540E\u987B\u6838\u5BF9\u7ED3\u679C\u6587\u4EF6\u3001current \u548C\u5BB9\u5668\u72B6\u6001\uFF0C\u518D\u7528\u65B0\u5305\u6062\u590D\u3002
|
|
1960
|
+
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
|
|
1961
|
+
\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
|
|
1962
|
+
\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
|
|
1963
|
+
\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
|
|
1964
|
+
`;
|
|
1965
|
+
}
|
|
1767
1966
|
function generateReadme(name, appTypeLabel, pm, appType) {
|
|
1768
1967
|
const install = getInstallCommand(pm);
|
|
1769
1968
|
return `# ${name}
|
|
1770
1969
|
|
|
1970
|
+
## \u7B80\u4ECB
|
|
1971
|
+
|
|
1771
1972
|
\u57FA\u4E8E hai Agent Framework \u6784\u5EFA\u7684${appTypeLabel}\u5E94\u7528\u3002
|
|
1772
1973
|
|
|
1773
|
-
## \
|
|
1974
|
+
## \u6838\u5FC3\u529F\u80FD\u6E05\u5355
|
|
1774
1975
|
|
|
1775
|
-
|
|
1976
|
+
- \u5F00\u53D1\u670D\u52A1\u5668\u3001\u6E90\u7801\u7F16\u8BD1\u4E0E\u6D4F\u89C8\u5668\u6D4B\u8BD5\u3002
|
|
1977
|
+
- \u6309\u6240\u9009\u6A21\u5757\u63D0\u4F9B\u9875\u9762\u548C\u670D\u52A1\u914D\u7F6E\u3002
|
|
1978
|
+
- \u72EC\u7ACB Dockerfile \u4E0E\u955C\u50CF\u5F52\u6863\uFF0C\u4FBF\u4E8E\u4EA4\u4ED8\u3002
|
|
1776
1979
|
|
|
1777
|
-
## \
|
|
1980
|
+
## \u4F7F\u7528\u5165\u95E8
|
|
1981
|
+
|
|
1982
|
+
### \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
|
|
1983
|
+
|
|
1984
|
+
\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"}${appType === "mobile-app" ? "\n\n\u9009\u62E9 IAM \u6216 crypto \u7684\u79FB\u52A8\u5BA2\u6237\u7AEF\u9ED8\u8BA4\u542F\u7528\u52A0\u5BC6 transport\uFF0C\u5148\u521D\u59CB\u5316 crypto\uFF0C\u518D\u521D\u59CB\u5316 typed API client\uFF1B\u7ED3\u675F\u65F6\u6309\u76F8\u53CD\u987A\u5E8F\u5173\u95ED\u3002\u4EC5\u660E\u786E\u8FDE\u63A5\u5339\u914D\u7684\u660E\u6587 API \u65F6\u8BBE\u7F6E PUBLIC_API_TRANSPORT=off\uFF1BPUBLIC_API_KEY_EXCHANGE_PATH \u4EC5\u8986\u76D6\u516C\u5F00\u534F\u5546\u5B50\u8DEF\u5F84\u3002\u63E1\u624B\u5931\u8D25\u4E0D\u4F1A\u9000\u56DE\u660E\u6587\uFF0C\u670D\u52A1\u7AEF\u79C1\u94A5\u4E0D\u8FDB\u5165\u5BA2\u6237\u7AEF\u3002\u672A\u9009\u62E9 crypto \u7684\u5BA2\u6237\u7AEF\u4FDD\u7559\u660E\u6587 API \u6A21\u5F0F\u3002" : ""}
|
|
1985
|
+
|
|
1986
|
+
### \u5F00\u53D1
|
|
1778
1987
|
|
|
1779
1988
|
\`\`\`bash
|
|
1780
1989
|
${install}
|
|
@@ -1783,46 +1992,57 @@ ${getRunCommand(pm, "dev")}
|
|
|
1783
1992
|
|
|
1784
1993
|
${generateEnvironmentReadme()}
|
|
1785
1994
|
|
|
1786
|
-
|
|
1995
|
+
### \u6784\u5EFA
|
|
1787
1996
|
|
|
1788
1997
|
\`\`\`bash
|
|
1789
1998
|
${getRunCommand(pm, "build")}
|
|
1790
|
-
${getRunCommand(pm, "
|
|
1999
|
+
${getRunCommand(pm, "paraglide:compile")}
|
|
1791
2000
|
${getRunCommand(pm, "preview")}
|
|
1792
2001
|
\`\`\`
|
|
1793
2002
|
|
|
1794
|
-
|
|
2003
|
+
${generateBuildDeploymentReadme(pm, `.`)}
|
|
1795
2004
|
|
|
1796
|
-
|
|
1797
|
-
# \u9ED8\u8BA4\u53EA\u7F16\u8BD1\u6E90\u7801\uFF1B\u4E0D\u9009\u62E9\u90E8\u7F72\u73AF\u5883
|
|
1798
|
-
${pm} build
|
|
1799
|
-
# \u6784\u5EFA\u5E76\u5BFC\u51FA API/Web \u955C\u50CF\uFF0Cauto \u4F18\u5148 Podman\u3001\u5176\u6B21 Docker
|
|
1800
|
-
${pm} build --target docker
|
|
1801
|
-
# \u9009\u62E9\u73AF\u5883\u3001\u4E2D\u95F4\u4EF6\u5E76\u4E0A\u4F20\u4E00\u4E2A Compose tar\uFF1B\u4E0D\u4F1A\u91CD\u65B0\u6784\u5EFA
|
|
1802
|
-
${pm} exec hai deploy . --env test
|
|
1803
|
-
# \u65E0\u6CD5\u8FDE\u63A5\u670D\u52A1\u5668\u65F6\u53EA\u751F\u6210\u5305\uFF0C\u6309\u8F93\u51FA\u8BF4\u660E\u624B\u5DE5\u642C\u8FD0\u548C\u542F\u52A8
|
|
1804
|
-
${pm} exec hai deploy . --env prod --bundle-only
|
|
1805
|
-
\`\`\`
|
|
1806
|
-
|
|
1807
|
-
\u955C\u50CF\u6784\u5EFA\u914D\u7F6E\u89C1 \`hai.build.json\`\u3002\u6784\u5EFA\u4EA7\u7269\u4FDD\u5B58\u5728 \`.hai/build/docker/<buildId>\`\uFF0C\u53EF\u7528 \`--artifact-dir\` \u9009\u62E9\u540C\u4E00\u4EFD\u4EA7\u7269\u90E8\u7F72\u5230 test/prod\u3002\u90E8\u7F72\u5305\u542B\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u80FD\u63D0\u4EA4 Git\u3002\u73AF\u5883\u5DF2\u914D\u7F6E\u5B8C\u5907\u53EF\u52A0 \`--skip-middleware\`\u3002\u670D\u52A1\u8FD0\u884C\u6570\u636E\u7531 volume \u6301\u4E45\u5316\uFF0C\u955C\u50CF\u4E0D\u6253\u5305\u672C\u673A data\u3002API/Web \u8054\u5408\u90E8\u7F72\u516C\u5F00 Web \u5165\u53E3\u5E76\u4EE3\u7406\u540C\u6E90 API\uFF1B\u6CA1\u6709 Web \u4EA7\u7269\u65F6\u53EA\u90E8\u7F72\u670D\u52A1\u3002
|
|
1808
|
-
|
|
1809
|
-
${generateAiDevelopmentReadme()}## \u6587\u6863
|
|
2005
|
+
${generateAiDevelopmentReadme()}### \u6587\u6863
|
|
1810
2006
|
|
|
1811
2007
|
- [hai Agent Framework](https://github.com/hai-series/hai-framework)
|
|
1812
2008
|
- [Svelte 5](https://svelte.dev/)
|
|
2009
|
+
|
|
2010
|
+
## \u5F00\u53D1\u6D4B\u8BD5
|
|
2011
|
+
|
|
2012
|
+
\`\`\`sh
|
|
2013
|
+
${getRunCommand(pm, "typecheck")}
|
|
2014
|
+
${getRunCommand(pm, "lint")}
|
|
2015
|
+
${getRunCommand(pm, "build")}
|
|
2016
|
+
${getRunCommand(pm, "test")}
|
|
2017
|
+
${getRunCommand(pm, "test:e2e")}
|
|
2018
|
+
\`\`\`
|
|
2019
|
+
|
|
2020
|
+
## \u8BB8\u53EF\u8BC1
|
|
2021
|
+
|
|
2022
|
+
Apache-2.0
|
|
1813
2023
|
`;
|
|
1814
2024
|
}
|
|
1815
2025
|
function generateApiWorkspaceReadme(name, pm) {
|
|
1816
2026
|
return `# ${name}
|
|
1817
2027
|
|
|
1818
|
-
|
|
2028
|
+
## \u7B80\u4ECB
|
|
2029
|
+
|
|
2030
|
+
\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
|
|
2031
|
+
|
|
2032
|
+
## \u6838\u5FC3\u529F\u80FD\u6E05\u5355
|
|
2033
|
+
|
|
2034
|
+
- \u5171\u4EAB contract\u3001\u8F93\u5165\u8F93\u51FA\u6821\u9A8C\u4E0E\u670D\u52A1 procedures\u3002
|
|
2035
|
+
- \u6A21\u5757\u914D\u7F6E\u3001\u5065\u5EB7\u68C0\u67E5\u548C HTTP \u670D\u52A1\u3002
|
|
2036
|
+
- \u72EC\u7ACB\u955C\u50CF\u6784\u5EFA\u4E0E\u79BB\u7EBF\u4EA4\u4ED8\u3002
|
|
2037
|
+
|
|
2038
|
+
## \u4F7F\u7528\u5165\u95E8
|
|
1819
2039
|
|
|
1820
|
-
|
|
2040
|
+
### \u5DE5\u7A0B\u7ED3\u6784
|
|
1821
2041
|
|
|
1822
2042
|
- \`apps/${name}-contract\`\uFF1A\u5171\u4EAB API contract\u3001Zod schema \u4E0E\u7C7B\u578B
|
|
1823
2043
|
- \`apps/${name}-service\`\uFF1A\u540E\u7AEF API service\uFF0C\u57FA\u4E8E \`@h-ai/serv\` \u66B4\u9732 HTTP \u5E94\u7528
|
|
1824
2044
|
|
|
1825
|
-
|
|
2045
|
+
### \u5F00\u53D1
|
|
1826
2046
|
|
|
1827
2047
|
\`\`\`bash
|
|
1828
2048
|
${pm} install
|
|
@@ -1831,52 +2051,55 @@ ${pm} dev
|
|
|
1831
2051
|
|
|
1832
2052
|
${generateEnvironmentReadme()}
|
|
1833
2053
|
|
|
1834
|
-
## \u8D28\u91CF\u95E8\u7981
|
|
1835
|
-
|
|
1836
|
-
\`\`\`bash
|
|
1837
|
-
${pm} typecheck
|
|
1838
|
-
${pm} lint
|
|
1839
|
-
${pm} build
|
|
1840
|
-
${pm} test
|
|
1841
|
-
${pm} test:e2e
|
|
1842
|
-
\`\`\`
|
|
1843
|
-
|
|
1844
|
-
## \u6784\u5EFA\u4E0E\u90E8\u7F72
|
|
1845
|
-
|
|
1846
|
-
\`\`\`bash
|
|
1847
|
-
# \u9ED8\u8BA4\u53EA\u7F16\u8BD1\u6E90\u7801\uFF1B\u4E0D\u9009\u62E9\u90E8\u7F72\u73AF\u5883
|
|
1848
|
-
${pm} build
|
|
1849
|
-
# \u6784\u5EFA\u5E76\u5BFC\u51FA API/Web \u955C\u50CF\uFF0Cauto \u4F18\u5148 Podman\u3001\u5176\u6B21 Docker
|
|
1850
|
-
${pm} build --target docker
|
|
1851
|
-
# \u9009\u62E9\u73AF\u5883\u3001\u4E2D\u95F4\u4EF6\u5E76\u4E0A\u4F20\u4E00\u4E2A Compose tar\uFF1B\u4E0D\u4F1A\u91CD\u65B0\u6784\u5EFA
|
|
1852
|
-
${pm} exec hai deploy apps/${name}-service --env test
|
|
1853
|
-
# \u65E0\u6CD5\u8FDE\u63A5\u670D\u52A1\u5668\u65F6\u53EA\u751F\u6210\u5305\uFF0C\u6309\u8F93\u51FA\u8BF4\u660E\u624B\u5DE5\u642C\u8FD0\u548C\u542F\u52A8
|
|
1854
|
-
${pm} exec hai deploy apps/${name}-service --env prod --bundle-only
|
|
1855
|
-
\`\`\`
|
|
1856
2054
|
|
|
1857
|
-
|
|
2055
|
+
${generateBuildDeploymentReadme(pm, `apps/${name}-service`)}
|
|
1858
2056
|
|
|
1859
|
-
|
|
2057
|
+
### \u6269\u5C55\u5EFA\u8BAE
|
|
1860
2058
|
|
|
1861
2059
|
- \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
|
|
1862
2060
|
- \u5728 \`${name}-service\` \u4E2D\u6269\u5C55 procedures\u3001\u6A21\u5757\u521D\u59CB\u5316\u4E0E\u914D\u7F6E\u3002
|
|
1863
2061
|
- \u9700\u8981 IAM / storage / ai \u65F6\uFF0C\u4F18\u5148\u590D\u7528 \`@h-ai/api-contract\` \u4E0E \`@h-ai/serv/features/*\`\u3002
|
|
1864
2062
|
|
|
1865
|
-
${generateAiDevelopmentReadme()}
|
|
2063
|
+
${generateAiDevelopmentReadme()}### \u6587\u6863
|
|
1866
2064
|
|
|
1867
2065
|
- [hai Agent Framework](https://github.com/hai-series/hai-framework)
|
|
1868
2066
|
- [@h-ai/serv](./.agents/skills/hai-serv/SKILL.md)
|
|
1869
2067
|
- [@h-ai/api-contract](./.agents/skills/hai-api-contract/SKILL.md)
|
|
1870
2068
|
- [@h-ai/api-client](./.agents/skills/hai-api-client/SKILL.md)
|
|
2069
|
+
|
|
2070
|
+
## \u5F00\u53D1\u6D4B\u8BD5
|
|
2071
|
+
|
|
2072
|
+
\`\`\`bash
|
|
2073
|
+
${pm} typecheck
|
|
2074
|
+
${pm} lint
|
|
2075
|
+
${pm} build
|
|
2076
|
+
${pm} test
|
|
2077
|
+
${pm} test:e2e
|
|
2078
|
+
\`\`\`
|
|
2079
|
+
|
|
2080
|
+
## \u8BB8\u53EF\u8BC1
|
|
2081
|
+
|
|
2082
|
+
Apache-2.0
|
|
1871
2083
|
`;
|
|
1872
2084
|
}
|
|
1873
2085
|
function generateFullstackReadme(name, pm, frontends) {
|
|
1874
2086
|
const enabledFrontends = frontends.map((target) => FRONTEND_TARGETS[target].name).join(" / ");
|
|
1875
2087
|
return `# ${name}
|
|
1876
2088
|
|
|
1877
|
-
|
|
2089
|
+
## \u7B80\u4ECB
|
|
2090
|
+
|
|
2091
|
+
\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
|
|
2092
|
+
|
|
2093
|
+
## \u6838\u5FC3\u529F\u80FD\u6E05\u5355
|
|
2094
|
+
|
|
2095
|
+
- API \u670D\u52A1\u3001\u5171\u4EAB\u754C\u9762\u4E0E\u6309\u9700\u751F\u6210\u7684\u5BA2\u6237\u7AEF\u3002
|
|
2096
|
+
- \u7528\u6237\u7BA1\u7406\u3001\u89D2\u8272\u3001\u5BA1\u8BA1\u548C\u4E2A\u4EBA\u8BBE\u7F6E\u793A\u4F8B\u3002
|
|
2097
|
+
- \u767B\u5F55\u3001\u4E3B\u9898\u3001\u8BED\u8A00\u3001\u65B0\u624B\u5F15\u5BFC\u548C\u5E2E\u52A9\u4E2D\u5FC3\u3002
|
|
2098
|
+
- API/Web \u72EC\u7ACB Dockerfile \u4E0E\u955C\u50CF\u5F52\u6863\u3002
|
|
2099
|
+
|
|
2100
|
+
## \u4F7F\u7528\u5165\u95E8
|
|
1878
2101
|
|
|
1879
|
-
|
|
2102
|
+
### \u5DE5\u7A0B\u7ED3\u6784
|
|
1880
2103
|
|
|
1881
2104
|
- \`packages/${name}-contract\`\uFF1A\u524D\u540E\u7AEF\u5171\u4EAB API \u5951\u7EA6\u3001Zod Schema \u4E0E\u7C7B\u578B
|
|
1882
2105
|
- \`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
|
|
@@ -1888,11 +2111,11 @@ function generateFullstackReadme(name, pm, frontends) {
|
|
|
1888
2111
|
|
|
1889
2112
|
\u5DF2\u542F\u7528\u524D\u7AEF\uFF1A${enabledFrontends || "Web / App / Desktop"}
|
|
1890
2113
|
|
|
1891
|
-
|
|
2114
|
+
### \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
|
|
1892
2115
|
|
|
1893
2116
|
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
|
|
1894
2117
|
|
|
1895
|
-
|
|
2118
|
+
### \u9ED8\u8BA4\u7BA1\u7406\u529F\u80FD
|
|
1896
2119
|
|
|
1897
2120
|
\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
|
|
1898
2121
|
|
|
@@ -1902,11 +2125,13 @@ CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env.local\`\uFF0C\u5176\u4E2D
|
|
|
1902
2125
|
|
|
1903
2126
|
\u672C\u5730\u5F00\u53D1\u9ED8\u8BA4 \`PUBLIC_API_BASE=http://127.0.0.1:3000\`\uFF0CVite \u4F7F\u7528\u540C\u6E90\u4EE3\u7406\uFF1B\u90E8\u7F72\u65F6\u6539\u4E3A\u5B9E\u9645\u516C\u7F51 API Origin\uFF0CAndroid \u6A21\u62DF\u5668\u8BBF\u95EE\u5BBF\u4E3B\u673A\u65F6\u4F7F\u7528 \`http://10.0.2.2:3000\`\u3002\u4E0D\u8981\u5C06\u5BC6\u94A5\u653E\u5165\u4EFB\u4F55 \`PUBLIC_\` \u6216 \`VITE_\` \u53D8\u91CF\u3002
|
|
1904
2127
|
|
|
2128
|
+
\u670D\u52A1\u7AEF\u7248\u672C\u5316\u4F20\u8F93\u5BC6\u94A5\u73AF\u5728\u521B\u5EFA\u65F6\u4E00\u6B21\u6027\u5199\u5165\u88AB\u5FFD\u7565\u7684 \`.env.local\`\uFF0C\u53D8\u91CF\u4E3A \`HAI_CRYPTO_TRANSPORT_SERVERKEYRING\`\uFF1B\u8FD0\u884C\u65F6\u4E0D\u751F\u6210\u5BC6\u94A5\u3002\u90E8\u7F72\u7531 Secret Manager \u5411\u6240\u6709\u8282\u70B9\u5206\u53D1\u540C\u4E00\u53D7\u7BA1 ring\uFF0C\u914D\u5408\u5171\u4EAB registry\u3002\u79C1\u94A5\u4E0D\u8FDB\u5165\u516C\u5F00\u914D\u7F6E\u3001PUBLIC_/VITE_ \u6216\u65E5\u5FD7\u3002
|
|
2129
|
+
|
|
1905
2130
|
\u6240\u6709\u4E1A\u52A1 JSON \u8BF7\u6C42\u548C\u54CD\u5E94\u9ED8\u8BA4\u901A\u8FC7 \`@h-ai/crypto\` transport \u53CC\u5411\u52A0\u5BC6\uFF08\u542B\u767B\u5F55\u3001\u5237\u65B0\u3001\u5934\u50CF\u4E0A\u4F20\uFF09\uFF1B\u524D\u7AEF\u7EDF\u4E00\u4F7F\u7528\u5171\u4EAB typed API client\u3002Web \u8BBF\u95EE\u4EE4\u724C\u4EC5\u5B58\u5185\u5B58\u3001\u5237\u65B0\u4EE4\u724C\u7531 HttpOnly Cookie \u4FDD\u5B58\uFF1B\u53D7\u4FE1\u539F\u751F\u7AEF\u4F7F\u7528\u5E73\u53F0\u5B89\u5168\u5B58\u50A8\u6216\u5185\u5B58\u3002\u5934\u50CF\u4E3A\u968F\u673A\u8DEF\u5F84\u53EA\u8BFB\u56FE\u7247\u8D44\u6E90\uFF0C\u652F\u6301 PNG/JPEG/WebP\uFF0C\u6700\u5927 512 KiB\u3002\u5065\u5EB7\u68C0\u67E5\u548C\u53EA\u8BFB\u5934\u50CF\u56FE\u7247\u53EF\u76F4\u63A5\u8BBF\u95EE\u3002
|
|
1906
2131
|
|
|
1907
|
-
\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\
|
|
2132
|
+
\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\u5F00\u53D1\u9ED8\u8BA4 SQLite\u3001\u672C\u5730\u5B58\u50A8\u4E0E\u5185\u5B58\u7F13\u5B58\uFF1B\u751F\u4EA7\u542F\u52A8\u62D2\u7EDD\u8FD9\u4E9B\u672C\u5730 Provider\uFF0C\u5FC5\u987B\u5C06\u6240\u6709\u8282\u70B9\u8FDE\u63A5\u5230\u540C\u4E00\u5171\u4EAB\u6570\u636E\u5E93\u3001Redis \u4E0E S3\u3002\u4F20\u8F93\u5FC5\u987B\u5728\u6240\u6709\u8282\u70B9\u6CE8\u5165\u76F8\u540C\u53D7\u7BA1\u7248\u672C\u5316\u5BC6\u94A5\u73AF\u4E0E\u5171\u4EAB registry\uFF0C\u65E0\u9700\u4F1A\u8BDD\u7C98\u6027\uFF1B\u91CD\u542F\u8BFB\u53D6\u539F ring\uFF0C\u8F6E\u6362\u5148\u5206\u53D1\u65B0\u65E7\u7248\u672C\u518D\u5207 activeKeyId\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
|
|
1908
2133
|
|
|
1909
|
-
|
|
2134
|
+
### \u914D\u7F6E\u6269\u5C55
|
|
1910
2135
|
|
|
1911
2136
|
\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
|
|
1912
2137
|
|
|
@@ -1918,7 +2143,7 @@ CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env.local\`\uFF0C\u5176\u4E2D
|
|
|
1918
2143
|
|
|
1919
2144
|
${generateEnvironmentReadme()}
|
|
1920
2145
|
|
|
1921
|
-
|
|
2146
|
+
### \u5F00\u53D1\u4E0E\u6784\u5EFA
|
|
1922
2147
|
|
|
1923
2148
|
\`\`\`bash
|
|
1924
2149
|
${pm} install
|
|
@@ -1932,22 +2157,9 @@ ${frontends.includes("desktop") ? `${pm} build --target desktop --platform windo
|
|
|
1932
2157
|
|
|
1933
2158
|
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
|
|
1934
2159
|
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
\`\`\`bash
|
|
1938
|
-
# \u9ED8\u8BA4\u53EA\u7F16\u8BD1\u6E90\u7801\uFF1B\u4E0D\u9009\u62E9\u90E8\u7F72\u73AF\u5883
|
|
1939
|
-
${pm} build
|
|
1940
|
-
# \u6784\u5EFA\u5E76\u5BFC\u51FA API/Web \u955C\u50CF\uFF0Cauto \u4F18\u5148 Podman\u3001\u5176\u6B21 Docker
|
|
1941
|
-
${pm} build --target docker
|
|
1942
|
-
# \u9009\u62E9\u73AF\u5883\u3001\u4E2D\u95F4\u4EF6\u5E76\u4E0A\u4F20\u4E00\u4E2A Compose tar\uFF1B\u4E0D\u4F1A\u91CD\u65B0\u6784\u5EFA
|
|
1943
|
-
${pm} exec hai deploy packages/${name}-serv --env test
|
|
1944
|
-
# \u65E0\u6CD5\u8FDE\u63A5\u670D\u52A1\u5668\u65F6\u53EA\u751F\u6210\u5305\uFF0C\u6309\u8F93\u51FA\u8BF4\u660E\u624B\u5DE5\u642C\u8FD0\u548C\u542F\u52A8
|
|
1945
|
-
${pm} exec hai deploy packages/${name}-serv --env prod --bundle-only
|
|
1946
|
-
\`\`\`
|
|
1947
|
-
|
|
1948
|
-
\u955C\u50CF\u6784\u5EFA\u914D\u7F6E\u89C1 \`hai.build.json\`\u3002\u6784\u5EFA\u4EA7\u7269\u4FDD\u5B58\u5728 \`.hai/build/docker/<buildId>\`\uFF0C\u53EF\u7528 \`--artifact-dir\` \u9009\u62E9\u540C\u4E00\u4EFD\u4EA7\u7269\u90E8\u7F72\u5230 test/prod\u3002\u90E8\u7F72\u5305\u542B\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u80FD\u63D0\u4EA4 Git\u3002\u73AF\u5883\u5DF2\u914D\u7F6E\u5B8C\u5907\u53EF\u52A0 \`--skip-middleware\`\u3002\u670D\u52A1\u8FD0\u884C\u6570\u636E\u7531 volume \u6301\u4E45\u5316\uFF0C\u955C\u50CF\u4E0D\u6253\u5305\u672C\u673A data\u3002API/Web \u8054\u5408\u90E8\u7F72\u516C\u5F00 Web \u5165\u53E3\u5E76\u4EE3\u7406\u540C\u6E90 API\uFF1B\u6CA1\u6709 Web \u4EA7\u7269\u65F6\u53EA\u90E8\u7F72\u670D\u52A1\u3002
|
|
2160
|
+
${generateBuildDeploymentReadme(pm, `packages/${name}-serv`)}
|
|
1949
2161
|
|
|
1950
|
-
|
|
2162
|
+
### i18n \u4E0E shared \u534F\u540C
|
|
1951
2163
|
|
|
1952
2164
|
- shared\uFF1A\`packages/${name}-shared/project.inlang\` + \`messages/\`\uFF0C\u53EA\u653E\u8DE8\u7AEF\u5171\u4EAB\u7EC4\u4EF6\u6587\u6848\u3002
|
|
1953
2165
|
- 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
|
|
@@ -1955,13 +2167,20 @@ ${pm} exec hai deploy packages/${name}-serv --env prod --bundle-only
|
|
|
1955
2167
|
- \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
|
|
1956
2168
|
- \`${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
|
|
1957
2169
|
|
|
1958
|
-
|
|
2170
|
+
### \u81EA\u5B9A\u4E49\u5EFA\u8BAE
|
|
1959
2171
|
|
|
1960
2172
|
- \u6539\u5168\u5C40 Shell/\u4E3B\u9898/\u8BED\u8A00\u5207\u6362\uFF1A\u7F16\u8F91 \`packages/${name}-shared\`\u3002
|
|
1961
|
-
- \u6539\u67D0\u4E2A\u524D\u7AEF\u9875\u9762\u548C\u6587\u6848\uFF1A\u7F16\u8F91\
|
|
2173
|
+
- \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
|
|
1962
2174
|
- \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
|
|
1963
2175
|
|
|
1964
|
-
|
|
2176
|
+
|
|
2177
|
+
${generateAiDevelopmentReadme()}### \u6587\u6863
|
|
2178
|
+
|
|
2179
|
+
- [hai Agent Framework](https://github.com/hai-series/hai-framework)
|
|
2180
|
+
- [@h-ai/serv](./.agents/skills/hai-serv/SKILL.md)
|
|
2181
|
+
- [@h-ai/ui](./.agents/skills/hai-ui/SKILL.md)
|
|
2182
|
+
|
|
2183
|
+
## \u5F00\u53D1\u6D4B\u8BD5
|
|
1965
2184
|
|
|
1966
2185
|
\`\`\`bash
|
|
1967
2186
|
${pm} typecheck
|
|
@@ -1971,19 +2190,17 @@ ${pm} test
|
|
|
1971
2190
|
${pm} test:e2e
|
|
1972
2191
|
\`\`\`
|
|
1973
2192
|
|
|
1974
|
-
|
|
2193
|
+
## \u8BB8\u53EF\u8BC1
|
|
1975
2194
|
|
|
1976
|
-
-
|
|
1977
|
-
- [@h-ai/serv](./.agents/skills/hai-serv/SKILL.md)
|
|
1978
|
-
- [@h-ai/ui](./.agents/skills/hai-ui/SKILL.md)
|
|
2195
|
+
Apache-2.0
|
|
1979
2196
|
`;
|
|
1980
2197
|
}
|
|
1981
2198
|
function printCompletionMessage(options) {
|
|
1982
2199
|
writeCliMessage("");
|
|
1983
|
-
writeCliMessage(chalk.green("
|
|
2200
|
+
writeCliMessage(chalk.green(cliM("cli_createSuccess")));
|
|
1984
2201
|
writeCliMessage("");
|
|
1985
2202
|
if (options.features.length > 0) {
|
|
1986
|
-
writeCliMessage(chalk.bold("
|
|
2203
|
+
writeCliMessage(chalk.bold(cliM("cli_createFeatures")));
|
|
1987
2204
|
for (const featureId of options.features) {
|
|
1988
2205
|
const feature = FEATURES[featureId];
|
|
1989
2206
|
if (feature) {
|
|
@@ -1992,7 +2209,7 @@ function printCompletionMessage(options) {
|
|
|
1992
2209
|
}
|
|
1993
2210
|
writeCliMessage("");
|
|
1994
2211
|
}
|
|
1995
|
-
writeCliMessage("
|
|
2212
|
+
writeCliMessage(cliM("cli_nextSteps"));
|
|
1996
2213
|
writeCliMessage(chalk.cyan(` cd ${options.name}`));
|
|
1997
2214
|
if (options.appType === "fullstack")
|
|
1998
2215
|
writeCliMessage(chalk.cyan(cliM("cli_adminPasswordSaved")));
|
|
@@ -2033,25 +2250,61 @@ async function detectProject(cwd) {
|
|
|
2033
2250
|
// src/commands/cli-add.ts
|
|
2034
2251
|
var MODULE_MAP = {
|
|
2035
2252
|
// iam 依赖 crypto(password hash)/ db(user 存储)/ cache(session 缓存)
|
|
2036
|
-
"iam": { packages: ["@h-ai/iam"], deps: ["crypto", "db", "cache"], configKey: "iam", description
|
|
2253
|
+
"iam": { packages: ["@h-ai/iam"], deps: ["crypto", "db", "cache"], configKey: "iam", get description() {
|
|
2254
|
+
return cliM("cli_featureIamName");
|
|
2255
|
+
} },
|
|
2037
2256
|
// 'db' 和 'reldb' 都映射到 @h-ai/reldb,兼容用户习惯(hai add db 和 hai add reldb 均可)
|
|
2038
|
-
"db": { packages: ["@h-ai/reldb"], configKey: "db", description
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
"
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
"
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
"
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
"
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
"
|
|
2054
|
-
|
|
2257
|
+
"db": { packages: ["@h-ai/reldb"], configKey: "db", get description() {
|
|
2258
|
+
return cliM("cli_featureDbName");
|
|
2259
|
+
} },
|
|
2260
|
+
"reldb": { packages: ["@h-ai/reldb"], configKey: "db", get description() {
|
|
2261
|
+
return cliM("cli_featureDbName");
|
|
2262
|
+
} },
|
|
2263
|
+
"cache": { packages: ["@h-ai/cache"], configKey: "cache", get description() {
|
|
2264
|
+
return cliM("cli_featureCacheName");
|
|
2265
|
+
} },
|
|
2266
|
+
"ai": { packages: ["@h-ai/ai"], deps: ["db", "vecdb"], configKey: "ai", get description() {
|
|
2267
|
+
return cliM("cli_featureAiName");
|
|
2268
|
+
} },
|
|
2269
|
+
"storage": { packages: ["@h-ai/storage"], configKey: "storage", get description() {
|
|
2270
|
+
return cliM("cli_featureStorageName");
|
|
2271
|
+
} },
|
|
2272
|
+
"crypto": { packages: ["@h-ai/crypto"], deps: ["db"], get description() {
|
|
2273
|
+
return cliM("cli_featureCryptoName");
|
|
2274
|
+
} },
|
|
2275
|
+
"kit": { packages: ["@h-ai/kit"], get description() {
|
|
2276
|
+
return cliM("cli_featureKitName");
|
|
2277
|
+
} },
|
|
2278
|
+
"ui": { packages: ["@h-ai/ui"], get description() {
|
|
2279
|
+
return cliM("cli_featureUiName");
|
|
2280
|
+
} },
|
|
2281
|
+
"deploy": { packages: ["@h-ai/deploy"], configKey: "deploy", get description() {
|
|
2282
|
+
return cliM("cli_featureDeployName");
|
|
2283
|
+
} },
|
|
2284
|
+
"vecdb": { packages: ["@h-ai/vecdb"], configKey: "vecdb", get description() {
|
|
2285
|
+
return cliM("cli_featureVecdbName");
|
|
2286
|
+
} },
|
|
2287
|
+
"datapipe": { packages: ["@h-ai/datapipe"], get description() {
|
|
2288
|
+
return cliM("cli_featureDatapipeName");
|
|
2289
|
+
} },
|
|
2290
|
+
"reach": { packages: ["@h-ai/reach"], configKey: "reach", get description() {
|
|
2291
|
+
return cliM("cli_featureReachName");
|
|
2292
|
+
} },
|
|
2293
|
+
"scheduler": { packages: ["@h-ai/scheduler"], deps: ["db"], configKey: "scheduler", get description() {
|
|
2294
|
+
return cliM("cli_featureSchedulerName");
|
|
2295
|
+
} },
|
|
2296
|
+
"audit": { packages: ["@h-ai/audit"], deps: ["db"], configKey: "audit", get description() {
|
|
2297
|
+
return cliM("cli_featureAuditName");
|
|
2298
|
+
} },
|
|
2299
|
+
"payment": { packages: ["@h-ai/payment"], configKey: "payment", get description() {
|
|
2300
|
+
return cliM("cli_featurePaymentName");
|
|
2301
|
+
} },
|
|
2302
|
+
"capacitor": { packages: ["@h-ai/capacitor"], get description() {
|
|
2303
|
+
return cliM("cli_featureCapacitorName");
|
|
2304
|
+
} },
|
|
2305
|
+
"api-client": { packages: ["@h-ai/api-client"], get description() {
|
|
2306
|
+
return cliM("cli_featureApiClientName");
|
|
2307
|
+
} }
|
|
2055
2308
|
};
|
|
2056
2309
|
async function addModule(options) {
|
|
2057
2310
|
const spinner = ora();
|
|
@@ -2059,7 +2312,7 @@ async function addModule(options) {
|
|
|
2059
2312
|
try {
|
|
2060
2313
|
const project = await detectProject(cwd);
|
|
2061
2314
|
if (!project) {
|
|
2062
|
-
|
|
2315
|
+
writeCliError(chalk.red(cliM("cli_projectNotFound")));
|
|
2063
2316
|
return;
|
|
2064
2317
|
}
|
|
2065
2318
|
let moduleName = options.module;
|
|
@@ -2070,13 +2323,14 @@ async function addModule(options) {
|
|
|
2070
2323
|
value: key
|
|
2071
2324
|
}));
|
|
2072
2325
|
if (choices.length === 0) {
|
|
2073
|
-
|
|
2326
|
+
writeCliMessage(chalk.green(cliM("cli_addAllInstalled")));
|
|
2074
2327
|
return;
|
|
2075
2328
|
}
|
|
2076
2329
|
const { selected } = await prompts({
|
|
2077
2330
|
type: "select",
|
|
2331
|
+
hint: cliM("cli_selectHint"),
|
|
2078
2332
|
name: "selected",
|
|
2079
|
-
message: "
|
|
2333
|
+
message: cliM("cli_addModulePrompt"),
|
|
2080
2334
|
choices
|
|
2081
2335
|
});
|
|
2082
2336
|
if (!selected)
|
|
@@ -2085,11 +2339,11 @@ async function addModule(options) {
|
|
|
2085
2339
|
}
|
|
2086
2340
|
const moduleDef = MODULE_MAP[moduleName];
|
|
2087
2341
|
if (!moduleDef) {
|
|
2088
|
-
|
|
2089
|
-
|
|
2342
|
+
writeCliError(chalk.red(cliM("cli_addUnknownModule", { params: { module: moduleName } })));
|
|
2343
|
+
writeCliMessage(cliM("cli_addAvailableModules", { params: { modules: Object.keys(MODULE_MAP).join(", ") } }));
|
|
2090
2344
|
return;
|
|
2091
2345
|
}
|
|
2092
|
-
const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-
|
|
2346
|
+
const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-US5CCAJ4.js');
|
|
2093
2347
|
if (moduleDef.packages.every((p) => project.haiPackages.includes(p))) {
|
|
2094
2348
|
const generatedFiles2 = await generateProjectAiSupport(
|
|
2095
2349
|
cwd,
|
|
@@ -2100,12 +2354,12 @@ async function addModule(options) {
|
|
|
2100
2354
|
}
|
|
2101
2355
|
);
|
|
2102
2356
|
if (generatedFiles2.length > 0) {
|
|
2103
|
-
|
|
2357
|
+
writeCliMessage(chalk.yellow(cliM("cli_addSupportCompleted", { params: { module: moduleName } })));
|
|
2104
2358
|
for (const generatedFile of generatedFiles2) {
|
|
2105
|
-
|
|
2359
|
+
writeCliMessage(chalk.gray(cliM("cli_addSupportFile", { params: { file: generatedFile } })));
|
|
2106
2360
|
}
|
|
2107
2361
|
} else {
|
|
2108
|
-
|
|
2362
|
+
writeCliMessage(chalk.yellow(cliM("cli_addAlreadyInstalled", { params: { module: moduleName } })));
|
|
2109
2363
|
}
|
|
2110
2364
|
return;
|
|
2111
2365
|
}
|
|
@@ -2120,7 +2374,7 @@ async function addModule(options) {
|
|
|
2120
2374
|
}
|
|
2121
2375
|
}
|
|
2122
2376
|
}
|
|
2123
|
-
spinner.start("
|
|
2377
|
+
spinner.start(cliM("cli_addUpdateManifest"));
|
|
2124
2378
|
const pkgPath = path2.join(cwd, "package.json");
|
|
2125
2379
|
const pkg = await fse.readJson(pkgPath);
|
|
2126
2380
|
if (!pkg.dependencies)
|
|
@@ -2128,7 +2382,10 @@ async function addModule(options) {
|
|
|
2128
2382
|
const existingHaiSpecifier = Object.entries(pkg.dependencies).find(([pkgName]) => pkgName.startsWith("@h-ai/"))?.[1];
|
|
2129
2383
|
const depVersion = existingHaiSpecifier === "catalog:" ? "catalog:" : getCliVersion();
|
|
2130
2384
|
for (const pkgName of allPackages) {
|
|
2131
|
-
if (
|
|
2385
|
+
if (pkgName === "@h-ai/deploy") {
|
|
2386
|
+
pkg.devDependencies ??= {};
|
|
2387
|
+
pkg.devDependencies[pkgName] ??= depVersion;
|
|
2388
|
+
} else if (!pkg.dependencies[pkgName]) {
|
|
2132
2389
|
pkg.dependencies[pkgName] = depVersion;
|
|
2133
2390
|
}
|
|
2134
2391
|
}
|
|
@@ -2138,12 +2395,12 @@ async function addModule(options) {
|
|
|
2138
2395
|
const configDir = path2.join(cwd, "config");
|
|
2139
2396
|
const configPath = path2.join(configDir, `_${moduleDef.configKey}.yml`);
|
|
2140
2397
|
if (!await fileExists(configPath)) {
|
|
2141
|
-
spinner.start(
|
|
2398
|
+
spinner.start(cliM("cli_addConfig", { params: { config: moduleDef.configKey } }));
|
|
2142
2399
|
const content = generateConfigFile(moduleDef.configKey);
|
|
2143
2400
|
await writeFile(configPath, content);
|
|
2144
2401
|
spinner.succeed();
|
|
2145
2402
|
} else {
|
|
2146
|
-
|
|
2403
|
+
writeCliMessage(chalk.gray(cliM("cli_addConfigExists", { params: { config: moduleDef.configKey } })));
|
|
2147
2404
|
}
|
|
2148
2405
|
}
|
|
2149
2406
|
if (moduleDef.deps) {
|
|
@@ -2154,7 +2411,7 @@ async function addModule(options) {
|
|
|
2154
2411
|
if (!await fileExists(depConfigPath)) {
|
|
2155
2412
|
const content = generateConfigFile(depDef.configKey);
|
|
2156
2413
|
await writeFile(depConfigPath, content);
|
|
2157
|
-
|
|
2414
|
+
writeCliMessage(chalk.gray(cliM("cli_addDependencyConfig", { params: { config: depDef.configKey } })));
|
|
2158
2415
|
}
|
|
2159
2416
|
}
|
|
2160
2417
|
}
|
|
@@ -2170,28 +2427,36 @@ async function addModule(options) {
|
|
|
2170
2427
|
);
|
|
2171
2428
|
if (generatedFiles.length > 0) {
|
|
2172
2429
|
for (const generatedFile of generatedFiles) {
|
|
2173
|
-
|
|
2430
|
+
writeCliMessage(chalk.gray(cliM("cli_addSupportFile", { params: { file: generatedFile } })));
|
|
2174
2431
|
}
|
|
2175
2432
|
}
|
|
2433
|
+
const productionPath = path2.join(cwd, ".env.prod");
|
|
2434
|
+
const previousProduction = await fileExists(productionPath) ? await fse.readFile(productionPath, "utf8") : "";
|
|
2435
|
+
const configured = new Set([...previousProduction.matchAll(/^([A-Z][A-Z0-9_]*)=/gm)].map((match) => match[1]));
|
|
2436
|
+
const previousValues = parseEnv(previousProduction);
|
|
2437
|
+
const production = generateProductionEnvironment([moduleName, ...moduleDef.deps ?? []], previousValues.HAI_DB_TYPE === "mysql" ? { db: { type: "mysql", database: previousValues.HAI_DB_DATABASE } } : void 0).split("\n").filter((line) => !/^[A-Z][A-Z0-9_]*=/.test(line) || !configured.has(line.split("=")[0]));
|
|
2438
|
+
await writeFile(productionPath, `${previousProduction.trimEnd()}
|
|
2439
|
+
|
|
2440
|
+
${production.join("\n")}`);
|
|
2176
2441
|
const doInstall = options.install !== false;
|
|
2177
2442
|
if (doInstall) {
|
|
2178
|
-
spinner.start("
|
|
2443
|
+
spinner.start(cliM("cli_addInstall"));
|
|
2179
2444
|
const { execSync: execSync2 } = await import('child_process');
|
|
2180
2445
|
const pm = await detectPackageManager(cwd);
|
|
2181
2446
|
execSync2(`${pm} install`, { cwd, stdio: "ignore" });
|
|
2182
2447
|
spinner.succeed();
|
|
2183
2448
|
}
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2449
|
+
writeCliMessage("");
|
|
2450
|
+
writeCliMessage(chalk.green(cliM("cli_addSuccess", { params: { module: moduleName } })));
|
|
2451
|
+
writeCliMessage("");
|
|
2452
|
+
writeCliMessage(cliM("cli_addPackages"));
|
|
2188
2453
|
for (const pkg2 of allPackages) {
|
|
2189
|
-
|
|
2454
|
+
writeCliMessage(chalk.cyan(` ${pkg2}`));
|
|
2190
2455
|
}
|
|
2191
|
-
|
|
2456
|
+
writeCliMessage("");
|
|
2192
2457
|
} catch (error) {
|
|
2193
2458
|
spinner.fail();
|
|
2194
|
-
|
|
2459
|
+
writeCliError(chalk.red(cliM("cli_addFailed")), { error });
|
|
2195
2460
|
throw error;
|
|
2196
2461
|
}
|
|
2197
2462
|
}
|
|
@@ -2216,8 +2481,8 @@ async function pnpm(args, cwd) {
|
|
|
2216
2481
|
const executable = join(directory, "pnpm.exe");
|
|
2217
2482
|
if (existsSync(executable))
|
|
2218
2483
|
return run(executable, args, cwd);
|
|
2219
|
-
for (const
|
|
2220
|
-
const candidate = join(directory,
|
|
2484
|
+
for (const relative2 of ["node_modules/pnpm/bin/pnpm.cjs", "pnpm.cjs"]) {
|
|
2485
|
+
const candidate = join(directory, relative2);
|
|
2221
2486
|
if (existsSync(candidate))
|
|
2222
2487
|
return run(process4.execPath, [candidate, ...args], cwd);
|
|
2223
2488
|
}
|
|
@@ -2231,7 +2496,7 @@ async function buildCommand(options) {
|
|
|
2231
2496
|
const target = options.target ?? "compile";
|
|
2232
2497
|
if (!["compile", "docker", "desktop"].includes(target))
|
|
2233
2498
|
throw new Error(cliM("cli_build_target_invalid"));
|
|
2234
|
-
if (target !== "docker" && (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)) {
|
|
2499
|
+
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)) {
|
|
2235
2500
|
throw new Error(cliM("cli_build_options_invalid"));
|
|
2236
2501
|
}
|
|
2237
2502
|
if (target === "compile") {
|
|
@@ -2256,12 +2521,20 @@ async function buildCommand(options) {
|
|
|
2256
2521
|
const bundles = platform === "windows" ? "nsis" : platform === "macos" ? "app,dmg" : "deb,appimage";
|
|
2257
2522
|
for (const directory of config.desktop)
|
|
2258
2523
|
await pnpm(["exec", "tauri", "build", "--target", rustTarget, "--bundles", bundles, "--config", JSON.stringify({ version: appVersion })], resolve(cwd, directory));
|
|
2259
|
-
|
|
2524
|
+
writeCliMessage(cliM("cli_build_native_ready", { params: { target: rustTarget } }));
|
|
2260
2525
|
return;
|
|
2261
2526
|
}
|
|
2262
|
-
if (!Array.isArray(config.images) || !config.images.length || 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) || image.containerPort !== void 0 && (!Number.isInteger(image.containerPort) || image.containerPort < 1 || image.containerPort > 65535))) {
|
|
2527
|
+
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))) {
|
|
2263
2528
|
throw new Error(cliM("cli_build_images_missing"));
|
|
2264
2529
|
}
|
|
2530
|
+
const selected = options.images?.split(",").map((name) => name.trim());
|
|
2531
|
+
if (selected && (selected.some((name) => !config.images.some((image) => image.name === name)) || new Set(selected).size !== selected.length))
|
|
2532
|
+
throw new Error(cliM("cli_build_selection_invalid"));
|
|
2533
|
+
const images = config.images.filter((image) => !selected || selected.includes(image.name));
|
|
2534
|
+
for (const image of images) {
|
|
2535
|
+
if (!existsSync(resolve(cwd, image.dockerfile)))
|
|
2536
|
+
throw new Error(cliM("cli_build_dockerfile_missing", { params: { path: image.dockerfile } }));
|
|
2537
|
+
}
|
|
2265
2538
|
const candidates = options.runtime && options.runtime !== "auto" ? [options.runtime] : ["podman", "docker"];
|
|
2266
2539
|
if (candidates.some((runtime2) => !["podman", "docker"].includes(runtime2)))
|
|
2267
2540
|
throw new Error(cliM("cli_build_runtime_missing"));
|
|
@@ -2282,21 +2555,27 @@ async function buildCommand(options) {
|
|
|
2282
2555
|
throw new Error(cliM("cli_build_mirror_invalid"));
|
|
2283
2556
|
}
|
|
2284
2557
|
const buildId = `${appVersion}-${Date.now()}-${randomBytes(3).toString("hex")}`;
|
|
2285
|
-
const artifact = { version:
|
|
2558
|
+
const artifact = { version: 3, appVersion, buildId, partial: images.length !== config.images.length, hasWeb: config.images.some((image) => image.name === "web"), images: [] };
|
|
2559
|
+
for (const image of config.images) {
|
|
2560
|
+
if (image.containerPort !== void 0)
|
|
2561
|
+
artifact[image.name === "app" ? "appContainerPort" : "webContainerPort"] = image.containerPort;
|
|
2562
|
+
}
|
|
2286
2563
|
const parent = join(cwd, ".hai/build/docker");
|
|
2287
2564
|
mkdirSync(parent, { recursive: true });
|
|
2288
2565
|
const staging = mkdtempSync(join(parent, ".building-"));
|
|
2289
2566
|
const built = [];
|
|
2290
2567
|
try {
|
|
2291
|
-
for (const
|
|
2568
|
+
for (const context of new Set(images.map((image) => resolve(cwd, image.context ?? "."))))
|
|
2569
|
+
prepareDependencyInputs(context);
|
|
2570
|
+
for (const image of images) {
|
|
2292
2571
|
if (!["app", "web"].includes(image.name))
|
|
2293
2572
|
throw new Error(cliM("cli_build_images_missing"));
|
|
2294
2573
|
const context = resolve(cwd, image.context ?? ".");
|
|
2295
2574
|
const imageRef = `localhost/hai-${basenameSafe(cwd)}-${image.name}:${buildId.toLowerCase().replaceAll("+", "_")}`;
|
|
2296
|
-
const args = ["build", "-f", resolve(
|
|
2575
|
+
const args = ["build", "-f", resolve(cwd, image.dockerfile), "-t", imageRef];
|
|
2297
2576
|
if (options.network !== void 0)
|
|
2298
2577
|
args.push("--network", options.network);
|
|
2299
|
-
const ignore = `${resolve(
|
|
2578
|
+
const ignore = `${resolve(cwd, image.dockerfile)}.dockerignore`;
|
|
2300
2579
|
if (runtime === "podman" && existsSync(ignore))
|
|
2301
2580
|
args.push("--ignorefile", ignore);
|
|
2302
2581
|
if (runtime === "podman") {
|
|
@@ -2308,8 +2587,6 @@ async function buildCommand(options) {
|
|
|
2308
2587
|
}
|
|
2309
2588
|
}
|
|
2310
2589
|
}
|
|
2311
|
-
if (image.target)
|
|
2312
|
-
args.push("--target", image.target);
|
|
2313
2590
|
if (options.sqlitePrebuildMirror)
|
|
2314
2591
|
args.push("--build-arg", `SQLITE_PREBUILD_MIRROR=${options.sqlitePrebuildMirror}`);
|
|
2315
2592
|
built.push(imageRef);
|
|
@@ -2328,7 +2605,7 @@ async function buildCommand(options) {
|
|
|
2328
2605
|
writeFileSync(join(parent, `latest-${buildId}`), `${buildId}
|
|
2329
2606
|
`);
|
|
2330
2607
|
renameSync(join(parent, `latest-${buildId}`), join(parent, "latest"));
|
|
2331
|
-
|
|
2608
|
+
writeCliMessage(cliM("cli_build_ready", { params: { path: output, runtime } }));
|
|
2332
2609
|
} catch (error) {
|
|
2333
2610
|
for (const imageRef of built) {
|
|
2334
2611
|
try {
|
|
@@ -2341,41 +2618,172 @@ async function buildCommand(options) {
|
|
|
2341
2618
|
rmSync(staging, { recursive: true, force: true });
|
|
2342
2619
|
}
|
|
2343
2620
|
}
|
|
2344
|
-
function
|
|
2345
|
-
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
const
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
url.username = "***";
|
|
2368
|
-
url.password = "***";
|
|
2369
|
-
}
|
|
2370
|
-
for (const key of url.searchParams.keys()) {
|
|
2371
|
-
if (/password|secret|token|key|authorization/i.test(key))
|
|
2372
|
-
url.searchParams.set(key, "***");
|
|
2621
|
+
function prepareDependencyInputs(cwd) {
|
|
2622
|
+
const destination = join(cwd, "images", "dependencies");
|
|
2623
|
+
mkdirSync(destination, { recursive: true });
|
|
2624
|
+
const tracking = join(destination, ".managed.json");
|
|
2625
|
+
const previous = existsSync(tracking) ? JSON.parse(readFileSync(tracking, "utf8")) : [];
|
|
2626
|
+
const files = [];
|
|
2627
|
+
const walk = (directory) => {
|
|
2628
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
2629
|
+
const source = join(directory, entry.name);
|
|
2630
|
+
if (source === destination || ["node_modules", ".git", ".hai", ".turbo", ".svelte-kit", ".pnpm-store", ".cache", "dist", "build", "data", "tmp", "test-results", "playwright-report"].includes(entry.name))
|
|
2631
|
+
continue;
|
|
2632
|
+
if (entry.isDirectory()) {
|
|
2633
|
+
walk(source);
|
|
2634
|
+
continue;
|
|
2635
|
+
}
|
|
2636
|
+
if (!entry.isFile() || !(entry.name === "package.json" || entry.name === ".pnpmfile.cjs" || /\.(?:tgz|patch)$/.test(entry.name)))
|
|
2637
|
+
continue;
|
|
2638
|
+
const file = relative(cwd, source);
|
|
2639
|
+
mkdirSync(dirname(join(destination, file)), { recursive: true });
|
|
2640
|
+
const target = join(destination, file);
|
|
2641
|
+
if (!existsSync(target) || !readFileSync(source).equals(readFileSync(target)))
|
|
2642
|
+
copyFileSync(source, target);
|
|
2643
|
+
files.push(file);
|
|
2373
2644
|
}
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2645
|
+
};
|
|
2646
|
+
walk(cwd);
|
|
2647
|
+
for (const file of previous) {
|
|
2648
|
+
const target = resolve(destination, file);
|
|
2649
|
+
if (!files.includes(file) && !relative(destination, target).startsWith(".."))
|
|
2650
|
+
rmSync(target, { force: true });
|
|
2377
2651
|
}
|
|
2652
|
+
const content = `${JSON.stringify(files)}
|
|
2653
|
+
`;
|
|
2654
|
+
if (!existsSync(tracking) || readFileSync(tracking, "utf8") !== content)
|
|
2655
|
+
writeFileSync(tracking, content);
|
|
2656
|
+
}
|
|
2657
|
+
function basenameSafe(directory) {
|
|
2658
|
+
return directory.slice(dirname(directory).length + 1).toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+/, "") || "app";
|
|
2378
2659
|
}
|
|
2660
|
+
|
|
2661
|
+
// ../reldb/dist/chunk-SDS2ZT3V.js
|
|
2662
|
+
external_exports.enum(["sqlite", "postgresql", "mysql"]);
|
|
2663
|
+
var ReldbOperationLogConfigSchema = external_exports.object({
|
|
2664
|
+
read: external_exports.boolean().default(false),
|
|
2665
|
+
write: external_exports.boolean().default(false),
|
|
2666
|
+
/** 安全数量摘要的最大字符数;0 表示省略摘要,永不记录 SQL 原文或参数值。 */
|
|
2667
|
+
maxLength: external_exports.number().int().min(0).default(1e3),
|
|
2668
|
+
level: external_exports.enum(["info", "debug", "trace"]).default("debug")
|
|
2669
|
+
});
|
|
2670
|
+
var OperationLogConfigFieldSchema = {
|
|
2671
|
+
operationLog: ReldbOperationLogConfigSchema.optional()
|
|
2672
|
+
};
|
|
2673
|
+
var PoolConfigSchema = external_exports.object({
|
|
2674
|
+
/** PostgreSQL 已建立连接的最小保留数(默认 1,不主动预建) */
|
|
2675
|
+
min: external_exports.number().int().min(0).default(1),
|
|
2676
|
+
/** 最大连接数(默认 10) */
|
|
2677
|
+
max: external_exports.number().int().min(1).default(10),
|
|
2678
|
+
/** PostgreSQL 空闲连接超时,毫秒(默认 30000;0 禁用) */
|
|
2679
|
+
idleTimeout: external_exports.number().int().min(0).default(3e4),
|
|
2680
|
+
/** 获取连接超时,毫秒(默认10000);PostgreSQL >=0,0禁用;MySQL排队+建连总期限为1–2147483647 */
|
|
2681
|
+
acquireTimeout: external_exports.number().int().min(0).default(1e4)
|
|
2682
|
+
});
|
|
2683
|
+
var MysqlPoolConfigSchema = external_exports.object({
|
|
2684
|
+
/** 最大连接数(默认 10) */
|
|
2685
|
+
max: external_exports.number().int().min(1).default(10),
|
|
2686
|
+
/** 获取连接超时,毫秒(默认10000);PostgreSQL >=0,0禁用;MySQL排队+建连总期限为1–2147483647 */
|
|
2687
|
+
acquireTimeout: external_exports.number().int().min(1).max(2147483647).default(1e4),
|
|
2688
|
+
/** MySQL 等待队列最大请求数(默认 1000;正整数,不支持无限队列) */
|
|
2689
|
+
queueLimit: external_exports.number().int().min(1).default(1e3)
|
|
2690
|
+
}).strict();
|
|
2691
|
+
var SslConfigSchema = external_exports.union([
|
|
2692
|
+
external_exports.boolean(),
|
|
2693
|
+
external_exports.enum(["require", "prefer", "allow", "disable"]),
|
|
2694
|
+
external_exports.record(external_exports.string(), external_exports.unknown())
|
|
2695
|
+
]);
|
|
2696
|
+
var MysqlSslConfigSchema = external_exports.union([
|
|
2697
|
+
external_exports.literal(false),
|
|
2698
|
+
external_exports.object({
|
|
2699
|
+
/** MySQL 是否校验证书链(默认 true);false 明确放弃服务端认证 */
|
|
2700
|
+
rejectUnauthorized: external_exports.boolean().default(true),
|
|
2701
|
+
/** MySQL 是否验证 DNS 主机身份(默认 true);仅证书链验证开启时有效 */
|
|
2702
|
+
verifyIdentity: external_exports.boolean().default(true),
|
|
2703
|
+
/** MySQL 信任的 CA,PEM 内容;省略时使用 Node 信任库 */
|
|
2704
|
+
ca: external_exports.string().min(1).optional(),
|
|
2705
|
+
/** MySQL 客户端证书,PEM 内容;必须同时提供 key */
|
|
2706
|
+
cert: external_exports.string().min(1).optional(),
|
|
2707
|
+
/** MySQL 客户端私钥,PEM 内容;必须同时提供 cert */
|
|
2708
|
+
key: external_exports.string().min(1).optional()
|
|
2709
|
+
}).strict().refine((ssl) => Boolean(ssl.cert) === Boolean(ssl.key), { path: ["key"] })
|
|
2710
|
+
]);
|
|
2711
|
+
var SqliteOptionsSchema = external_exports.object({
|
|
2712
|
+
/** 是否启用 WAL 模式(默认 true),可提高并发读写性能 */
|
|
2713
|
+
walMode: external_exports.boolean().default(true),
|
|
2714
|
+
/** 是否只读模式(默认 false) */
|
|
2715
|
+
readonly: external_exports.boolean().default(false),
|
|
2716
|
+
/**
|
|
2717
|
+
* 执行方式(默认 sync)。
|
|
2718
|
+
*
|
|
2719
|
+
* - `sync`:主线程直接执行 better-sqlite3(同步阻塞主线程事件循环)。
|
|
2720
|
+
* - `worker`:在专用 worker 线程持有唯一连接执行,把阻塞移出主线程。
|
|
2721
|
+
* 注意 WAL 只改善数据库锁并发,无法避免 better-sqlite3 同步调用占用调用线程。
|
|
2722
|
+
*/
|
|
2723
|
+
executor: external_exports.enum(["sync", "worker"]).default("sync"),
|
|
2724
|
+
/** worker 执行方式下等待连接打开的超时(毫秒,默认 10000) */
|
|
2725
|
+
workerConnectTimeoutMs: external_exports.number().int().positive().default(1e4)
|
|
2726
|
+
});
|
|
2727
|
+
var MysqlOptionsSchema = external_exports.object({
|
|
2728
|
+
/** 字符集(默认 utf8mb4),推荐使用 utf8mb4 支持完整 Unicode */
|
|
2729
|
+
charset: external_exports.string().default("utf8mb4")
|
|
2730
|
+
}).strict();
|
|
2731
|
+
var NetworkConfigSchema = external_exports.object({
|
|
2732
|
+
/**
|
|
2733
|
+
* 连接字符串(可选)
|
|
2734
|
+
* 如果提供 url,将优先使用,忽略 host/port 等字段
|
|
2735
|
+
*/
|
|
2736
|
+
url: external_exports.string().optional(),
|
|
2737
|
+
/** 数据库主机地址(默认 localhost) */
|
|
2738
|
+
host: external_exports.string().default("localhost"),
|
|
2739
|
+
/**
|
|
2740
|
+
* 数据库端口
|
|
2741
|
+
* - PostgreSQL 默认 5432
|
|
2742
|
+
* - MySQL 默认 3306
|
|
2743
|
+
*/
|
|
2744
|
+
port: external_exports.number().int().min(1).max(65535).optional(),
|
|
2745
|
+
/** 数据库名称 */
|
|
2746
|
+
database: external_exports.string(),
|
|
2747
|
+
/** 数据库用户名 */
|
|
2748
|
+
user: external_exports.string().optional(),
|
|
2749
|
+
/** 数据库密码 */
|
|
2750
|
+
password: external_exports.string().optional(),
|
|
2751
|
+
/** SSL/TLS 连接配置 */
|
|
2752
|
+
ssl: SslConfigSchema.optional(),
|
|
2753
|
+
/** 连接池配置 */
|
|
2754
|
+
pool: PoolConfigSchema.optional()
|
|
2755
|
+
});
|
|
2756
|
+
var SqliteConfigSchema = external_exports.object({
|
|
2757
|
+
type: external_exports.literal("sqlite"),
|
|
2758
|
+
/** 数据库文件路径(如 './data.db')或 ':memory:'(内存数据库) */
|
|
2759
|
+
database: external_exports.string(),
|
|
2760
|
+
/** SQLite 特定选项 */
|
|
2761
|
+
sqlite: SqliteOptionsSchema.optional(),
|
|
2762
|
+
...OperationLogConfigFieldSchema
|
|
2763
|
+
});
|
|
2764
|
+
var PostgresqlConfigSchema = NetworkConfigSchema.extend({
|
|
2765
|
+
type: external_exports.literal("postgresql"),
|
|
2766
|
+
...OperationLogConfigFieldSchema
|
|
2767
|
+
});
|
|
2768
|
+
var MysqlConfigSchema = NetworkConfigSchema.extend({
|
|
2769
|
+
type: external_exports.literal("mysql"),
|
|
2770
|
+
/** 网络数据库URL;MySQL空串表示使用分字段,非空仅mysql://且拒绝query/fragment,优先于host/port/database/user/password */
|
|
2771
|
+
url: external_exports.union([external_exports.literal("").transform(() => void 0), external_exports.url({ protocol: /^mysql$/ }).refine((value) => !value.includes("?") && !value.includes("#"))]).optional(),
|
|
2772
|
+
/** 数据库:SQLite文件路径或:memory:;PostgreSQL必填名称;MySQL无URL时必填,URL模式从URL读取 */
|
|
2773
|
+
database: external_exports.string().min(1).optional(),
|
|
2774
|
+
/** TLS:PostgreSQL使用其模式;MySQL仅false或严格对象,空对象默认校验证书链和DNS身份 */
|
|
2775
|
+
ssl: MysqlSslConfigSchema.optional(),
|
|
2776
|
+
/** 连接池配置:PostgreSQL支持min/idleTimeout;MySQL仅max/acquireTimeout/queueLimit,拒绝其他字段 */
|
|
2777
|
+
pool: MysqlPoolConfigSchema.optional(),
|
|
2778
|
+
/** MySQL 特定选项 */
|
|
2779
|
+
mysql: MysqlOptionsSchema.optional(),
|
|
2780
|
+
...OperationLogConfigFieldSchema
|
|
2781
|
+
}).refine((config) => Boolean(config.url) || Boolean(config.database), { path: ["database"] });
|
|
2782
|
+
var ReldbConfigSchema = external_exports.discriminatedUnion("type", [
|
|
2783
|
+
SqliteConfigSchema,
|
|
2784
|
+
PostgresqlConfigSchema,
|
|
2785
|
+
MysqlConfigSchema
|
|
2786
|
+
]);
|
|
2379
2787
|
var CLOUD_FIELDS = {
|
|
2380
2788
|
"neon": ["apiKey"],
|
|
2381
2789
|
"upstash": ["email", "apiKey"],
|
|
@@ -2421,7 +2829,7 @@ async function ask(question) {
|
|
|
2421
2829
|
throw new Error(cliM("cli_deploy_cancelled"));
|
|
2422
2830
|
return answer.value;
|
|
2423
2831
|
}
|
|
2424
|
-
function validateField(value, field) {
|
|
2832
|
+
function validateField(value, field, backend) {
|
|
2425
2833
|
if (value === "")
|
|
2426
2834
|
return field.required ? cliM("cli_deploy_required") : true;
|
|
2427
2835
|
if (field.kind === "port")
|
|
@@ -2439,8 +2847,8 @@ function validateField(value, field) {
|
|
|
2439
2847
|
}
|
|
2440
2848
|
if (field.name === "ssl") {
|
|
2441
2849
|
const parsed2 = parseDocument(value);
|
|
2442
|
-
const
|
|
2443
|
-
return
|
|
2850
|
+
const schema = backend === "mysql" ? MysqlSslConfigSchema : SslConfigSchema;
|
|
2851
|
+
return parsed2.errors.length === 0 && schema.safeParse(parsed2.toJS()).success || cliM("cli_deploy_invalid_value");
|
|
2444
2852
|
}
|
|
2445
2853
|
if (field.name === "headers") {
|
|
2446
2854
|
try {
|
|
@@ -2459,21 +2867,21 @@ function ensureConfigNode(appDir, requirement, backend) {
|
|
|
2459
2867
|
return;
|
|
2460
2868
|
const directory = join(appDir, "config");
|
|
2461
2869
|
mkdirSync(directory, { recursive: true });
|
|
2462
|
-
const
|
|
2463
|
-
const document = parseDocument(existsSync(
|
|
2870
|
+
const path9 = join(directory, `_${requirement.configName}.yml`);
|
|
2871
|
+
const document = parseDocument(existsSync(path9) ? readFileSync(path9, "utf8") : "{}\n");
|
|
2464
2872
|
if (document.errors.length)
|
|
2465
2873
|
throw new Error(cliM("cli_deploy_invalid_value"));
|
|
2466
2874
|
if (requirement.configPath.length && !document.hasIn(requirement.configPath))
|
|
2467
|
-
document.setIn(requirement.configPath, requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service, type: backend } : {});
|
|
2875
|
+
document.setIn(requirement.configPath, document.createNode(requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service, type: backend } : {}));
|
|
2468
2876
|
if (requirement.service === "ldap") {
|
|
2469
2877
|
if (!document.hasIn(["login"]))
|
|
2470
|
-
document.
|
|
2878
|
+
document.set("login", document.createNode({}));
|
|
2471
2879
|
if (!document.hasIn(["login", "ldap"]))
|
|
2472
2880
|
document.setIn(["login", "ldap"], `\${HAI_IAM_LOGIN_LDAP:false}`);
|
|
2473
2881
|
}
|
|
2474
2882
|
const content = document.toString();
|
|
2475
|
-
if (!existsSync(
|
|
2476
|
-
writeFileSync(
|
|
2883
|
+
if (!existsSync(path9) || readFileSync(path9, "utf8") !== content)
|
|
2884
|
+
writeFileSync(path9, content);
|
|
2477
2885
|
}
|
|
2478
2886
|
function connectionEnv(requirement, backend, config) {
|
|
2479
2887
|
if (requirement.service === "ldap")
|
|
@@ -2490,7 +2898,7 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
|
|
|
2490
2898
|
const current = choices.find((item) => item.backend === requirement.backend);
|
|
2491
2899
|
if (!current)
|
|
2492
2900
|
throw new Error(cliM("cli_deploy_cloud_unsupported", { params: { backend: requirement.backend } }));
|
|
2493
|
-
|
|
2901
|
+
writeCliMessage(cliM("cli_deploy_middleware", { params: { service: requirement.service, backend: current.label, purpose: module.describeMiddlewareBackend(current), source: requirement.source } }));
|
|
2494
2902
|
const canKeep = canKeepMiddleware(requirement, current, environment);
|
|
2495
2903
|
const mode = yes ? canKeep ? "keep" : "default" : await ask({
|
|
2496
2904
|
type: "select",
|
|
@@ -2529,8 +2937,11 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
|
|
|
2529
2937
|
if (deployment.errors.length)
|
|
2530
2938
|
throw new Error(cliM("cli_deploy_invalid_value"));
|
|
2531
2939
|
if (!deployment.hasIn(["container", "s3Engine"])) {
|
|
2532
|
-
|
|
2533
|
-
|
|
2940
|
+
const node = deployment.get("container", true);
|
|
2941
|
+
if (node === void 0 || isScalar(node) && node.value === null)
|
|
2942
|
+
deployment.set("container", deployment.createNode({}));
|
|
2943
|
+
else if (!isMap(node))
|
|
2944
|
+
throw new Error(cliM("cli_deploy_yaml_node", { params: { path: deploymentFile, field: "container" } }));
|
|
2534
2945
|
deployment.setIn(["container", "s3Engine"], `\${HAI_DEPLOY_CONTAINER_S3ENGINE:rustfs}`);
|
|
2535
2946
|
writeFileSync(deploymentFile, deployment.toString());
|
|
2536
2947
|
}
|
|
@@ -2544,15 +2955,16 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
|
|
|
2544
2955
|
for (const name of CLOUD_FIELDS[backend.provisioner]) {
|
|
2545
2956
|
const key = `HAI_DEPLOY_SERVICES_${requirement.service.toUpperCase()}_${name.toUpperCase()}`;
|
|
2546
2957
|
const initial = String(priorConfig[name] ?? environment.values[key] ?? "");
|
|
2547
|
-
const
|
|
2958
|
+
const label = `${backend.label} \u2014 ${cliM(`cli_deploy_cloud_input_${name}`)}`;
|
|
2959
|
+
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") }));
|
|
2548
2960
|
if (!value)
|
|
2549
|
-
throw new Error(`${
|
|
2961
|
+
throw new Error(`${label}: ${cliM("cli_deploy_required")}`);
|
|
2550
2962
|
cloud[name] = value;
|
|
2551
2963
|
updates[key] = value;
|
|
2552
2964
|
}
|
|
2553
2965
|
services[requirement.service] = cloud;
|
|
2554
2966
|
} else {
|
|
2555
|
-
if (mode === "default" &&
|
|
2967
|
+
if (mode === "default" && ["aliyun-sms", "api"].includes(backendName))
|
|
2556
2968
|
throw new Error(cliM("cli_deploy_cloud_unsupported", { params: { backend: backend.label } }));
|
|
2557
2969
|
const connection = mode === "custom" && backendName === current.backend ? { ...requirement.config } : requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service } : {};
|
|
2558
2970
|
for (const field of backend.fields) {
|
|
@@ -2560,22 +2972,27 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
|
|
|
2560
2972
|
const initial = previous2 === void 0 ? field.defaultValue ?? "" : typeof previous2 === "object" ? JSON.stringify(previous2) : String(previous2);
|
|
2561
2973
|
const variable = `${requirement.envPrefix}_${field.name.toUpperCase()}`;
|
|
2562
2974
|
const info = moduleEnvironmentVariables(requirement.configName).find((item) => item.name === variable);
|
|
2563
|
-
const value = mode === "custom" && !yes ? String(await ask({ type: field.secret ? "password" : "text", name: "value", message: cliM("cli_deploy_field", { params: {
|
|
2975
|
+
const value = mode === "custom" && !yes ? String(await ask({ type: field.secret ? "password" : "text", name: "value", message: cliM("cli_deploy_field", { params: { description: info?.description ?? field.name, constraint: info?.constraint ?? field.kind ?? "string" } }), initial, validate: (input) => validateField(String(input), field, backendName) })) : initial;
|
|
2976
|
+
if (field.name === "ssl" && validateField(value, field, backendName) !== true)
|
|
2977
|
+
throw new Error(cliM("cli_deploy_invalid_value"));
|
|
2564
2978
|
if (value !== "") {
|
|
2565
2979
|
connection[field.name] = field.kind === "number" || field.kind === "port" ? Number(value) : field.kind === "boolean" ? value === "true" : field.name === "headers" || field.name === "ssl" ? parseDocument(value).toJS() : value;
|
|
2566
2980
|
} else {
|
|
2567
2981
|
delete connection[field.name];
|
|
2568
2982
|
}
|
|
2569
2983
|
}
|
|
2984
|
+
if (requirement.service === "db") {
|
|
2985
|
+
const validated = ReldbConfigSchema.safeParse({ ...connection, type: backendName });
|
|
2986
|
+
if (!validated.success)
|
|
2987
|
+
throw new Error(cliM("cli_deploy_config_invalid", { params: { path: join(scan.appDir ?? ".", "config", "_db.yml"), fields: validated.error.issues.map((issue) => issue.path.join(".")).join(", ") } }));
|
|
2988
|
+
}
|
|
2570
2989
|
Object.assign(updates, connectionEnv(requirement, backendName, connection));
|
|
2571
2990
|
}
|
|
2572
2991
|
ensureConfigNode(scan.appDir ?? ".", requirement, backendName);
|
|
2573
2992
|
const saved = module.saveDeploymentEnvironment(environment, updates);
|
|
2574
2993
|
if (!saved.success)
|
|
2575
2994
|
throw new Error(saved.error.message);
|
|
2576
|
-
|
|
2577
|
-
core.logger.info(cliM("cli_deploy_saved", { params: { name, value: displayDeployValue(name, value) } }));
|
|
2578
|
-
}
|
|
2995
|
+
writeCliMessage(cliM("cli_deploy_saved", { params: { service: requirement.service } }));
|
|
2579
2996
|
}
|
|
2580
2997
|
config.services = Object.keys(services).length ? services : void 0;
|
|
2581
2998
|
return modes;
|
|
@@ -2591,14 +3008,19 @@ async function configureDeployTarget(module, config, environment, nonInteractive
|
|
|
2591
3008
|
if (existing.type && existing.type !== "docker-ssh")
|
|
2592
3009
|
throw new Error(cliM("cli_deploy_bundle_provider"));
|
|
2593
3010
|
config.provider = { type: "docker-ssh", remote: { runtime: "auto" }, expose: { hostPort: 3e3 }, ...existing };
|
|
3011
|
+
const candidate = module.ProviderConfigSchema.safeParse(config.provider);
|
|
3012
|
+
if (!candidate.success && candidate.error.issues.some((issue) => issue.path[0] === "ssh")) {
|
|
3013
|
+
const provider2 = configObject(config.provider);
|
|
3014
|
+
delete provider2.ssh;
|
|
3015
|
+
}
|
|
2594
3016
|
}
|
|
2595
3017
|
const current = module.ProviderConfigSchema.safeParse(config.provider);
|
|
2596
3018
|
if (current.success && (bundleOnly || current.data.type !== "docker-ssh" || current.data.ssh)) {
|
|
2597
3019
|
config.provider = current.data;
|
|
2598
|
-
|
|
3020
|
+
writeCliMessage(bundleOnly ? cliM("cli_deploy_bundle_target") : 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}` : "Vercel" } }));
|
|
2599
3021
|
return;
|
|
2600
3022
|
}
|
|
2601
|
-
if (nonInteractive)
|
|
3023
|
+
if (nonInteractive || bundleOnly)
|
|
2602
3024
|
throw new Error(cliM("cli_deploy_target_missing", { params: { fields: current.success ? "provider.ssh" : current.error.issues.map((issue) => `provider.${issue.path.join(".")}`).join(", ") } }));
|
|
2603
3025
|
const original = configObject(config.provider);
|
|
2604
3026
|
const platform = await prompts({
|
|
@@ -2614,9 +3036,9 @@ async function configureDeployTarget(module, config, environment, nonInteractive
|
|
|
2614
3036
|
const answer = await prompts({
|
|
2615
3037
|
type: options.secret ? "password" : "text",
|
|
2616
3038
|
name: "value",
|
|
2617
|
-
message: cliM("
|
|
3039
|
+
message: cliM(`cli_deploy_input${name.split("_").at(-1)}`),
|
|
2618
3040
|
initial: options.secret ? void 0 : initial === void 0 ? "" : String(initial),
|
|
2619
|
-
validate: (value) => options.port ? /^\d+$/.test(value) && Number(value) >= 1 && Number(value) <= 65535 || cliM("
|
|
3041
|
+
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")
|
|
2620
3042
|
});
|
|
2621
3043
|
if (answer.value === void 0)
|
|
2622
3044
|
throw new Error(cliM("cli_deploy_cancelled"));
|
|
@@ -2624,7 +3046,7 @@ async function configureDeployTarget(module, config, environment, nonInteractive
|
|
|
2624
3046
|
};
|
|
2625
3047
|
let provider;
|
|
2626
3048
|
if (platform.value === "docker-ssh") {
|
|
2627
|
-
|
|
3049
|
+
writeCliMessage(cliM("cli_deploy_target_ssh_hint"));
|
|
2628
3050
|
const ssh = configObject(original.ssh);
|
|
2629
3051
|
const remote = configObject(original.remote);
|
|
2630
3052
|
const expose = configObject(original.expose);
|
|
@@ -2655,15 +3077,27 @@ async function configureDeployTarget(module, config, environment, nonInteractive
|
|
|
2655
3077
|
if (!saved.success)
|
|
2656
3078
|
throw new Error(saved.error.message);
|
|
2657
3079
|
config.provider = validated.data;
|
|
2658
|
-
|
|
3080
|
+
writeCliMessage(cliM("cli_deploy_target_saved"));
|
|
2659
3081
|
}
|
|
2660
3082
|
async function deployCommand(options) {
|
|
2661
3083
|
const spinner = ora();
|
|
2662
3084
|
const cwd = options.cwd ?? process4.cwd();
|
|
2663
3085
|
let appDir = resolve(cwd, options.appDir ?? ".");
|
|
2664
3086
|
let closeDeploy = null;
|
|
3087
|
+
let restoreProjectLogger;
|
|
2665
3088
|
const originalEnv = { ...process4.env };
|
|
3089
|
+
const previousLogLevel = core.logger.getLevel();
|
|
3090
|
+
core.logger.setLevel("fatal");
|
|
2666
3091
|
try {
|
|
3092
|
+
if (options.runtime && !["auto", "podman", "docker"].includes(options.runtime))
|
|
3093
|
+
throw new Error(cliM("cli_build_runtime_missing"));
|
|
3094
|
+
if (options.artifactDir && options.build)
|
|
3095
|
+
throw new Error(cliM("cli_deploy_build_artifact_conflict"));
|
|
3096
|
+
if (!options.build && (options.network !== void 0 || options.sqlitePrebuildMirror !== void 0 || options.httpProxy === false))
|
|
3097
|
+
throw new Error(cliM("cli_deploy_build_options_required"));
|
|
3098
|
+
let selectedServices = options.services?.split(",").map((name) => name.trim());
|
|
3099
|
+
if (selectedServices && (!selectedServices.length || selectedServices.some((name) => !/^[a-z][a-z0-9-]*$/.test(name)) || new Set(selectedServices).size !== selectedServices.length))
|
|
3100
|
+
throw new Error(cliM("cli_build_selection_invalid"));
|
|
2667
3101
|
let environmentName = options.env;
|
|
2668
3102
|
if (!environmentName) {
|
|
2669
3103
|
if (!process4.stdin.isTTY)
|
|
@@ -2678,15 +3112,24 @@ async function deployCommand(options) {
|
|
|
2678
3112
|
let deployModule;
|
|
2679
3113
|
try {
|
|
2680
3114
|
const localRequire = createRequire(resolve(appDir, "package.json"));
|
|
3115
|
+
try {
|
|
3116
|
+
const { core: projectCore } = await import(pathToFileURL(localRequire.resolve("@h-ai/core")).href);
|
|
3117
|
+
if (projectCore !== core) {
|
|
3118
|
+
const level = projectCore.logger.getLevel();
|
|
3119
|
+
projectCore.logger.setLevel("fatal");
|
|
3120
|
+
restoreProjectLogger = () => projectCore.logger.setLevel(level);
|
|
3121
|
+
}
|
|
3122
|
+
} catch {
|
|
3123
|
+
}
|
|
2681
3124
|
let localModule;
|
|
2682
3125
|
try {
|
|
2683
3126
|
localModule = localRequire.resolve("@h-ai/deploy");
|
|
2684
3127
|
} catch {
|
|
2685
3128
|
}
|
|
2686
|
-
deployModule = localModule ? await import(pathToFileURL(localModule).href) : await import('./src-
|
|
3129
|
+
deployModule = localModule ? await import(pathToFileURL(localModule).href) : await import('./src-DSJJXM2F.js');
|
|
2687
3130
|
} catch {
|
|
2688
|
-
|
|
2689
|
-
|
|
3131
|
+
writeCliError(chalk.red(cliM("cli_deploy_module_missing")));
|
|
3132
|
+
writeCliMessage(chalk.cyan(" pnpm add -D @h-ai/deploy"));
|
|
2690
3133
|
throw new Error(cliM("cli_deploy_module_missing"));
|
|
2691
3134
|
}
|
|
2692
3135
|
const { deploy } = deployModule;
|
|
@@ -2695,6 +3138,15 @@ async function deployCommand(options) {
|
|
|
2695
3138
|
throw new Error(cliM("cli_deploy_scan_failed", { params: { message: scanResult.error.message } }));
|
|
2696
3139
|
const scan = scanResult.data;
|
|
2697
3140
|
appDir = scan.appDir ?? appDir;
|
|
3141
|
+
let buildRoot = appDir;
|
|
3142
|
+
while (!existsSync(resolve(buildRoot, "hai.build.json")) && dirname(buildRoot) !== buildRoot)
|
|
3143
|
+
buildRoot = dirname(buildRoot);
|
|
3144
|
+
const buildFile = resolve(buildRoot, "hai.build.json");
|
|
3145
|
+
const buildConfig = existsSync(buildFile) ? JSON.parse(readFileSync(buildFile, "utf8")) : {};
|
|
3146
|
+
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)];
|
|
3147
|
+
const available = [...new Set(names)];
|
|
3148
|
+
if (selectedServices?.some((name) => !available.includes(name)))
|
|
3149
|
+
throw new Error(cliM("cli_deploy_services_invalid", { params: { services: available.join(", ") } }));
|
|
2698
3150
|
const environment = deployModule.loadDeploymentEnvironment({ environment: environmentName, envDir: scan.envDir ?? appDir });
|
|
2699
3151
|
if (!environment.success)
|
|
2700
3152
|
throw new Error(environment.error.message);
|
|
@@ -2709,7 +3161,7 @@ async function deployCommand(options) {
|
|
|
2709
3161
|
const configPath = resolve(appDir, "config", "_deploy.yml");
|
|
2710
3162
|
if (!existsSync(configPath)) {
|
|
2711
3163
|
spinner.fail(chalk.red(cliM("cli_deploy_config_missing", { params: { path: configPath } })));
|
|
2712
|
-
|
|
3164
|
+
writeCliMessage(chalk.cyan(cliM("cli_deploy_config_hint")));
|
|
2713
3165
|
throw new Error(cliM("cli_deploy_config_missing", { params: { path: configPath } }));
|
|
2714
3166
|
}
|
|
2715
3167
|
const loadedConfig = deployModule.loadDeploymentConfig("deploy", configPath, process4.env);
|
|
@@ -2717,9 +3169,51 @@ async function deployCommand(options) {
|
|
|
2717
3169
|
throw new Error(loadedConfig.error.message);
|
|
2718
3170
|
const deployConfig = loadedConfig.data;
|
|
2719
3171
|
spinner.succeed(cliM("cli_deploy_config_loaded"));
|
|
3172
|
+
if (!options.build && !selectedServices && configObject(deployConfig.provider).type === "docker-ssh") {
|
|
3173
|
+
let artifactDirectory = options.artifactDir ? resolve(cwd, options.artifactDir) : resolve(buildRoot, ".hai/build/docker");
|
|
3174
|
+
if (existsSync(resolve(artifactDirectory, "latest"))) {
|
|
3175
|
+
const pointer = readFileSync(resolve(artifactDirectory, "latest"), "utf8").trim();
|
|
3176
|
+
if (!/^[a-z0-9][\w.+-]*$/i.test(pointer))
|
|
3177
|
+
throw new Error(cliM("cli_deploy_artifact_invalid"));
|
|
3178
|
+
artifactDirectory = resolve(artifactDirectory, pointer);
|
|
3179
|
+
}
|
|
3180
|
+
if (existsSync(resolve(artifactDirectory, "manifest.json"))) {
|
|
3181
|
+
try {
|
|
3182
|
+
const artifact = await deployModule.readDeploymentArtifact(artifactDirectory);
|
|
3183
|
+
if (artifact.partial)
|
|
3184
|
+
selectedServices = artifact.images.map((image) => image.name);
|
|
3185
|
+
} catch {
|
|
3186
|
+
throw new Error(cliM("cli_deploy_artifact_invalid"));
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
2720
3190
|
await configureDeployTarget(deployModule, deployConfig, environment.data, options.yes, options.bundleOnly);
|
|
3191
|
+
if (configObject(deployConfig.provider).type !== "docker-ssh" && (selectedServices || options.build))
|
|
3192
|
+
throw new Error(cliM("cli_deploy_bundle_provider"));
|
|
3193
|
+
if (!options.yes && !selectedServices && configObject(deployConfig.provider).type === "docker-ssh") {
|
|
3194
|
+
if (existsSync(buildFile)) {
|
|
3195
|
+
if (available.length > 1) {
|
|
3196
|
+
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 });
|
|
3197
|
+
if (!Array.isArray(choice.value) || choice.value.length === 0)
|
|
3198
|
+
throw new Error(cliM("cli_deploy_cancelled"));
|
|
3199
|
+
if (choice.value.length !== available.length)
|
|
3200
|
+
selectedServices = choice.value;
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
2721
3204
|
spinner.succeed(cliM("cli_deploy_scanned", { params: { name: scan.appName, sveltekit: String(scan.isSvelteKit), services: scan.requiredServices.join(", ") || cliM("cli_deploy_none") } }));
|
|
2722
|
-
|
|
3205
|
+
if (options.build) {
|
|
3206
|
+
if (!existsSync(buildFile))
|
|
3207
|
+
throw new Error(cliM("cli_build_images_missing"));
|
|
3208
|
+
const imageNames = selectedServices?.filter((name) => ["app", "web"].includes(name));
|
|
3209
|
+
if (!selectedServices || imageNames?.length) {
|
|
3210
|
+
writeCliMessage(cliM("cli_deploy_build_started"));
|
|
3211
|
+
await buildCommand({ cwd: buildRoot, target: "docker", images: imageNames?.join(","), runtime: options.runtime, network: options.network, sqlitePrebuildMirror: options.sqlitePrebuildMirror, httpProxy: options.httpProxy });
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
if (selectedServices)
|
|
3215
|
+
writeCliMessage(cliM("cli_deploy_services", { params: { services: selectedServices.join(", ") } }));
|
|
3216
|
+
let skipMiddleware = options.skipMiddleware || selectedServices?.every((name) => ["app", "web"].includes(name));
|
|
2723
3217
|
if (!skipMiddleware && !options.yes && scan.requiredServices.length) {
|
|
2724
3218
|
const action = await prompts({
|
|
2725
3219
|
type: "select",
|
|
@@ -2732,8 +3226,8 @@ async function deployCommand(options) {
|
|
|
2732
3226
|
skipMiddleware = action.value === "skip";
|
|
2733
3227
|
}
|
|
2734
3228
|
if (skipMiddleware)
|
|
2735
|
-
|
|
2736
|
-
const serviceModes = skipMiddleware ? Object.fromEntries(scan.requiredServices.map((service) => [service, "keep"])) : await configureDeployMiddleware(deployModule, scan, environment.data, deployConfig, options.yes);
|
|
3229
|
+
writeCliMessage(cliM("cli_deploy_middleware_skipped"));
|
|
3230
|
+
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);
|
|
2737
3231
|
if (skipMiddleware)
|
|
2738
3232
|
deployConfig.services = void 0;
|
|
2739
3233
|
for (const key of Object.keys(process4.env)) {
|
|
@@ -2748,7 +3242,7 @@ async function deployCommand(options) {
|
|
|
2748
3242
|
spinner.start(cliM("cli_deploy_initializing"));
|
|
2749
3243
|
const validatedConfig = deployModule.DeployConfigSchema.safeParse(deployConfig);
|
|
2750
3244
|
if (!validatedConfig.success)
|
|
2751
|
-
throw new Error(cliM("
|
|
3245
|
+
throw new Error(cliM("cli_deploy_config_invalid", { params: { path: configPath, fields: validatedConfig.error.issues.map((issue) => issue.path.join(".")).join(", ") } }));
|
|
2752
3246
|
const initResult = await deploy.init(validatedConfig.data);
|
|
2753
3247
|
if (!initResult.success) {
|
|
2754
3248
|
throw new Error(cliM("cli_deploy_init_failed", { params: { message: initResult.error.message } }));
|
|
@@ -2758,6 +3252,9 @@ async function deployCommand(options) {
|
|
|
2758
3252
|
spinner.start(cliM("cli_deploy_deploying"));
|
|
2759
3253
|
const deployResult = await deploy.deployApp(appDir, {
|
|
2760
3254
|
projectName: options.projectName,
|
|
3255
|
+
selectedServices,
|
|
3256
|
+
runtime: options.runtime,
|
|
3257
|
+
onProgress: (stage) => writeCliMessage(cliM(`cli_deploy_stage_${stage}`)),
|
|
2761
3258
|
skipMiddleware,
|
|
2762
3259
|
artifactDir: options.artifactDir ? resolve(cwd, options.artifactDir) : void 0,
|
|
2763
3260
|
bundleOnly: options.bundleOnly,
|
|
@@ -2767,51 +3264,40 @@ async function deployCommand(options) {
|
|
|
2767
3264
|
serviceModes
|
|
2768
3265
|
});
|
|
2769
3266
|
if (!deployResult.success) {
|
|
3267
|
+
const execution = deployModule.getDeployCommandState(deployResult.error);
|
|
3268
|
+
if (execution) {
|
|
3269
|
+
const localKey = execution.localTermination === "not-started" ? "cli_deploy_local_not_started" : execution.localTermination === "confirmed" ? "cli_deploy_local_confirmed" : "cli_deploy_local_unknown";
|
|
3270
|
+
writeCliMessage(cliM(localKey));
|
|
3271
|
+
if (execution.remoteExecution)
|
|
3272
|
+
writeCliMessage(cliM(execution.remoteExecution === "finished" ? "cli_deploy_remote_finished" : "cli_deploy_remote_unknown"));
|
|
3273
|
+
if (execution.deploymentId && execution.resultFile)
|
|
3274
|
+
writeCliMessage(cliM("cli_deploy_result_check", { params: { id: execution.deploymentId, path: execution.resultFile } }));
|
|
3275
|
+
}
|
|
2770
3276
|
if (deployResult.error.ext?.recovery) {
|
|
2771
|
-
|
|
2772
|
-
recovery: deployResult.error.ext.recovery
|
|
2773
|
-
});
|
|
3277
|
+
writeCliMessage(deployResult.error.suggestion ?? cliM("cli_deploy_command_failed"));
|
|
2774
3278
|
}
|
|
2775
3279
|
throw new Error(cliM("cli_deploy_failed", { params: { message: deployResult.error.message } }));
|
|
2776
3280
|
}
|
|
2777
3281
|
spinner.succeed(chalk.green(cliM(deployResult.data.status === "packaged" ? "cli_deploy_bundle_ready" : "cli_deploy_success")));
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
}
|
|
2782
|
-
Object.assign(process4.env, originalEnv);
|
|
2783
|
-
const completedEnvironment = deployModule.loadDeploymentEnvironment({ environment: environment.data.environment, envDir: environment.data.envDir });
|
|
2784
|
-
if (!completedEnvironment.success)
|
|
2785
|
-
throw new Error(completedEnvironment.error.message);
|
|
2786
|
-
for (const requirement of scan.middlewares ?? []) {
|
|
2787
|
-
if ((serviceModes[requirement.envPrefix] ?? serviceModes[requirement.service]) === "keep")
|
|
2788
|
-
continue;
|
|
2789
|
-
const value = completedEnvironment.data.values[requirement.envPrefix];
|
|
2790
|
-
if (value !== void 0)
|
|
2791
|
-
core.logger.info(cliM("cli_deploy_saved", { params: { name: requirement.envPrefix, value: displayDeployValue(requirement.envPrefix, value) } }));
|
|
2792
|
-
}
|
|
2793
|
-
core.logger.info("");
|
|
2794
|
-
if (deployResult.data.bundlePath)
|
|
2795
|
-
core.logger.info(cliM("cli_deploy_bundle_path", { params: { path: deployResult.data.bundlePath } }));
|
|
3282
|
+
writeCliMessage("");
|
|
3283
|
+
if (deployResult.data.bundlePath && (deployResult.data.status !== "packaged" || !deployResult.data.instructions))
|
|
3284
|
+
writeCliMessage(cliM("cli_deploy_bundle_path", { params: { path: deployResult.data.bundlePath } }));
|
|
2796
3285
|
if (deployResult.data.status === "packaged")
|
|
2797
|
-
|
|
3286
|
+
writeCliMessage(deployResult.data.instructions ?? "");
|
|
2798
3287
|
else
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
core.logger.info(chalk.gray(cliM("cli_deploy_env", { params: { names: deployResult.data.envVarsSet.join(", ") } })));
|
|
2803
|
-
}
|
|
2804
|
-
core.logger.info("");
|
|
3288
|
+
writeCliMessage(chalk.cyan(cliM("cli_deploy_url", { params: { url: deployResult.data.url } })));
|
|
3289
|
+
writeCliMessage(chalk.gray(cliM("cli_deploy_id", { params: { id: deployResult.data.deploymentId } })));
|
|
3290
|
+
writeCliMessage("");
|
|
2805
3291
|
} catch (error) {
|
|
2806
3292
|
spinner.fail(chalk.red(cliM("cli_deploy_command_failed")));
|
|
2807
|
-
|
|
3293
|
+
writeCliError(error instanceof Error ? error.message : String(error));
|
|
2808
3294
|
throw error;
|
|
2809
3295
|
} finally {
|
|
2810
3296
|
if (closeDeploy) {
|
|
2811
3297
|
try {
|
|
2812
3298
|
await closeDeploy();
|
|
2813
3299
|
} catch (error) {
|
|
2814
|
-
|
|
3300
|
+
writeCliError("Failed to close deploy module", error);
|
|
2815
3301
|
process4.exitCode = 1;
|
|
2816
3302
|
}
|
|
2817
3303
|
}
|
|
@@ -2820,31 +3306,217 @@ async function deployCommand(options) {
|
|
|
2820
3306
|
delete process4.env[key];
|
|
2821
3307
|
}
|
|
2822
3308
|
Object.assign(process4.env, originalEnv);
|
|
3309
|
+
core.logger.setLevel(previousLogLevel);
|
|
3310
|
+
restoreProjectLogger?.();
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
function appendAt(source, marker, entry) {
|
|
3314
|
+
if (source.split(marker).length !== 2)
|
|
3315
|
+
throw new Error(cliM("cli_generateUnsupportedAggregator"));
|
|
3316
|
+
if (source.split(/\r?\n/).some((line) => line.trim() === entry.trim()))
|
|
3317
|
+
return source;
|
|
3318
|
+
const moduleName = /^import .+ from ['"](.+)['"]$/.exec(entry)?.[1];
|
|
3319
|
+
if (moduleName) {
|
|
3320
|
+
for (const match of source.matchAll(/^import (?!type\b)[^\r\n]+ from ['"](\.[^'"]+)['"]\r?$/gm)) {
|
|
3321
|
+
if (match[1].localeCompare(moduleName) > 0)
|
|
3322
|
+
return `${source.slice(0, match.index)}${entry}
|
|
3323
|
+
${source.slice(match.index)}`;
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
return source.replace(marker, `${entry}
|
|
3327
|
+
${marker}`);
|
|
3328
|
+
}
|
|
3329
|
+
async function generateServApi(context, target) {
|
|
3330
|
+
const contracts = target.packages.filter((pkg) => pkg.dependencies["@h-ai/api-contract"] && pkg.dependencies.zod && pkg.name in target.target.dependencies);
|
|
3331
|
+
if (contracts.length !== 1)
|
|
3332
|
+
throw new Error(cliM("cli_generateUnsupportedAggregator"));
|
|
3333
|
+
const contract = contracts[0];
|
|
3334
|
+
const contractSources = await fse.readdir(path2.join(contract.directory, "src"));
|
|
3335
|
+
const aggregators = [];
|
|
3336
|
+
for (const name of contractSources.filter((name2) => name2.endsWith(".ts"))) {
|
|
3337
|
+
const file = path2.join(contract.directory, "src", name);
|
|
3338
|
+
const content = await fse.readFile(file, "utf8");
|
|
3339
|
+
if (content.includes("// @hai-generator:contracts:entries"))
|
|
3340
|
+
aggregators.push({ path: file, content });
|
|
3341
|
+
}
|
|
3342
|
+
if (aggregators.length !== 1)
|
|
3343
|
+
throw new Error(cliM("cli_generateUnsupportedAggregator"));
|
|
3344
|
+
const aggregator = aggregators[0];
|
|
3345
|
+
const contractIndex = path2.join(contract.directory, "src/index.ts");
|
|
3346
|
+
const procedureIndex = path2.join(target.target.directory, "src/server/procedures/index.ts");
|
|
3347
|
+
const indexSource = await fse.readFile(contractIndex, "utf8");
|
|
3348
|
+
const procedureSource = await fse.readFile(procedureIndex, "utf8");
|
|
3349
|
+
const contractName = `${context.camelCase}Contract`;
|
|
3350
|
+
const factoryName = `create${context.pascalCase}Procedures`;
|
|
3351
|
+
const domainMarker = `// @hai-generator:domain ${context.camelCase}`;
|
|
3352
|
+
const contractFile = path2.join(contract.directory, "src", `${context.kebabCase}-contract.ts`);
|
|
3353
|
+
const procedureFile = path2.join(target.target.directory, "src/server/procedures", `${context.kebabCase}-procedures.ts`);
|
|
3354
|
+
const importLine = `import { ${contractName} } from './${context.kebabCase}-contract.js'`;
|
|
3355
|
+
const entryLine = ` ${context.camelCase}: ${contractName},`;
|
|
3356
|
+
const factoryImport = `import { ${factoryName} } from './${context.kebabCase}-procedures.js'`;
|
|
3357
|
+
const domainProperty = new RegExp(`^\\s*(?:${context.camelCase}|['"]${context.camelCase}['"]):`, "m");
|
|
3358
|
+
if (domainProperty.test(aggregator.content) || domainProperty.test(procedureSource)) {
|
|
3359
|
+
for (const file of [contractFile, procedureFile]) {
|
|
3360
|
+
if (!await fse.pathExists(file) || !(await fse.readFile(file, "utf8")).includes(domainMarker))
|
|
3361
|
+
throw new Error(cliM("cli_generateDomainExists", { params: { name: context.camelCase } }));
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
const contractContent = `${domainMarker}
|
|
3365
|
+
import { apiContract } from '@h-ai/api-contract'
|
|
3366
|
+
import { z } from 'zod'
|
|
3367
|
+
|
|
3368
|
+
/**
|
|
3369
|
+
* ${context.pascalCase} \u793A\u4F8B\u5951\u7EA6\uFF1AGET \u5217\u8868\u4E0E POST \u6821\u9A8C\u56DE\u663E\uFF0C\u4E0D\u6267\u884C\u6301\u4E45\u5199\u5165\u3002
|
|
3370
|
+
* @example client.${context.camelCase}.create({ name: 'Example' })
|
|
3371
|
+
*/
|
|
3372
|
+
export const ${contractName} = {
|
|
3373
|
+
list: apiContract
|
|
3374
|
+
.route({ method: 'GET', path: '/${context.kebabCase}', operationId: '${context.camelCase}.list', tags: ['${context.kebabCase}'] })
|
|
3375
|
+
.output(apiContract.haiResultSchema(z.object({ items: z.array(z.object({ name: z.string() })) }))),
|
|
3376
|
+
create: apiContract
|
|
3377
|
+
.route({ method: 'POST', path: '/${context.kebabCase}', operationId: '${context.camelCase}.create', tags: ['${context.kebabCase}'] })
|
|
3378
|
+
.input(z.object({ name: z.string().min(1) }))
|
|
3379
|
+
.output(apiContract.haiResultSchema(z.object({ name: z.string() }))),
|
|
3380
|
+
}
|
|
3381
|
+
`;
|
|
3382
|
+
const procedureContent = `${domainMarker}
|
|
3383
|
+
import type { ServContext } from '@h-ai/serv'
|
|
3384
|
+
import { ok } from '@h-ai/core'
|
|
3385
|
+
import { serv } from '@h-ai/serv'
|
|
3386
|
+
import { ${contractName} } from '${contract.name}'
|
|
3387
|
+
|
|
3388
|
+
/**
|
|
3389
|
+
* \u521B\u5EFA ${context.pascalCase} \u793A\u4F8B\u8FC7\u7A0B\uFF1B\u65B0\u589E\u6301\u4E45\u5199\u5165\u524D\u63A5\u5165\u6388\u6743\u4E0E\u4E1A\u52A1\u670D\u52A1\u3002
|
|
3390
|
+
* @returns \u4E0E\u5171\u4EAB\u5951\u7EA6\u4E00\u81F4\u7684\u516C\u5F00\u8FC7\u7A0B\uFF1B\u793A\u4F8B\u4E0D\u8BBF\u95EE\u7528\u6237\u6570\u636E\u3002
|
|
3391
|
+
* @example const procedures = { ${context.camelCase}: ${factoryName}() }
|
|
3392
|
+
*/
|
|
3393
|
+
export function ${factoryName}() {
|
|
3394
|
+
return serv
|
|
3395
|
+
.implement(${contractName})
|
|
3396
|
+
.context<ServContext>()
|
|
3397
|
+
.route('list', () => ok({ items: [] }))
|
|
3398
|
+
.route('create', ({ input }) => ok({ name: input.name }))
|
|
3399
|
+
.build()
|
|
3400
|
+
}
|
|
3401
|
+
`;
|
|
3402
|
+
return [
|
|
3403
|
+
{ path: contractFile, content: contractContent },
|
|
3404
|
+
{ path: procedureFile, content: procedureContent },
|
|
3405
|
+
{ path: aggregator.path, previousContent: aggregator.content, content: appendAt(appendAt(aggregator.content, "// @hai-generator:contracts:imports", importLine), " // @hai-generator:contracts:entries", entryLine) },
|
|
3406
|
+
{ path: contractIndex, previousContent: indexSource, content: appendAt(indexSource, "// @hai-generator:contracts:exports", `export * from './${context.kebabCase}-contract.js'`) },
|
|
3407
|
+
{ path: procedureIndex, previousContent: procedureSource, content: appendAt(appendAt(procedureSource, "// @hai-generator:procedures:imports", factoryImport), " // @hai-generator:procedures:entries", ` ${context.camelCase}: ${factoryName}(),`) }
|
|
3408
|
+
];
|
|
3409
|
+
}
|
|
3410
|
+
function isInside(root, file) {
|
|
3411
|
+
const relative2 = path2.relative(root, file);
|
|
3412
|
+
return relative2 !== ".." && !relative2.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative2);
|
|
3413
|
+
}
|
|
3414
|
+
async function readPackage(directory) {
|
|
3415
|
+
if (!await fse.pathExists(path2.join(directory, "package.json")))
|
|
3416
|
+
return void 0;
|
|
3417
|
+
const value = await fse.readJson(path2.join(directory, "package.json"));
|
|
3418
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3419
|
+
throw new Error(cliM("cli_generateUnsupportedProject"));
|
|
3420
|
+
const dependencies = {};
|
|
3421
|
+
for (const key of ["dependencies", "devDependencies"]) {
|
|
3422
|
+
const entries = key in value ? Reflect.get(value, key) : void 0;
|
|
3423
|
+
if (entries && typeof entries === "object" && !Array.isArray(entries)) {
|
|
3424
|
+
for (const [name2, version] of Object.entries(entries)) {
|
|
3425
|
+
if (typeof version === "string")
|
|
3426
|
+
dependencies[name2] = version;
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
const name = "name" in value && typeof value.name === "string" ? value.name : path2.basename(directory);
|
|
3431
|
+
if (!/^(?:@[\w.-]+\/)?[\w.-]+$/.test(name))
|
|
3432
|
+
throw new Error(cliM("cli_generateUnsupportedProject"));
|
|
3433
|
+
return { directory, name, dependencies };
|
|
3434
|
+
}
|
|
3435
|
+
function generateArchitecture(pkg) {
|
|
3436
|
+
if (pkg.dependencies["@sveltejs/kit"])
|
|
3437
|
+
return "sveltekit";
|
|
3438
|
+
if (pkg.dependencies["@h-ai/serv"])
|
|
3439
|
+
return "serv";
|
|
3440
|
+
return pkg.dependencies.svelte ? "svelte" : "other";
|
|
3441
|
+
}
|
|
3442
|
+
async function resolveGenerateTarget(options) {
|
|
3443
|
+
const cwd = await fse.realpath(path2.resolve(options.cwd ?? "."));
|
|
3444
|
+
let root = cwd;
|
|
3445
|
+
for (let current = cwd; ; current = path2.dirname(current)) {
|
|
3446
|
+
if (await fse.pathExists(path2.join(current, "pnpm-workspace.yaml"))) {
|
|
3447
|
+
root = current;
|
|
3448
|
+
break;
|
|
3449
|
+
}
|
|
3450
|
+
if (path2.dirname(current) === current)
|
|
3451
|
+
break;
|
|
3452
|
+
}
|
|
3453
|
+
const packages = [];
|
|
3454
|
+
const addPackage = async (directory) => {
|
|
3455
|
+
if (!isInside(root, directory))
|
|
3456
|
+
throw new Error(cliM("cli_generateOutsideWorkspace"));
|
|
3457
|
+
const real = await fse.realpath(directory);
|
|
3458
|
+
if (!isInside(root, real))
|
|
3459
|
+
throw new Error(cliM("cli_generateOutsideWorkspace"));
|
|
3460
|
+
const pkg = await readPackage(real);
|
|
3461
|
+
if (pkg && !packages.some((value) => value.directory === real))
|
|
3462
|
+
packages.push(pkg);
|
|
3463
|
+
};
|
|
3464
|
+
await addPackage(root);
|
|
3465
|
+
if (await fse.pathExists(path2.join(root, "pnpm-workspace.yaml"))) {
|
|
3466
|
+
const workspace = parse(await fse.readFile(path2.join(root, "pnpm-workspace.yaml"), "utf8"));
|
|
3467
|
+
const patterns = workspace && typeof workspace === "object" && "packages" in workspace && Array.isArray(workspace.packages) ? workspace.packages : [];
|
|
3468
|
+
for (const pattern of patterns) {
|
|
3469
|
+
if (typeof pattern !== "string" || pattern.startsWith("!") || pattern.split(/[\\/]/).includes("..") || path2.isAbsolute(pattern))
|
|
3470
|
+
throw new Error(cliM("cli_generateUnsupportedProject"));
|
|
3471
|
+
for await (const manifest of glob(`${pattern}/package.json`, { cwd: root, exclude: ["**/node_modules/**"] }))
|
|
3472
|
+
await addPackage(path2.dirname(path2.resolve(root, manifest)));
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
if (cwd !== root)
|
|
3476
|
+
await addPackage(cwd);
|
|
3477
|
+
const selected = options.workspace ? packages.filter((pkg) => pkg.name === options.workspace || pkg.directory === path2.resolve(root, options.workspace)) : packages.filter((pkg) => pkg.directory === cwd);
|
|
3478
|
+
const applicable = (pkg) => {
|
|
3479
|
+
const architecture = generateArchitecture(pkg);
|
|
3480
|
+
return options.type === "api" ? architecture === "serv" || architecture === "sveltekit" && Boolean(pkg.dependencies["@h-ai/kit"]) && Boolean(pkg.dependencies.zod) : options.type === "page" ? architecture === "sveltekit" : options.type === "component" ? ["sveltekit", "svelte"].includes(architecture) : Object.keys(pkg.dependencies).some((name) => name.startsWith("@h-ai/"));
|
|
3481
|
+
};
|
|
3482
|
+
const candidates = options.workspace || selected.length === 1 && (cwd !== root || applicable(selected[0])) ? selected.filter(applicable) : packages.filter(applicable);
|
|
3483
|
+
if (candidates.length !== 1)
|
|
3484
|
+
throw new Error(cliM(candidates.length > 1 ? "cli_generateAmbiguousWorkspace" : "cli_generateUnsupportedProject"));
|
|
3485
|
+
const target = candidates[0];
|
|
3486
|
+
if (generateArchitecture(target) === "sveltekit" && (options.type === "page" || options.type === "api")) {
|
|
3487
|
+
for (const name of ["svelte.config.js", "svelte.config.mjs", "svelte.config.cjs", "svelte.config.ts"]) {
|
|
3488
|
+
const configFile = path2.join(target.directory, name);
|
|
3489
|
+
if (await fse.pathExists(configFile) && /\bfiles\s*[:.[(]/.test(await fse.readFile(configFile, "utf8")))
|
|
3490
|
+
throw new Error(cliM("cli_generateCustomRoutes"));
|
|
3491
|
+
}
|
|
2823
3492
|
}
|
|
3493
|
+
return { root, target, packages };
|
|
2824
3494
|
}
|
|
3495
|
+
|
|
3496
|
+
// src/commands/cli-generate.ts
|
|
2825
3497
|
var GENERATORS = {
|
|
2826
3498
|
page: {
|
|
2827
|
-
description:
|
|
3499
|
+
description: () => cliM("cli_generatePageDescription"),
|
|
2828
3500
|
defaultPath: "src/routes",
|
|
2829
3501
|
generate: generatePage
|
|
2830
3502
|
},
|
|
2831
3503
|
component: {
|
|
2832
|
-
description:
|
|
3504
|
+
description: () => cliM("cli_generateComponentDescription"),
|
|
2833
3505
|
defaultPath: "src/lib/components",
|
|
2834
3506
|
generate: generateComponent
|
|
2835
3507
|
},
|
|
2836
3508
|
api: {
|
|
2837
|
-
description:
|
|
3509
|
+
description: () => cliM("cli_generateApiDescription"),
|
|
2838
3510
|
defaultPath: "src/routes/api",
|
|
2839
3511
|
generate: generateApi
|
|
2840
3512
|
},
|
|
2841
3513
|
model: {
|
|
2842
|
-
description: "
|
|
3514
|
+
description: () => cliM("cli_generateModelDescription"),
|
|
2843
3515
|
defaultPath: "src/lib/models",
|
|
2844
3516
|
generate: generateModel
|
|
2845
3517
|
},
|
|
2846
3518
|
migration: {
|
|
2847
|
-
description: "
|
|
3519
|
+
description: () => cliM("cli_generateMigrationDescription"),
|
|
2848
3520
|
defaultPath: "migrations",
|
|
2849
3521
|
generate: generateMigration
|
|
2850
3522
|
}
|
|
@@ -2853,55 +3525,43 @@ async function generate(options) {
|
|
|
2853
3525
|
const spinner = ora();
|
|
2854
3526
|
const cwd = options.cwd ?? ".";
|
|
2855
3527
|
try {
|
|
2856
|
-
const project = await detectProject(cwd);
|
|
2857
|
-
if (!project?.isHaiProject) {
|
|
2858
|
-
core.logger.warn(chalk.yellow("\u8B66\u544A: \u5F53\u524D\u76EE\u5F55\u4E0D\u662F hai \u9879\u76EE"));
|
|
2859
|
-
}
|
|
2860
3528
|
const resolvedOptions = await resolveGenerateOptions(options);
|
|
2861
3529
|
const generator = GENERATORS[resolvedOptions.type];
|
|
2862
|
-
if (!generator) {
|
|
2863
|
-
throw new Error(`\u672A\u77E5\u7684\u751F\u6210\u5668\u7C7B\u578B: ${resolvedOptions.type}`);
|
|
2864
|
-
}
|
|
2865
3530
|
if (!/^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i.test(resolvedOptions.name))
|
|
2866
3531
|
throw new Error(cliM("cli_invalidGenerateName"));
|
|
2867
|
-
const
|
|
2868
|
-
const
|
|
2869
|
-
|
|
2870
|
-
|
|
3532
|
+
const target = await resolveGenerateTarget(resolvedOptions);
|
|
3533
|
+
const architecture = generateArchitecture(target.target);
|
|
3534
|
+
const outputDir = path2.resolve(target.target.directory, resolvedOptions.output || generator.defaultPath);
|
|
3535
|
+
if (["page", "api"].includes(resolvedOptions.type) && architecture === "sveltekit") {
|
|
3536
|
+
const routes = path2.join(target.target.directory, "src/routes");
|
|
3537
|
+
if (!outputDir.startsWith(routes + path2.sep) && outputDir !== routes)
|
|
3538
|
+
throw new Error(cliM("cli_generateInvalidRouteOutput"));
|
|
2871
3539
|
}
|
|
3540
|
+
if (resolvedOptions.type === "api" && architecture === "serv" && resolvedOptions.output)
|
|
3541
|
+
throw new Error(cliM("cli_generateInvalidRouteOutput"));
|
|
3542
|
+
const relativeOutput = path2.relative(target.root, outputDir);
|
|
3543
|
+
if (relativeOutput === ".." || relativeOutput.startsWith(`..${path2.sep}`) || path2.isAbsolute(relativeOutput))
|
|
3544
|
+
throw new Error(cliM("cli_generateOutsideWorkspace"));
|
|
2872
3545
|
const context = createTemplateContext(resolvedOptions.name, {
|
|
2873
|
-
projectName:
|
|
3546
|
+
projectName: target.target.name
|
|
2874
3547
|
});
|
|
2875
|
-
spinner.start(
|
|
2876
|
-
const files = await generator.generate(context, outputDir);
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
let ancestor = file.path;
|
|
2882
|
-
while (!await fse.pathExists(ancestor))
|
|
2883
|
-
ancestor = path2.dirname(ancestor);
|
|
2884
|
-
const realAncestor = await fse.realpath(ancestor);
|
|
2885
|
-
const relative = path2.relative(realCwd, realAncestor);
|
|
2886
|
-
if (relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative))
|
|
2887
|
-
throw new Error(cliM("cli_generateOutsideWorkspace"));
|
|
2888
|
-
}
|
|
2889
|
-
for (const file of files) {
|
|
2890
|
-
await fse.ensureDir(path2.dirname(file.path));
|
|
2891
|
-
await fse.writeFile(file.path, file.content, { encoding: "utf-8", flag: resolvedOptions.force ? "w" : "wx" });
|
|
2892
|
-
}
|
|
2893
|
-
spinner.succeed();
|
|
2894
|
-
core.logger.info("", {});
|
|
2895
|
-
core.logger.info(chalk.green("\u2714 \u751F\u6210\u5B8C\u6210\uFF01"));
|
|
2896
|
-
core.logger.info("", {});
|
|
2897
|
-
core.logger.info("\u521B\u5EFA\u7684\u6587\u4EF6:");
|
|
3548
|
+
spinner.start(cliM("cli_generateProgress", { params: { description: generator.description() } }));
|
|
3549
|
+
const files = resolvedOptions.type === "api" && architecture === "serv" ? await generateServApi(context, target) : await generator.generate(context, outputDir);
|
|
3550
|
+
await writeGeneratedFiles(files, target.root, resolvedOptions.force);
|
|
3551
|
+
spinner.succeed(cliM("cli_generateComplete"));
|
|
3552
|
+
writeCliMessage("");
|
|
3553
|
+
writeCliMessage(cliM("cli_generateFiles"));
|
|
2898
3554
|
files.forEach((file) => {
|
|
2899
|
-
|
|
3555
|
+
writeCliMessage(chalk.cyan(` ${path2.relative(cwd, file.path)}`));
|
|
2900
3556
|
});
|
|
2901
|
-
|
|
3557
|
+
writeCliMessage("");
|
|
2902
3558
|
} catch (error) {
|
|
2903
|
-
spinner.fail();
|
|
2904
|
-
|
|
3559
|
+
spinner.fail(cliM("cli_generateFailed"));
|
|
3560
|
+
writeCliError(cliM("cli_generateFailureDetail"), { error });
|
|
3561
|
+
if (error instanceof AggregateError) {
|
|
3562
|
+
for (const cause of error.errors)
|
|
3563
|
+
writeCliError(cliM("cli_generateFailureDetail"), { error: cause });
|
|
3564
|
+
}
|
|
2905
3565
|
throw error;
|
|
2906
3566
|
}
|
|
2907
3567
|
}
|
|
@@ -2911,9 +3571,9 @@ async function resolveGenerateOptions(options) {
|
|
|
2911
3571
|
questions.push({
|
|
2912
3572
|
type: "select",
|
|
2913
3573
|
name: "type",
|
|
2914
|
-
message: "
|
|
3574
|
+
message: cliM("cli_generateTypePrompt"),
|
|
2915
3575
|
choices: Object.entries(GENERATORS).map(([key, value]) => ({
|
|
2916
|
-
title: `${key} - ${value.description}`,
|
|
3576
|
+
title: `${key} - ${value.description()}`,
|
|
2917
3577
|
value: key
|
|
2918
3578
|
}))
|
|
2919
3579
|
});
|
|
@@ -2922,24 +3582,102 @@ async function resolveGenerateOptions(options) {
|
|
|
2922
3582
|
questions.push({
|
|
2923
3583
|
type: "text",
|
|
2924
3584
|
name: "name",
|
|
2925
|
-
message: "
|
|
3585
|
+
message: cliM("cli_generateNamePrompt"),
|
|
2926
3586
|
validate: (value) => {
|
|
2927
3587
|
if (!value.trim())
|
|
2928
|
-
return "
|
|
3588
|
+
return cliM("cli_generateNameRequired");
|
|
2929
3589
|
return true;
|
|
2930
3590
|
}
|
|
2931
3591
|
});
|
|
2932
3592
|
}
|
|
2933
|
-
const answers = questions.length > 0 ? await prompts(questions
|
|
3593
|
+
const answers = questions.length > 0 ? await prompts(questions, { onCancel: () => {
|
|
3594
|
+
throw new Error(cliM("cli_generateCancelled"));
|
|
3595
|
+
} }) : {};
|
|
3596
|
+
const type = options.type || ("type" in answers ? answers.type : void 0);
|
|
3597
|
+
const name = options.name || ("name" in answers ? answers.name : void 0);
|
|
3598
|
+
if (!isGeneratorType(type))
|
|
3599
|
+
throw new Error(cliM("cli_generateUnknownType", { params: { type: String(type ?? "") } }));
|
|
3600
|
+
if (typeof name !== "string" || !name.trim())
|
|
3601
|
+
throw new Error(cliM("cli_generateNameRequired"));
|
|
2934
3602
|
return {
|
|
2935
|
-
type
|
|
2936
|
-
name
|
|
3603
|
+
type,
|
|
3604
|
+
name,
|
|
2937
3605
|
output: options.output ?? "",
|
|
2938
3606
|
force: options.force ?? false,
|
|
2939
3607
|
verbose: options.verbose ?? false,
|
|
2940
|
-
cwd: options.cwd ?? "."
|
|
3608
|
+
cwd: options.cwd ?? ".",
|
|
3609
|
+
workspace: options.workspace ?? ""
|
|
2941
3610
|
};
|
|
2942
3611
|
}
|
|
3612
|
+
function isGeneratorType(value) {
|
|
3613
|
+
return typeof value === "string" && Object.hasOwn(GENERATORS, value);
|
|
3614
|
+
}
|
|
3615
|
+
async function writeGeneratedFiles(files, root, force) {
|
|
3616
|
+
if (new Set(files.map((file) => path2.resolve(file.path))).size !== files.length)
|
|
3617
|
+
throw new Error(cliM("cli_generateUnsupportedAggregator"));
|
|
3618
|
+
const previous = /* @__PURE__ */ new Map();
|
|
3619
|
+
const stages = /* @__PURE__ */ new Map();
|
|
3620
|
+
const written = [];
|
|
3621
|
+
const failures = [];
|
|
3622
|
+
for (const file of files) {
|
|
3623
|
+
const relativeFile = path2.relative(root, file.path);
|
|
3624
|
+
if (relativeFile === ".." || relativeFile.startsWith(`..${path2.sep}`) || path2.isAbsolute(relativeFile))
|
|
3625
|
+
throw new Error(cliM("cli_generateOutsideWorkspace"));
|
|
3626
|
+
let ancestor = file.path;
|
|
3627
|
+
while (!await fse.pathExists(ancestor))
|
|
3628
|
+
ancestor = path2.dirname(ancestor);
|
|
3629
|
+
const relative2 = path2.relative(root, await fse.realpath(ancestor));
|
|
3630
|
+
if (relative2 === ".." || relative2.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative2))
|
|
3631
|
+
throw new Error(cliM("cli_generateOutsideWorkspace"));
|
|
3632
|
+
const content = await fse.pathExists(file.path) ? await fse.readFile(file.path, "utf8") : void 0;
|
|
3633
|
+
if (file.previousContent !== void 0 ? content !== file.previousContent : content !== void 0 && !force)
|
|
3634
|
+
throw new Error(cliM("cli_generateExists", { params: { path: file.path } }));
|
|
3635
|
+
previous.set(file.path, content);
|
|
3636
|
+
}
|
|
3637
|
+
try {
|
|
3638
|
+
for (const file of files) {
|
|
3639
|
+
await fse.ensureDir(path2.dirname(file.path));
|
|
3640
|
+
const stage = path2.join(path2.dirname(file.path), `.${path2.basename(file.path)}.${randomUUID()}.tmp`);
|
|
3641
|
+
stages.set(file.path, stage);
|
|
3642
|
+
await fse.writeFile(stage, file.content, { encoding: "utf8", flag: "wx" });
|
|
3643
|
+
}
|
|
3644
|
+
for (const file of files) {
|
|
3645
|
+
const current = await fse.pathExists(file.path) ? await fse.readFile(file.path, "utf8") : void 0;
|
|
3646
|
+
if (current !== previous.get(file.path))
|
|
3647
|
+
throw new Error(cliM("cli_generateExists", { params: { path: file.path } }));
|
|
3648
|
+
await fse.rename(stages.get(file.path), file.path);
|
|
3649
|
+
written.push(file);
|
|
3650
|
+
}
|
|
3651
|
+
} catch (error) {
|
|
3652
|
+
failures.push(error);
|
|
3653
|
+
for (const file of written.reverse()) {
|
|
3654
|
+
try {
|
|
3655
|
+
if (await fse.readFile(file.path, "utf8") !== file.content)
|
|
3656
|
+
throw new Error(cliM("cli_generateExists", { params: { path: file.path } }));
|
|
3657
|
+
const original = previous.get(file.path);
|
|
3658
|
+
if (original === void 0)
|
|
3659
|
+
await fse.unlink(file.path);
|
|
3660
|
+
else
|
|
3661
|
+
await fse.writeFile(file.path, original, "utf8");
|
|
3662
|
+
} catch (rollbackError) {
|
|
3663
|
+
failures.push(rollbackError);
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
} finally {
|
|
3667
|
+
for (const stage of stages.values()) {
|
|
3668
|
+
try {
|
|
3669
|
+
await fse.unlink(stage);
|
|
3670
|
+
} catch (error) {
|
|
3671
|
+
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT")
|
|
3672
|
+
failures.push(error);
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
if (failures.length > 1)
|
|
3677
|
+
throw new AggregateError(failures, cliM("cli_generateRollbackFailed"));
|
|
3678
|
+
if (failures.length === 1)
|
|
3679
|
+
throw failures[0];
|
|
3680
|
+
}
|
|
2943
3681
|
async function generatePage(context, outputDir) {
|
|
2944
3682
|
const files = [];
|
|
2945
3683
|
const pageDir = path2.join(outputDir, context.kebabCase);
|
|
@@ -3116,19 +3854,45 @@ export function down() {
|
|
|
3116
3854
|
return files;
|
|
3117
3855
|
}
|
|
3118
3856
|
var MODULE_CONFIG_MAP = {
|
|
3119
|
-
"@h-ai/core": { configKey: "core", label
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
"@h-ai/
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
"@h-ai/
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
"@h-ai/
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
"@h-ai/
|
|
3857
|
+
"@h-ai/core": { configKey: "core", get label() {
|
|
3858
|
+
return cliM("cli_coreLabel");
|
|
3859
|
+
} },
|
|
3860
|
+
"@h-ai/serv": { configKey: "serv", get label() {
|
|
3861
|
+
return cliM("cli_serviceLabel");
|
|
3862
|
+
} },
|
|
3863
|
+
"@h-ai/reldb": { configKey: "db", get label() {
|
|
3864
|
+
return cliM("cli_featureDbName");
|
|
3865
|
+
} },
|
|
3866
|
+
"@h-ai/cache": { configKey: "cache", get label() {
|
|
3867
|
+
return cliM("cli_featureCacheName");
|
|
3868
|
+
} },
|
|
3869
|
+
"@h-ai/iam": { configKey: "iam", get label() {
|
|
3870
|
+
return cliM("cli_featureIamName");
|
|
3871
|
+
} },
|
|
3872
|
+
"@h-ai/storage": { configKey: "storage", get label() {
|
|
3873
|
+
return cliM("cli_featureStorageName");
|
|
3874
|
+
} },
|
|
3875
|
+
"@h-ai/ai": { configKey: "ai", get label() {
|
|
3876
|
+
return cliM("cli_featureAiName");
|
|
3877
|
+
} },
|
|
3878
|
+
"@h-ai/vecdb": { configKey: "vecdb", get label() {
|
|
3879
|
+
return cliM("cli_featureVecdbName");
|
|
3880
|
+
} },
|
|
3881
|
+
"@h-ai/reach": { configKey: "reach", get label() {
|
|
3882
|
+
return cliM("cli_featureReachName");
|
|
3883
|
+
} },
|
|
3884
|
+
"@h-ai/scheduler": { configKey: "scheduler", get label() {
|
|
3885
|
+
return cliM("cli_featureSchedulerName");
|
|
3886
|
+
} },
|
|
3887
|
+
"@h-ai/audit": { configKey: "audit", get label() {
|
|
3888
|
+
return cliM("cli_featureAuditName");
|
|
3889
|
+
} },
|
|
3890
|
+
"@h-ai/payment": { configKey: "payment", get label() {
|
|
3891
|
+
return cliM("cli_featurePaymentName");
|
|
3892
|
+
} },
|
|
3893
|
+
"@h-ai/deploy": { configKey: "deploy", get label() {
|
|
3894
|
+
return cliM("cli_featureDeployName");
|
|
3895
|
+
} }
|
|
3132
3896
|
};
|
|
3133
3897
|
async function validateProject(cwd) {
|
|
3134
3898
|
const project = await detectProject(cwd);
|
|
@@ -3154,49 +3918,49 @@ async function initProject(options) {
|
|
|
3154
3918
|
const spinner = ora();
|
|
3155
3919
|
const cwd = options.cwd ?? ".";
|
|
3156
3920
|
try {
|
|
3157
|
-
spinner.start("
|
|
3921
|
+
spinner.start(cliM("cli_initDetecting"));
|
|
3158
3922
|
const result = await validateProject(cwd);
|
|
3159
3923
|
if (!result) {
|
|
3160
|
-
spinner.fail(chalk.red("
|
|
3924
|
+
spinner.fail(chalk.red(cliM("cli_projectNotFound")));
|
|
3161
3925
|
return;
|
|
3162
3926
|
}
|
|
3163
3927
|
const { project, existing, missing } = result;
|
|
3164
|
-
spinner.succeed(
|
|
3928
|
+
spinner.succeed(cliM("cli_initDetected", { params: { name: chalk.cyan(project.name) } }));
|
|
3165
3929
|
if (project.isHaiProject) {
|
|
3166
|
-
const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-
|
|
3930
|
+
const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-US5CCAJ4.js');
|
|
3167
3931
|
const generatedFiles = await generateProjectAiSupport(cwd, resolveSkillNamesForPackages(project.haiPackages), {
|
|
3168
3932
|
overwriteSkills: false,
|
|
3169
3933
|
overwriteBridgeFiles: false
|
|
3170
3934
|
});
|
|
3171
3935
|
for (const generatedFile of generatedFiles) {
|
|
3172
|
-
|
|
3936
|
+
writeCliMessage(chalk.gray(` + ${generatedFile}`));
|
|
3173
3937
|
}
|
|
3174
3938
|
}
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3939
|
+
writeCliMessage("");
|
|
3940
|
+
writeCliMessage(chalk.bold(cliM("cli_initStatus")));
|
|
3941
|
+
writeCliMessage(cliM("cli_initName", { params: { name: chalk.cyan(project.name) } }));
|
|
3942
|
+
writeCliMessage(cliM("cli_initVersion", { params: { version: project.version } }));
|
|
3943
|
+
writeCliMessage(cliM("cli_initModules", { params: { modules: project.haiPackages.length > 0 ? project.haiPackages.map((p) => chalk.cyan(p)).join(", ") : chalk.gray(cliM("cli_none")) } }));
|
|
3944
|
+
writeCliMessage("");
|
|
3181
3945
|
if (existing.length > 0) {
|
|
3182
|
-
|
|
3946
|
+
writeCliMessage(chalk.bold(cliM("cli_initExisting")));
|
|
3183
3947
|
for (const key of existing) {
|
|
3184
|
-
|
|
3948
|
+
writeCliMessage(chalk.green(` \u2714 config/_${key}.yml`));
|
|
3185
3949
|
}
|
|
3186
3950
|
}
|
|
3187
3951
|
if (missing.length === 0 && !options.force) {
|
|
3188
|
-
|
|
3189
|
-
|
|
3952
|
+
writeCliMessage("");
|
|
3953
|
+
writeCliMessage(chalk.green(cliM("cli_initReady")));
|
|
3190
3954
|
return;
|
|
3191
3955
|
}
|
|
3192
3956
|
if (missing.length > 0) {
|
|
3193
|
-
|
|
3194
|
-
|
|
3957
|
+
writeCliMessage("");
|
|
3958
|
+
writeCliMessage(chalk.bold(cliM("cli_initMissing")));
|
|
3195
3959
|
for (const item of missing) {
|
|
3196
|
-
|
|
3960
|
+
writeCliMessage(chalk.yellow(` \u2718 config/_${item.configKey}.yml (${item.label})`));
|
|
3197
3961
|
}
|
|
3198
|
-
|
|
3199
|
-
spinner.start("
|
|
3962
|
+
writeCliMessage("");
|
|
3963
|
+
spinner.start(cliM("cli_initGenerating"));
|
|
3200
3964
|
let generated = 0;
|
|
3201
3965
|
for (const item of missing) {
|
|
3202
3966
|
const configPath = path2.join(cwd, "config", `_${item.configKey}.yml`);
|
|
@@ -3204,11 +3968,11 @@ async function initProject(options) {
|
|
|
3204
3968
|
await writeFile(configPath, content);
|
|
3205
3969
|
generated++;
|
|
3206
3970
|
}
|
|
3207
|
-
spinner.succeed(
|
|
3971
|
+
spinner.succeed(cliM("cli_initGenerated", { params: { count: generated } }));
|
|
3208
3972
|
}
|
|
3209
3973
|
if (options.force && existing.length > 0) {
|
|
3210
|
-
|
|
3211
|
-
spinner.start("
|
|
3974
|
+
writeCliMessage("");
|
|
3975
|
+
spinner.start(cliM("cli_initRegenerating"));
|
|
3212
3976
|
let regenerated = 0;
|
|
3213
3977
|
for (const pkg of project.haiPackages) {
|
|
3214
3978
|
const mapping = MODULE_CONFIG_MAP[pkg];
|
|
@@ -3219,25 +3983,25 @@ async function initProject(options) {
|
|
|
3219
3983
|
await writeFile(configPath, content);
|
|
3220
3984
|
regenerated++;
|
|
3221
3985
|
}
|
|
3222
|
-
spinner.succeed(
|
|
3986
|
+
spinner.succeed(cliM("cli_initRegenerated", { params: { count: regenerated } }));
|
|
3223
3987
|
}
|
|
3224
3988
|
const coreConfigPath = path2.join(cwd, "config", "_core.yml");
|
|
3225
3989
|
if (!await fileExists(coreConfigPath)) {
|
|
3226
|
-
spinner.start("
|
|
3990
|
+
spinner.start(cliM("cli_initCore"));
|
|
3227
3991
|
const content = generateConfigFile("core");
|
|
3228
3992
|
await writeFile(coreConfigPath, content);
|
|
3229
3993
|
spinner.succeed();
|
|
3230
3994
|
}
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3995
|
+
writeCliMessage("");
|
|
3996
|
+
writeCliMessage(chalk.green(cliM("cli_initSuccess")));
|
|
3997
|
+
writeCliMessage("");
|
|
3998
|
+
writeCliMessage(chalk.gray(cliM("cli_nextSteps")));
|
|
3999
|
+
writeCliMessage(chalk.gray(cliM("cli_initEditConfig")));
|
|
4000
|
+
writeCliMessage(chalk.gray(cliM("cli_initStartDev")));
|
|
4001
|
+
writeCliMessage("");
|
|
3238
4002
|
} catch (error) {
|
|
3239
4003
|
spinner.fail();
|
|
3240
|
-
|
|
4004
|
+
writeCliError(chalk.red(cliM("cli_initFailed")), { error });
|
|
3241
4005
|
throw error;
|
|
3242
4006
|
}
|
|
3243
4007
|
}
|
|
@@ -3245,9 +4009,9 @@ async function initProject(options) {
|
|
|
3245
4009
|
// src/cli-main.ts
|
|
3246
4010
|
var VERSION = getCliVersion();
|
|
3247
4011
|
var cli = cac("hai");
|
|
3248
|
-
cli.option("-v, --verbose", "
|
|
3249
|
-
cli.option("-C, --cwd <path>", "
|
|
3250
|
-
cli.command("create [name]", "
|
|
4012
|
+
cli.option("-v, --verbose", cliM("cli_help_verbose"));
|
|
4013
|
+
cli.option("-C, --cwd <path>", cliM("cli_help_cwd"));
|
|
4014
|
+
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) => {
|
|
3251
4015
|
try {
|
|
3252
4016
|
const features = typeof options.features === "string" ? options.features.split(",").map((f) => f.trim()) : void 0;
|
|
3253
4017
|
const frontends = typeof options.frontends === "string" ? options.frontends.split(",").map((f) => f.trim()).filter(Boolean) : void 0;
|
|
@@ -3257,6 +4021,7 @@ cli.command("create [name]", "\u521B\u5EFA\u65B0\u7684 hai \u9879\u76EE").option
|
|
|
3257
4021
|
frontends,
|
|
3258
4022
|
template: options.template,
|
|
3259
4023
|
features,
|
|
4024
|
+
deploy: options.deploy,
|
|
3260
4025
|
examples: options.examples,
|
|
3261
4026
|
install: options.install,
|
|
3262
4027
|
packageManager: options.packageManager,
|
|
@@ -3270,7 +4035,7 @@ cli.command("create [name]", "\u521B\u5EFA\u65B0\u7684 hai \u9879\u76EE").option
|
|
|
3270
4035
|
process4.exitCode = 1;
|
|
3271
4036
|
}
|
|
3272
4037
|
});
|
|
3273
|
-
cli.command("add [module]", "
|
|
4038
|
+
cli.command("add [module]", cliM("cli_help_add")).option("--no-install", cliM("cli_help_noInstall")).action(async (module, options) => {
|
|
3274
4039
|
try {
|
|
3275
4040
|
await addModule({
|
|
3276
4041
|
module,
|
|
@@ -3282,7 +4047,7 @@ cli.command("add [module]", "\u5411\u73B0\u6709\u9879\u76EE\u6DFB\u52A0\u6A21\u5
|
|
|
3282
4047
|
process4.exit(1);
|
|
3283
4048
|
}
|
|
3284
4049
|
});
|
|
3285
|
-
cli.command("init", "
|
|
4050
|
+
cli.command("init", cliM("cli_help_init")).option("-f, --force", cliM("cli_help_forceConfig")).action(async (options) => {
|
|
3286
4051
|
try {
|
|
3287
4052
|
await initProject({
|
|
3288
4053
|
force: options.force,
|
|
@@ -3293,12 +4058,13 @@ cli.command("init", "\u521D\u59CB\u5316/\u6821\u9A8C\u9879\u76EE\u914D\u7F6E").o
|
|
|
3293
4058
|
process4.exit(1);
|
|
3294
4059
|
}
|
|
3295
4060
|
});
|
|
3296
|
-
cli.command("generate [type] [name]", "
|
|
4061
|
+
cli.command("generate [type] [name]", cliM("cli_help_generate")).option("-w, --workspace <name>", cliM("cli_help_workspace")).alias("g").option("-o, --output <path>", cliM("cli_help_output")).option("-f, --force", cliM("cli_help_force")).action(async (type, name, options) => {
|
|
3297
4062
|
try {
|
|
3298
4063
|
await generate({
|
|
3299
4064
|
type,
|
|
3300
4065
|
name: name ?? "",
|
|
3301
4066
|
output: options.output,
|
|
4067
|
+
...options.workspace ? { workspace: String(options.workspace) } : {},
|
|
3302
4068
|
force: options.force,
|
|
3303
4069
|
verbose: options.verbose,
|
|
3304
4070
|
cwd: options.cwd
|
|
@@ -3307,7 +4073,7 @@ cli.command("generate [type] [name]", "\u751F\u6210\u4EE3\u7801").alias("g").opt
|
|
|
3307
4073
|
process4.exit(1);
|
|
3308
4074
|
}
|
|
3309
4075
|
});
|
|
3310
|
-
cli.command("build", "
|
|
4076
|
+
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) => {
|
|
3311
4077
|
try {
|
|
3312
4078
|
await buildCommand(options);
|
|
3313
4079
|
} catch (error) {
|
|
@@ -3315,7 +4081,7 @@ cli.command("build", "\u6784\u5EFA\u6E90\u7801\u3001Docker \u955C\u50CF\u6216 Ta
|
|
|
3315
4081
|
process4.exitCode = 1;
|
|
3316
4082
|
}
|
|
3317
4083
|
});
|
|
3318
|
-
cli.command("deploy [appDir]", "
|
|
4084
|
+
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) => {
|
|
3319
4085
|
try {
|
|
3320
4086
|
await deployCommand({
|
|
3321
4087
|
appDir,
|
|
@@ -3325,26 +4091,32 @@ cli.command("deploy [appDir]", "\u90E8\u7F72\u5E94\u7528\u5230\u4E91\u5E73\u53F0
|
|
|
3325
4091
|
projectName: options.projectName,
|
|
3326
4092
|
skipMiddleware: options.skipMiddleware,
|
|
3327
4093
|
artifactDir: options.artifactDir,
|
|
4094
|
+
services: options.services,
|
|
4095
|
+
build: options.build,
|
|
4096
|
+
network: options.network,
|
|
4097
|
+
sqlitePrebuildMirror: options.sqlitePrebuildMirror,
|
|
4098
|
+
httpProxy: options.httpProxy,
|
|
4099
|
+
runtime: options.runtime,
|
|
3328
4100
|
bundleOnly: options.bundleOnly,
|
|
3329
4101
|
output: options.output,
|
|
3330
4102
|
verbose: options.verbose,
|
|
3331
4103
|
cwd: options.cwd
|
|
3332
4104
|
});
|
|
3333
4105
|
} catch {
|
|
3334
|
-
process4.
|
|
4106
|
+
process4.exitCode = 1;
|
|
3335
4107
|
}
|
|
3336
4108
|
});
|
|
3337
|
-
cli.command("g:page <name>", "
|
|
3338
|
-
await generate({ type: "page", name, cwd: options.cwd });
|
|
4109
|
+
cli.command("g:page <name>", cliM("cli_help_generatePage")).option("-w, --workspace <name>", cliM("cli_help_workspace")).action(async (name, options) => {
|
|
4110
|
+
await generate({ type: "page", name, cwd: options.cwd, ...options.workspace ? { workspace: String(options.workspace) } : {} });
|
|
3339
4111
|
});
|
|
3340
|
-
cli.command("g:component <name>", "
|
|
3341
|
-
await generate({ type: "component", name, cwd: options.cwd });
|
|
4112
|
+
cli.command("g:component <name>", cliM("cli_help_generateComponent")).option("-w, --workspace <name>", cliM("cli_help_workspace")).action(async (name, options) => {
|
|
4113
|
+
await generate({ type: "component", name, cwd: options.cwd, ...options.workspace ? { workspace: String(options.workspace) } : {} });
|
|
3342
4114
|
});
|
|
3343
|
-
cli.command("g:api <name>", "
|
|
3344
|
-
await generate({ type: "api", name, cwd: options.cwd });
|
|
4115
|
+
cli.command("g:api <name>", cliM("cli_help_generateApi")).option("-w, --workspace <name>", cliM("cli_help_workspace")).action(async (name, options) => {
|
|
4116
|
+
await generate({ type: "api", name, cwd: options.cwd, ...options.workspace ? { workspace: String(options.workspace) } : {} });
|
|
3345
4117
|
});
|
|
3346
|
-
cli.command("g:model <name>", "
|
|
3347
|
-
await generate({ type: "model", name, cwd: options.cwd });
|
|
4118
|
+
cli.command("g:model <name>", cliM("cli_help_generateModel")).option("-w, --workspace <name>", cliM("cli_help_workspace")).action(async (name, options) => {
|
|
4119
|
+
await generate({ type: "model", name, cwd: options.cwd, ...options.workspace ? { workspace: String(options.workspace) } : {} });
|
|
3348
4120
|
});
|
|
3349
4121
|
cli.version(VERSION, "-V, --version");
|
|
3350
4122
|
cli.help();
|