@h-ai/cli 0.1.0-alpha.64 → 0.1.0-alpha.66
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 +31 -9
- package/README.ja.md +30 -8
- package/README.md +38 -16
- package/dist/{chunk-X3RSYNBG.js → chunk-FWN5B54L.js} +94 -4
- package/dist/chunk-FWN5B54L.js.map +1 -0
- package/dist/chunk-QYX6V7H2.js +5613 -0
- package/dist/chunk-QYX6V7H2.js.map +1 -0
- package/dist/cli-config-templates-3XSFIXBP.js +5 -0
- package/dist/{cli-config-templates-QRF6B5T4.js.map → cli-config-templates-3XSFIXBP.js.map} +1 -1
- package/dist/{cli-skill-templates-EZFW7D43.js → cli-skill-templates-TSYEQTCY.js} +3 -3
- package/dist/{cli-skill-templates-EZFW7D43.js.map → cli-skill-templates-TSYEQTCY.js.map} +1 -1
- package/dist/index.js +733 -210
- package/dist/index.js.map +1 -1
- package/dist/{src-DUGLX4YB.js → src-RDQ2EIBT.js} +1054 -660
- package/dist/{src-DUGLX4YB.js.map → src-RDQ2EIBT.js.map} +1 -1
- package/package.json +9 -7
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +4 -2
- package/templates/apps/api/.dockerignore.hbs +4 -0
- package/templates/apps/api/.gitignore.hbs +6 -5
- package/templates/apps/api/Dockerfile.hbs +5 -8
- package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +2 -0
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +2 -2
- package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +2 -2
- package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +4 -4
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +8 -2
- package/templates/apps/api/hai.build.json.hbs +1 -0
- package/templates/apps/api/package.json.hbs +13 -7
- package/templates/apps/fullstack/.dockerignore.hbs +4 -3
- package/templates/apps/fullstack/.gitignore.hbs +24 -10
- package/templates/apps/fullstack/Dockerfile.hbs +6 -8
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +0 -3
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +5 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +8 -4
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +3 -6
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +6 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +73 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +8 -4
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +2 -6
- package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +5 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +8 -4
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +24 -1
- package/templates/apps/fullstack/hai.build.json.hbs +7 -0
- package/templates/apps/fullstack/package.json.hbs +16 -11
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +2 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/tests/contract.test.ts.hbs +8 -14
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +8 -6
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -8
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +8 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +4 -4
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +4 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +2 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +14 -12
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +17 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +15 -5
- package/templates/apps/fullstack/playwright.config.ts.hbs +6 -1
- package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +0 -1
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +13 -13
- package/templates/apps/mobile-app/index.html.hbs +1 -0
- package/templates/apps/mobile-app/public/runtime-config.js +1 -0
- package/templates/apps/mobile-app/src/App.svelte.hbs +46 -117
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +2 -1
- package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +3 -2
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +58 -0
- package/templates/apps/mobile-app/src/lib/pages/profile.svelte.hbs +13 -0
- package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +16 -0
- package/templates/base/.dockerignore.hbs +4 -0
- package/templates/base/.gitignore.hbs +20 -5
- package/templates/base/Dockerfile.hbs +10 -11
- package/templates/base/docker/nginx.conf.hbs +6 -0
- package/templates/base/e2e/app.spec.ts.hbs +14 -0
- package/templates/base/hai.build.json.hbs +1 -0
- package/templates/base/package.json.hbs +25 -17
- package/templates/base/svelte.config.js.hbs +2 -0
- package/templates/base/tests/scaffold.test.ts.hbs +9 -3
- package/templates/base/vite.config.ts.hbs +11 -5
- package/templates/shared/.gitattributes +5 -0
- package/templates/shared/container-runtime-config.sh +30 -0
- package/templates/shared/hash-router.svelte.ts +83 -0
- package/templates/shared/run-e2e.mjs +65 -0
- package/templates/shared/vite-runtime-config.ts +54 -0
- package/templates/skills/AGENTS.md +15 -1
- package/templates/skills/bridges/admin/AGENTS.md +17 -1
- package/templates/skills/bridges/api/AGENTS.md +17 -3
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +35 -11
- package/templates/skills/bridges/h5/AGENTS.md +17 -1
- package/templates/skills/bridges/mobile-app/AGENTS.md +19 -3
- package/templates/skills/bridges/website/AGENTS.md +17 -1
- package/templates/skills/hai-ai/reference.md +2 -2
- package/templates/skills/hai-app-create/SKILL.md +5 -3
- package/templates/skills/hai-app-create/development.md +128 -2
- package/templates/skills/hai-app-create/reference.md +4 -4
- package/templates/skills/hai-app-review/SKILL.md +2 -2
- package/templates/skills/hai-app-tests/SKILL.md +2 -1
- package/templates/skills/hai-app-tests/reference.md +1 -1
- package/templates/skills/hai-build/SKILL.md +16 -1
- package/templates/skills/hai-core/SKILL.md +2 -1
- package/templates/skills/hai-deploy/SKILL.md +52 -252
- package/templates/skills/hai-deploy/middleware.md +88 -0
- package/templates/skills/hai-framework-sync/SKILL.md +1 -1
- package/templates/skills/hai-framework-sync/sync.md +1 -1
- package/templates/skills/hai-reach/reference.md +8 -8
- package/templates/skills/hai-spec-implement/SKILL.md +12 -9
- package/templates/skills/hai-spec-implement/changes.md +67 -0
- package/templates/skills/hai-spec-implement/templates.md +25 -3
- package/templates/skills/hai-spec-implement/workflow.md +7 -6
- package/templates/skills/hai-spec-review/SKILL.md +14 -5
- package/templates/skills/hai-spec-review/checklist.md +4 -3
- package/templates/skills/hai-storage/SKILL.md +2 -0
- package/templates/skills/hai-ui/reference.md +2 -0
- package/dist/chunk-WW2MIO6T.js +0 -992
- package/dist/chunk-WW2MIO6T.js.map +0 -1
- package/dist/chunk-X3RSYNBG.js.map +0 -1
- package/dist/cli-config-templates-QRF6B5T4.js +0 -5
- /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/AuditManagement.svelte.hbs → pages/audit.svelte.hbs} +0 -0
- /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/RoleManagement.svelte.hbs → pages/roles.svelte.hbs} +0 -0
- /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/ProfileSettings.svelte.hbs → pages/settings.svelte.hbs} +0 -0
- /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/UserManagement.svelte.hbs → pages/users.svelte.hbs} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { generateConfigFile } from './chunk-
|
|
3
|
-
import { cliM } from './chunk-
|
|
2
|
+
import { generateConfigFile, runtimeEnvironmentVariables, renderEnvironment, moduleEnvironmentVariables, parameterizeConfig } from './chunk-QYX6V7H2.js';
|
|
3
|
+
import { cliM } from './chunk-FWN5B54L.js';
|
|
4
4
|
import './chunk-2H7UOFLK.js';
|
|
5
5
|
import process4 from 'process';
|
|
6
6
|
import { cac } from 'cac';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
|
-
import path2, { resolve } from 'path';
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
8
|
+
import path2, { resolve, join, dirname } from 'path';
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
10
10
|
import { wrapAnsi } from 'fast-wrap-ansi';
|
|
11
11
|
import fse from 'fs-extra';
|
|
12
12
|
import Handlebars2 from 'handlebars';
|
|
@@ -14,10 +14,11 @@ import { core } from '@h-ai/core';
|
|
|
14
14
|
import ora from 'ora';
|
|
15
15
|
import prompts from 'prompts';
|
|
16
16
|
import { Buffer } from 'buffer';
|
|
17
|
-
import { execSync, execFileSync } from 'child_process';
|
|
18
|
-
import { randomBytes } from 'crypto';
|
|
19
|
-
import { stringify,
|
|
20
|
-
import { existsSync,
|
|
17
|
+
import { execSync, execFileSync, spawn } from 'child_process';
|
|
18
|
+
import { randomBytes, createHash } from 'crypto';
|
|
19
|
+
import { stringify, parseDocument } from 'yaml';
|
|
20
|
+
import { readFileSync, mkdirSync, mkdtempSync, existsSync, createReadStream, writeFileSync, renameSync, rmSync } from 'fs';
|
|
21
|
+
import { createRequire } from 'module';
|
|
21
22
|
|
|
22
23
|
var CAMEL_CASE_SEPARATOR_REGEX = /[-_\s]+(.)?/g;
|
|
23
24
|
var LEADING_CHAR_REGEX = /^./;
|
|
@@ -121,7 +122,7 @@ Handlebars2.registerHelper("yaml", (key, value) => stringify({ [key]: value }).t
|
|
|
121
122
|
Handlebars2.registerHelper("jsString", (value) => `'${JSON.stringify(value).slice(1, -1).replace(/\\"/g, '"').replace(/'/g, "\\'")}'`);
|
|
122
123
|
Handlebars2.registerHelper("moduleConfig", function(moduleKey, enabled) {
|
|
123
124
|
const content = generateConfigFile(moduleKey, this.moduleConfigs).trimEnd();
|
|
124
|
-
const configured = this.appType === "fullstack" && moduleKey === "iam" ? content.replace("
|
|
125
|
+
const configured = this.appType === "fullstack" && moduleKey === "iam" ? content.replace("HAI_IAM_SEEDDEFAULTDATA:true", "HAI_IAM_SEEDDEFAULTDATA:false").replace("variant: card", "variant: split") : content;
|
|
125
126
|
return enabled ? configured : `# # \u672A\u542F\u7528\uFF1A\u5B89\u88C5\u5BF9\u5E94\u6A21\u5757\u5E76\u5728\u670D\u52A1\u7AEF runtime \u4E2D\u521D\u59CB\u5316\uFF0C\u518D\u53D6\u6D88\u6240\u9700\u914D\u7F6E\u7684\u6CE8\u91CA\u3002
|
|
126
127
|
${configured.split("\n").map((line) => line ? `# ${line}` : "").join("\n")}`;
|
|
127
128
|
});
|
|
@@ -207,6 +208,7 @@ var HAI_CATALOG_PACKAGE_NAMES = [
|
|
|
207
208
|
"@h-ai/audit",
|
|
208
209
|
"@h-ai/cache",
|
|
209
210
|
"@h-ai/capacitor",
|
|
211
|
+
"@h-ai/cli",
|
|
210
212
|
"@h-ai/core",
|
|
211
213
|
"@h-ai/crypto",
|
|
212
214
|
"@h-ai/datapipe",
|
|
@@ -387,6 +389,7 @@ function buildFullstackPackageSpecifiers(useCatalogProtocol) {
|
|
|
387
389
|
function buildApiWorkspaceCatalogPackages(featureMap) {
|
|
388
390
|
const packageNames = [
|
|
389
391
|
...API_WORKSPACE_EXTERNAL_CATALOG_PACKAGE_NAMES,
|
|
392
|
+
"@h-ai/cli",
|
|
390
393
|
"@h-ai/api-client",
|
|
391
394
|
"@h-ai/api-contract",
|
|
392
395
|
"@h-ai/core",
|
|
@@ -421,7 +424,7 @@ function buildApiWorkspaceCatalogPackages(featureMap) {
|
|
|
421
424
|
return pickCatalogPackages(packageNames, FULLSTACK_CATALOG_PACKAGES);
|
|
422
425
|
}
|
|
423
426
|
function buildAppHaiCatalogPackages(options) {
|
|
424
|
-
const packageNames = [];
|
|
427
|
+
const packageNames = ["@h-ai/cli"];
|
|
425
428
|
if (options.isSvelteOnlyApp) {
|
|
426
429
|
packageNames.push("@h-ai/api-client", "@h-ai/api-contract", "@h-ai/capacitor", "@h-ai/ui");
|
|
427
430
|
return pickCatalogPackages(packageNames, HAI_CATALOG_PACKAGES);
|
|
@@ -465,9 +468,10 @@ function buildAppHaiCatalogPackages(options) {
|
|
|
465
468
|
packageNames.push("@h-ai/capacitor");
|
|
466
469
|
return pickCatalogPackages(packageNames, HAI_CATALOG_PACKAGES);
|
|
467
470
|
}
|
|
468
|
-
function buildFullstackCatalogPackages(frontends) {
|
|
471
|
+
function buildFullstackCatalogPackages(frontends, deploy = false) {
|
|
469
472
|
const packageNames = [
|
|
470
473
|
...FULLSTACK_ROOT_CATALOG_PACKAGE_NAMES,
|
|
474
|
+
"@h-ai/cli",
|
|
471
475
|
...FULLSTACK_CONTRACT_CATALOG_PACKAGE_NAMES,
|
|
472
476
|
...FULLSTACK_SERV_CATALOG_PACKAGE_NAMES,
|
|
473
477
|
...FULLSTACK_SHARED_CATALOG_PACKAGE_NAMES
|
|
@@ -478,6 +482,8 @@ function buildFullstackCatalogPackages(frontends) {
|
|
|
478
482
|
packageNames.push(...FULLSTACK_APP_CATALOG_PACKAGE_NAMES);
|
|
479
483
|
if (frontends.includes("desktop"))
|
|
480
484
|
packageNames.push(...FULLSTACK_DESKTOP_CATALOG_PACKAGE_NAMES);
|
|
485
|
+
if (deploy)
|
|
486
|
+
packageNames.push("@h-ai/deploy");
|
|
481
487
|
return pickCatalogPackages(packageNames, FULLSTACK_CATALOG_PACKAGES);
|
|
482
488
|
}
|
|
483
489
|
var FEATURE_ROUTE_DIRS = {
|
|
@@ -594,7 +600,7 @@ function buildTemplateContext(options) {
|
|
|
594
600
|
const selectedFrontends = options.frontends ?? DEFAULT_FULLSTACK_FRONTENDS;
|
|
595
601
|
let haiCatalogPackages;
|
|
596
602
|
if (isFullstack) {
|
|
597
|
-
haiCatalogPackages = buildFullstackCatalogPackages(selectedFrontends);
|
|
603
|
+
haiCatalogPackages = buildFullstackCatalogPackages(selectedFrontends, featureMap.deploy);
|
|
598
604
|
} else if (isApi) {
|
|
599
605
|
haiCatalogPackages = buildApiWorkspaceCatalogPackages(featureMap);
|
|
600
606
|
} else {
|
|
@@ -769,6 +775,16 @@ async function generateFromTemplates(projectPath, context) {
|
|
|
769
775
|
}
|
|
770
776
|
}
|
|
771
777
|
await renderDynamicFiles(root, projectPath, context);
|
|
778
|
+
await fse.copy(path2.join(root, "shared", "run-e2e.mjs"), path2.join(projectPath, "scripts", "run-e2e.mjs"));
|
|
779
|
+
await fse.copy(path2.join(root, "shared", ".gitattributes"), path2.join(projectPath, ".gitattributes"));
|
|
780
|
+
if (context.isSvelteOnlyApp) {
|
|
781
|
+
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, "docker", "19-runtime-config.sh"));
|
|
783
|
+
}
|
|
784
|
+
if (context.isSvelteOnlyApp || context.appType === "fullstack") {
|
|
785
|
+
const navigationDir = context.appType === "fullstack" ? path2.join(projectPath, "packages", `${context.projectName}-shared`, "src", "lib", "navigation") : path2.join(projectPath, "src", "lib", "navigation");
|
|
786
|
+
await fse.copy(path2.join(root, "shared", "hash-router.svelte.ts"), path2.join(navigationDir, "hash-router.svelte.ts"));
|
|
787
|
+
}
|
|
772
788
|
if (context.hasI18n) {
|
|
773
789
|
const i18nDir = path2.join(root, "i18n");
|
|
774
790
|
if (await fse.pathExists(i18nDir)) {
|
|
@@ -839,7 +855,8 @@ async function renderHbsInDir(dir, destRoot, context) {
|
|
|
839
855
|
continue;
|
|
840
856
|
}
|
|
841
857
|
const destPath = path2.join(destRoot, outRelPath);
|
|
842
|
-
const
|
|
858
|
+
const configMatch = /(?:^|[/\\])config[/\\]_([^/\\]+)\.ya?ml$/.exec(outRelPath);
|
|
859
|
+
const normalizedRendered = normalizeRenderedContent(configMatch ? parameterizeConfig(configMatch[1], rendered) : rendered, outRelPath);
|
|
843
860
|
await fse.ensureDir(path2.dirname(destPath));
|
|
844
861
|
await fse.writeFile(destPath, normalizedRendered, "utf-8");
|
|
845
862
|
}
|
|
@@ -1124,7 +1141,7 @@ async function createProject(options) {
|
|
|
1124
1141
|
});
|
|
1125
1142
|
await generateFromTemplates(projectPath, context);
|
|
1126
1143
|
{
|
|
1127
|
-
const { generateConfigFile: generateConfigFile2, generateEnvExample } = await import('./cli-config-templates-
|
|
1144
|
+
const { generateConfigFile: generateConfigFile2, generateEnvExample } = await import('./cli-config-templates-3XSFIXBP.js');
|
|
1128
1145
|
const configs = resolvedOptions.moduleConfigs;
|
|
1129
1146
|
const isApiWorkspace = resolvedOptions.appType === "api";
|
|
1130
1147
|
const isFullstack = resolvedOptions.appType === "fullstack";
|
|
@@ -1145,26 +1162,60 @@ async function createProject(options) {
|
|
|
1145
1162
|
generateConfigFile2("core", configs)
|
|
1146
1163
|
);
|
|
1147
1164
|
}
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1165
|
+
const configContents = {};
|
|
1166
|
+
for (const file of await fse.readdir(configDir)) {
|
|
1167
|
+
if (/^_.*\.yml$/.test(file))
|
|
1168
|
+
configContents[file.slice(1, -4)] = await fse.readFile(path2.join(configDir, file), "utf8");
|
|
1169
|
+
}
|
|
1170
|
+
const envFeatures = resolvedOptions.features;
|
|
1171
|
+
const envContents = generateEnvExample(envFeatures, configs, isFullstack, configContents);
|
|
1172
|
+
let envExample = isApiWorkspace ? envContents : isFullstack ? `${envContents}
|
|
1173
|
+
# First-start administrator password; must satisfy the IAM password policy.
|
|
1155
1174
|
HAI_ADMIN_DEFAULT_PASSWORD=
|
|
1156
1175
|
|
|
1157
|
-
#
|
|
1176
|
+
# Public API origin; never include credentials.
|
|
1158
1177
|
PUBLIC_API_BASE=http://127.0.0.1:3000
|
|
1159
|
-
` :
|
|
1178
|
+
` : context.isSvelteOnlyApp ? `${envContents}
|
|
1179
|
+
# Public API URL including /api/v1; never include credentials.
|
|
1180
|
+
PUBLIC_API_BASE=http://10.0.2.2:3000/api/v1
|
|
1181
|
+
` : envContents;
|
|
1182
|
+
const runtimeVariables = /* @__PURE__ */ new Map();
|
|
1183
|
+
for (const file of await fse.readdir(projectPath, { recursive: true, encoding: "utf8" })) {
|
|
1184
|
+
if (!/\.(?:ts|js|mjs|svelte|envsh)$/.test(file))
|
|
1185
|
+
continue;
|
|
1186
|
+
for (const variable of runtimeEnvironmentVariables(await fse.readFile(path2.join(projectPath, file), "utf8")))
|
|
1187
|
+
runtimeVariables.set(variable.name, variable);
|
|
1188
|
+
}
|
|
1189
|
+
const existingNames = new Set([...envExample.matchAll(/^(?:#\s*)?([A-Z][A-Z0-9_]*)=/gm)].map((match) => match[1]));
|
|
1190
|
+
const runtimeExample = renderEnvironment([...runtimeVariables.values()].filter((variable) => !existingNames.has(variable.name)));
|
|
1191
|
+
if (runtimeExample)
|
|
1192
|
+
envExample += `
|
|
1193
|
+
# \u53EF\u9009\u8FD0\u884C\u5165\u53E3\uFF08\u6309\u7528\u9014\u6CE8\u5165\uFF0C\u4E0D\u8FDB\u5165\u516C\u5171\u9ED8\u8BA4\u503C\uFF09\uFF1A
|
|
1194
|
+
${runtimeExample.split("\n").map((line) => line && !line.startsWith("#") ? `# ${line}` : line).join("\n")}
|
|
1195
|
+
`;
|
|
1160
1196
|
await writeFile(envExamplePath, envExample);
|
|
1197
|
+
const envDirectory = path2.dirname(envExamplePath);
|
|
1198
|
+
const sharedVariables = envExample.split("\n").filter((line) => /^[A-Z][A-Z0-9_]*=/.test(line));
|
|
1199
|
+
await writeFile(path2.join(envDirectory, ".env"), `# \u516C\u5171\u57FA\u7840\u914D\u7F6E\uFF08\u53EF\u63D0\u4EA4\uFF09\uFF1B\u5B8C\u6574\u5B57\u6BB5\u8BF4\u660E\u89C1 .env.example\u3002
|
|
1200
|
+
${sharedVariables.join("\n")}
|
|
1201
|
+
`);
|
|
1202
|
+
for (const [mode, environment] of [["dev", "development"], ["test", "test"], ["prod", "production"]]) {
|
|
1203
|
+
await writeFile(path2.join(envDirectory, `.env.${mode}`), `# \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
|
+
HAI_CORE_ENV=${environment}
|
|
1205
|
+
HAI_CORE_DEBUG=false
|
|
1206
|
+
HAI_CORE_LOGGING_LEVEL=${mode === "test" ? "warn" : mode === "dev" ? "debug" : "info"}
|
|
1207
|
+
`);
|
|
1208
|
+
}
|
|
1161
1209
|
if (isFullstack) {
|
|
1162
1210
|
const password = `Aa1!${randomBytes(24).toString("base64url")}`;
|
|
1163
|
-
await fse.writeFile(path2.join(projectPath, ".env"),
|
|
1164
|
-
|
|
1211
|
+
await fse.writeFile(path2.join(projectPath, ".env.local"), `# \u672C\u673A\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u63D0\u4EA4 Git\u3002
|
|
1212
|
+
HAI_ADMIN_DEFAULT_PASSWORD=${password}
|
|
1213
|
+
`, { mode: 384 });
|
|
1214
|
+
} else {
|
|
1215
|
+
await writeFile(path2.join(envDirectory, ".env.local"), "# \u672C\u673A\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u63D0\u4EA4 Git\uFF1B\u8986\u76D6\u516C\u5171\u57FA\u7840\u548C\u73AF\u5883\u914D\u7F6E\u3002\n");
|
|
1165
1216
|
}
|
|
1166
1217
|
}
|
|
1167
|
-
const { generateSkillFiles } = await import('./cli-skill-templates-
|
|
1218
|
+
const { generateSkillFiles } = await import('./cli-skill-templates-TSYEQTCY.js');
|
|
1168
1219
|
await generateSkillFiles(
|
|
1169
1220
|
projectPath,
|
|
1170
1221
|
resolvedOptions.features,
|
|
@@ -1277,7 +1328,7 @@ async function resolveOptions(options) {
|
|
|
1277
1328
|
let selectedFeatures = [];
|
|
1278
1329
|
const appTypeDefaults = APP_TYPES[selectedAppType].defaultFeatures;
|
|
1279
1330
|
if (selectedAppType === "fullstack") {
|
|
1280
|
-
selectedFeatures = resolveFeatureDependencies(appTypeDefaults);
|
|
1331
|
+
selectedFeatures = resolveFeatureDependencies([...appTypeDefaults, ...options.features?.includes("deploy") ? ["deploy"] : []]);
|
|
1281
1332
|
} else if (selectedTemplate === "custom" && !options.features && useDefaults) {
|
|
1282
1333
|
selectedFeatures = resolveFeatureDependencies(appTypeDefaults);
|
|
1283
1334
|
} else if (selectedTemplate === "custom" && !options.features) {
|
|
@@ -1685,39 +1736,44 @@ function generateAiDevelopmentReadme() {
|
|
|
1685
1736
|
|
|
1686
1737
|
\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
|
|
1687
1738
|
|
|
1739
|
+
\u4E1A\u52A1\u4EE5\u7528\u6237\u5F53\u524D\u660E\u786E\u8981\u6C42\u53CA\u5176\u6307\u5B9A\u7684\u9700\u6C42/\u8BBE\u8BA1\u6587\u6863\u4E3A\u51C6\u3002\u6A21\u677F AGENTS\u3001README\u3001skills\u3001\u751F\u6210\u4EE3\u7801\u4E0E\u6D4B\u8BD5\u4E2D\u7684\u89D2\u8272/\u6743\u9650\u3001\u6D41\u7A0B\u3001\u9650\u989D\u548C\u67B6\u6784\u662F\u53EF\u8C03\u6574\u7684\u521D\u59CB\u65B9\u6848\uFF1B\u5BA1\u67E5\u5C06\u5DEE\u5F02\u5217\u4E3A\u6A21\u677F\u9002\u914D\u9879\uFF0C\u5B9E\u65BD\u6309\u6388\u6743\u540C\u6B65\u51B2\u7A81\u4EE3\u7801\u3001\u6D4B\u8BD5\u4E0E\u8FC7\u671F\u89C4\u5219\uFF0C\u4E0D\u8981\u6C42\u628A\u9700\u6C42\u6539\u56DE\u6A21\u677F\u3002\u6846\u67B6 API \u4EE5\u5F53\u524D\u5B89\u88C5\u7248\u672C\u516C\u5F00\u5951\u7EA6\u4E3A\u51C6\uFF0C\u670D\u52A1\u7AEF\u6388\u6743\u3001\u8D44\u6E90\u9694\u79BB\u3001\u8F93\u5165\u6821\u9A8C\u548C\u51ED\u636E\u4FDD\u62A4\u4ECD\u9700\u843D\u5B9E\u3002
|
|
1740
|
+
|
|
1688
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
|
|
1689
1742
|
|
|
1690
|
-
\u6839\u636E\u6587\u6863\u89C4\u5212\u3001\u5B9E\u65BD\
|
|
1743
|
+
\u6839\u636E\u6587\u6863\u89C4\u5212\u3001\u5B9E\u65BD\u3001\u8FD4\u4FEE\u6216\u6309\u5DF2\u63D0\u4EA4\u6587\u6863\u5DEE\u5F02\u540C\u6B65\u5B9E\u73B0\u65F6\uFF0C\u4F7F\u7528 [\u6587\u6863\u5B9E\u65BD Skill](./.agents/skills/hai-spec-implement/SKILL.md)\uFF1A\u7EF4\u62A4 docs/task-list.md \u4E0E docs/tasks/\uFF0C\u8BB0\u5F55\u5DF2\u9A8C\u6536\u6587\u6863\u548C\u5B9E\u73B0\u7684 Git SHA\uFF0C\u6BD4\u8F83\u56FA\u5B9A\u76EE\u6807\u7248\u672C\u540E\u66F4\u65B0\u539F\u4EFB\u52A1\u6216\u65B0\u5EFA\u4EFB\u52A1\uFF0C\u6BCF\u9879\u72EC\u7ACB\u5206\u652F TDD\u3001\u9A8C\u8BC1\u5E76\u672C\u5730\u5408\u5E76\uFF1B\u5168\u90E8\u5DEE\u5F02\u4E0E\u6574\u4F53\u9A8C\u6536\u901A\u8FC7\u540E\u624D\u63A8\u8FDB\u57FA\u7EBF\u3002\u8FD4\u4FEE\u6CBF\u7528\u539F\u4EFB\u52A1\u4E0E\u5206\u652F\u3002\u4EC5\u89C4\u5212\u4E0D\u5F00\u59CB\u5B9E\u73B0\uFF0C\u6307\u5B9A\u4EFB\u52A1\u5148\u6838\u9A8C\u4F9D\u8D56\u3002
|
|
1691
1744
|
|
|
1692
1745
|
\u4F18\u5148\u590D\u7528\u9002\u7528\u7684 hai-framework \u516C\u5F00 API \u4E0E\u5DF2\u6709\u4E1A\u52A1\u5B9E\u73B0\u3002\u5B8C\u6210\u65F6\u8BF4\u660E\u590D\u7528\u7684\u80FD\u529B\u3001\u76F8\u5173\u80FD\u529B\u672A\u91C7\u7528\u7684\u539F\u56E0\uFF0C\u4EE5\u53CA\u5B89\u5168\u8FB9\u754C\u548C\u7C7B\u578B\u3001\u914D\u7F6E\u3001\u6587\u6848\u3001\u8C03\u7528\u65B9\u7684\u540C\u6B65\u60C5\u51B5\u3002\u8D28\u91CF\u9A8C\u8BC1\u6309\u5F53\u524D package.json \u6267\u884C typecheck \u2192 lint \u2192 build \u2192 test\uFF1BUI\u3001\u8DEF\u7531\u4E0E\u7AEF\u5230\u7AEF\u6D41\u7A0B\u53E6\u8FD0\u884C test:e2e\uFF0C\u672A\u8FD0\u884C\u9879\u8BF4\u660E\u539F\u56E0\u3002\u5B89\u5168\u4E0E\u8D28\u91CF\u89C4\u5219\u65E2\u8981\u6309\u89C4\u8303\u5BA1\u67E5\uFF0C\u4E5F\u8981\u8FD0\u884C\u95E8\u7981\u9A8C\u8BC1\u3002
|
|
1693
1746
|
|
|
1694
|
-
\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\u666E\u901A\u5F00\u53D1\u5165\u53E3\u4E0E\u89C4\u8303\u663E\u5F0F\u5408\u5E76\u5230\u65E7\u6307\u5F15\u3002
|
|
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
|
|
1695
1748
|
|
|
1696
1749
|
`;
|
|
1697
1750
|
}
|
|
1751
|
+
function generateEnvironmentReadme() {
|
|
1752
|
+
return `## \u5206\u73AF\u5883\u914D\u7F6E
|
|
1753
|
+
|
|
1754
|
+
\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
|
+
|
|
1756
|
+
| \u7528\u9014 | \u547D\u4EE4 |
|
|
1757
|
+
| --- | --- |
|
|
1758
|
+
| \u5F00\u53D1 / \u6D4B\u8BD5\u6A21\u5F0F / \u751F\u4EA7\u6A21\u5F0F\u9884\u89C8 | \`pnpm dev\` / \`dev:test\` / \`dev:prod\` |
|
|
1759
|
+
| \u7C7B\u578B\u68C0\u67E5\uFF08check \u4E3A\u522B\u540D\uFF09 | \`pnpm typecheck\` |
|
|
1760
|
+
| \u68C0\u67E5 / \u81EA\u52A8\u4FEE\u590D\u683C\u5F0F | \`pnpm lint\` / \`lint:fix\` |
|
|
1761
|
+
| \u6E90\u7801\u6784\u5EFA / \u955C\u50CF\u6784\u5EFA | \`pnpm build\` / \`pnpm build --target docker\` |
|
|
1762
|
+
| \u5355\u5143\u6D4B\u8BD5 / \u76D1\u542C / \u6D4F\u89C8\u5668\u9A8C\u6536 | \`pnpm test\` / \`test:watch\` / \`test:e2e\` |
|
|
1763
|
+
| Node \u670D\u52A1\u6309\u73AF\u5883\u542F\u52A8\uFF08\u6784\u5EFA\u540E\uFF09 | \`pnpm start\` / \`start:test\` / \`start:prod\` |
|
|
1764
|
+
|
|
1765
|
+
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
|
+
}
|
|
1698
1767
|
function generateReadme(name, appTypeLabel, pm, appType) {
|
|
1699
1768
|
const install = getInstallCommand(pm);
|
|
1700
|
-
const isStaticImage = appType === "mobile-app";
|
|
1701
|
-
const containerPort = isStaticImage ? 8080 : 3e3;
|
|
1702
|
-
const runtimeOptions = isStaticImage ? "" : ` --env-file .env --mount type=bind,source=/absolute/path/to/config,target=/app/config,readonly --mount type=volume,source=${name}-data,target=/app/data`;
|
|
1703
|
-
const embeddedDataBuild = isStaticImage ? "" : `
|
|
1704
|
-
# \u5E26 data/ \u521D\u59CB\u6570\u636E\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
|
|
1705
|
-
${getRunCommand(pm, "docker:build:data")}
|
|
1706
|
-
${getRunCommand(pm, "podman:build:data")}`;
|
|
1707
|
-
const runtimeNotes = isStaticImage ? "- \u8BE5\u6A21\u677F\u751F\u6210\u9759\u6001 Web \u955C\u50CF\uFF0C\u4E0D\u5305\u542B\u53EF\u5199\u6570\u636E\u5E93\u76EE\u5F55\u3002" : `\u4EE5\u4E0A\u4E09\u4E2A\u8FD0\u884C\u65F6\u53C2\u6570\u5747\u53EF\u6309\u9700\u5220\u9664\uFF1A
|
|
1708
|
-
|
|
1709
|
-
| \u53C2\u6570 | \u7528\u9014 | \u53EF\u9009\u6027 |
|
|
1710
|
-
| --- | --- | --- |
|
|
1711
|
-
| \`--env-file .env\` | \u6279\u91CF\u4F20\u5165\u73AF\u5883\u53D8\u91CF\uFF1B\u4E5F\u53EF\u6539\u7528\u591A\u4E2A \`-e NAME=value\`\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u548C YAML \u9ED8\u8BA4\u503C\u3002 |
|
|
1712
|
-
| \`--mount type=bind,...target=/app/config,readonly\` | \u7528\u5BBF\u4E3B\u673A\u914D\u7F6E\u76EE\u5F55\u8986\u76D6\u955C\u50CF\u5185 \`/app/config\`\uFF1B\u5FC5\u987B\u628A \`source\` \u6539\u6210\u771F\u5B9E\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\`readonly\` \u9632\u6B62\u5BB9\u5668\u4FEE\u6539\u914D\u7F6E\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u81EA\u5E26\u914D\u7F6E\u3002 |
|
|
1713
|
-
| \`--mount type=volume,...target=/app/data\` | \u521B\u5EFA\u6216\u590D\u7528 named volume\uFF0C\u6301\u4E45\u5316 SQLite \u548C\u672C\u5730\u5B58\u50A8\u3002 | \u53EF\u9009\u4F46\u751F\u4EA7\u73AF\u5883\u5EFA\u8BAE\u4FDD\u7559\uFF1B\u5220\u9664\u540E\u6570\u636E\u53EA\u5199\u5165\u5BB9\u5668\u5C42\uFF0C\u4F7F\u7528 \`--rm\` \u5220\u9664\u5BB9\u5668\u65F6\u4F1A\u4E22\u5931\u3002 |
|
|
1714
|
-
|
|
1715
|
-
- \u5E26\u6570\u636E\u6784\u5EFA\u4F1A\u628A\u5F53\u524D \`data/\`\uFF08\u4F8B\u5982\u9884\u7F6E SQLite \u6587\u4EF6\uFF09\u56FA\u5316\u4E3A\u955C\u50CF\u521D\u59CB\u5FEB\u7167\uFF1B\u8FD0\u884C\u540E\u4EA7\u751F\u7684\u65B0\u6570\u636E\u4ECD\u5E94\u5199\u5165 \`/app/data\` volume\u3002`;
|
|
1716
|
-
const testPath = isStaticImage ? "/health" : "/";
|
|
1717
1769
|
return `# ${name}
|
|
1718
1770
|
|
|
1719
1771
|
\u57FA\u4E8E hai Agent Framework \u6784\u5EFA\u7684${appTypeLabel}\u5E94\u7528\u3002
|
|
1720
1772
|
|
|
1773
|
+
## \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
|
|
1774
|
+
|
|
1775
|
+
\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"}
|
|
1776
|
+
|
|
1721
1777
|
## \u5F00\u53D1
|
|
1722
1778
|
|
|
1723
1779
|
\`\`\`bash
|
|
@@ -1725,6 +1781,8 @@ ${install}
|
|
|
1725
1781
|
${getRunCommand(pm, "dev")}
|
|
1726
1782
|
\`\`\`
|
|
1727
1783
|
|
|
1784
|
+
${generateEnvironmentReadme()}
|
|
1785
|
+
|
|
1728
1786
|
## \u6784\u5EFA
|
|
1729
1787
|
|
|
1730
1788
|
\`\`\`bash
|
|
@@ -1733,31 +1791,20 @@ ${getRunCommand(pm, "i18n:compile")}
|
|
|
1733
1791
|
${getRunCommand(pm, "preview")}
|
|
1734
1792
|
\`\`\`
|
|
1735
1793
|
|
|
1736
|
-
## \
|
|
1737
|
-
|
|
1738
|
-
### \u6784\u5EFA\u955C\u50CF
|
|
1794
|
+
## \u6784\u5EFA\u4E0E\u90E8\u7F72
|
|
1739
1795
|
|
|
1740
1796
|
\`\`\`bash
|
|
1741
|
-
# \
|
|
1742
|
-
${
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
# Docker\uFF1B\u4F7F\u7528 Podman \u65F6\u5C06 docker \u66FF\u6362\u4E3A podman
|
|
1750
|
-
docker run --rm -d --name ${name} -p ${containerPort}:${containerPort}${runtimeOptions} ${name}:latest
|
|
1751
|
-
\`\`\`
|
|
1752
|
-
|
|
1753
|
-
### \u6D4B\u8BD5\u955C\u50CF
|
|
1754
|
-
|
|
1755
|
-
\`\`\`bash
|
|
1756
|
-
curl --fail http://127.0.0.1:${containerPort}${testPath}
|
|
1757
|
-
docker stop ${name}
|
|
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
|
|
1758
1805
|
\`\`\`
|
|
1759
1806
|
|
|
1760
|
-
|
|
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
|
|
1761
1808
|
|
|
1762
1809
|
${generateAiDevelopmentReadme()}## \u6587\u6863
|
|
1763
1810
|
|
|
@@ -1782,6 +1829,8 @@ ${pm} install
|
|
|
1782
1829
|
${pm} dev
|
|
1783
1830
|
\`\`\`
|
|
1784
1831
|
|
|
1832
|
+
${generateEnvironmentReadme()}
|
|
1833
|
+
|
|
1785
1834
|
## \u8D28\u91CF\u95E8\u7981
|
|
1786
1835
|
|
|
1787
1836
|
\`\`\`bash
|
|
@@ -1792,50 +1841,20 @@ ${pm} test
|
|
|
1792
1841
|
${pm} test:e2e
|
|
1793
1842
|
\`\`\`
|
|
1794
1843
|
|
|
1795
|
-
## \
|
|
1796
|
-
|
|
1797
|
-
### \u6784\u5EFA\u955C\u50CF
|
|
1844
|
+
## \u6784\u5EFA\u4E0E\u90E8\u7F72
|
|
1798
1845
|
|
|
1799
1846
|
\`\`\`bash
|
|
1800
|
-
# \
|
|
1801
|
-
${pm}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
# \
|
|
1805
|
-
${pm}
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
### \u8FD0\u884C\u955C\u50CF
|
|
1810
|
-
|
|
1811
|
-
\`\`\`bash
|
|
1812
|
-
# \u4F7F\u7528 Podman \u65F6\u5C06 docker \u66FF\u6362\u4E3A podman
|
|
1813
|
-
docker run --rm -d --name ${name} -p 3000:3000 --env-file .env \\
|
|
1814
|
-
--mount type=bind,source=/absolute/path/to/config,target=/app/config,readonly \\
|
|
1815
|
-
--mount type=volume,source=${name}-data,target=/app/data \\
|
|
1816
|
-
${name}:latest
|
|
1817
|
-
\`\`\`
|
|
1818
|
-
|
|
1819
|
-
### \u6D4B\u8BD5\u955C\u50CF
|
|
1820
|
-
|
|
1821
|
-
\`\`\`bash
|
|
1822
|
-
curl --fail http://127.0.0.1:3000/health
|
|
1823
|
-
curl --fail http://127.0.0.1:3000/ready
|
|
1824
|
-
curl --fail --request POST http://127.0.0.1:3000/api/v1/app/info \\
|
|
1825
|
-
--header "Content-Type: application/json" --data '{}'
|
|
1826
|
-
docker stop ${name}
|
|
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
|
|
1827
1855
|
\`\`\`
|
|
1828
1856
|
|
|
1829
|
-
- \u914D\u7F6E\
|
|
1830
|
-
- \u5E26\u6570\u636E\u6784\u5EFA\u4F1A\u628A \`apps/${name}-service/data\` \u56FA\u5316\u4E3A\u955C\u50CF\u521D\u59CB\u6570\u636E\uFF1B\u751F\u4EA7\u5199\u5165\u4ECD\u5EFA\u8BAE\u4F7F\u7528 \`/app/data\` volume\u3002
|
|
1831
|
-
|
|
1832
|
-
\u4EE5\u4E0A\u4E09\u4E2A\u8FD0\u884C\u65F6\u53C2\u6570\u5747\u53EF\u6309\u9700\u5220\u9664\uFF1A
|
|
1833
|
-
|
|
1834
|
-
| \u53C2\u6570 | \u7528\u9014 | \u53EF\u9009\u6027 |
|
|
1835
|
-
| --- | --- | --- |
|
|
1836
|
-
| \`--env-file .env\` | \u6279\u91CF\u4F20\u5165\u73AF\u5883\u53D8\u91CF\uFF1B\u4E5F\u53EF\u6539\u7528\u591A\u4E2A \`-e NAME=value\`\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u548C YAML \u9ED8\u8BA4\u503C\u3002 |
|
|
1837
|
-
| \`--mount type=bind,...target=/app/config,readonly\` | \u7528\u5BBF\u4E3B\u673A\u914D\u7F6E\u76EE\u5F55\u8986\u76D6\u955C\u50CF\u5185 \`/app/config\`\uFF1B\u5FC5\u987B\u628A \`source\` \u6539\u6210\u771F\u5B9E\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\`readonly\` \u9632\u6B62\u5BB9\u5668\u4FEE\u6539\u914D\u7F6E\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u81EA\u5E26\u914D\u7F6E\u3002 |
|
|
1838
|
-
| \`--mount type=volume,...target=/app/data\` | \u521B\u5EFA\u6216\u590D\u7528 named volume\uFF0C\u6301\u4E45\u5316 SQLite \u548C\u672C\u5730\u5B58\u50A8\u3002 | \u53EF\u9009\u4F46\u751F\u4EA7\u73AF\u5883\u5EFA\u8BAE\u4FDD\u7559\uFF1B\u5220\u9664\u540E\u6570\u636E\u53EA\u5199\u5165\u5BB9\u5668\u5C42\uFF0C\u4F7F\u7528 \`--rm\` \u5220\u9664\u5BB9\u5668\u65F6\u4F1A\u4E22\u5931\u3002 |
|
|
1857
|
+
\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
|
|
1839
1858
|
|
|
1840
1859
|
## \u6269\u5C55\u5EFA\u8BAE
|
|
1841
1860
|
|
|
@@ -1853,20 +1872,6 @@ ${generateAiDevelopmentReadme()}## \u6587\u6863
|
|
|
1853
1872
|
}
|
|
1854
1873
|
function generateFullstackReadme(name, pm, frontends) {
|
|
1855
1874
|
const enabledFrontends = frontends.map((target) => FRONTEND_TARGETS[target].name).join(" / ");
|
|
1856
|
-
const webBuildCommands = frontends.includes("web") ? `
|
|
1857
|
-
# Web \u9759\u6001\u955C\u50CF\uFF08\u4E8C\u9009\u4E00\uFF09
|
|
1858
|
-
${pm} docker:build:web
|
|
1859
|
-
${pm} podman:build:web` : "";
|
|
1860
|
-
const webRunCommand = frontends.includes("web") ? `
|
|
1861
|
-
|
|
1862
|
-
# Web\uFF1BPUBLIC_API_BASE \u5728\u5BB9\u5668\u542F\u52A8\u65F6\u6CE8\u5165\uFF0C\u4E0D\u9700\u8981\u91CD\u65B0\u6784\u5EFA\u955C\u50CF
|
|
1863
|
-
docker run --rm -d --name ${name}-web -p 8080:8080 \\
|
|
1864
|
-
-e PUBLIC_API_BASE=https://api.example.com \\
|
|
1865
|
-
${name}-web:latest` : "";
|
|
1866
|
-
const webTestCommands = frontends.includes("web") ? `curl --fail http://127.0.0.1:8080/health
|
|
1867
|
-
curl --fail http://127.0.0.1:8080/runtime-config.js
|
|
1868
|
-
` : "";
|
|
1869
|
-
const containerNames = frontends.includes("web") ? `${name} ${name}-web` : name;
|
|
1870
1875
|
return `# ${name}
|
|
1871
1876
|
|
|
1872
1877
|
\u57FA\u4E8E hai Agent Framework \u6784\u5EFA\u7684\u524D\u540E\u7AEF\u5206\u79BB\u591A\u5305\u5DE5\u7A0B\u3002
|
|
@@ -1883,11 +1888,17 @@ curl --fail http://127.0.0.1:8080/runtime-config.js
|
|
|
1883
1888
|
|
|
1884
1889
|
\u5DF2\u542F\u7528\u524D\u7AEF\uFF1A${enabledFrontends || "Web / App / Desktop"}
|
|
1885
1890
|
|
|
1891
|
+
## \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
|
|
1892
|
+
|
|
1893
|
+
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
|
+
|
|
1886
1895
|
## \u9ED8\u8BA4\u7BA1\u7406\u529F\u80FD
|
|
1887
1896
|
|
|
1888
|
-
\
|
|
1897
|
+
\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
|
+
|
|
1899
|
+
\u9ED8\u8BA4\u542F\u7528 IAM\u3001reldb\u3001cache\u3001audit\u3001storage \u548C crypto\u3002\u521D\u59CB\u7BA1\u7406\u5458\u53EF\u4EE5\u7BA1\u7406\u7528\u6237\uFF08\u65B0\u589E\u3001\u7F16\u8F91\u3001\u7981\u7528\u3001\u5220\u9664\u3001\u89D2\u8272\u5206\u914D\u3001\u5934\u50CF\u4E0A\u4F20\uFF09\u3001\u67E5\u770B admin/user \u89D2\u8272\u53CA\u4EBA\u6570\u3001\u7EF4\u62A4\u89D2\u8272\u8BF4\u660E\u3001\u5206\u9875\u7B5B\u9009\u5BA1\u8BA1\u65E5\u5FD7\uFF1B\u521D\u59CB\u666E\u901A\u7528\u6237\u8FDB\u5165\u4E2A\u4EBA\u8D44\u6599\u3001\u5934\u50CF\u3001\u8BED\u8A00\u3001\u4E3B\u9898\u548C\u5BC6\u7801\u8BBE\u7F6E\u3002\u9ED8\u8BA4\u5BC6\u7801\u4FEE\u6539\u540E\u5168\u90E8\u4F1A\u8BDD\u5931\u6548\uFF0C\u9700\u8981\u91CD\u65B0\u767B\u5F55\uFF1B\u89D2\u8272\u6A21\u578B\u3001\u8BBF\u95EE\u8303\u56F4\u548C\u4F1A\u8BDD\u7B56\u7565\u7531\u4E1A\u52A1\u9700\u6C42\u51B3\u5B9A\u3002
|
|
1889
1900
|
|
|
1890
|
-
CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env\`\uFF0C\u5176\u4E2D \`HAI_ADMIN_DEFAULT_PASSWORD\` \u662F\u6BCF\u4E2A\u9879\u76EE\u72EC\u7ACB\u751F\u6210\u7684\u968F\u673A\u5F3A\u5BC6\u7801\u3002\u76F4\u63A5\u8FD0\u884C \`dev\` / \`start\` \u5373\u53EF\uFF0C\u9996\u6B21\u767B\u5F55\u4F7F\u7528\u7528\u6237\u540D \`admin\` \u548C\u8BE5\u5BC6\u7801\uFF1B\u4E5F\u53EF\u901A\u8FC7\u540C\u540D\u73AF\u5883\u53D8\u91CF\u8986\u76D6\u3002\u5BC6\u7801\u987B\u6EE1\u8DB3 \`_iam.yml\` \u7B56\u7565\uFF0C\u542F\u52A8\u5931\u8D25\u4F1A\u663E\u793A\u5177\u4F53\u6821\u9A8C\u539F\u56E0\u53CA\u9519\u8BEF\u7801\u3002\`.env\` \u5DF2\u88AB Git \u548C\u5BB9\u5668\u6784\u5EFA\u5FFD\u7565\uFF0C\`.env.example\` \u4E0D\u542B\u771F\u5B9E\u5BC6\u7801\uFF1B\u90E8\u7F72\u65F6\u8BBE\u7F6E\u76EE\u6807\u73AF\u5883\u81EA\u5DF1\u7684\u5BC6\u7801\u3002\u5DF2\u6709\u7528\u6237\u65F6\u4E0D\u4F1A\u91CD\u590D\u521B\u5EFA\u7BA1\u7406\u5458\u6216\u91CD\u8BBE\u5BC6\u7801\u3002
|
|
1901
|
+
CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env.local\`\uFF0C\u5176\u4E2D \`HAI_ADMIN_DEFAULT_PASSWORD\` \u662F\u6BCF\u4E2A\u9879\u76EE\u72EC\u7ACB\u751F\u6210\u7684\u968F\u673A\u5F3A\u5BC6\u7801\u3002\u76F4\u63A5\u8FD0\u884C \`dev\` / \`start\` \u5373\u53EF\uFF0C\u9996\u6B21\u767B\u5F55\u4F7F\u7528\u7528\u6237\u540D \`admin\` \u548C\u8BE5\u5BC6\u7801\uFF1B\u4E5F\u53EF\u901A\u8FC7\u540C\u540D\u73AF\u5883\u53D8\u91CF\u8986\u76D6\u3002\u5BC6\u7801\u987B\u6EE1\u8DB3 \`_iam.yml\` \u7B56\u7565\uFF0C\u542F\u52A8\u5931\u8D25\u4F1A\u663E\u793A\u5177\u4F53\u6821\u9A8C\u539F\u56E0\u53CA\u9519\u8BEF\u7801\u3002\`.env.local\` \u5DF2\u88AB Git \u548C\u5BB9\u5668\u6784\u5EFA\u5FFD\u7565\uFF0C\`.env.example\` \u4E0D\u542B\u771F\u5B9E\u5BC6\u7801\uFF1B\u90E8\u7F72\u65F6\u8BBE\u7F6E\u76EE\u6807\u73AF\u5883\u81EA\u5DF1\u7684\u5BC6\u7801\u3002\u5DF2\u6709\u7528\u6237\u65F6\u4E0D\u4F1A\u91CD\u590D\u521B\u5EFA\u7BA1\u7406\u5458\u6216\u91CD\u8BBE\u5BC6\u7801\u3002
|
|
1891
1902
|
|
|
1892
1903
|
\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
|
|
1893
1904
|
|
|
@@ -1899,82 +1910,42 @@ CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env\`\uFF0C\u5176\u4E2D \`HAI_
|
|
|
1899
1910
|
|
|
1900
1911
|
\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
|
|
1901
1912
|
|
|
1902
|
-
\u81EA\u52A8\u751F\u6210\u7684 \`.env\` \
|
|
1913
|
+
\u81EA\u52A8\u751F\u6210\u7684 \`.env\` \u5305\u542B\u5DF2\u542F\u7528\u6A21\u5757\u7684\u73AF\u5883\u53C2\u6570\uFF1BYAML \u663E\u5F0F\u5F15\u7528\u76F8\u540C\u53D8\u91CF\u5E76\u4FDD\u7559\u672C\u5730\u56DE\u9000\u3002\u7F16\u8F91\u73AF\u5883\u53C2\u6570\u4F18\u5148\u4FEE\u6539\u5BF9\u5E94 \`.env\`\uFF1B\u672A\u542F\u7528\u6A21\u5757\u4ECD\u4FDD\u7559\u6CE8\u91CA\u3002
|
|
1903
1914
|
|
|
1904
1915
|
\u6269\u5C55\u65F6\u5148\u5728\u670D\u52A1\u7AEF workspace \u5B89\u88C5\u9700\u8981\u7684\u5305\uFF0C\u5728 \`src/server/runtime.ts\` \u6309\u6A21\u5757\u516C\u5F00 Schema \u6821\u9A8C\u914D\u7F6E\u3001\u521D\u59CB\u5316\u5E76\u53CD\u5E8F\u5173\u95ED\uFF0C\u518D\u63A5\u5165 contract/procedure \u548C\u754C\u9762\u3002\u6574\u4EFD\u6CE8\u91CA\u6587\u4EF6\u5148\u53BB\u6389\u4E00\u5C42\u5916\u5C42\u6CE8\u91CA\uFF0C\u53EF\u9009\u5757\u7EE7\u7EED\u6309\u9700\u5F00\u542F\uFF1B\u914D\u7F6E\u6587\u4EF6\u5B58\u5728\u4E0D\u4EE3\u8868\u4E1A\u52A1\u529F\u80FD\u5DF2\u63A5\u5165\u3002\`crypto.init()\` \u65E0\u914D\u7F6E\u53C2\u6570\uFF0Cdatapipe \u4E3A\u7EAF\u51FD\u6570\uFF0C\u4E0D\u751F\u6210\u865A\u6784 YAML\u3002
|
|
1905
1916
|
|
|
1906
1917
|
\u73AF\u5883\u53D8\u91CF\u53EA\u8986\u76D6 YAML \u5DF2\u6709\u8DEF\u5F84\uFF0C\u6CE8\u91CA\u952E\u987B\u5148\u542F\u7528\uFF0C\u6216\u7528 \`HAI_DB\` / \`HAI_CACHE\` \u7B49\u6574\u4F53\u5BF9\u8C61\u8986\u76D6\u3002camelCase \u4E0D\u62C6\u8BCD\uFF0C\u4F8B\u5982 \`connectTimeout\` \u5BF9\u5E94 \`HAI_CACHE_CONNECTTIMEOUT\`\u3002\u4FEE\u6539\u914D\u7F6E\u540E\u91CD\u542F\u670D\u52A1\uFF1B\u8BED\u8A00\u5217\u8868\u5728\u5404\u524D\u7AEF/shared \u7684 \`project.inlang/settings.json\` \u4E2D\u540C\u6B65\u3002\`**/data/\` \u5FFD\u7565\u4EFB\u610F\u5C42\u7EA7\u7684\u6570\u636E\u76EE\u5F55\u53CA \`.gitkeep\`\uFF0C\u8FD0\u884C\u6570\u636E\u4E0D\u5165 Git\uFF0C\u8FD0\u884C\u65F6\u81EA\u52A8\u521B\u5EFA\u76EE\u5F55\u3002
|
|
1907
1918
|
|
|
1908
|
-
|
|
1919
|
+
${generateEnvironmentReadme()}
|
|
1920
|
+
|
|
1921
|
+
## \u5F00\u53D1\u4E0E\u6784\u5EFA
|
|
1909
1922
|
|
|
1910
1923
|
\`\`\`bash
|
|
1911
1924
|
${pm} install
|
|
1912
1925
|
${pm} dev
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
${pm}
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
${pm} package
|
|
1919
|
-
|
|
1920
|
-
# \u90E8\u7F72\u5165\u53E3\uFF1A\u9ED8\u8BA4\u6267\u884C package\uFF0C\u53EF\u66FF\u6362\u4E3A\u56E2\u961F\u81EA\u5DF1\u7684 CI/CD \u53D1\u5E03\u811A\u672C
|
|
1921
|
-
${pm} deploy
|
|
1922
|
-
\`\`\`
|
|
1923
|
-
|
|
1924
|
-
## \u5BB9\u5668\u8FD0\u884C
|
|
1925
|
-
|
|
1926
|
-
\u9ED8\u8BA4\u955C\u50CF\u76F4\u63A5\u8FD0\u884C API Service\uFF0C\u5916\u90E8\u7AEF\u53E3\u4E3A 3000\u3002
|
|
1927
|
-
|
|
1928
|
-
### \u6784\u5EFA\u955C\u50CF
|
|
1929
|
-
|
|
1930
|
-
\`\`\`bash
|
|
1931
|
-
# Service \u666E\u901A\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
|
|
1932
|
-
${pm} docker:build
|
|
1933
|
-
${pm} podman:build
|
|
1934
|
-
|
|
1935
|
-
# \u5C06 packages/${name}-serv/data \u4F5C\u4E3A\u955C\u50CF\u521D\u59CB\u6570\u636E\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
|
|
1936
|
-
${pm} docker:build:data
|
|
1937
|
-
${pm} podman:build:data
|
|
1938
|
-
${webBuildCommands}
|
|
1926
|
+
${pm} typecheck
|
|
1927
|
+
${pm} build
|
|
1928
|
+
${pm} test
|
|
1929
|
+
${pm} test:e2e
|
|
1930
|
+
${frontends.includes("desktop") ? `${pm} build --target desktop --platform windows # Windows \u4E3B\u673A\uFF1BLinux/macOS \u5728\u5BF9\u5E94\u4E3B\u673A\u6784\u5EFA` : ""}
|
|
1939
1931
|
\`\`\`
|
|
1940
1932
|
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
\`\`\`bash
|
|
1944
|
-
# Service\uFF1B\u4F7F\u7528 Podman \u65F6\u5C06 docker \u66FF\u6362\u4E3A podman
|
|
1945
|
-
docker run --rm -d --name ${name} -p 3000:3000 --env-file .env \\
|
|
1946
|
-
--mount type=bind,source=/absolute/path/to/config,target=/app/config,readonly \\
|
|
1947
|
-
--mount type=volume,source=${name}-data,target=/app/data \\
|
|
1948
|
-
${name}:latest
|
|
1949
|
-
${webRunCommand}
|
|
1950
|
-
\`\`\`
|
|
1933
|
+
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
|
|
1951
1934
|
|
|
1952
|
-
|
|
1935
|
+
## \u6784\u5EFA\u4E0E\u90E8\u7F72
|
|
1953
1936
|
|
|
1954
1937
|
\`\`\`bash
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
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
|
|
1958
1946
|
\`\`\`
|
|
1959
1947
|
|
|
1960
|
-
|
|
1961
|
-
- \u5E26\u6570\u636E\u6784\u5EFA\u4F1A\u628A \`packages/${name}-serv/data\` \u56FA\u5316\u4E3A\u955C\u50CF\u521D\u59CB\u6570\u636E\uFF1B\u6301\u4E45\u5316\u5199\u5165\u4ECD\u5E94\u4F7F\u7528 volume\u3002
|
|
1962
|
-
- Service \u7531 \`@h-ai/serv\` \u76F4\u63A5\u5BF9\u5916\u76D1\u542C\uFF0C\u4E0D\u989D\u5916\u542F\u52A8 Web \u8F6C\u53D1\u5C42\u3002
|
|
1963
|
-
${frontends.includes("web") ? `- Web \u4F7F\u7528\u72EC\u7ACB\u9759\u6001\u955C\u50CF\u5E76\u652F\u6301\u4E00\u6B21\u6784\u5EFA\u3001\u591A\u73AF\u5883\u90E8\u7F72\uFF1B\u6784\u5EFA\u53EA\u9700\u8FD0\u884C \`${pm} docker:build:web\`\uFF0C\u542F\u52A8\u5BB9\u5668\u65F6\u901A\u8FC7 \`-e PUBLIC_API_BASE=https://api.example.com\` \u6307\u5411 Service \u5BF9\u5916\u5730\u5740\u3002\u672A\u8BBE\u7F6E\u65F6\u9ED8\u8BA4\u4F7F\u7528 Web \u9875\u9762\u81EA\u8EAB origin\u3002` : ""}
|
|
1964
|
-
|
|
1965
|
-
\u4EE5\u4E0A\u4E09\u4E2A Service \u8FD0\u884C\u65F6\u53C2\u6570\u5747\u53EF\u6309\u9700\u5220\u9664\uFF1A
|
|
1966
|
-
|
|
1967
|
-
| \u53C2\u6570 | \u7528\u9014 | \u53EF\u9009\u6027 |
|
|
1968
|
-
| --- | --- | --- |
|
|
1969
|
-
| \`--env-file .env\` | \u6279\u91CF\u4F20\u5165\u73AF\u5883\u53D8\u91CF\uFF1B\u4E5F\u53EF\u6539\u7528\u591A\u4E2A \`-e NAME=value\`\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u548C YAML \u9ED8\u8BA4\u503C\u3002 |
|
|
1970
|
-
| \`--mount type=bind,...target=/app/config,readonly\` | \u7528\u5BBF\u4E3B\u673A\u914D\u7F6E\u76EE\u5F55\u8986\u76D6\u955C\u50CF\u5185 \`/app/config\`\uFF1B\u5FC5\u987B\u628A \`source\` \u6539\u6210\u771F\u5B9E\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\`readonly\` \u9632\u6B62\u5BB9\u5668\u4FEE\u6539\u914D\u7F6E\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u81EA\u5E26\u914D\u7F6E\u3002 |
|
|
1971
|
-
| \`--mount type=volume,...target=/app/data\` | \u521B\u5EFA\u6216\u590D\u7528 named volume\uFF0C\u6301\u4E45\u5316 SQLite \u548C\u672C\u5730\u5B58\u50A8\u3002 | \u53EF\u9009\u4F46\u751F\u4EA7\u73AF\u5883\u5EFA\u8BAE\u4FDD\u7559\uFF1B\u5220\u9664\u540E\u6570\u636E\u53EA\u5199\u5165\u5BB9\u5668\u5C42\uFF0C\u4F7F\u7528 \`--rm\` \u5220\u9664\u5BB9\u5668\u65F6\u4F1A\u4E22\u5931\u3002 |
|
|
1972
|
-
|
|
1973
|
-
\u524D\u7AEF\u5355\u72EC\u4EA4\u4ED8\u547D\u4EE4\uFF1A
|
|
1974
|
-
|
|
1975
|
-
- Web\uFF1A\`${pm} --filter ${name}-web build\` / \`${pm} --filter ${name}-web package\`
|
|
1976
|
-
- App\uFF1A\`${pm} --filter ${name}-app package\`\uFF0CAndroid/iOS \u53EF\u7528 \`cap:build:android:debug\` / \`cap:build:android:release\` / \`cap:build:ios\`
|
|
1977
|
-
- Desktop\uFF1A\`${pm} --filter ${name}-desktop tauri:dev\` / \`${pm} --filter ${name}-desktop package\`
|
|
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
|
|
1978
1949
|
|
|
1979
1950
|
## i18n \u4E0E shared \u534F\u540C
|
|
1980
1951
|
|
|
@@ -2118,7 +2089,7 @@ async function addModule(options) {
|
|
|
2118
2089
|
core.logger.info(`\u53EF\u7528\u6A21\u5757: ${Object.keys(MODULE_MAP).join(", ")}`);
|
|
2119
2090
|
return;
|
|
2120
2091
|
}
|
|
2121
|
-
const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-
|
|
2092
|
+
const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-TSYEQTCY.js');
|
|
2122
2093
|
if (moduleDef.packages.every((p) => project.haiPackages.includes(p))) {
|
|
2123
2094
|
const generatedFiles2 = await generateProjectAiSupport(
|
|
2124
2095
|
cwd,
|
|
@@ -2224,21 +2195,510 @@ async function addModule(options) {
|
|
|
2224
2195
|
throw error;
|
|
2225
2196
|
}
|
|
2226
2197
|
}
|
|
2198
|
+
async function run(command, args, cwd, quiet = false) {
|
|
2199
|
+
await new Promise((resolve3, reject) => {
|
|
2200
|
+
const env = Object.fromEntries(Object.entries(process4.env).filter(([key]) => !/^(?:HAI_|PUBLIC_|VITE_)/.test(key)));
|
|
2201
|
+
env.NODE_ENV = "production";
|
|
2202
|
+
if (env.CI === "1" || env.CI === "0")
|
|
2203
|
+
env.CI = env.CI === "1" ? "true" : "false";
|
|
2204
|
+
const child = spawn(command, args, { cwd, env, windowsHide: true, stdio: quiet ? "ignore" : "inherit", shell: false });
|
|
2205
|
+
child.once("error", reject);
|
|
2206
|
+
child.once("exit", (code) => code === 0 ? resolve3() : reject(new Error(cliM("cli_build_failed", { params: { command, code: String(code) } }))));
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
async function pnpm(args, cwd) {
|
|
2210
|
+
const entry = process4.env.npm_execpath;
|
|
2211
|
+
if (entry && /(?:^|[/\\])pnpm(?:-native)?(?:\.exe|\.[cm]?js)?$/i.test(entry))
|
|
2212
|
+
return /\.[cm]?js$/i.test(entry) ? run(process4.execPath, [entry, ...args], cwd) : run(entry, args, cwd);
|
|
2213
|
+
if (process4.platform === "win32") {
|
|
2214
|
+
const directories = (process4.env.PATH ?? "").split(";");
|
|
2215
|
+
for (const directory of directories) {
|
|
2216
|
+
const executable = join(directory, "pnpm.exe");
|
|
2217
|
+
if (existsSync(executable))
|
|
2218
|
+
return run(executable, args, cwd);
|
|
2219
|
+
for (const relative of ["node_modules/pnpm/bin/pnpm.cjs", "pnpm.cjs"]) {
|
|
2220
|
+
const candidate = join(directory, relative);
|
|
2221
|
+
if (existsSync(candidate))
|
|
2222
|
+
return run(process4.execPath, [candidate, ...args], cwd);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
throw new Error(cliM("cli_build_use_pnpm"));
|
|
2226
|
+
}
|
|
2227
|
+
return run("pnpm", args, cwd);
|
|
2228
|
+
}
|
|
2229
|
+
async function buildCommand(options) {
|
|
2230
|
+
const cwd = resolve(options.cwd ?? process4.cwd());
|
|
2231
|
+
const target = options.target ?? "compile";
|
|
2232
|
+
if (!["compile", "docker", "desktop"].includes(target))
|
|
2233
|
+
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)) {
|
|
2235
|
+
throw new Error(cliM("cli_build_options_invalid"));
|
|
2236
|
+
}
|
|
2237
|
+
if (target === "compile") {
|
|
2238
|
+
await pnpm(["run", "compile"], cwd);
|
|
2239
|
+
return;
|
|
2240
|
+
}
|
|
2241
|
+
const metadata = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
2242
|
+
const appVersion = metadata.version;
|
|
2243
|
+
if (typeof appVersion !== "string" || appVersion.length > 80 || !/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9a-z-]+(?:\.[0-9a-z-]+)*)?(?:\+[0-9a-z-]+(?:\.[0-9a-z-]+)*)?$/i.test(appVersion))
|
|
2244
|
+
throw new Error(cliM("cli_build_version_invalid"));
|
|
2245
|
+
const config = JSON.parse(readFileSync(join(cwd, "hai.build.json"), "utf8"));
|
|
2246
|
+
if (target === "desktop") {
|
|
2247
|
+
const platforms = { win32: "windows", linux: "linux", darwin: "macos" };
|
|
2248
|
+
const host = platforms[process4.platform];
|
|
2249
|
+
const platform = options.platform ?? host;
|
|
2250
|
+
const arch = options.arch ?? process4.arch;
|
|
2251
|
+
if (!platform || platform !== host || !["x64", "arm64"].includes(arch))
|
|
2252
|
+
throw new Error(cliM("cli_build_native_host"));
|
|
2253
|
+
if (!config.desktop?.length)
|
|
2254
|
+
throw new Error(cliM("cli_build_desktop_missing"));
|
|
2255
|
+
const rustTarget = `${arch === "x64" ? "x86_64" : "aarch64"}-${platform === "windows" ? "pc-windows-msvc" : platform === "macos" ? "apple-darwin" : "unknown-linux-gnu"}`;
|
|
2256
|
+
const bundles = platform === "windows" ? "nsis" : platform === "macos" ? "app,dmg" : "deb,appimage";
|
|
2257
|
+
for (const directory of config.desktop)
|
|
2258
|
+
await pnpm(["exec", "tauri", "build", "--target", rustTarget, "--bundles", bundles, "--config", JSON.stringify({ version: appVersion })], resolve(cwd, directory));
|
|
2259
|
+
core.logger.info(cliM("cli_build_native_ready", { params: { target: rustTarget } }));
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
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))) {
|
|
2263
|
+
throw new Error(cliM("cli_build_images_missing"));
|
|
2264
|
+
}
|
|
2265
|
+
const candidates = options.runtime && options.runtime !== "auto" ? [options.runtime] : ["podman", "docker"];
|
|
2266
|
+
if (candidates.some((runtime2) => !["podman", "docker"].includes(runtime2)))
|
|
2267
|
+
throw new Error(cliM("cli_build_runtime_missing"));
|
|
2268
|
+
let runtime;
|
|
2269
|
+
for (const candidate of candidates) {
|
|
2270
|
+
try {
|
|
2271
|
+
await run(candidate, ["version"], cwd, true);
|
|
2272
|
+
runtime = candidate;
|
|
2273
|
+
break;
|
|
2274
|
+
} catch {
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
if (!runtime)
|
|
2278
|
+
throw new Error(cliM("cli_build_runtime_missing"));
|
|
2279
|
+
if (options.sqlitePrebuildMirror) {
|
|
2280
|
+
const url = new URL(options.sqlitePrebuildMirror);
|
|
2281
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash)
|
|
2282
|
+
throw new Error(cliM("cli_build_mirror_invalid"));
|
|
2283
|
+
}
|
|
2284
|
+
const buildId = `${appVersion}-${Date.now()}-${randomBytes(3).toString("hex")}`;
|
|
2285
|
+
const artifact = { version: 2, appVersion, buildId, images: [] };
|
|
2286
|
+
const parent = join(cwd, ".hai/build/docker");
|
|
2287
|
+
mkdirSync(parent, { recursive: true });
|
|
2288
|
+
const staging = mkdtempSync(join(parent, ".building-"));
|
|
2289
|
+
const built = [];
|
|
2290
|
+
try {
|
|
2291
|
+
for (const image of config.images) {
|
|
2292
|
+
if (!["app", "web"].includes(image.name))
|
|
2293
|
+
throw new Error(cliM("cli_build_images_missing"));
|
|
2294
|
+
const context = resolve(cwd, image.context ?? ".");
|
|
2295
|
+
const imageRef = `localhost/hai-${basenameSafe(cwd)}-${image.name}:${buildId.toLowerCase().replaceAll("+", "_")}`;
|
|
2296
|
+
const args = ["build", "-f", resolve(context, image.dockerfile ?? "Dockerfile"), "-t", imageRef];
|
|
2297
|
+
if (options.network !== void 0)
|
|
2298
|
+
args.push("--network", options.network);
|
|
2299
|
+
const ignore = `${resolve(context, image.dockerfile ?? "Dockerfile")}.dockerignore`;
|
|
2300
|
+
if (runtime === "podman" && existsSync(ignore))
|
|
2301
|
+
args.push("--ignorefile", ignore);
|
|
2302
|
+
if (runtime === "podman") {
|
|
2303
|
+
args.push("--format", "docker", "--jobs", "1", ...options.httpProxy === false ? ["--http-proxy=false"] : []);
|
|
2304
|
+
if (options.httpProxy !== false) {
|
|
2305
|
+
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "FTP_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "ftp_proxy", "no_proxy"]) {
|
|
2306
|
+
if (process4.env[key])
|
|
2307
|
+
args.push("--build-arg", `${key}=${process4.env[key]}`);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
if (image.target)
|
|
2312
|
+
args.push("--target", image.target);
|
|
2313
|
+
if (options.sqlitePrebuildMirror)
|
|
2314
|
+
args.push("--build-arg", `SQLITE_PREBUILD_MIRROR=${options.sqlitePrebuildMirror}`);
|
|
2315
|
+
built.push(imageRef);
|
|
2316
|
+
await run(runtime, [...args, context], cwd);
|
|
2317
|
+
const file = `${image.name}-${appVersion}.tar`;
|
|
2318
|
+
await run(runtime, ["save", ...runtime === "podman" ? ["--format", "docker-archive"] : [], "-o", join(staging, file), imageRef], cwd);
|
|
2319
|
+
const hash = createHash("sha256");
|
|
2320
|
+
for await (const chunk of createReadStream(join(staging, file)))
|
|
2321
|
+
hash.update(chunk);
|
|
2322
|
+
artifact.images.push({ name: image.name, file, imageRef, sha256: hash.digest("hex"), ...image.containerPort ? { containerPort: image.containerPort } : {} });
|
|
2323
|
+
}
|
|
2324
|
+
const output = join(parent, buildId);
|
|
2325
|
+
writeFileSync(join(staging, "manifest.json"), `${JSON.stringify(artifact, null, 2)}
|
|
2326
|
+
`);
|
|
2327
|
+
renameSync(staging, output);
|
|
2328
|
+
writeFileSync(join(parent, `latest-${buildId}`), `${buildId}
|
|
2329
|
+
`);
|
|
2330
|
+
renameSync(join(parent, `latest-${buildId}`), join(parent, "latest"));
|
|
2331
|
+
core.logger.info(cliM("cli_build_ready", { params: { path: output, runtime } }));
|
|
2332
|
+
} catch (error) {
|
|
2333
|
+
for (const imageRef of built) {
|
|
2334
|
+
try {
|
|
2335
|
+
await run(runtime, ["image", "rm", imageRef], cwd, true);
|
|
2336
|
+
} catch {
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
throw error;
|
|
2340
|
+
} finally {
|
|
2341
|
+
rmSync(staging, { recursive: true, force: true });
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
function basenameSafe(directory) {
|
|
2345
|
+
return directory.slice(dirname(directory).length + 1).toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+/, "") || "app";
|
|
2346
|
+
}
|
|
2347
|
+
function displayDeployValue(name, value) {
|
|
2348
|
+
if (/PASSWORD|SECRET|TOKEN|APIKEY|PRIVATEKEY|ACCESSKEY|AUTHORIZATION/i.test(name.replace(/[^a-z0-9]/gi, "")) || /(?:^|_)PASS$/i.test(name) || value.includes("PRIVATE KEY"))
|
|
2349
|
+
return cliM("cli_deploy_secret");
|
|
2350
|
+
try {
|
|
2351
|
+
const parsed2 = JSON.parse(value);
|
|
2352
|
+
if (parsed2 && typeof parsed2 === "object") {
|
|
2353
|
+
const sanitize = (object) => {
|
|
2354
|
+
if (Array.isArray(object))
|
|
2355
|
+
return object.map(sanitize);
|
|
2356
|
+
if (object && typeof object === "object")
|
|
2357
|
+
return Object.fromEntries(Object.entries(object).map(([key, item]) => [key, /PASSWORD|SECRET|TOKEN|APIKEY|PRIVATEKEY|ACCESSKEY|AUTHORIZATION/i.test(key.replace(/[^a-z0-9]/gi, "")) || /(?:^|_)PASS$/i.test(key) ? cliM("cli_deploy_secret") : typeof item === "string" ? displayDeployValue(key, item) : sanitize(item)]));
|
|
2358
|
+
return object;
|
|
2359
|
+
};
|
|
2360
|
+
return JSON.stringify(sanitize(parsed2));
|
|
2361
|
+
}
|
|
2362
|
+
} catch {
|
|
2363
|
+
}
|
|
2364
|
+
try {
|
|
2365
|
+
const url = new URL(value);
|
|
2366
|
+
if (url.username || url.password) {
|
|
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, "***");
|
|
2373
|
+
}
|
|
2374
|
+
return url.toString();
|
|
2375
|
+
} catch {
|
|
2376
|
+
return value;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
var CLOUD_FIELDS = {
|
|
2380
|
+
"neon": ["apiKey"],
|
|
2381
|
+
"upstash": ["email", "apiKey"],
|
|
2382
|
+
"cloudflare-r2": ["accountId", "apiToken", "accessKeyId", "secretAccessKey"],
|
|
2383
|
+
"resend": ["apiKey", "from"],
|
|
2384
|
+
"aliyun": ["accessKeyId", "accessKeySecret", "signName"]
|
|
2385
|
+
};
|
|
2386
|
+
function targetConfig(requirement, environment) {
|
|
2387
|
+
const result = {};
|
|
2388
|
+
const parent = environment.targetValues[requirement.envPrefix];
|
|
2389
|
+
if (parent) {
|
|
2390
|
+
try {
|
|
2391
|
+
Object.assign(result, JSON.parse(parent));
|
|
2392
|
+
} catch {
|
|
2393
|
+
return {};
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
for (const [key, value] of Object.entries(environment.targetValues)) {
|
|
2397
|
+
if (key.startsWith(`${requirement.envPrefix}_`))
|
|
2398
|
+
result[key.slice(requirement.envPrefix.length + 1).toLowerCase()] = value;
|
|
2399
|
+
}
|
|
2400
|
+
return result;
|
|
2401
|
+
}
|
|
2402
|
+
function canKeepMiddleware(requirement, backend, environment) {
|
|
2403
|
+
const target = targetConfig(requirement, environment);
|
|
2404
|
+
if (requirement.service === "ldap" && backend.backend === "disabled")
|
|
2405
|
+
return environment.targetValues.HAI_IAM_LOGIN_LDAP === "false";
|
|
2406
|
+
if (requirement.service === "ldap") {
|
|
2407
|
+
if (!Object.keys(target).length)
|
|
2408
|
+
return false;
|
|
2409
|
+
} else if (target.type !== backend.backend) {
|
|
2410
|
+
return false;
|
|
2411
|
+
}
|
|
2412
|
+
return backend.fields.filter((field) => field.required && !field.secret).every((field) => {
|
|
2413
|
+
if ((target.url || requirement.config.url && Object.keys(target).length) && ["host", "port"].includes(field.name))
|
|
2414
|
+
return true;
|
|
2415
|
+
return target[field.name] !== void 0 && target[field.name] !== "" || target[field.name.toLowerCase()] !== void 0 && target[field.name.toLowerCase()] !== "";
|
|
2416
|
+
}) && backend.fields.filter((field) => field.required && field.secret).every((field) => Boolean(requirement.config[field.name] || environment.values[`${requirement.envPrefix}_${field.name.toUpperCase()}`]));
|
|
2417
|
+
}
|
|
2418
|
+
async function ask(question) {
|
|
2419
|
+
const answer = await prompts({ ...question, name: "value" });
|
|
2420
|
+
if (answer.value === void 0)
|
|
2421
|
+
throw new Error(cliM("cli_deploy_cancelled"));
|
|
2422
|
+
return answer.value;
|
|
2423
|
+
}
|
|
2424
|
+
function validateField(value, field) {
|
|
2425
|
+
if (value === "")
|
|
2426
|
+
return field.required ? cliM("cli_deploy_required") : true;
|
|
2427
|
+
if (field.kind === "port")
|
|
2428
|
+
return /^\d+$/.test(value) && Number(value) >= 1 && Number(value) <= 65535 || cliM("cli_deploy_invalid_value");
|
|
2429
|
+
if (field.kind === "number")
|
|
2430
|
+
return /^\d+$/.test(value) && Number(value) >= (field.minimum ?? 0) && Number(value) <= (field.maximum ?? Number.MAX_SAFE_INTEGER) || cliM("cli_deploy_invalid_value");
|
|
2431
|
+
if (field.kind === "boolean")
|
|
2432
|
+
return ["true", "false"].includes(value) || cliM("cli_deploy_invalid_value");
|
|
2433
|
+
if (field.kind === "url") {
|
|
2434
|
+
try {
|
|
2435
|
+
return Boolean(new URL(value).protocol);
|
|
2436
|
+
} catch {
|
|
2437
|
+
return cliM("cli_deploy_invalid_value");
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
if (field.name === "ssl") {
|
|
2441
|
+
const parsed2 = parseDocument(value);
|
|
2442
|
+
const ssl = parsed2.errors.length ? void 0 : parsed2.toJS();
|
|
2443
|
+
return typeof ssl === "boolean" || ["require", "prefer", "allow", "disable"].includes(String(ssl)) || ssl !== null && typeof ssl === "object" && !Array.isArray(ssl) || cliM("cli_deploy_invalid_value");
|
|
2444
|
+
}
|
|
2445
|
+
if (field.name === "headers") {
|
|
2446
|
+
try {
|
|
2447
|
+
const headers = JSON.parse(value);
|
|
2448
|
+
return headers !== null && typeof headers === "object" && !Array.isArray(headers) && Object.values(headers).every((item) => typeof item === "string") || cliM("cli_deploy_invalid_value");
|
|
2449
|
+
} catch {
|
|
2450
|
+
return cliM("cli_deploy_invalid_value");
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
if (field.name === "method")
|
|
2454
|
+
return ["POST", "PUT"].includes(value) || cliM("cli_deploy_invalid_value");
|
|
2455
|
+
return true;
|
|
2456
|
+
}
|
|
2457
|
+
function ensureConfigNode(appDir, requirement, backend) {
|
|
2458
|
+
if (requirement.service === "ldap" && backend === "disabled")
|
|
2459
|
+
return;
|
|
2460
|
+
const directory = join(appDir, "config");
|
|
2461
|
+
mkdirSync(directory, { recursive: true });
|
|
2462
|
+
const path7 = join(directory, `_${requirement.configName}.yml`);
|
|
2463
|
+
const document = parseDocument(existsSync(path7) ? readFileSync(path7, "utf8") : "{}\n");
|
|
2464
|
+
if (document.errors.length)
|
|
2465
|
+
throw new Error(cliM("cli_deploy_invalid_value"));
|
|
2466
|
+
if (requirement.configPath.length && !document.hasIn(requirement.configPath))
|
|
2467
|
+
document.setIn(requirement.configPath, requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service, type: backend } : {});
|
|
2468
|
+
if (requirement.service === "ldap") {
|
|
2469
|
+
if (!document.hasIn(["login"]))
|
|
2470
|
+
document.setIn(["login"], {});
|
|
2471
|
+
if (!document.hasIn(["login", "ldap"]))
|
|
2472
|
+
document.setIn(["login", "ldap"], `\${HAI_IAM_LOGIN_LDAP:false}`);
|
|
2473
|
+
}
|
|
2474
|
+
const content = document.toString();
|
|
2475
|
+
if (!existsSync(path7) || readFileSync(path7, "utf8") !== content)
|
|
2476
|
+
writeFileSync(path7, content);
|
|
2477
|
+
}
|
|
2478
|
+
function connectionEnv(requirement, backend, config) {
|
|
2479
|
+
if (requirement.service === "ldap")
|
|
2480
|
+
return backend === "disabled" ? { HAI_IAM_LOGIN_LDAP: "false" } : { HAI_IAM_LOGIN_LDAP: "true", [requirement.envPrefix]: JSON.stringify(config) };
|
|
2481
|
+
return { [requirement.envPrefix]: JSON.stringify({ ...config, type: backend }) };
|
|
2482
|
+
}
|
|
2483
|
+
async function configureDeployMiddleware(module, scan, environment, config, yes = false) {
|
|
2484
|
+
const modes = {};
|
|
2485
|
+
const services = {};
|
|
2486
|
+
const existingServices = config.services && typeof config.services === "object" ? config.services : {};
|
|
2487
|
+
const provider = config.provider;
|
|
2488
|
+
for (const requirement of scan.middlewares ?? []) {
|
|
2489
|
+
const choices = module.DEPLOY_MIDDLEWARE[requirement.service];
|
|
2490
|
+
const current = choices.find((item) => item.backend === requirement.backend);
|
|
2491
|
+
if (!current)
|
|
2492
|
+
throw new Error(cliM("cli_deploy_cloud_unsupported", { params: { backend: requirement.backend } }));
|
|
2493
|
+
core.logger.info(cliM("cli_deploy_middleware", { params: { service: requirement.service, backend: current.label, purpose: module.describeMiddlewareBackend(current), source: requirement.source } }));
|
|
2494
|
+
const canKeep = canKeepMiddleware(requirement, current, environment);
|
|
2495
|
+
const mode = yes ? canKeep ? "keep" : "default" : await ask({
|
|
2496
|
+
type: "select",
|
|
2497
|
+
name: "value",
|
|
2498
|
+
message: cliM("cli_deploy_action", { params: { service: requirement.service } }),
|
|
2499
|
+
choices: [
|
|
2500
|
+
{ title: cliM("cli_deploy_default"), value: "default" },
|
|
2501
|
+
{ title: cliM("cli_deploy_custom"), value: "custom" },
|
|
2502
|
+
...canKeep ? [{ title: cliM("cli_deploy_keep"), value: "keep" }] : []
|
|
2503
|
+
]
|
|
2504
|
+
});
|
|
2505
|
+
modes[requirement.envPrefix] = mode;
|
|
2506
|
+
const previous = modes[requirement.service];
|
|
2507
|
+
modes[requirement.service] = previous === "default" || mode === "default" ? "default" : previous === "custom" || mode === "custom" ? "custom" : "keep";
|
|
2508
|
+
if (mode === "keep") {
|
|
2509
|
+
if (!canKeep)
|
|
2510
|
+
throw new Error(cliM("cli_deploy_missing_keep", { params: { service: requirement.service } }));
|
|
2511
|
+
continue;
|
|
2512
|
+
}
|
|
2513
|
+
const backendName = yes ? current.backend : String(await ask({ type: "select", name: "value", message: cliM("cli_deploy_backend", { params: { service: requirement.service } }), initial: choices.indexOf(current), choices: choices.map((item) => ({ title: `${item.label} \u2014 ${module.describeMiddlewareBackend(item)}`, value: item.backend })) }));
|
|
2514
|
+
const backend = choices.find((item) => item.backend === backendName);
|
|
2515
|
+
if (!backend)
|
|
2516
|
+
throw new Error(cliM("cli_deploy_invalid_value"));
|
|
2517
|
+
requirement.backend = backendName;
|
|
2518
|
+
const updates = {};
|
|
2519
|
+
if (mode === "default" && backendName === "s3" && provider?.type === "docker-ssh") {
|
|
2520
|
+
const container = config.container && typeof config.container === "object" ? config.container : {};
|
|
2521
|
+
const currentEngine = container.s3Engine === "minio" ? "minio" : "rustfs";
|
|
2522
|
+
const engine = yes ? currentEngine : String(await ask({ type: "select", name: "value", message: cliM("cli_deploy_s3_engine"), initial: currentEngine === "rustfs" ? 0 : 1, choices: [{ title: "RustFS", value: "rustfs" }, { title: "MinIO", value: "minio" }] }));
|
|
2523
|
+
if (!["rustfs", "minio"].includes(engine))
|
|
2524
|
+
throw new Error(cliM("cli_deploy_invalid_value"));
|
|
2525
|
+
config.container = { ...container, s3Engine: engine };
|
|
2526
|
+
updates.HAI_DEPLOY_CONTAINER_S3ENGINE = engine;
|
|
2527
|
+
const deploymentFile = join(scan.appDir ?? ".", "config", "_deploy.yml");
|
|
2528
|
+
const deployment = parseDocument(readFileSync(deploymentFile, "utf8"));
|
|
2529
|
+
if (deployment.errors.length)
|
|
2530
|
+
throw new Error(cliM("cli_deploy_invalid_value"));
|
|
2531
|
+
if (!deployment.hasIn(["container", "s3Engine"])) {
|
|
2532
|
+
if (!deployment.hasIn(["container"]))
|
|
2533
|
+
deployment.setIn(["container"], {});
|
|
2534
|
+
deployment.setIn(["container", "s3Engine"], `\${HAI_DEPLOY_CONTAINER_S3ENGINE:rustfs}`);
|
|
2535
|
+
writeFileSync(deploymentFile, deployment.toString());
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
if (mode === "default" && provider?.type === "vercel") {
|
|
2539
|
+
if (!backend.provisioner)
|
|
2540
|
+
throw new Error(cliM("cli_deploy_cloud_unsupported", { params: { backend: backend.label } }));
|
|
2541
|
+
const prior = existingServices[requirement.service];
|
|
2542
|
+
const priorConfig = prior && typeof prior === "object" ? prior : {};
|
|
2543
|
+
const cloud = { provisioner: backend.provisioner };
|
|
2544
|
+
for (const name of CLOUD_FIELDS[backend.provisioner]) {
|
|
2545
|
+
const key = `HAI_DEPLOY_SERVICES_${requirement.service.toUpperCase()}_${name.toUpperCase()}`;
|
|
2546
|
+
const initial = String(priorConfig[name] ?? environment.values[key] ?? "");
|
|
2547
|
+
const value = yes ? initial : String(await ask({ type: /key|token|secret/i.test(name) ? "password" : "text", name: "value", message: key, initial, validate: (input) => Boolean(input) || cliM("cli_deploy_required") }));
|
|
2548
|
+
if (!value)
|
|
2549
|
+
throw new Error(`${key}: ${cliM("cli_deploy_required")}`);
|
|
2550
|
+
cloud[name] = value;
|
|
2551
|
+
updates[key] = value;
|
|
2552
|
+
}
|
|
2553
|
+
services[requirement.service] = cloud;
|
|
2554
|
+
} else {
|
|
2555
|
+
if (mode === "default" && (requirement.service === "sms" || backendName === "api"))
|
|
2556
|
+
throw new Error(cliM("cli_deploy_cloud_unsupported", { params: { backend: backend.label } }));
|
|
2557
|
+
const connection = mode === "custom" && backendName === current.backend ? { ...requirement.config } : requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service } : {};
|
|
2558
|
+
for (const field of backend.fields) {
|
|
2559
|
+
const previous2 = backendName === current.backend ? requirement.config[field.name] : void 0;
|
|
2560
|
+
const initial = previous2 === void 0 ? field.defaultValue ?? "" : typeof previous2 === "object" ? JSON.stringify(previous2) : String(previous2);
|
|
2561
|
+
const variable = `${requirement.envPrefix}_${field.name.toUpperCase()}`;
|
|
2562
|
+
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: { name: variable, description: info?.description ?? field.name, constraint: info?.constraint ?? field.kind ?? "string" } }), initial, validate: (input) => validateField(String(input), field) })) : initial;
|
|
2564
|
+
if (value !== "") {
|
|
2565
|
+
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
|
+
} else {
|
|
2567
|
+
delete connection[field.name];
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
Object.assign(updates, connectionEnv(requirement, backendName, connection));
|
|
2571
|
+
}
|
|
2572
|
+
ensureConfigNode(scan.appDir ?? ".", requirement, backendName);
|
|
2573
|
+
const saved = module.saveDeploymentEnvironment(environment, updates);
|
|
2574
|
+
if (!saved.success)
|
|
2575
|
+
throw new Error(saved.error.message);
|
|
2576
|
+
for (const [name, value] of Object.entries(updates)) {
|
|
2577
|
+
core.logger.info(cliM("cli_deploy_saved", { params: { name, value: displayDeployValue(name, value) } }));
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
config.services = Object.keys(services).length ? services : void 0;
|
|
2581
|
+
return modes;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
// src/commands/cli-deploy.ts
|
|
2585
|
+
function configObject(value) {
|
|
2586
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2587
|
+
}
|
|
2588
|
+
async function configureDeployTarget(module, config, environment, nonInteractive, bundleOnly) {
|
|
2589
|
+
if (bundleOnly) {
|
|
2590
|
+
const existing = configObject(config.provider);
|
|
2591
|
+
if (existing.type && existing.type !== "docker-ssh")
|
|
2592
|
+
throw new Error(cliM("cli_deploy_bundle_provider"));
|
|
2593
|
+
config.provider = { type: "docker-ssh", remote: { runtime: "auto" }, expose: { hostPort: 3e3 }, ...existing };
|
|
2594
|
+
}
|
|
2595
|
+
const current = module.ProviderConfigSchema.safeParse(config.provider);
|
|
2596
|
+
if (current.success && (bundleOnly || current.data.type !== "docker-ssh" || current.data.ssh)) {
|
|
2597
|
+
config.provider = current.data;
|
|
2598
|
+
core.logger.info(cliM("cli_deploy_target_reused", { params: { target: current.data.type === "docker-ssh" ? current.data.ssh ? `${current.data.ssh.username}@${current.data.ssh.host}:${current.data.ssh.port}` : cliM("cli_deploy_bundle_local") : "Vercel" } }));
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2601
|
+
if (nonInteractive)
|
|
2602
|
+
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
|
+
const original = configObject(config.provider);
|
|
2604
|
+
const platform = await prompts({
|
|
2605
|
+
type: "select",
|
|
2606
|
+
name: "value",
|
|
2607
|
+
message: cliM("cli_deploy_target_platform"),
|
|
2608
|
+
choices: [{ title: cliM("cli_deploy_target_ssh"), value: "docker-ssh" }, { title: "Vercel", value: "vercel" }],
|
|
2609
|
+
initial: original.type === "vercel" ? 1 : 0
|
|
2610
|
+
});
|
|
2611
|
+
if (!platform.value)
|
|
2612
|
+
throw new Error(cliM("cli_deploy_cancelled"));
|
|
2613
|
+
const input = async (name, initial, options = {}) => {
|
|
2614
|
+
const answer = await prompts({
|
|
2615
|
+
type: options.secret ? "password" : "text",
|
|
2616
|
+
name: "value",
|
|
2617
|
+
message: cliM("cli_deploy_target_field", { params: { name } }),
|
|
2618
|
+
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("cli_deploy_invalid_value") : options.optional || !!value.trim() || cliM("cli_deploy_invalid_value")
|
|
2620
|
+
});
|
|
2621
|
+
if (answer.value === void 0)
|
|
2622
|
+
throw new Error(cliM("cli_deploy_cancelled"));
|
|
2623
|
+
return String(answer.value).trim();
|
|
2624
|
+
};
|
|
2625
|
+
let provider;
|
|
2626
|
+
if (platform.value === "docker-ssh") {
|
|
2627
|
+
core.logger.info(cliM("cli_deploy_target_ssh_hint"));
|
|
2628
|
+
const ssh = configObject(original.ssh);
|
|
2629
|
+
const remote = configObject(original.remote);
|
|
2630
|
+
const expose = configObject(original.expose);
|
|
2631
|
+
const host = await input("HAI_DEPLOY_PROVIDER_SSH_HOST", ssh.host);
|
|
2632
|
+
const port = Number(await input("HAI_DEPLOY_PROVIDER_SSH_PORT", ssh.port ?? 22, { port: true }));
|
|
2633
|
+
const username = await input("HAI_DEPLOY_PROVIDER_SSH_USERNAME", ssh.username);
|
|
2634
|
+
const identityFile = await input("HAI_DEPLOY_PROVIDER_SSH_IDENTITYFILE", ssh.identityFile, { optional: true });
|
|
2635
|
+
const knownHostsFile = await input("HAI_DEPLOY_PROVIDER_SSH_KNOWNHOSTSFILE", ssh.knownHostsFile, { optional: true });
|
|
2636
|
+
const baseDir = await input("HAI_DEPLOY_PROVIDER_REMOTE_BASEDIR", remote.baseDir || "/opt/hai/apps");
|
|
2637
|
+
const runtime = await prompts({ type: "select", name: "value", message: cliM("cli_deploy_target_runtime"), choices: ["auto", "podman", "docker"].map((value) => ({ title: value, value })), initial: ["auto", "podman", "docker"].indexOf(String(remote.runtime ?? "auto")) });
|
|
2638
|
+
if (!runtime.value)
|
|
2639
|
+
throw new Error(cliM("cli_deploy_cancelled"));
|
|
2640
|
+
const hostPort = Number(await input("HAI_DEPLOY_PROVIDER_EXPOSE_HOSTPORT", expose.hostPort || 3e3, { port: true }));
|
|
2641
|
+
const containerPort = Number(await input("HAI_DEPLOY_PROVIDER_EXPOSE_CONTAINERPORT", expose.containerPort || 3e3, { port: true }));
|
|
2642
|
+
provider = {
|
|
2643
|
+
type: "docker-ssh",
|
|
2644
|
+
ssh: { ...ssh, host, port, username, identityFile: identityFile || void 0, knownHostsFile: knownHostsFile || void 0 },
|
|
2645
|
+
remote: { ...remote, baseDir, runtime: runtime.value },
|
|
2646
|
+
expose: { type: "port", hostPort, containerPort }
|
|
2647
|
+
};
|
|
2648
|
+
} else {
|
|
2649
|
+
provider = { type: "vercel", token: await input("HAI_DEPLOY_PROVIDER_TOKEN", void 0, { secret: true }) };
|
|
2650
|
+
}
|
|
2651
|
+
const validated = module.ProviderConfigSchema.safeParse(provider);
|
|
2652
|
+
if (!validated.success)
|
|
2653
|
+
throw new Error(cliM("cli_deploy_invalid_value"));
|
|
2654
|
+
const saved = module.saveDeploymentEnvironment(environment, { HAI_DEPLOY_PROVIDER: JSON.stringify(validated.data) }, { private: true });
|
|
2655
|
+
if (!saved.success)
|
|
2656
|
+
throw new Error(saved.error.message);
|
|
2657
|
+
config.provider = validated.data;
|
|
2658
|
+
core.logger.info(cliM("cli_deploy_target_saved"));
|
|
2659
|
+
}
|
|
2227
2660
|
async function deployCommand(options) {
|
|
2228
2661
|
const spinner = ora();
|
|
2229
2662
|
const cwd = options.cwd ?? process4.cwd();
|
|
2230
|
-
|
|
2663
|
+
let appDir = resolve(cwd, options.appDir ?? ".");
|
|
2231
2664
|
let closeDeploy = null;
|
|
2665
|
+
const originalEnv = { ...process4.env };
|
|
2232
2666
|
try {
|
|
2667
|
+
let environmentName = options.env;
|
|
2668
|
+
if (!environmentName) {
|
|
2669
|
+
if (!process4.stdin.isTTY)
|
|
2670
|
+
throw new Error(cliM("cli_deploy_environment_hint"));
|
|
2671
|
+
const answer = await prompts({ type: "text", name: "environment", message: cliM("cli_deploy_environment"), initial: "prod" });
|
|
2672
|
+
environmentName = answer.environment;
|
|
2673
|
+
if (!environmentName)
|
|
2674
|
+
throw new Error(cliM("cli_deploy_cancelled"));
|
|
2675
|
+
}
|
|
2676
|
+
if (!options.yes && !process4.stdin.isTTY)
|
|
2677
|
+
throw new Error(cliM("cli_deploy_environment_hint"));
|
|
2233
2678
|
let deployModule;
|
|
2234
2679
|
try {
|
|
2235
|
-
|
|
2680
|
+
const localRequire = createRequire(resolve(appDir, "package.json"));
|
|
2681
|
+
let localModule;
|
|
2682
|
+
try {
|
|
2683
|
+
localModule = localRequire.resolve("@h-ai/deploy");
|
|
2684
|
+
} catch {
|
|
2685
|
+
}
|
|
2686
|
+
deployModule = localModule ? await import(pathToFileURL(localModule).href) : await import('./src-RDQ2EIBT.js');
|
|
2236
2687
|
} catch {
|
|
2237
2688
|
core.logger.error(chalk.red(cliM("cli_deploy_module_missing")));
|
|
2238
2689
|
core.logger.info(chalk.cyan(" pnpm add @h-ai/deploy"));
|
|
2239
2690
|
throw new Error(cliM("cli_deploy_module_missing"));
|
|
2240
2691
|
}
|
|
2241
2692
|
const { deploy } = deployModule;
|
|
2693
|
+
const scanResult = await deploy.scan(appDir, { environment: environmentName, envDir: options.envDir ? resolve(cwd, options.envDir) : void 0 });
|
|
2694
|
+
if (!scanResult.success)
|
|
2695
|
+
throw new Error(cliM("cli_deploy_scan_failed", { params: { message: scanResult.error.message } }));
|
|
2696
|
+
const scan = scanResult.data;
|
|
2697
|
+
appDir = scan.appDir ?? appDir;
|
|
2698
|
+
const environment = deployModule.loadDeploymentEnvironment({ environment: environmentName, envDir: scan.envDir ?? appDir });
|
|
2699
|
+
if (!environment.success)
|
|
2700
|
+
throw new Error(environment.error.message);
|
|
2701
|
+
Object.assign(process4.env, environment.data.values);
|
|
2242
2702
|
spinner.start(cliM("cli_deploy_load_credentials"));
|
|
2243
2703
|
const credResult = deploy.credentials.load();
|
|
2244
2704
|
if (!credResult.success) {
|
|
@@ -2252,19 +2712,44 @@ async function deployCommand(options) {
|
|
|
2252
2712
|
core.logger.info(chalk.cyan(cliM("cli_deploy_config_hint")));
|
|
2253
2713
|
throw new Error(cliM("cli_deploy_config_missing", { params: { path: configPath } }));
|
|
2254
2714
|
}
|
|
2255
|
-
const
|
|
2256
|
-
|
|
2257
|
-
|
|
2715
|
+
const loadedConfig = deployModule.loadDeploymentConfig("deploy", configPath, process4.env);
|
|
2716
|
+
if (!loadedConfig.success)
|
|
2717
|
+
throw new Error(loadedConfig.error.message);
|
|
2718
|
+
const deployConfig = loadedConfig.data;
|
|
2258
2719
|
spinner.succeed(cliM("cli_deploy_config_loaded"));
|
|
2259
|
-
|
|
2260
|
-
const scanResult = await deploy.scan(appDir);
|
|
2261
|
-
if (!scanResult.success) {
|
|
2262
|
-
throw new Error(cliM("cli_deploy_scan_failed", { params: { message: scanResult.error.message } }));
|
|
2263
|
-
}
|
|
2264
|
-
const scan = scanResult.data;
|
|
2720
|
+
await configureDeployTarget(deployModule, deployConfig, environment.data, options.yes, options.bundleOnly);
|
|
2265
2721
|
spinner.succeed(cliM("cli_deploy_scanned", { params: { name: scan.appName, sveltekit: String(scan.isSvelteKit), services: scan.requiredServices.join(", ") || cliM("cli_deploy_none") } }));
|
|
2722
|
+
let skipMiddleware = options.skipMiddleware;
|
|
2723
|
+
if (!skipMiddleware && !options.yes && scan.requiredServices.length) {
|
|
2724
|
+
const action = await prompts({
|
|
2725
|
+
type: "select",
|
|
2726
|
+
name: "value",
|
|
2727
|
+
message: cliM("cli_deploy_middleware_plan"),
|
|
2728
|
+
choices: [{ title: cliM("cli_deploy_middleware_configure"), value: "configure" }, { title: cliM("cli_deploy_middleware_skip"), value: "skip" }]
|
|
2729
|
+
});
|
|
2730
|
+
if (!action.value)
|
|
2731
|
+
throw new Error(cliM("cli_deploy_cancelled"));
|
|
2732
|
+
skipMiddleware = action.value === "skip";
|
|
2733
|
+
}
|
|
2734
|
+
if (skipMiddleware)
|
|
2735
|
+
core.logger.info(cliM("cli_deploy_middleware_skipped"));
|
|
2736
|
+
const serviceModes = skipMiddleware ? Object.fromEntries(scan.requiredServices.map((service) => [service, "keep"])) : await configureDeployMiddleware(deployModule, scan, environment.data, deployConfig, options.yes);
|
|
2737
|
+
if (skipMiddleware)
|
|
2738
|
+
deployConfig.services = void 0;
|
|
2739
|
+
for (const key of Object.keys(process4.env)) {
|
|
2740
|
+
if (originalEnv[key] === void 0)
|
|
2741
|
+
delete process4.env[key];
|
|
2742
|
+
}
|
|
2743
|
+
Object.assign(process4.env, originalEnv);
|
|
2744
|
+
const refreshed = deployModule.loadDeploymentEnvironment({ environment: environment.data.environment, envDir: environment.data.envDir });
|
|
2745
|
+
if (!refreshed.success)
|
|
2746
|
+
throw new Error(refreshed.error.message);
|
|
2747
|
+
Object.assign(process4.env, refreshed.data.values);
|
|
2266
2748
|
spinner.start(cliM("cli_deploy_initializing"));
|
|
2267
|
-
const
|
|
2749
|
+
const validatedConfig = deployModule.DeployConfigSchema.safeParse(deployConfig);
|
|
2750
|
+
if (!validatedConfig.success)
|
|
2751
|
+
throw new Error(cliM("cli_deploy_init_failed", { params: { message: cliM("cli_deploy_invalid_value") } }));
|
|
2752
|
+
const initResult = await deploy.init(validatedConfig.data);
|
|
2268
2753
|
if (!initResult.success) {
|
|
2269
2754
|
throw new Error(cliM("cli_deploy_init_failed", { params: { message: initResult.error.message } }));
|
|
2270
2755
|
}
|
|
@@ -2273,8 +2758,13 @@ async function deployCommand(options) {
|
|
|
2273
2758
|
spinner.start(cliM("cli_deploy_deploying"));
|
|
2274
2759
|
const deployResult = await deploy.deployApp(appDir, {
|
|
2275
2760
|
projectName: options.projectName,
|
|
2276
|
-
|
|
2277
|
-
|
|
2761
|
+
skipMiddleware,
|
|
2762
|
+
artifactDir: options.artifactDir ? resolve(cwd, options.artifactDir) : void 0,
|
|
2763
|
+
bundleOnly: options.bundleOnly,
|
|
2764
|
+
output: options.output ? resolve(cwd, options.output) : void 0,
|
|
2765
|
+
environment: environment.data.environment,
|
|
2766
|
+
envDir: environment.data.envDir,
|
|
2767
|
+
serviceModes
|
|
2278
2768
|
});
|
|
2279
2769
|
if (!deployResult.success) {
|
|
2280
2770
|
if (deployResult.error.ext?.recovery) {
|
|
@@ -2284,9 +2774,29 @@ async function deployCommand(options) {
|
|
|
2284
2774
|
}
|
|
2285
2775
|
throw new Error(cliM("cli_deploy_failed", { params: { message: deployResult.error.message } }));
|
|
2286
2776
|
}
|
|
2287
|
-
spinner.succeed(chalk.green(cliM("cli_deploy_success")));
|
|
2777
|
+
spinner.succeed(chalk.green(cliM(deployResult.data.status === "packaged" ? "cli_deploy_bundle_ready" : "cli_deploy_success")));
|
|
2778
|
+
for (const key of Object.keys(process4.env)) {
|
|
2779
|
+
if (originalEnv[key] === void 0)
|
|
2780
|
+
delete process4.env[key];
|
|
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
|
+
}
|
|
2288
2793
|
core.logger.info("");
|
|
2289
|
-
|
|
2794
|
+
if (deployResult.data.bundlePath)
|
|
2795
|
+
core.logger.info(cliM("cli_deploy_bundle_path", { params: { path: deployResult.data.bundlePath } }));
|
|
2796
|
+
if (deployResult.data.status === "packaged")
|
|
2797
|
+
core.logger.info(deployResult.data.instructions ?? "");
|
|
2798
|
+
else
|
|
2799
|
+
core.logger.info(chalk.cyan(cliM("cli_deploy_url", { params: { url: deployResult.data.url } })));
|
|
2290
2800
|
core.logger.info(chalk.gray(cliM("cli_deploy_id", { params: { id: deployResult.data.deploymentId } })));
|
|
2291
2801
|
if (deployResult.data.envVarsSet.length > 0) {
|
|
2292
2802
|
core.logger.info(chalk.gray(cliM("cli_deploy_env", { params: { names: deployResult.data.envVarsSet.join(", ") } })));
|
|
@@ -2305,13 +2815,13 @@ async function deployCommand(options) {
|
|
|
2305
2815
|
process4.exitCode = 1;
|
|
2306
2816
|
}
|
|
2307
2817
|
}
|
|
2818
|
+
for (const key of Object.keys(process4.env)) {
|
|
2819
|
+
if (originalEnv[key] === void 0)
|
|
2820
|
+
delete process4.env[key];
|
|
2821
|
+
}
|
|
2822
|
+
Object.assign(process4.env, originalEnv);
|
|
2308
2823
|
}
|
|
2309
2824
|
}
|
|
2310
|
-
function interpolateEnvFallback(content) {
|
|
2311
|
-
return content.replace(/\$\{([^:}]+)(?::([^}]*))?\}/g, (_match, key, defaultValue) => {
|
|
2312
|
-
return process4.env[key] ?? defaultValue ?? "";
|
|
2313
|
-
});
|
|
2314
|
-
}
|
|
2315
2825
|
var GENERATORS = {
|
|
2316
2826
|
page: {
|
|
2317
2827
|
description: "SvelteKit \u9875\u9762",
|
|
@@ -2653,7 +3163,7 @@ async function initProject(options) {
|
|
|
2653
3163
|
const { project, existing, missing } = result;
|
|
2654
3164
|
spinner.succeed(`\u68C0\u6D4B\u5230\u9879\u76EE: ${chalk.cyan(project.name)}`);
|
|
2655
3165
|
if (project.isHaiProject) {
|
|
2656
|
-
const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-
|
|
3166
|
+
const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-TSYEQTCY.js');
|
|
2657
3167
|
const generatedFiles = await generateProjectAiSupport(cwd, resolveSkillNamesForPackages(project.haiPackages), {
|
|
2658
3168
|
overwriteSkills: false,
|
|
2659
3169
|
overwriteBridgeFiles: false
|
|
@@ -2797,13 +3307,26 @@ cli.command("generate [type] [name]", "\u751F\u6210\u4EE3\u7801").alias("g").opt
|
|
|
2797
3307
|
process4.exit(1);
|
|
2798
3308
|
}
|
|
2799
3309
|
});
|
|
2800
|
-
cli.command("
|
|
3310
|
+
cli.command("build", "\u6784\u5EFA\u6E90\u7801\u3001Docker \u955C\u50CF\u6216 Tauri \u5B89\u88C5\u5305\uFF08\u4E0D\u533A\u5206\u90E8\u7F72\u73AF\u5883\uFF09").option("--target <target>", "\u4EA7\u7269\u7C7B\u578B\uFF1Acompile\uFF08\u9ED8\u8BA4\uFF09/ docker / desktop").option("--runtime <runtime>", "\u672C\u5730\u5BB9\u5668\u5F15\u64CE\uFF1Aauto\uFF08\u4F18\u5148 Podman\uFF09/ podman / docker").option("--network <network>", "\u5BB9\u5668\u6784\u5EFA\u7F51\u7EDC\uFF08\u9ED8\u8BA4\u5F15\u64CE\u8BBE\u7F6E\uFF09\uFF0C\u4F8B\u5982 rootless Podman \u7684 pasta").option("--platform <platform>", "Tauri \u76EE\u6807\u7CFB\u7EDF\uFF1Awindows / linux / macos\uFF08\u5728\u5BF9\u5E94\u7CFB\u7EDF\u6784\u5EFA\uFF09").option("--arch <arch>", "Tauri \u76EE\u6807\u67B6\u6784\uFF1Ax64 / arm64").option("--sqlite-prebuild-mirror <url>", "SQLite \u9884\u7F16\u8BD1\u4E8C\u8FDB\u5236\u955C\u50CF\u6839\u5730\u5740").option("--no-http-proxy", "Podman \u6784\u5EFA\u4E0D\u7EE7\u627F\u672C\u673A\u4EE3\u7406").action(async (options) => {
|
|
3311
|
+
try {
|
|
3312
|
+
await buildCommand(options);
|
|
3313
|
+
} catch (error) {
|
|
3314
|
+
writeCliMessage(error instanceof Error ? error.message : String(error));
|
|
3315
|
+
process4.exitCode = 1;
|
|
3316
|
+
}
|
|
3317
|
+
});
|
|
3318
|
+
cli.command("deploy [appDir]", "\u90E8\u7F72\u5E94\u7528\u5230\u4E91\u5E73\u53F0").option("--env <environment>", "\u90E8\u7F72\u73AF\u5883\uFF08test / prod \u7B49\uFF0C\u5FC5\u987B\u5B58\u5728\u5BF9\u5E94\u73AF\u5883\u6587\u4EF6\uFF09").option("--env-dir <path>", "\u73AF\u5883\u6587\u4EF6\u6240\u5728\u76EE\u5F55\uFF08\u9ED8\u8BA4\u81EA\u52A8\u67E5\u627E\uFF09").option("-y, --yes", "\u6309\u5F53\u524D\u540E\u7AEF\u9ED8\u8BA4\u914D\u7F6E\u90E8\u7F72\uFF0C\u76EE\u6807\u73AF\u5883\u5DF2\u6709\u5B8C\u6574\u914D\u7F6E\u65F6\u4FDD\u6301\u4E0D\u53D8").option("--project-name <name>", "\u9879\u76EE\u540D\u79F0\uFF08\u9ED8\u8BA4\u670D\u52A1\u5305\u540D\uFF0C\u7F3A\u5931\u65F6\u4E3A\u76EE\u5F55\u540D\uFF1B\u9644\u52A0\u73AF\u5883\u540E\u7F00\uFF09").option("--skip-middleware", "\u914D\u7F6E\u5DF2\u5B8C\u5907\uFF0C\u8DF3\u8FC7\u5168\u90E8\u4E2D\u95F4\u4EF6\u5904\u7406\uFF0C\u76F4\u63A5\u90E8\u7F72").option("--artifact-dir <path>", "\u5DF2\u6709\u955C\u50CF\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\uFF08\u542B manifest.json\uFF09").option("--bundle-only", "\u53EA\u751F\u6210 Compose \u90E8\u7F72 tar\uFF0C\u624B\u5DE5\u642C\u8FD0\uFF1B\u4E0D\u8FDE\u63A5 SSH").option("--output <path>", "\u90E8\u7F72 tar \u8F93\u51FA\u8DEF\u5F84\uFF0C\u81EA\u52A8\u8865\u5E94\u7528\u7248\u672C\u540E\u7F00").action(async (appDir, options) => {
|
|
2801
3319
|
try {
|
|
2802
3320
|
await deployCommand({
|
|
2803
3321
|
appDir,
|
|
3322
|
+
env: options.env,
|
|
3323
|
+
envDir: options.envDir,
|
|
3324
|
+
yes: options.yes,
|
|
2804
3325
|
projectName: options.projectName,
|
|
2805
|
-
|
|
2806
|
-
|
|
3326
|
+
skipMiddleware: options.skipMiddleware,
|
|
3327
|
+
artifactDir: options.artifactDir,
|
|
3328
|
+
bundleOnly: options.bundleOnly,
|
|
3329
|
+
output: options.output,
|
|
2807
3330
|
verbose: options.verbose,
|
|
2808
3331
|
cwd: options.cwd
|
|
2809
3332
|
});
|
|
@@ -2823,7 +3346,7 @@ cli.command("g:api <name>", "\u751F\u6210 API").action(async (name, options) =>
|
|
|
2823
3346
|
cli.command("g:model <name>", "\u751F\u6210\u6570\u636E\u6A21\u578B").action(async (name, options) => {
|
|
2824
3347
|
await generate({ type: "model", name, cwd: options.cwd });
|
|
2825
3348
|
});
|
|
2826
|
-
cli.version(VERSION);
|
|
3349
|
+
cli.version(VERSION, "-V, --version");
|
|
2827
3350
|
cli.help();
|
|
2828
3351
|
var parsed = cli.parse();
|
|
2829
3352
|
if (!cli.matchedCommand && !parsed.options.help && !parsed.options.version) {
|