@h-ai/cli 0.1.0-alpha.16 → 0.1.0-alpha.19

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.
Files changed (134) hide show
  1. package/dist/{chunk-LANIDJC7.js → chunk-BPO34NRL.js} +3 -3
  2. package/dist/chunk-BPO34NRL.js.map +1 -0
  3. package/dist/{chunk-YV7WPUI6.js → chunk-GSMXGUYF.js} +6 -2
  4. package/dist/chunk-GSMXGUYF.js.map +1 -0
  5. package/dist/cli-config-templates-JCS7T3NU.js +5 -0
  6. package/dist/cli-config-templates-JCS7T3NU.js.map +1 -0
  7. package/dist/cli-skill-templates-IMYZPQRG.js +5 -0
  8. package/dist/cli-skill-templates-IMYZPQRG.js.map +1 -0
  9. package/dist/index.js +391 -58
  10. package/dist/index.js.map +1 -1
  11. package/dist/{src-RZ4DPYNJ.js → src-N4NPS7AG.js} +1239 -493
  12. package/dist/src-N4NPS7AG.js.map +1 -0
  13. package/package.json +8 -7
  14. package/templates/README.md +10 -3
  15. package/templates/apps/android-app/src/lib/api.ts.hbs +1 -1
  16. package/templates/apps/api/tests/health.test.ts.hbs +26 -0
  17. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +21 -0
  18. package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +23 -0
  19. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/en-US.json.hbs +34 -0
  20. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/zh-CN.json.hbs +34 -0
  21. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +50 -0
  22. package/templates/apps/fullstack/apps/{{projectName}}-app/project.inlang/settings.json.hbs +17 -0
  23. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.css.hbs +18 -0
  24. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.d.ts.hbs +9 -0
  25. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.html.hbs +14 -0
  26. package/templates/apps/fullstack/apps/{{projectName}}-app/src/routes/+layout.svelte.hbs +18 -0
  27. package/templates/apps/fullstack/apps/{{projectName}}-app/src/routes/+page.svelte.hbs +162 -0
  28. package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +20 -0
  29. package/templates/apps/fullstack/apps/{{projectName}}-app/tests/api.test.ts.hbs +28 -0
  30. package/templates/apps/fullstack/apps/{{projectName}}-app/tsconfig.json.hbs +17 -0
  31. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +29 -0
  32. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +21 -0
  33. package/templates/apps/fullstack/apps/{{projectName}}-desktop/eslint.config.js.hbs +23 -0
  34. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/en-US.json.hbs +34 -0
  35. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/zh-CN.json.hbs +34 -0
  36. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +50 -0
  37. package/templates/apps/fullstack/apps/{{projectName}}-desktop/project.inlang/settings.json.hbs +17 -0
  38. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.css.hbs +18 -0
  39. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.d.ts.hbs +9 -0
  40. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.html.hbs +14 -0
  41. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/routes/+layout.svelte.hbs +18 -0
  42. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/routes/+page.svelte.hbs +162 -0
  43. package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +20 -0
  44. package/templates/apps/fullstack/apps/{{projectName}}-desktop/tests/api.test.ts.hbs +28 -0
  45. package/templates/apps/fullstack/apps/{{projectName}}-desktop/tsconfig.json.hbs +17 -0
  46. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +29 -0
  47. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +13 -0
  48. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/en-US.json.hbs +12 -0
  49. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/zh-CN.json.hbs +12 -0
  50. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/project.inlang/settings.json.hbs +17 -0
  51. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +21 -0
  52. package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +23 -0
  53. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/en-US.json.hbs +34 -0
  54. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/zh-CN.json.hbs +34 -0
  55. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +50 -0
  56. package/templates/apps/fullstack/apps/{{projectName}}-web/project.inlang/settings.json.hbs +17 -0
  57. package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.css.hbs +18 -0
  58. package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.d.ts.hbs +9 -0
  59. package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.html.hbs +14 -0
  60. package/templates/apps/fullstack/apps/{{projectName}}-web/src/routes/+layout.svelte.hbs +18 -0
  61. package/templates/apps/fullstack/apps/{{projectName}}-web/src/routes/+page.svelte.hbs +162 -0
  62. package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +20 -0
  63. package/templates/apps/fullstack/apps/{{projectName}}-web/tests/api.test.ts.hbs +28 -0
  64. package/templates/apps/fullstack/apps/{{projectName}}-web/tsconfig.json.hbs +17 -0
  65. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +29 -0
  66. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +30 -0
  67. package/templates/apps/fullstack/eslint.config.js +44 -0
  68. package/templates/apps/fullstack/package.json.hbs +42 -0
  69. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +15 -0
  70. package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +27 -0
  71. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/app-contract.ts.hbs +21 -0
  72. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/app-routes.ts.hbs +27 -0
  73. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/app-schemas.ts.hbs +34 -0
  74. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +4 -0
  75. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +9 -0
  76. package/templates/apps/fullstack/packages/{{projectName}}-contract/tests/contract.test.ts.hbs +48 -0
  77. package/templates/apps/fullstack/packages/{{projectName}}-contract/tsconfig.json.hbs +9 -0
  78. package/templates/apps/fullstack/packages/{{projectName}}-contract/tsup.config.ts.hbs +9 -0
  79. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +14 -0
  80. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +29 -0
  81. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +15 -0
  82. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/app-procedures.ts.hbs +38 -0
  83. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +13 -0
  84. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server-app.ts.hbs +42 -0
  85. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/server-app.test.ts.hbs +49 -0
  86. package/templates/apps/fullstack/packages/{{projectName}}-serv/tsconfig.json.hbs +10 -0
  87. package/templates/apps/fullstack/packages/{{projectName}}-serv/tsup.config.ts.hbs +9 -0
  88. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +3 -0
  89. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +39 -0
  90. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/en-US.json.hbs +24 -0
  91. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/zh-CN.json.hbs +24 -0
  92. package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +44 -0
  93. package/templates/apps/fullstack/packages/{{projectName}}-shared/project.inlang/settings.json.hbs +11 -0
  94. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/index.ts.hbs +8 -0
  95. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +49 -0
  96. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +60 -0
  97. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/LanguageSwitcher.svelte.hbs +27 -0
  98. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ThemeSwitcher.svelte.hbs +18 -0
  99. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/theme.svelte.ts.hbs +55 -0
  100. package/templates/apps/fullstack/packages/{{projectName}}-shared/tsconfig.json.hbs +17 -0
  101. package/templates/apps/fullstack/playwright.config.ts.hbs +34 -0
  102. package/templates/apps/fullstack/pnpm-workspace.yaml +7 -0
  103. package/templates/apps/fullstack/tsconfig.base.json +15 -0
  104. package/templates/base/e2e/app.spec.ts.hbs +7 -0
  105. package/templates/base/eslint.config.js +6 -0
  106. package/templates/base/package.json.hbs +46 -39
  107. package/templates/base/src/app.css.hbs +1 -0
  108. package/templates/base/src/lib/server/init.ts.hbs +3 -3
  109. package/templates/base/tests/scaffold.test.ts.hbs +27 -0
  110. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +15 -7
  111. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +1 -2
  112. package/templates/skills/hai-ai/SKILL.md +2 -0
  113. package/templates/skills/hai-api-client/SKILL.md +4 -0
  114. package/templates/skills/hai-audit/SKILL.md +7 -2
  115. package/templates/skills/hai-cache/SKILL.md +2 -0
  116. package/templates/skills/hai-capacitor/SKILL.md +11 -9
  117. package/templates/skills/hai-core/SKILL.md +4 -1
  118. package/templates/skills/hai-crypto/SKILL.md +42 -8
  119. package/templates/skills/hai-iam/SKILL.md +2 -0
  120. package/templates/skills/hai-kit/SKILL.md +9 -2
  121. package/templates/skills/hai-payment/SKILL.md +5 -0
  122. package/templates/skills/hai-reach/SKILL.md +3 -1
  123. package/templates/skills/hai-reldb/SKILL.md +2 -0
  124. package/templates/skills/hai-scheduler/SKILL.md +2 -0
  125. package/templates/skills/hai-serv/SKILL.md +14 -9
  126. package/templates/skills/hai-storage/SKILL.md +10 -1
  127. package/templates/skills/hai-vecdb/SKILL.md +8 -1
  128. package/dist/chunk-LANIDJC7.js.map +0 -1
  129. package/dist/chunk-YV7WPUI6.js.map +0 -1
  130. package/dist/config-templates-S5RI2MC6.js +0 -5
  131. package/dist/config-templates-S5RI2MC6.js.map +0 -1
  132. package/dist/skill-templates-N5DU5FNX.js +0 -5
  133. package/dist/skill-templates-N5DU5FNX.js.map +0 -1
  134. package/dist/src-RZ4DPYNJ.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { generateConfigFile } from './chunk-LANIDJC7.js';
3
- import './chunk-YV7WPUI6.js';
2
+ import { generateConfigFile } from './chunk-BPO34NRL.js';
3
+ import './chunk-GSMXGUYF.js';
4
4
  import './chunk-2H7UOFLK.js';
5
5
  import { readFileSync, existsSync } from 'fs';
6
6
  import path2, { resolve } from 'path';
@@ -83,6 +83,67 @@ registerHelpers();
83
83
  Handlebars.registerHelper("if_eq", function(a, b, options) {
84
84
  return a === b ? options.fn(this) : options.inverse(this);
85
85
  });
86
+ var HAI_VERSION = `^${fse.readJsonSync(fileURLToPath(new URL("../../package.json", import.meta.url))).version}`;
87
+ var TEMPLATE_SKIP_IF_EMPTY_MARKER = "@skipIfEmpty";
88
+ var NORMALIZED_RENDER_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".js", ".svelte", ".json", ".css"]);
89
+ var VERSIONS = {
90
+ antfuEslintConfig: "^9.0.0",
91
+ apiContract: HAI_VERSION,
92
+ apiClient: HAI_VERSION,
93
+ core: HAI_VERSION,
94
+ serv: HAI_VERSION,
95
+ ui: HAI_VERSION,
96
+ // UI / Icon
97
+ bitsUi: "^2.18.1",
98
+ iconifyJsonTabler: "^1.2.35",
99
+ iconifyTailwind: "^1.2.3",
100
+ // Tailwind / DaisyUI
101
+ daisyui: "^5.5.20",
102
+ tailwindcss: "^4.3.0",
103
+ tailwindcssVite: "^4.3.0",
104
+ // i18n
105
+ inlangParaglide: "^2.18.1",
106
+ inlangMessageFormat: "^4.4.0",
107
+ // Svelte / Kit
108
+ svelte: "^5.55.9",
109
+ svelteKit: "^2.61.1",
110
+ svelteCheck: "^4.4.8",
111
+ sveltePlugin: "^7.1.2",
112
+ adapterAuto: "^7.0.1",
113
+ adapterStatic: "^3.0.10",
114
+ // Build / Test
115
+ vite: "^8.0.14",
116
+ vitest: "^4.1.7",
117
+ tsup: "^8.5.1",
118
+ rimraf: "^6.1.3",
119
+ // ESLint
120
+ eslint: "^10.4.0",
121
+ eslintPluginFormat: "^2.0.1",
122
+ eslintPluginSvelte: "^3.17.1",
123
+ svelteEslintParser: "^1.6.1",
124
+ // Node / TS
125
+ nodeTypes: "^25.9.1",
126
+ typescript: "^6.0.3",
127
+ // E2E
128
+ playwright: "^1.60.0",
129
+ // Validation
130
+ zod: "^4.4.3"
131
+ };
132
+ var FRONTEND_PORTS_DEFAULT = {
133
+ web: 4173,
134
+ app: 4174,
135
+ desktop: 4175
136
+ };
137
+ var FRONTEND_LABELS = {
138
+ web: "Web",
139
+ app: "App",
140
+ miniapp: "Miniapp",
141
+ desktop: "Desktop"
142
+ };
143
+ var IMPLEMENTED_FRONTENDS = ["web", "app", "desktop"];
144
+ var DEFAULT_FULLSTACK_FRONTENDS = ["web", "app", "desktop"];
145
+ var DEFAULT_THEMES = ["light", "dark"];
146
+ var DEFAULT_THEME_NAME = "light";
86
147
  var FEATURE_ROUTE_DIRS = {
87
148
  iam: ["routes-shared"],
88
149
  storage: ["routes"],
@@ -99,6 +160,8 @@ var FEATURE_APP_ROUTE_DIRS = {
99
160
  };
100
161
  var FEATURE_ID_REGEX = /^[a-z0-9-]+$/;
101
162
  var HBS_SUFFIX_REGEX = /\.hbs$/;
163
+ var NON_IDENTIFIER_CHAR_REGEX = /[^a-z0-9]+/i;
164
+ var IDENTIFIER_START_REGEX = /^[a-z_$]/i;
102
165
  function getTemplatesRoot() {
103
166
  const currentDir = path2.dirname(fileURLToPath(import.meta.url));
104
167
  const distPath = path2.resolve(currentDir, "..", "templates");
@@ -114,11 +177,14 @@ async function copyStaticDir(src, dest, excludePaths) {
114
177
  await fse.copy(src, dest, {
115
178
  overwrite: true,
116
179
  filter: (filePath) => {
180
+ const rel = path2.relative(src, filePath);
181
+ if (rel.includes("{{") || rel.includes("}}")) {
182
+ return false;
183
+ }
117
184
  if (filePath.endsWith(".hbs")) {
118
185
  return false;
119
186
  }
120
187
  if (excludePaths && excludePaths.length > 0) {
121
- const rel = path2.relative(src, filePath);
122
188
  return !excludePaths.some((p) => rel === p || rel.startsWith(`${p}${path2.sep}`));
123
189
  }
124
190
  return true;
@@ -158,21 +224,99 @@ function buildTemplateContext(options) {
158
224
  featureMap.db = true;
159
225
  }
160
226
  const isApi = options.appType === "api";
227
+ const isFullstack = options.appType === "fullstack";
161
228
  const isCapacitorApp = options.appType === "android-app";
162
229
  return {
163
230
  projectName: options.name,
164
231
  appType: options.appType,
165
232
  features: featureMap,
166
- hasUi: !isApi,
167
- hasI18n: !isApi,
233
+ hasUi: !isApi && !isFullstack,
234
+ hasI18n: !isApi && !isFullstack,
168
235
  isCapacitorApp,
169
236
  defaultLocale: options.moduleConfigs?.core?.defaultLocale ?? "zh-CN",
170
- packageManager: options.packageManager
237
+ packageManager: options.packageManager,
238
+ fullstack: isFullstack ? buildFullstackTemplateContext(
239
+ options.name,
240
+ options.frontends ?? DEFAULT_FULLSTACK_FRONTENDS,
241
+ options.moduleConfigs
242
+ ) : void 0
243
+ };
244
+ }
245
+ function buildFullstackTemplateContext(projectName, frontendsInput, moduleConfigs) {
246
+ const selectedFrontends = [...new Set(frontendsInput.length > 0 ? frontendsInput : DEFAULT_FULLSTACK_FRONTENDS)];
247
+ const portsConfig = moduleConfigs?.fullstack?.ports ?? {};
248
+ const portFor = (target) => portsConfig[target] ?? FRONTEND_PORTS_DEFAULT[target];
249
+ const frontendApps = selectedFrontends.filter(isImplementedFrontend).map((target) => ({
250
+ target,
251
+ packageName: `${projectName}-${target}`,
252
+ label: FRONTEND_LABELS[target],
253
+ port: portFor(target)
254
+ }));
255
+ return {
256
+ versions: VERSIONS,
257
+ contractPackageName: `${projectName}-contract`,
258
+ contractExportName: `${toIdentifier(projectName)}Contract`,
259
+ servPackageName: `${projectName}-serv`,
260
+ sharedPackageName: `${projectName}-shared`,
261
+ selectedFrontends,
262
+ selectedFrontendsJson: `[${selectedFrontends.map((f) => `'${f}'`).join(", ")}]`,
263
+ frontendApps,
264
+ e2eFrontend: frontendApps[0],
265
+ frontends: {
266
+ web: selectedFrontends.includes("web"),
267
+ app: selectedFrontends.includes("app"),
268
+ miniapp: selectedFrontends.includes("miniapp"),
269
+ desktop: selectedFrontends.includes("desktop")
270
+ },
271
+ ports: {
272
+ web: portFor("web"),
273
+ app: portFor("app"),
274
+ desktop: portFor("desktop")
275
+ },
276
+ hasMiniapp: selectedFrontends.includes("miniapp"),
277
+ i18n: resolveI18nContext(moduleConfigs),
278
+ theme: resolveThemeContext(moduleConfigs)
171
279
  };
172
280
  }
281
+ function resolveI18nContext(moduleConfigs) {
282
+ const coreLocale = moduleConfigs?.core?.defaultLocale ?? "zh-CN";
283
+ const baseLocale = moduleConfigs?.fullstack?.i18n?.baseLocale ?? coreLocale;
284
+ const fallbackLocale = baseLocale === "en-US" ? "zh-CN" : "en-US";
285
+ const requested = moduleConfigs?.fullstack?.i18n?.locales;
286
+ const locales = requested && requested.length > 0 ? [...new Set(requested)] : [baseLocale, fallbackLocale];
287
+ if (!locales.includes(baseLocale)) {
288
+ locales.unshift(baseLocale);
289
+ }
290
+ return { baseLocale, locales };
291
+ }
292
+ function resolveThemeContext(moduleConfigs) {
293
+ const defaultTheme = moduleConfigs?.fullstack?.theme?.defaultTheme ?? DEFAULT_THEME_NAME;
294
+ const requested = moduleConfigs?.fullstack?.theme?.themes;
295
+ const themes = requested && requested.length > 0 ? [...new Set(requested)] : [...DEFAULT_THEMES];
296
+ if (!themes.includes(defaultTheme)) {
297
+ themes.unshift(defaultTheme);
298
+ }
299
+ return { defaultTheme, themes };
300
+ }
301
+ function isImplementedFrontend(target) {
302
+ return IMPLEMENTED_FRONTENDS.includes(target);
303
+ }
304
+ function toIdentifier(value) {
305
+ const words = value.split(NON_IDENTIFIER_CHAR_REGEX).filter(Boolean);
306
+ const camel = words.map((word, index) => {
307
+ const normalized = word.toLowerCase();
308
+ return index === 0 ? normalized : `${normalized.charAt(0).toUpperCase()}${normalized.slice(1)}`;
309
+ }).join("");
310
+ if (!camel)
311
+ return "app";
312
+ return IDENTIFIER_START_REGEX.test(camel) ? camel : `app${camel}`;
313
+ }
173
314
  async function generateFromTemplates(projectPath, context) {
174
315
  const root = getTemplatesRoot();
175
- await copyStaticDir(path2.join(root, "base"), projectPath);
316
+ const includeBaseTemplate = context.appType !== "fullstack";
317
+ if (includeBaseTemplate) {
318
+ await copyStaticDir(path2.join(root, "base"), projectPath);
319
+ }
176
320
  const appDir = path2.join(root, "apps", context.appType);
177
321
  if (await fse.pathExists(appDir)) {
178
322
  const appExcludes = context.hasI18n ? [] : ["messages"];
@@ -211,10 +355,12 @@ async function generateFromTemplates(projectPath, context) {
211
355
  await fse.copy(i18nDir, projectPath, { overwrite: true });
212
356
  }
213
357
  }
214
- await fse.ensureDir(path2.join(projectPath, "static"));
358
+ if (includeBaseTemplate) {
359
+ await fse.ensureDir(path2.join(projectPath, "static"));
360
+ }
215
361
  }
216
362
  async function renderDynamicFiles(templatesRoot, projectPath, context) {
217
- const rootDirs = [
363
+ const rootDirs = context.appType === "fullstack" ? [path2.join(templatesRoot, "apps", context.appType)] : [
218
364
  path2.join(templatesRoot, "base"),
219
365
  path2.join(templatesRoot, "apps", context.appType)
220
366
  ];
@@ -248,17 +394,46 @@ async function renderHbsInDir(dir, destRoot, context) {
248
394
  const hbsFiles = await findHbsFiles(dir);
249
395
  for (const relPath of hbsFiles) {
250
396
  const srcPath = path2.join(dir, relPath);
251
- const outRelPath = relPath.replace(HBS_SUFFIX_REGEX, "");
252
- const destPath = path2.join(destRoot, outRelPath);
397
+ const outRelPathTemplate = relPath.replace(HBS_SUFFIX_REGEX, "");
253
398
  const template = await fse.readFile(srcPath, "utf-8");
254
399
  const compiled = Handlebars.compile(template, { noEscape: true });
255
400
  const rendered = compiled(context);
401
+ if (shouldSkipRenderedFile(template, rendered)) {
402
+ continue;
403
+ }
404
+ const outRelPath = renderOutputPath(outRelPathTemplate, context);
405
+ if (!isSafeRelativePath(outRelPath)) {
406
+ continue;
407
+ }
408
+ const destPath = path2.join(destRoot, outRelPath);
409
+ const normalizedRendered = normalizeRenderedContent(rendered, outRelPath);
256
410
  await fse.ensureDir(path2.dirname(destPath));
257
- await fse.writeFile(destPath, rendered, "utf-8");
411
+ await fse.writeFile(destPath, normalizedRendered, "utf-8");
258
412
  }
259
413
  }
414
+ function normalizeRenderedContent(content, outRelPath) {
415
+ const normalizedLineEndings = content.replace(/\r\n/g, "\n");
416
+ const ext = path2.extname(outRelPath);
417
+ const normalized = NORMALIZED_RENDER_EXTENSIONS.has(ext) ? normalizedLineEndings.replace(/\n{3,}/g, "\n\n") : normalizedLineEndings;
418
+ if (normalized.trim().length === 0) {
419
+ return "";
420
+ }
421
+ return normalized.endsWith("\n") ? normalized : `${normalized}
422
+ `;
423
+ }
424
+ function shouldSkipRenderedFile(template, rendered) {
425
+ return template.includes(TEMPLATE_SKIP_IF_EMPTY_MARKER) && rendered.trim().length === 0;
426
+ }
427
+ function renderOutputPath(pathTemplate, context) {
428
+ const normalizedTemplate = pathTemplate.split(path2.sep).join("/");
429
+ return Handlebars.compile(normalizedTemplate, { noEscape: true })(context);
430
+ }
431
+ function isSafeRelativePath(relativePath) {
432
+ const normalized = path2.normalize(relativePath);
433
+ return !path2.isAbsolute(normalized) && normalized !== ".." && !normalized.startsWith(`..${path2.sep}`);
434
+ }
260
435
 
261
- // src/commands/create.ts
436
+ // src/commands/cli-create.ts
262
437
  var RE_HTTPS_URL = /^https:\/\/.+/;
263
438
  var RE_GIT_SSH_URL = /^git@.+:.+/;
264
439
  var APP_TYPES = {
@@ -286,8 +461,20 @@ var APP_TYPES = {
286
461
  name: "Android \u5E94\u7528",
287
462
  description: "Android \u539F\u751F\u5E94\u7528\uFF08Capacitor + SvelteKit SPA\uFF09\uFF0C\u53EF\u6253\u5305\u4E3A APK",
288
463
  defaultFeatures: ["iam", "db", "cache", "crypto", "api-client", "capacitor"]
464
+ },
465
+ "fullstack": {
466
+ name: "\u524D\u540E\u7AEF\u5206\u79BB\u5DE5\u7A0B",
467
+ description: "\u591A\u5305\u5DE5\u4F5C\u533A\uFF0C\u5305\u542B contract\u3001serv\u3001Web/App/Desktop \u524D\u7AEF",
468
+ defaultFeatures: []
289
469
  }
290
470
  };
471
+ var FRONTEND_TARGETS = {
472
+ web: { name: "Web", description: "\u6D4F\u89C8\u5668 Web \u5E94\u7528", implemented: true },
473
+ app: { name: "App", description: "\u79FB\u52A8\u7AEF H5/App \u524D\u7AEF", implemented: true },
474
+ miniapp: { name: "\u5C0F\u7A0B\u5E8F", description: "\u9884\u7559\u76EE\u6807\uFF0C\u5F53\u524D\u53EA\u751F\u6210\u8BF4\u660E\u5360\u4F4D", implemented: false },
475
+ desktop: { name: "\u684C\u9762\u7AEF", description: "\u684C\u9762\u7AEF\u524D\u7AEF\u5DE5\u7A0B\uFF08Tauri \u9884\u7559\uFF09", implemented: true }
476
+ };
477
+ var DEFAULT_FULLSTACK_FRONTENDS2 = ["web", "app", "desktop"];
291
478
  var FEATURES = {
292
479
  "iam": {
293
480
  id: "iam",
@@ -470,34 +657,38 @@ async function createProject(options) {
470
657
  name: resolvedOptions.name,
471
658
  appType: resolvedOptions.appType,
472
659
  features: resolvedOptions.features,
660
+ frontends: resolvedOptions.frontends,
473
661
  moduleConfigs: resolvedOptions.moduleConfigs,
474
662
  packageManager: resolvedOptions.packageManager
475
663
  });
476
664
  await generateFromTemplates(projectPath, context);
477
- const { generateConfigFile: generateConfigFile2 } = await import('./config-templates-S5RI2MC6.js');
478
- const configs = resolvedOptions.moduleConfigs;
479
- for (const featureId of resolvedOptions.features) {
480
- const configKey = getFeatureConfigKey(featureId);
481
- if (configKey) {
482
- const content = generateConfigFile2(configKey, configs);
483
- await writeFile(path2.join(projectPath, "config", `_${configKey}.yml`), content);
665
+ if (resolvedOptions.appType !== "fullstack") {
666
+ const { generateConfigFile: generateConfigFile2 } = await import('./cli-config-templates-JCS7T3NU.js');
667
+ const configs = resolvedOptions.moduleConfigs;
668
+ for (const featureId of resolvedOptions.features) {
669
+ const configKey = getFeatureConfigKey(featureId);
670
+ if (configKey) {
671
+ const content = generateConfigFile2(configKey, configs);
672
+ await writeFile(path2.join(projectPath, "config", `_${configKey}.yml`), content);
673
+ }
484
674
  }
675
+ await writeFile(
676
+ path2.join(projectPath, "config", "_core.yml"),
677
+ generateConfigFile2("core", configs)
678
+ );
679
+ const { generateEnvExample } = await import('./cli-config-templates-JCS7T3NU.js');
680
+ await writeFile(
681
+ path2.join(projectPath, ".env.example"),
682
+ generateEnvExample(resolvedOptions.features, configs)
683
+ );
485
684
  }
486
- await writeFile(
487
- path2.join(projectPath, "config", "_core.yml"),
488
- generateConfigFile2("core", configs)
489
- );
490
- const { generateEnvExample } = await import('./config-templates-S5RI2MC6.js');
491
- await writeFile(
492
- path2.join(projectPath, ".env.example"),
493
- generateEnvExample(resolvedOptions.features, configs)
494
- );
495
- const { generateSkillFiles } = await import('./skill-templates-N5DU5FNX.js');
685
+ const { generateSkillFiles } = await import('./cli-skill-templates-IMYZPQRG.js');
496
686
  await generateSkillFiles(projectPath, resolvedOptions.features, resolvedOptions.appType);
497
687
  const appTypeLabel = APP_TYPES[resolvedOptions.appType].name;
688
+ const readmeContent = resolvedOptions.appType === "fullstack" ? generateFullstackReadme(resolvedOptions.name, resolvedOptions.packageManager, resolvedOptions.frontends) : generateReadme(resolvedOptions.name, appTypeLabel, resolvedOptions.packageManager);
498
689
  await writeFile(
499
690
  path2.join(projectPath, "README.md"),
500
- generateReadme(resolvedOptions.name, appTypeLabel, resolvedOptions.packageManager)
691
+ readmeContent
501
692
  );
502
693
  spinner.succeed();
503
694
  if (resolvedOptions.git) {
@@ -527,6 +718,7 @@ async function createProject(options) {
527
718
  }
528
719
  }
529
720
  async function resolveOptions(options) {
721
+ const useDefaults = options.yes ?? false;
530
722
  core.logger.info("", {});
531
723
  core.logger.info(chalk.bold.cyan(" \u{1F680} hai Agent Framework"));
532
724
  core.logger.info(chalk.gray(" AI-Native \xB7 Configuration-Driven \xB7 Security-First"));
@@ -536,7 +728,9 @@ async function resolveOptions(options) {
536
728
  process3.exit(1);
537
729
  };
538
730
  let projectName = options.name;
539
- if (!projectName) {
731
+ if (!projectName && useDefaults) {
732
+ projectName = "my-app";
733
+ } else if (!projectName) {
540
734
  const { name } = await prompts({
541
735
  type: "text",
542
736
  name: "name",
@@ -553,7 +747,9 @@ async function resolveOptions(options) {
553
747
  projectName = name;
554
748
  }
555
749
  let resolvedAppType = options.appType;
556
- if (!resolvedAppType) {
750
+ if (!resolvedAppType && useDefaults) {
751
+ resolvedAppType = "admin";
752
+ } else if (!resolvedAppType) {
557
753
  const { appType } = await prompts({
558
754
  type: "select",
559
755
  name: "appType",
@@ -567,23 +763,34 @@ async function resolveOptions(options) {
567
763
  resolvedAppType = appType;
568
764
  }
569
765
  const selectedAppType = resolvedAppType || "admin";
766
+ const selectedFrontends = selectedAppType === "fullstack" ? await resolveFrontendTargets(options.frontends, useDefaults, onCancel) : [];
570
767
  let selectedTemplate = options.template;
571
- if (!selectedTemplate) {
572
- const { template } = await prompts({
573
- type: "select",
574
- name: "template",
575
- message: "\u9009\u62E9\u6A21\u677F:",
576
- choices: Object.values(PROJECT_TEMPLATES).map((t) => ({
577
- title: `${chalk.bold(t.name.padEnd(10))} ${chalk.gray(t.description)}`,
578
- value: t.name
579
- })),
580
- initial: 1
581
- }, { onCancel });
582
- selectedTemplate = template;
768
+ if (selectedAppType === "fullstack") {
769
+ selectedTemplate = selectedTemplate ?? "custom";
770
+ } else if (!selectedTemplate) {
771
+ if (useDefaults) {
772
+ selectedTemplate = "default";
773
+ } else {
774
+ const { template } = await prompts({
775
+ type: "select",
776
+ name: "template",
777
+ message: "\u9009\u62E9\u6A21\u677F:",
778
+ choices: Object.values(PROJECT_TEMPLATES).map((t) => ({
779
+ title: `${chalk.bold(t.name.padEnd(10))} ${chalk.gray(t.description)}`,
780
+ value: t.name
781
+ })),
782
+ initial: 1
783
+ }, { onCancel });
784
+ selectedTemplate = template;
785
+ }
583
786
  }
584
787
  let selectedFeatures = [];
585
788
  const appTypeDefaults = APP_TYPES[selectedAppType].defaultFeatures;
586
- if (selectedTemplate === "custom" && !options.features) {
789
+ if (selectedAppType === "fullstack") {
790
+ selectedFeatures = [];
791
+ } else if (selectedTemplate === "custom" && !options.features && useDefaults) {
792
+ selectedFeatures = resolveFeatureDependencies(appTypeDefaults);
793
+ } else if (selectedTemplate === "custom" && !options.features) {
587
794
  const featureChoices = SELECTABLE_FEATURES.map((id) => ({
588
795
  title: `${chalk.bold(FEATURES[id].name.padEnd(10))} ${chalk.gray(FEATURES[id].description)}`,
589
796
  value: id,
@@ -606,9 +813,11 @@ async function resolveOptions(options) {
606
813
  const merged = /* @__PURE__ */ new Set([...templateFeatures, ...appTypeDefaults]);
607
814
  selectedFeatures = resolveFeatureDependencies(Array.from(merged));
608
815
  }
609
- const moduleConfigs = options.moduleConfigs ?? await promptModuleConfigs(selectedFeatures, projectName);
816
+ const moduleConfigs = selectedAppType === "fullstack" ? options.moduleConfigs ?? {} : options.moduleConfigs ?? (useDefaults ? buildDefaultModuleConfigs(selectedFeatures, projectName) : await promptModuleConfigs(selectedFeatures, projectName));
610
817
  let addExamples = options.examples;
611
- if (addExamples === void 0) {
818
+ if (addExamples === void 0 && useDefaults) {
819
+ addExamples = true;
820
+ } else if (addExamples === void 0) {
612
821
  const { examples } = await prompts({
613
822
  type: "confirm",
614
823
  name: "examples",
@@ -618,7 +827,10 @@ async function resolveOptions(options) {
618
827
  addExamples = examples;
619
828
  }
620
829
  const detected = await detectPackageManager(options.cwd ?? ".");
621
- let packageManager = options.packageManager;
830
+ let packageManager = selectedAppType === "fullstack" ? "pnpm" : options.packageManager;
831
+ if (!packageManager && useDefaults) {
832
+ packageManager = detected;
833
+ }
622
834
  if (!packageManager) {
623
835
  const { pm } = await prompts({
624
836
  type: "select",
@@ -634,7 +846,9 @@ async function resolveOptions(options) {
634
846
  packageManager = pm;
635
847
  }
636
848
  let install = options.install;
637
- if (install === void 0) {
849
+ if (install === void 0 && useDefaults) {
850
+ install = true;
851
+ } else if (install === void 0) {
638
852
  const { doInstall } = await prompts({
639
853
  type: "confirm",
640
854
  name: "doInstall",
@@ -644,7 +858,9 @@ async function resolveOptions(options) {
644
858
  install = doInstall;
645
859
  }
646
860
  let git = options.git;
647
- if (git === void 0) {
861
+ if (git === void 0 && useDefaults) {
862
+ git = true;
863
+ } else if (git === void 0) {
648
864
  const { initGit } = await prompts({
649
865
  type: "confirm",
650
866
  name: "initGit",
@@ -654,7 +870,9 @@ async function resolveOptions(options) {
654
870
  git = initGit;
655
871
  }
656
872
  let gitRemote = options.gitRemote;
657
- if (git && gitRemote === void 0) {
873
+ if (git && gitRemote === void 0 && useDefaults) {
874
+ gitRemote = "";
875
+ } else if (git && gitRemote === void 0) {
658
876
  const { remote } = await prompts({
659
877
  type: "text",
660
878
  name: "remote",
@@ -673,6 +891,7 @@ async function resolveOptions(options) {
673
891
  return {
674
892
  name: projectName,
675
893
  appType: selectedAppType,
894
+ frontends: selectedFrontends,
676
895
  template: selectedTemplate ?? "default",
677
896
  features: selectedFeatures,
678
897
  moduleConfigs,
@@ -681,10 +900,58 @@ async function resolveOptions(options) {
681
900
  packageManager: packageManager || "pnpm",
682
901
  git: git ?? true,
683
902
  gitRemote: gitRemote ?? "",
903
+ yes: useDefaults,
684
904
  verbose: options.verbose ?? false,
685
905
  cwd: options.cwd ?? "."
686
906
  };
687
907
  }
908
+ function buildDefaultModuleConfigs(features, projectName) {
909
+ const configs = {
910
+ core: { name: projectName, defaultLocale: "zh-CN" }
911
+ };
912
+ if (features.includes("db")) {
913
+ configs.db = { type: "sqlite", database: "./data/app.db" };
914
+ }
915
+ if (features.includes("cache")) {
916
+ configs.cache = { type: "memory" };
917
+ }
918
+ if (features.includes("iam")) {
919
+ configs.iam = { loginPassword: true, loginOtp: false };
920
+ }
921
+ if (features.includes("storage")) {
922
+ configs.storage = { type: "local", localPath: "./data/uploads" };
923
+ }
924
+ if (features.includes("ai")) {
925
+ configs.ai = { defaultProvider: "openai", model: "gpt-4o-mini" };
926
+ }
927
+ return configs;
928
+ }
929
+ async function resolveFrontendTargets(frontends, useDefaults, onCancel) {
930
+ if (frontends && frontends.length > 0) {
931
+ return normalizeFrontendTargets(frontends);
932
+ }
933
+ if (useDefaults) {
934
+ return DEFAULT_FULLSTACK_FRONTENDS2;
935
+ }
936
+ const { selectedFrontends } = await prompts({
937
+ type: "multiselect",
938
+ name: "selectedFrontends",
939
+ message: "\u9009\u62E9\u524D\u7AEF\u76EE\u6807 (\u7A7A\u683C\u9009\u62E9\uFF0C\u56DE\u8F66\u786E\u8BA4):",
940
+ choices: Object.entries(FRONTEND_TARGETS).map(([id, target]) => ({
941
+ title: `${chalk.bold(target.name.padEnd(8))} ${chalk.gray(target.description)}`,
942
+ value: id,
943
+ selected: DEFAULT_FULLSTACK_FRONTENDS2.includes(id)
944
+ })),
945
+ hint: "- \u5C0F\u7A0B\u5E8F\u5F53\u524D\u4E3A\u9884\u7559\u5360\u4F4D\uFF0C\u4E0D\u751F\u6210\u53EF\u8FD0\u884C\u5DE5\u7A0B",
946
+ instructions: false
947
+ }, { onCancel });
948
+ return normalizeFrontendTargets(selectedFrontends ?? DEFAULT_FULLSTACK_FRONTENDS2);
949
+ }
950
+ function normalizeFrontendTargets(frontends) {
951
+ const validTargets = new Set(Object.keys(FRONTEND_TARGETS));
952
+ const result = frontends.filter((target) => validTargets.has(target));
953
+ return [...new Set(result.length > 0 ? result : DEFAULT_FULLSTACK_FRONTENDS2)];
954
+ }
688
955
  function resolveFeatureDependencies(features) {
689
956
  const result = new Set(features);
690
957
  let changed = true;
@@ -890,6 +1157,7 @@ ${pm} dev
890
1157
 
891
1158
  \`\`\`bash
892
1159
  ${pm} build
1160
+ ${pm} i18n:compile
893
1161
  ${pm} preview
894
1162
  \`\`\`
895
1163
 
@@ -900,6 +1168,62 @@ ${pm} preview
900
1168
  - [Svelte 5](https://svelte.dev/)
901
1169
  `;
902
1170
  }
1171
+ function generateFullstackReadme(name, pm, frontends) {
1172
+ const enabledFrontends = frontends.map((target) => FRONTEND_TARGETS[target].name).join(" / ");
1173
+ return `# ${name}
1174
+
1175
+ \u57FA\u4E8E hai Agent Framework \u6784\u5EFA\u7684\u524D\u540E\u7AEF\u5206\u79BB\u591A\u5305\u5DE5\u7A0B\u3002
1176
+
1177
+ ## \u5DE5\u7A0B\u7ED3\u6784
1178
+
1179
+ - \`packages/${name}-contract\`\uFF1A\u524D\u540E\u7AEF\u5171\u4EAB API \u5951\u7EA6\u3001Zod Schema \u4E0E\u7C7B\u578B
1180
+ - \`packages/${name}-serv\`\uFF1A\u540E\u7AEF API Service\uFF08\u901A\u8FC7 \`@h-ai/serv\` \u66B4\u9732\u6700\u5C0F HTTP App \u62BD\u8C61\uFF0C\u4E0D\u76F4\u63A5\u66B4\u9732 Hono\uFF09
1181
+ - \`packages/${name}-shared\`\uFF1A\u8DE8\u7AEF\u5171\u4EAB Shell\u3001\u4E3B\u9898\u3001\u8BED\u8A00\u5207\u6362\u3001API client \u4E0E\u5171\u4EAB messages
1182
+ - \`apps/${name}-web\`\uFF1AWeb \u524D\u7AEF${frontends.includes("web") ? "" : "\uFF08\u672A\u542F\u7528\uFF09"}
1183
+ - \`apps/${name}-app\`\uFF1A\u79FB\u52A8\u7AEF App/H5 \u524D\u7AEF${frontends.includes("app") ? "" : "\uFF08\u672A\u542F\u7528\uFF09"}
1184
+ - \`apps/${name}-desktop\`\uFF1A\u684C\u9762\u7AEF\u524D\u7AEF${frontends.includes("desktop") ? "" : "\uFF08\u672A\u542F\u7528\uFF09"}
1185
+ - \`apps/${name}-miniapp\`\uFF1A\u5C0F\u7A0B\u5E8F\u9884\u7559\u8BF4\u660E\u4E0E\u672C\u7AEF inlang messages${frontends.includes("miniapp") ? "\uFF08\u5360\u4F4D\uFF0C\u4E0D\u53C2\u4E0E\u6784\u5EFA\uFF09" : "\uFF08\u672A\u542F\u7528\uFF09"}
1186
+
1187
+ \u5DF2\u542F\u7528\u524D\u7AEF\uFF1A${enabledFrontends || "Web / App / Desktop"}
1188
+
1189
+ ## \u5F00\u53D1
1190
+
1191
+ \`\`\`bash
1192
+ ${pm} install
1193
+ ${pm} dev
1194
+ \`\`\`
1195
+
1196
+ ## i18n \u4E0E shared \u534F\u540C
1197
+
1198
+ - shared\uFF1A\`packages/${name}-shared/project.inlang\` + \`messages/\`\uFF0C\u53EA\u653E\u8DE8\u7AEF\u5171\u4EAB\u7EC4\u4EF6\u6587\u6848\u3002
1199
+ - web/app/desktop\uFF1A\u6BCF\u4E2A\u5E94\u7528\u90FD\u6709\u81EA\u5DF1\u7684 \`project.inlang\` + \`messages/\`\uFF0C\u7528\u4E8E\u672C\u7AEF\u9875\u9762\u4E0E\u4EA7\u54C1\u6587\u6848\u3002
1200
+ - miniapp\uFF1A\u5F53\u524D\u662F\u5360\u4F4D\uFF0C\u4F46\u4E5F\u6709\u81EA\u5DF1\u7684 \`project.inlang\` + \`messages/\`\uFF0C\u4FBF\u4E8E\u540E\u7EED\u63A5\u5165\u771F\u5B9E\u5C0F\u7A0B\u5E8F\u6846\u67B6\u3002
1201
+ - \u65B0\u589E locale \u65F6\uFF0Cshared \u4E0E\u6240\u6709\u542F\u7528\u524D\u7AEF\u7684 \`locales\` \u5FC5\u987B\u540C\u6B65\uFF1B\u8BED\u8A00\u5207\u6362\u5668\u4F1A\u540C\u65F6\u66F4\u65B0 shared runtime \u4E0E\u5BBF\u4E3B\u5E94\u7528 runtime\u3002
1202
+ - \`${pm} i18n:compile\` \u4F1A\u7EDF\u4E00\u7F16\u8BD1 shared \u4E0E\u6BCF\u4E2A\u542F\u7528\u524D\u7AEF\u7684 Paraglide \u8F93\u51FA\uFF1B\u5404\u524D\u7AEF\u4E5F\u53EF\u5355\u72EC\u8FD0\u884C \`${pm} --filter ${name}-web paraglide:compile\` \u7B49\u547D\u4EE4\u3002
1203
+
1204
+ ## \u81EA\u5B9A\u4E49\u5EFA\u8BAE
1205
+
1206
+ - \u6539\u5168\u5C40 Shell/\u4E3B\u9898/\u8BED\u8A00\u5207\u6362\uFF1A\u7F16\u8F91 \`packages/${name}-shared\`\u3002
1207
+ - \u6539\u67D0\u4E2A\u524D\u7AEF\u9875\u9762\u548C\u6587\u6848\uFF1A\u7F16\u8F91\u5BF9\u5E94 \`apps/${name}-*/src/routes\` \u4E0E \`apps/${name}-*/messages\`\u3002
1208
+ - \u6539\u4E3B\u9898\u5217\u8868\uFF1A\u8C03\u6574\u811A\u624B\u67B6\u914D\u7F6E\u4E2D\u7684 \`fullstack.theme.themes\`\uFF0C\u6216\u76F4\u63A5\u7F16\u8F91\u5404\u7AEF \`src/app.css\` \u7684 daisyUI \`themes\`\u3002
1209
+
1210
+ ## \u8D28\u91CF\u95E8\u7981
1211
+
1212
+ \`\`\`bash
1213
+ ${pm} typecheck
1214
+ ${pm} lint
1215
+ ${pm} test
1216
+ ${pm} test:e2e
1217
+ \`\`\`
1218
+
1219
+ ## \u6587\u6863
1220
+
1221
+ - [hai Agent Framework](https://github.com/hai-series/hai-framework)
1222
+ - [SvelteKit](https://kit.svelte.dev/)
1223
+ - [@h-ai/serv](./.agents/skills/hai-serv/SKILL.md)
1224
+ - [@h-ai/ui](./.agents/skills/hai-ui/SKILL.md)
1225
+ `;
1226
+ }
903
1227
  function printCompletionMessage(options) {
904
1228
  core.logger.info("", {});
905
1229
  core.logger.info(chalk.green("\u2714 \u9879\u76EE\u521B\u5EFA\u6210\u529F\uFF01"));
@@ -919,7 +1243,13 @@ function printCompletionMessage(options) {
919
1243
  if (!options.install) {
920
1244
  core.logger.info(chalk.cyan(` ${options.packageManager} install`));
921
1245
  }
922
- core.logger.info(chalk.cyan(` ${options.packageManager} dev`));
1246
+ if (options.appType === "fullstack") {
1247
+ core.logger.info(chalk.cyan(` ${options.packageManager} typecheck`));
1248
+ core.logger.info(chalk.cyan(` ${options.packageManager} test`));
1249
+ core.logger.info(chalk.cyan(` ${options.packageManager} test:e2e`));
1250
+ } else {
1251
+ core.logger.info(chalk.cyan(` ${options.packageManager} dev`));
1252
+ }
923
1253
  core.logger.info("", {});
924
1254
  }
925
1255
  async function detectProject(cwd) {
@@ -943,7 +1273,7 @@ async function detectProject(cwd) {
943
1273
  }
944
1274
  }
945
1275
 
946
- // src/commands/add.ts
1276
+ // src/commands/cli-add.ts
947
1277
  var MODULE_MAP = {
948
1278
  // iam 依赖 crypto(password hash)/ db(user 存储)/ cache(session 缓存)
949
1279
  "iam": { packages: ["@h-ai/iam"], deps: ["crypto", "db", "cache"], configKey: "iam", description: "\u8EAB\u4EFD\u4E0E\u8BBF\u95EE\u7BA1\u7406" },
@@ -1002,7 +1332,7 @@ async function addModule(options) {
1002
1332
  core.logger.info(`\u53EF\u7528\u6A21\u5757: ${Object.keys(MODULE_MAP).join(", ")}`);
1003
1333
  return;
1004
1334
  }
1005
- const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./skill-templates-N5DU5FNX.js');
1335
+ const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-IMYZPQRG.js');
1006
1336
  if (moduleDef.packages.every((p) => project.haiPackages.includes(p))) {
1007
1337
  const generatedFiles2 = await generateProjectAiSupport(
1008
1338
  cwd,
@@ -1116,7 +1446,7 @@ async function deployCommand(options) {
1116
1446
  try {
1117
1447
  let deployModule;
1118
1448
  try {
1119
- deployModule = await import('./src-RZ4DPYNJ.js');
1449
+ deployModule = await import('./src-N4NPS7AG.js');
1120
1450
  } catch {
1121
1451
  core.logger.error(chalk.red("Deploy module not found. Install @h-ai/deploy first:"));
1122
1452
  core.logger.info(chalk.cyan(" pnpm add @h-ai/deploy"));
@@ -1592,7 +1922,7 @@ async function initProject(options) {
1592
1922
  }
1593
1923
  }
1594
1924
 
1595
- // src/index.ts
1925
+ // src/cli-main.ts
1596
1926
  function readCliVersion() {
1597
1927
  try {
1598
1928
  const here = path2.dirname(fileURLToPath(import.meta.url));
@@ -1607,12 +1937,14 @@ var VERSION = readCliVersion();
1607
1937
  var cli = cac("hai");
1608
1938
  cli.option("-v, --verbose", "\u663E\u793A\u8BE6\u7EC6\u8F93\u51FA");
1609
1939
  cli.option("-C, --cwd <path>", "\u5DE5\u4F5C\u76EE\u5F55");
1610
- cli.command("create [name]", "\u521B\u5EFA\u65B0\u7684 hai \u9879\u76EE").option("--type <type>", "\u5E94\u7528\u7C7B\u578B (admin, website, h5, api)").option("-t, --template <template>", "\u9879\u76EE\u6A21\u677F (minimal, default, full, custom)").option("-f, --features <features>", "\u529F\u80FD\u5217\u8868 (\u9017\u53F7\u5206\u9694: iam,db,cache,ai,storage,crypto)").option("--examples", "\u6DFB\u52A0\u793A\u4F8B\u4EE3\u7801").option("--no-examples", "\u4E0D\u6DFB\u52A0\u793A\u4F8B\u4EE3\u7801").option("--no-install", "\u4E0D\u5B89\u88C5\u4F9D\u8D56").option("-p, --package-manager <pm>", "\u5305\u7BA1\u7406\u5668 (pnpm, npm, yarn)").option("--no-git", "\u4E0D\u521D\u59CB\u5316 Git").option("--git-remote <url>", "Git \u8FDC\u7A0B\u4ED3\u5E93\u5730\u5740").action(async (name, options) => {
1940
+ cli.command("create [name]", "\u521B\u5EFA\u65B0\u7684 hai \u9879\u76EE").option("--type <type>", "\u5E94\u7528\u7C7B\u578B (admin, website, h5, api, android-app, fullstack)").option("--frontends <targets>", "\u524D\u540E\u7AEF\u5206\u79BB\u5DE5\u7A0B\u524D\u7AEF\u76EE\u6807 (\u9017\u53F7\u5206\u9694: web,app,miniapp,desktop)").option("-t, --template <template>", "\u9879\u76EE\u6A21\u677F (minimal, default, full, custom)").option("-f, --features <features>", "\u529F\u80FD\u5217\u8868 (\u9017\u53F7\u5206\u9694: iam,db,cache,ai,storage,crypto)").option("--examples", "\u6DFB\u52A0\u793A\u4F8B\u4EE3\u7801").option("--no-examples", "\u4E0D\u6DFB\u52A0\u793A\u4F8B\u4EE3\u7801").option("--no-install", "\u4E0D\u5B89\u88C5\u4F9D\u8D56").option("-p, --package-manager <pm>", "\u5305\u7BA1\u7406\u5668 (pnpm, npm, yarn)").option("--no-git", "\u4E0D\u521D\u59CB\u5316 Git").option("--git-remote <url>", "Git \u8FDC\u7A0B\u4ED3\u5E93\u5730\u5740").option("-y, --yes", "\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E\uFF0C\u8DF3\u8FC7\u4EA4\u4E92").action(async (name, options) => {
1611
1941
  try {
1612
1942
  const features = typeof options.features === "string" ? options.features.split(",").map((f) => f.trim()) : void 0;
1943
+ const frontends = typeof options.frontends === "string" ? options.frontends.split(",").map((f) => f.trim()).filter(Boolean) : void 0;
1613
1944
  await createProject({
1614
1945
  name: name ?? "",
1615
1946
  appType: options.type,
1947
+ frontends,
1616
1948
  template: options.template,
1617
1949
  features,
1618
1950
  examples: options.examples,
@@ -1620,6 +1952,7 @@ cli.command("create [name]", "\u521B\u5EFA\u65B0\u7684 hai \u9879\u76EE").option
1620
1952
  packageManager: options.packageManager,
1621
1953
  git: options.git,
1622
1954
  gitRemote: options.gitRemote,
1955
+ yes: options.yes,
1623
1956
  verbose: options.verbose,
1624
1957
  cwd: options.cwd
1625
1958
  });