fragment-ts 2.0.2 → 2.0.4
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/API.md +1 -8
- package/DOCS.md +1 -5
- package/README.md +2 -11
- package/SETUP.md +4 -14
- package/USAGE.md +11 -4
- package/dist/core/decorators/middleware.decorator.d.ts +10 -1
- package/dist/core/decorators/middleware.decorator.d.ts.map +1 -1
- package/dist/core/decorators/middleware.decorator.js +35 -7
- package/dist/core/decorators/middleware.decorator.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/platform/cli/commands/install/command.d.ts +14 -0
- package/dist/platform/cli/commands/install/command.d.ts.map +1 -0
- package/dist/platform/cli/commands/install/command.js +145 -0
- package/dist/platform/cli/commands/install/command.js.map +1 -0
- package/dist/platform/cli/commands/register.d.ts.map +1 -1
- package/dist/platform/cli/commands/register.js +39 -32
- package/dist/platform/cli/commands/register.js.map +1 -1
- package/dist/platform/cli/commands/serve/command.d.ts.map +1 -1
- package/dist/platform/cli/commands/serve/command.js +9 -3
- package/dist/platform/cli/commands/serve/command.js.map +1 -1
- package/dist/platform/cli/index.js +77 -10
- package/dist/platform/cli/index.js.map +1 -1
- package/dist/platform/cli/scaffold/generate/component-generator.js +1 -1
- package/dist/platform/cli/scaffold/init/init.scaffold.d.ts.map +1 -1
- package/dist/platform/cli/scaffold/init/init.scaffold.js +1 -8
- package/dist/platform/cli/scaffold/init/init.scaffold.js.map +1 -1
- package/dist/platform/cli/web/commands/create/feature.d.ts +11 -0
- package/dist/platform/cli/web/commands/create/feature.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/create/feature.js +43 -0
- package/dist/platform/cli/web/commands/create/feature.js.map +1 -0
- package/dist/platform/cli/web/commands/create/module.d.ts +11 -0
- package/dist/platform/cli/web/commands/create/module.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/create/module.js +44 -0
- package/dist/platform/cli/web/commands/create/module.js.map +1 -0
- package/dist/platform/cli/web/commands/create/mvvm.d.ts +11 -0
- package/dist/platform/cli/web/commands/create/mvvm.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/create/mvvm.js +43 -0
- package/dist/platform/cli/web/commands/create/mvvm.js.map +1 -0
- package/dist/platform/cli/web/commands/init-fullstack.d.ts +12 -0
- package/dist/platform/cli/web/commands/init-fullstack.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/init-fullstack.js +42 -0
- package/dist/platform/cli/web/commands/init-fullstack.js.map +1 -0
- package/dist/platform/cli/web/commands/init-web.d.ts +12 -0
- package/dist/platform/cli/web/commands/init-web.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/init-web.js +70 -0
- package/dist/platform/cli/web/commands/init-web.js.map +1 -0
- package/dist/platform/cli/web/commands/install-api.d.ts +11 -0
- package/dist/platform/cli/web/commands/install-api.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/install-api.js +92 -0
- package/dist/platform/cli/web/commands/install-api.js.map +1 -0
- package/dist/platform/cli/web/commands/install-web.d.ts +13 -0
- package/dist/platform/cli/web/commands/install-web.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/install-web.js +102 -0
- package/dist/platform/cli/web/commands/install-web.js.map +1 -0
- package/dist/platform/cli/web/commands/install.d.ts +3 -0
- package/dist/platform/cli/web/commands/install.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/install.js +23 -0
- package/dist/platform/cli/web/commands/install.js.map +1 -0
- package/dist/platform/cli/web/commands/make/component.d.ts +16 -0
- package/dist/platform/cli/web/commands/make/component.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/make/component.js +73 -0
- package/dist/platform/cli/web/commands/make/component.js.map +1 -0
- package/dist/platform/cli/web/commands/make/guard.d.ts +13 -0
- package/dist/platform/cli/web/commands/make/guard.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/make/guard.js +46 -0
- package/dist/platform/cli/web/commands/make/guard.js.map +1 -0
- package/dist/platform/cli/web/commands/make/layout.d.ts +12 -0
- package/dist/platform/cli/web/commands/make/layout.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/make/layout.js +44 -0
- package/dist/platform/cli/web/commands/make/layout.js.map +1 -0
- package/dist/platform/cli/web/commands/make/page.d.ts +17 -0
- package/dist/platform/cli/web/commands/make/page.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/make/page.js +66 -0
- package/dist/platform/cli/web/commands/make/page.js.map +1 -0
- package/dist/platform/cli/web/commands/make/resource.d.ts +17 -0
- package/dist/platform/cli/web/commands/make/resource.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/make/resource.js +37 -0
- package/dist/platform/cli/web/commands/make/resource.js.map +1 -0
- package/dist/platform/cli/web/commands/make/service.d.ts +16 -0
- package/dist/platform/cli/web/commands/make/service.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/make/service.js +137 -0
- package/dist/platform/cli/web/commands/make/service.js.map +1 -0
- package/dist/platform/cli/web/commands/make/store.d.ts +16 -0
- package/dist/platform/cli/web/commands/make/store.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/make/store.js +72 -0
- package/dist/platform/cli/web/commands/make/store.js.map +1 -0
- package/dist/platform/cli/web/commands/web-sync.d.ts +10 -0
- package/dist/platform/cli/web/commands/web-sync.d.ts.map +1 -0
- package/dist/platform/cli/web/commands/web-sync.js +307 -0
- package/dist/platform/cli/web/commands/web-sync.js.map +1 -0
- package/dist/platform/cli/web/index.d.ts +3 -0
- package/dist/platform/cli/web/index.d.ts.map +1 -0
- package/dist/platform/cli/web/index.js +40 -0
- package/dist/platform/cli/web/index.js.map +1 -0
- package/dist/platform/cli/web/utils/config.d.ts +52 -0
- package/dist/platform/cli/web/utils/config.d.ts.map +1 -0
- package/dist/platform/cli/web/utils/config.js +89 -0
- package/dist/platform/cli/web/utils/config.js.map +1 -0
- package/dist/platform/cli/web/utils/format.d.ts +2 -0
- package/dist/platform/cli/web/utils/format.d.ts.map +1 -0
- package/dist/platform/cli/web/utils/format.js +28 -0
- package/dist/platform/cli/web/utils/format.js.map +1 -0
- package/dist/platform/cli/web/utils/header.d.ts +3 -0
- package/dist/platform/cli/web/utils/header.d.ts.map +1 -0
- package/dist/platform/cli/web/utils/header.js +11 -0
- package/dist/platform/cli/web/utils/header.js.map +1 -0
- package/dist/platform/cli/web/utils/logger.d.ts +10 -0
- package/dist/platform/cli/web/utils/logger.d.ts.map +1 -0
- package/dist/platform/cli/web/utils/logger.js +47 -0
- package/dist/platform/cli/web/utils/logger.js.map +1 -0
- package/dist/platform/cli/web/utils/names.d.ts +7 -0
- package/dist/platform/cli/web/utils/names.d.ts.map +1 -0
- package/dist/platform/cli/web/utils/names.js +43 -0
- package/dist/platform/cli/web/utils/names.js.map +1 -0
- package/dist/platform/cli/web/utils/resolve-paths.d.ts +10 -0
- package/dist/platform/cli/web/utils/resolve-paths.d.ts.map +1 -0
- package/dist/platform/cli/web/utils/resolve-paths.js +87 -0
- package/dist/platform/cli/web/utils/resolve-paths.js.map +1 -0
- package/dist/platform/cli/web/utils/write-file.d.ts +7 -0
- package/dist/platform/cli/web/utils/write-file.d.ts.map +1 -0
- package/dist/platform/cli/web/utils/write-file.js +31 -0
- package/dist/platform/cli/web/utils/write-file.js.map +1 -0
- package/dist/platform/web/application.d.ts.map +1 -1
- package/dist/platform/web/application.js +2 -0
- package/dist/platform/web/application.js.map +1 -1
- package/dist/platform/web/bootstrap/RouteRegistrar.d.ts +2 -0
- package/dist/platform/web/bootstrap/RouteRegistrar.d.ts.map +1 -1
- package/dist/platform/web/bootstrap/RouteRegistrar.js +33 -4
- package/dist/platform/web/bootstrap/RouteRegistrar.js.map +1 -1
- package/dist/shared/config.utils.d.ts +1 -0
- package/dist/shared/config.utils.d.ts.map +1 -1
- package/dist/shared/config.utils.js +36 -11
- package/dist/shared/config.utils.js.map +1 -1
- package/dist/shared/env.utils.d.ts +6 -1
- package/dist/shared/env.utils.d.ts.map +1 -1
- package/dist/shared/env.utils.js +92 -9
- package/dist/shared/env.utils.js.map +1 -1
- package/dist/shared/runtime.resolver.d.ts +1 -0
- package/dist/shared/runtime.resolver.d.ts.map +1 -1
- package/dist/shared/runtime.resolver.js +27 -6
- package/dist/shared/runtime.resolver.js.map +1 -1
- package/dist/web/cli/commands/create/feature.d.ts +11 -0
- package/dist/web/cli/commands/create/feature.d.ts.map +1 -0
- package/dist/web/cli/commands/create/feature.js +43 -0
- package/dist/web/cli/commands/create/feature.js.map +1 -0
- package/dist/web/cli/commands/create/module.d.ts +11 -0
- package/dist/web/cli/commands/create/module.d.ts.map +1 -0
- package/dist/web/cli/commands/create/module.js +44 -0
- package/dist/web/cli/commands/create/module.js.map +1 -0
- package/dist/web/cli/commands/create/mvvm.d.ts +11 -0
- package/dist/web/cli/commands/create/mvvm.d.ts.map +1 -0
- package/dist/web/cli/commands/create/mvvm.js +43 -0
- package/dist/web/cli/commands/create/mvvm.js.map +1 -0
- package/dist/web/cli/commands/init-fullstack.d.ts +12 -0
- package/dist/web/cli/commands/init-fullstack.d.ts.map +1 -0
- package/dist/web/cli/commands/init-fullstack.js +42 -0
- package/dist/web/cli/commands/init-fullstack.js.map +1 -0
- package/dist/web/cli/commands/init-web.d.ts +12 -0
- package/dist/web/cli/commands/init-web.d.ts.map +1 -0
- package/dist/web/cli/commands/init-web.js +70 -0
- package/dist/web/cli/commands/init-web.js.map +1 -0
- package/dist/web/cli/commands/install-api.d.ts +10 -0
- package/dist/web/cli/commands/install-api.d.ts.map +1 -0
- package/dist/web/cli/commands/install-api.js +91 -0
- package/dist/web/cli/commands/install-api.js.map +1 -0
- package/dist/web/cli/commands/install-web.d.ts +12 -0
- package/dist/web/cli/commands/install-web.d.ts.map +1 -0
- package/dist/web/cli/commands/install-web.js +101 -0
- package/dist/web/cli/commands/install-web.js.map +1 -0
- package/dist/web/cli/commands/make/component.d.ts +13 -0
- package/dist/web/cli/commands/make/component.d.ts.map +1 -0
- package/dist/web/cli/commands/make/component.js +40 -0
- package/dist/web/cli/commands/make/component.js.map +1 -0
- package/dist/web/cli/commands/make/guard.d.ts +11 -0
- package/dist/web/cli/commands/make/guard.d.ts.map +1 -0
- package/dist/web/cli/commands/make/guard.js +37 -0
- package/dist/web/cli/commands/make/guard.js.map +1 -0
- package/dist/web/cli/commands/make/layout.d.ts +11 -0
- package/dist/web/cli/commands/make/layout.d.ts.map +1 -0
- package/dist/web/cli/commands/make/layout.js +37 -0
- package/dist/web/cli/commands/make/layout.js.map +1 -0
- package/dist/web/cli/commands/make/page.d.ts +14 -0
- package/dist/web/cli/commands/make/page.d.ts.map +1 -0
- package/dist/web/cli/commands/make/page.js +42 -0
- package/dist/web/cli/commands/make/page.js.map +1 -0
- package/dist/web/cli/commands/make/resource.d.ts +13 -0
- package/dist/web/cli/commands/make/resource.d.ts.map +1 -0
- package/dist/web/cli/commands/make/resource.js +33 -0
- package/dist/web/cli/commands/make/resource.js.map +1 -0
- package/dist/web/cli/commands/make/service.d.ts +14 -0
- package/dist/web/cli/commands/make/service.d.ts.map +1 -0
- package/dist/web/cli/commands/make/service.js +56 -0
- package/dist/web/cli/commands/make/service.js.map +1 -0
- package/dist/web/cli/commands/make/store.d.ts +14 -0
- package/dist/web/cli/commands/make/store.d.ts.map +1 -0
- package/dist/web/cli/commands/make/store.js +47 -0
- package/dist/web/cli/commands/make/store.js.map +1 -0
- package/dist/web/cli/commands/web-sync.d.ts +10 -0
- package/dist/web/cli/commands/web-sync.d.ts.map +1 -0
- package/dist/web/cli/commands/web-sync.js +48 -0
- package/dist/web/cli/commands/web-sync.js.map +1 -0
- package/dist/web/cli/index.d.mts +5 -0
- package/dist/web/cli/index.d.ts +5 -0
- package/dist/web/cli/index.d.ts.map +1 -0
- package/dist/web/cli/index.js +36 -0
- package/dist/web/cli/index.js.map +1 -0
- package/dist/web/cli/index.mjs +984 -0
- package/dist/web/cli/utils/config.d.ts +52 -0
- package/dist/web/cli/utils/config.d.ts.map +1 -0
- package/dist/web/cli/utils/config.js +89 -0
- package/dist/web/cli/utils/config.js.map +1 -0
- package/dist/web/cli/utils/format.d.ts +2 -0
- package/dist/web/cli/utils/format.d.ts.map +1 -0
- package/dist/web/cli/utils/format.js +28 -0
- package/dist/web/cli/utils/format.js.map +1 -0
- package/dist/web/cli/utils/logger.d.ts +10 -0
- package/dist/web/cli/utils/logger.d.ts.map +1 -0
- package/dist/web/cli/utils/logger.js +34 -0
- package/dist/web/cli/utils/logger.js.map +1 -0
- package/dist/web/cli/utils/names.d.ts +7 -0
- package/dist/web/cli/utils/names.d.ts.map +1 -0
- package/dist/web/cli/utils/names.js +43 -0
- package/dist/web/cli/utils/names.js.map +1 -0
- package/dist/web/cli/utils/resolve-paths.d.ts +10 -0
- package/dist/web/cli/utils/resolve-paths.d.ts.map +1 -0
- package/dist/web/cli/utils/resolve-paths.js +87 -0
- package/dist/web/cli/utils/resolve-paths.js.map +1 -0
- package/dist/web/cli/utils/write-file.d.ts +7 -0
- package/dist/web/cli/utils/write-file.d.ts.map +1 -0
- package/dist/web/cli/utils/write-file.js +31 -0
- package/dist/web/cli/utils/write-file.js.map +1 -0
- package/dist/web/platform/cli/web/index.d.mts +5 -0
- package/dist/web/platform/cli/web/index.d.ts +5 -0
- package/dist/web/platform/cli/web/index.js +2035 -0
- package/dist/web/platform/cli/web/index.mjs +1998 -0
- package/dist/web/src/adapters/jotai.d.ts +2 -0
- package/dist/web/src/adapters/jotai.d.ts.map +1 -0
- package/dist/web/src/adapters/jotai.js +7 -0
- package/dist/web/src/adapters/jotai.js.map +1 -0
- package/dist/web/src/adapters/mobx.d.ts +2 -0
- package/dist/web/src/adapters/mobx.d.ts.map +1 -0
- package/dist/web/src/adapters/mobx.js +7 -0
- package/dist/web/src/adapters/mobx.js.map +1 -0
- package/dist/web/src/adapters/redux.d.ts +2 -0
- package/dist/web/src/adapters/redux.d.ts.map +1 -0
- package/dist/web/src/adapters/redux.js +7 -0
- package/dist/web/src/adapters/redux.js.map +1 -0
- package/dist/web/src/adapters/zustand.d.ts +2 -0
- package/dist/web/src/adapters/zustand.d.ts.map +1 -0
- package/dist/web/src/adapters/zustand.js +7 -0
- package/dist/web/src/adapters/zustand.js.map +1 -0
- package/dist/web/src/config/web-config.d.ts +34 -0
- package/dist/web/src/config/web-config.d.ts.map +1 -0
- package/dist/web/src/config/web-config.js +73 -0
- package/dist/web/src/config/web-config.js.map +1 -0
- package/dist/web/src/core/application.d.ts +8 -0
- package/dist/web/src/core/application.d.ts.map +1 -0
- package/dist/web/src/core/application.js +45 -0
- package/dist/web/src/core/application.js.map +1 -0
- package/dist/web/src/core/decorators/component.d.ts +20 -0
- package/dist/web/src/core/decorators/component.d.ts.map +1 -0
- package/dist/web/src/core/decorators/component.js +83 -0
- package/dist/web/src/core/decorators/component.js.map +1 -0
- package/dist/web/src/core/decorators/page.d.ts +27 -0
- package/dist/web/src/core/decorators/page.d.ts.map +1 -0
- package/dist/web/src/core/decorators/page.js +58 -0
- package/dist/web/src/core/decorators/page.js.map +1 -0
- package/dist/web/src/core/di/container.d.ts +8 -0
- package/dist/web/src/core/di/container.d.ts.map +1 -0
- package/dist/web/src/core/di/container.js +26 -0
- package/dist/web/src/core/di/container.js.map +1 -0
- package/dist/web/src/core/runtime.d.ts +3 -0
- package/dist/web/src/core/runtime.d.ts.map +1 -0
- package/dist/web/src/core/runtime.js +15 -0
- package/dist/web/src/core/runtime.js.map +1 -0
- package/dist/web/src/http/fragment-fetch.d.ts +55 -0
- package/dist/web/src/http/fragment-fetch.d.ts.map +1 -0
- package/dist/web/src/http/fragment-fetch.js +290 -0
- package/dist/web/src/http/fragment-fetch.js.map +1 -0
- package/dist/web/src/index.d.mts +137 -0
- package/dist/web/src/index.d.ts +7 -0
- package/dist/web/src/index.d.ts.map +1 -0
- package/dist/web/src/index.js +31 -0
- package/dist/web/src/index.js.map +1 -0
- package/dist/web/src/index.mjs +643 -0
- package/dist/web/src/router/fragment-router.d.ts +14 -0
- package/dist/web/src/router/fragment-router.d.ts.map +1 -0
- package/dist/web/src/router/fragment-router.js +94 -0
- package/dist/web/src/router/fragment-router.js.map +1 -0
- package/dist/web/web/src/index.d.mts +137 -0
- package/dist/web/web/src/index.d.ts +137 -0
- package/dist/web/web/src/index.js +702 -0
- package/dist/web/web/src/index.mjs +644 -0
- package/package.json +15 -3
|
@@ -0,0 +1,1998 @@
|
|
|
1
|
+
// src/platform/cli/web/commands/web-sync.ts
|
|
2
|
+
import fs3 from "fs";
|
|
3
|
+
import path4 from "path";
|
|
4
|
+
|
|
5
|
+
// src/platform/cli/web/utils/config.ts
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
var WEB_CONFIG_FILE = "fragment.web.json";
|
|
9
|
+
function resolveCwd(cwd) {
|
|
10
|
+
return cwd ? path.resolve(cwd) : process.cwd();
|
|
11
|
+
}
|
|
12
|
+
function webConfigPath(cwd) {
|
|
13
|
+
return path.join(resolveCwd(cwd), WEB_CONFIG_FILE);
|
|
14
|
+
}
|
|
15
|
+
function readJsonFile(filePath) {
|
|
16
|
+
if (!fs.existsSync(filePath)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
function readWebConfig(cwd) {
|
|
23
|
+
return readJsonFile(webConfigPath(cwd));
|
|
24
|
+
}
|
|
25
|
+
function ensureWebConfig(cwd) {
|
|
26
|
+
const config2 = readWebConfig(cwd);
|
|
27
|
+
if (!config2) {
|
|
28
|
+
throw new Error("fragment.web.json was not found in the current project");
|
|
29
|
+
}
|
|
30
|
+
return config2;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/platform/cli/web/utils/header.ts
|
|
34
|
+
function autoGeneratedSyncHeader(source) {
|
|
35
|
+
return `// \u26A0\uFE0F AUTO-GENERATED by frg web:sync \u2014 do not edit manually
|
|
36
|
+
// Source: ${source}
|
|
37
|
+
// Synced at: ${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/platform/cli/web/utils/logger.ts
|
|
41
|
+
import pc from "picocolors";
|
|
42
|
+
import path2 from "path";
|
|
43
|
+
function line(symbol, label, message) {
|
|
44
|
+
return ` ${symbol} ${label.padEnd(8, " ")} ${message}`;
|
|
45
|
+
}
|
|
46
|
+
function displayPath(value) {
|
|
47
|
+
if (!value) return value;
|
|
48
|
+
if (!path2.isAbsolute(value)) return value;
|
|
49
|
+
const rel = path2.relative(process.cwd(), value);
|
|
50
|
+
if (!rel) return ".";
|
|
51
|
+
if (rel.startsWith("..")) return value;
|
|
52
|
+
return rel;
|
|
53
|
+
}
|
|
54
|
+
var logger = {
|
|
55
|
+
section(title, subtitle) {
|
|
56
|
+
process.stdout.write(`
|
|
57
|
+
${pc.bold(title)}${subtitle ? ` ${subtitle}` : ""}
|
|
58
|
+
|
|
59
|
+
`);
|
|
60
|
+
},
|
|
61
|
+
created(path19) {
|
|
62
|
+
process.stdout.write(`${line(pc.green("\u2714"), "created", displayPath(path19))}
|
|
63
|
+
`);
|
|
64
|
+
},
|
|
65
|
+
updated(path19, detail) {
|
|
66
|
+
process.stdout.write(
|
|
67
|
+
`${line(pc.cyan("\u2714"), "updated", `${displayPath(path19)}${detail ? ` (${detail})` : ""}`)}
|
|
68
|
+
`
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
exists(path19, detail = "use --force to overwrite") {
|
|
72
|
+
process.stdout.write(`${line(pc.yellow("\u26A0"), "exists", `${displayPath(path19)} (${detail})`)}
|
|
73
|
+
`);
|
|
74
|
+
},
|
|
75
|
+
dryRun(path19) {
|
|
76
|
+
process.stdout.write(`${line(pc.blue("\u2139"), "dry-run", displayPath(path19))}
|
|
77
|
+
`);
|
|
78
|
+
},
|
|
79
|
+
error(message) {
|
|
80
|
+
process.stderr.write(`${line(pc.red("\u2716"), "error", message)}
|
|
81
|
+
`);
|
|
82
|
+
},
|
|
83
|
+
done(message) {
|
|
84
|
+
process.stdout.write(`
|
|
85
|
+
${pc.bold("Done.")} ${message}
|
|
86
|
+
`);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/platform/cli/web/utils/write-file.ts
|
|
91
|
+
import fs2 from "fs";
|
|
92
|
+
import path3 from "path";
|
|
93
|
+
function writeFileIfAllowed(filePath, content, options = {}) {
|
|
94
|
+
const exists = fs2.existsSync(filePath);
|
|
95
|
+
if (options.dryRun) {
|
|
96
|
+
logger.dryRun(filePath);
|
|
97
|
+
return "dry-run";
|
|
98
|
+
}
|
|
99
|
+
if (exists && !options.force) {
|
|
100
|
+
logger.exists(filePath);
|
|
101
|
+
return "exists";
|
|
102
|
+
}
|
|
103
|
+
fs2.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
104
|
+
fs2.writeFileSync(filePath, content, "utf8");
|
|
105
|
+
const mode = options.mode ?? (exists ? "updated" : "created");
|
|
106
|
+
if (mode === "updated") {
|
|
107
|
+
logger.updated(filePath);
|
|
108
|
+
} else {
|
|
109
|
+
logger.created(filePath);
|
|
110
|
+
}
|
|
111
|
+
return mode;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/platform/cli/web/commands/web-sync.ts
|
|
115
|
+
function toTypeName(raw) {
|
|
116
|
+
return raw.replace(/[^a-zA-Z0-9]+/g, " ").split(" ").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
117
|
+
}
|
|
118
|
+
function toIdentifier(raw) {
|
|
119
|
+
const cleaned = raw.replace(/[^a-zA-Z0-9]+/g, " ").split(" ").filter(Boolean).map(
|
|
120
|
+
(part, idx) => idx === 0 ? part.charAt(0).toLowerCase() + part.slice(1) : part.charAt(0).toUpperCase() + part.slice(1)
|
|
121
|
+
).join("");
|
|
122
|
+
return cleaned || "value";
|
|
123
|
+
}
|
|
124
|
+
function toConstKey(value) {
|
|
125
|
+
return value.replace(/[{}]/g, "").replace(/[^a-zA-Z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").toUpperCase();
|
|
126
|
+
}
|
|
127
|
+
function toFileName(raw) {
|
|
128
|
+
const out = raw.trim().replace(/([a-z\d])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
129
|
+
return out || "default";
|
|
130
|
+
}
|
|
131
|
+
function resolveRefName(ref) {
|
|
132
|
+
const parts = ref.split("/");
|
|
133
|
+
return parts[parts.length - 1] || "UnknownRef";
|
|
134
|
+
}
|
|
135
|
+
function schemaToTs(schema) {
|
|
136
|
+
if (!schema) return "unknown";
|
|
137
|
+
if (schema.$ref) {
|
|
138
|
+
return toTypeName(resolveRefName(schema.$ref));
|
|
139
|
+
}
|
|
140
|
+
if (schema.oneOf?.length) {
|
|
141
|
+
return schema.oneOf.map((s) => schemaToTs(s)).join(" | ");
|
|
142
|
+
}
|
|
143
|
+
if (schema.anyOf?.length) {
|
|
144
|
+
return schema.anyOf.map((s) => schemaToTs(s)).join(" | ");
|
|
145
|
+
}
|
|
146
|
+
if (schema.allOf?.length) {
|
|
147
|
+
return schema.allOf.map((s) => schemaToTs(s)).join(" & ");
|
|
148
|
+
}
|
|
149
|
+
if (schema.enum?.length) {
|
|
150
|
+
return schema.enum.map((v) => JSON.stringify(v)).join(" | ");
|
|
151
|
+
}
|
|
152
|
+
switch (schema.type) {
|
|
153
|
+
case "integer":
|
|
154
|
+
case "number":
|
|
155
|
+
return "number";
|
|
156
|
+
case "boolean":
|
|
157
|
+
return "boolean";
|
|
158
|
+
case "array":
|
|
159
|
+
return `${schemaToTs(schema.items)}[]`;
|
|
160
|
+
case "object": {
|
|
161
|
+
const properties = schema.properties || {};
|
|
162
|
+
const required = new Set(schema.required || []);
|
|
163
|
+
const fields = Object.entries(properties).map(
|
|
164
|
+
([key, prop]) => `${key}${required.has(key) ? "" : "?"}: ${schemaToTs(prop)};`
|
|
165
|
+
);
|
|
166
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
|
|
167
|
+
fields.push(`[key: string]: ${schemaToTs(schema.additionalProperties)};`);
|
|
168
|
+
}
|
|
169
|
+
if (fields.length === 0) {
|
|
170
|
+
return "Record<string, unknown>";
|
|
171
|
+
}
|
|
172
|
+
return `{ ${fields.join(" ")} }`;
|
|
173
|
+
}
|
|
174
|
+
case "string":
|
|
175
|
+
default:
|
|
176
|
+
return "string";
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function buildDtos(doc, source) {
|
|
180
|
+
const schemas = doc.components?.schemas || {};
|
|
181
|
+
const blocks = Object.entries(schemas).map(([rawName, schema]) => {
|
|
182
|
+
const name = toTypeName(rawName);
|
|
183
|
+
if (schema.type === "object" || schema.properties || schema.allOf?.length) {
|
|
184
|
+
const properties = schema.properties || {};
|
|
185
|
+
const required = new Set(schema.required || []);
|
|
186
|
+
const fields = Object.entries(properties).map(([key, prop]) => ` ${key}${required.has(key) ? "" : "?"}: ${schemaToTs(prop)};`).join("\n");
|
|
187
|
+
if (fields) {
|
|
188
|
+
return `export interface ${name} {
|
|
189
|
+
${fields}
|
|
190
|
+
}`;
|
|
191
|
+
}
|
|
192
|
+
return `export type ${name} = ${schemaToTs(schema)};`;
|
|
193
|
+
}
|
|
194
|
+
return `export type ${name} = ${schemaToTs(schema)};`;
|
|
195
|
+
});
|
|
196
|
+
return `${autoGeneratedSyncHeader(source)}
|
|
197
|
+
|
|
198
|
+
${blocks.join("\n\n")}
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
201
|
+
function getPreferredSchema(response) {
|
|
202
|
+
if (!response?.content) return void 0;
|
|
203
|
+
const appJson = response.content["application/json"]?.schema;
|
|
204
|
+
if (appJson) return appJson;
|
|
205
|
+
const first = Object.values(response.content)[0];
|
|
206
|
+
return first?.schema;
|
|
207
|
+
}
|
|
208
|
+
function getSuccessResponseTypes(operation) {
|
|
209
|
+
const responses = operation.responses || {};
|
|
210
|
+
const types = [];
|
|
211
|
+
Object.entries(responses).forEach(([code, response]) => {
|
|
212
|
+
if (!/^2\d\d$/.test(code)) return;
|
|
213
|
+
const schema = getPreferredSchema(response);
|
|
214
|
+
types.push(schemaToTs(schema));
|
|
215
|
+
});
|
|
216
|
+
const unique = Array.from(new Set(types.filter(Boolean)));
|
|
217
|
+
return unique.length ? unique : ["unknown"];
|
|
218
|
+
}
|
|
219
|
+
function getRequestBodySchema(operation) {
|
|
220
|
+
const content = operation.requestBody?.content;
|
|
221
|
+
if (!content) return void 0;
|
|
222
|
+
const appJson = content["application/json"]?.schema;
|
|
223
|
+
if (appJson) return appJson;
|
|
224
|
+
const first = Object.values(content)[0];
|
|
225
|
+
return first?.schema;
|
|
226
|
+
}
|
|
227
|
+
function toFunctionName(route, method, operationId) {
|
|
228
|
+
if (operationId) return toIdentifier(operationId);
|
|
229
|
+
const routePart = route.replace(/[{}]/g, "").split("/").filter(Boolean).map((part) => toTypeName(part)).join("");
|
|
230
|
+
return toIdentifier(`${method}${routePart || "Root"}`);
|
|
231
|
+
}
|
|
232
|
+
function buildPathTemplate(route) {
|
|
233
|
+
return "`" + route.replace(/\{([^}]+)\}/g, (_, token) => `\${${toIdentifier(token)}}`) + "`";
|
|
234
|
+
}
|
|
235
|
+
function buildServiceFunction(descriptor) {
|
|
236
|
+
const { method, route, operation } = descriptor;
|
|
237
|
+
const fn = toFunctionName(route, method, operation.operationId);
|
|
238
|
+
const responseTypeName = `${toTypeName(fn)}Response`;
|
|
239
|
+
const responseUnion = getSuccessResponseTypes(operation).join(" | ");
|
|
240
|
+
const requestType = schemaToTs(getRequestBodySchema(operation));
|
|
241
|
+
const allParams = operation.parameters || [];
|
|
242
|
+
const pathParams = allParams.filter((p) => p.in === "path");
|
|
243
|
+
const queryParams = allParams.filter((p) => p.in === "query");
|
|
244
|
+
const args = [];
|
|
245
|
+
pathParams.forEach((param) => {
|
|
246
|
+
args.push(`${toIdentifier(param.name)}: ${schemaToTs(param.schema)}`);
|
|
247
|
+
});
|
|
248
|
+
if (requestType !== "unknown" && ["post", "put", "patch"].includes(method)) {
|
|
249
|
+
args.push(`payload${operation.requestBody?.required ? "" : "?"}: ${requestType}`);
|
|
250
|
+
}
|
|
251
|
+
if (queryParams.length) {
|
|
252
|
+
const queryType = `{ ${queryParams.map((p) => `${toIdentifier(p.name)}${p.required ? "" : "?"}: ${schemaToTs(p.schema)};`).join(" ")} }`;
|
|
253
|
+
args.push(`query?: ${queryType}`);
|
|
254
|
+
}
|
|
255
|
+
const pathExpr = buildPathTemplate(route);
|
|
256
|
+
if (method === "get") {
|
|
257
|
+
return `export type ${responseTypeName} = ${responseUnion};
|
|
258
|
+
|
|
259
|
+
export async function ${fn}(${args.join(", ")}): Promise<${responseTypeName}> {
|
|
260
|
+
return FragmentFetch.get<${responseTypeName}>(${pathExpr}${queryParams.length ? ", { params: query }" : ""});
|
|
261
|
+
}`;
|
|
262
|
+
}
|
|
263
|
+
if (method === "delete") {
|
|
264
|
+
return `export type ${responseTypeName} = ${responseUnion};
|
|
265
|
+
|
|
266
|
+
export async function ${fn}(${args.join(", ")}): Promise<${responseTypeName}> {
|
|
267
|
+
return FragmentFetch.delete<${responseTypeName}>(${pathExpr}${queryParams.length ? ", { params: query }" : ""});
|
|
268
|
+
}`;
|
|
269
|
+
}
|
|
270
|
+
const methodCall = method === "post" ? "post" : method === "put" ? "put" : "patch";
|
|
271
|
+
const bodyArg = requestType !== "unknown" ? "payload" : "undefined";
|
|
272
|
+
return `export type ${responseTypeName} = ${responseUnion};
|
|
273
|
+
|
|
274
|
+
export async function ${fn}(${args.join(", ")}): Promise<${responseTypeName}> {
|
|
275
|
+
return FragmentFetch.${methodCall}<${responseTypeName}>(${pathExpr}, ${bodyArg}${queryParams.length ? ", { params: query }" : ""});
|
|
276
|
+
}`;
|
|
277
|
+
}
|
|
278
|
+
function groupOperationsByTag(doc) {
|
|
279
|
+
const byTag = {};
|
|
280
|
+
Object.entries(doc.paths || {}).forEach(([route, methods]) => {
|
|
281
|
+
["get", "post", "put", "patch", "delete"].forEach((method) => {
|
|
282
|
+
const operation = methods?.[method];
|
|
283
|
+
if (!operation) return;
|
|
284
|
+
const tags = operation.tags?.length ? operation.tags : ["default"];
|
|
285
|
+
tags.forEach((tag) => {
|
|
286
|
+
byTag[tag] = byTag[tag] || [];
|
|
287
|
+
byTag[tag].push({ route, method, operation });
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
return byTag;
|
|
292
|
+
}
|
|
293
|
+
function buildTagServiceFile(tag, operations, source) {
|
|
294
|
+
const fns = operations.map((op) => buildServiceFunction(op));
|
|
295
|
+
return `${autoGeneratedSyncHeader(source)}
|
|
296
|
+
|
|
297
|
+
import { FragmentFetch } from 'fragment-web';
|
|
298
|
+
|
|
299
|
+
${fns.join("\n\n")}
|
|
300
|
+
`;
|
|
301
|
+
}
|
|
302
|
+
function buildRootServiceBarrel(tags, source) {
|
|
303
|
+
const exports = tags.map((tag) => `export * from './services/${toFileName(tag)}.service';`).join("\n");
|
|
304
|
+
return `${autoGeneratedSyncHeader(source)}
|
|
305
|
+
|
|
306
|
+
${exports}
|
|
307
|
+
`;
|
|
308
|
+
}
|
|
309
|
+
function buildServicesIndex(tags, source) {
|
|
310
|
+
const exports = tags.map((tag) => `export * from './${toFileName(tag)}.service';`).join("\n");
|
|
311
|
+
return `${autoGeneratedSyncHeader(source)}
|
|
312
|
+
|
|
313
|
+
${exports}
|
|
314
|
+
`;
|
|
315
|
+
}
|
|
316
|
+
function buildRoutes(doc, source) {
|
|
317
|
+
const lines = Object.keys(doc.paths || {}).map((route) => ` ${toConstKey(route)}: '${route}',`);
|
|
318
|
+
return `${autoGeneratedSyncHeader(source)}
|
|
319
|
+
|
|
320
|
+
export const ApiRoutes = {
|
|
321
|
+
${lines.join("\n")}
|
|
322
|
+
} as const;
|
|
323
|
+
`;
|
|
324
|
+
}
|
|
325
|
+
async function fetchOpenApi(source) {
|
|
326
|
+
const response = await fetch(source);
|
|
327
|
+
if (!response.ok) {
|
|
328
|
+
throw new Error(`Could not connect to ${source}`);
|
|
329
|
+
}
|
|
330
|
+
return await response.json();
|
|
331
|
+
}
|
|
332
|
+
async function syncOnce(options) {
|
|
333
|
+
const config2 = ensureWebConfig();
|
|
334
|
+
const base = (options.url || config2.fetch.baseUrl).replace(/\/$/, "");
|
|
335
|
+
const source = `${base}${config2.serverRoot}/docs/api-json`;
|
|
336
|
+
const generatedDir = path4.join(process.cwd(), "src/generated");
|
|
337
|
+
const servicesDir = path4.join(generatedDir, "services");
|
|
338
|
+
const doc = await fetchOpenApi(source);
|
|
339
|
+
writeFileIfAllowed(path4.join(generatedDir, "routes.ts"), buildRoutes(doc, source), {
|
|
340
|
+
dryRun: options.dryRun,
|
|
341
|
+
force: true
|
|
342
|
+
});
|
|
343
|
+
writeFileIfAllowed(path4.join(generatedDir, "dtos.ts"), buildDtos(doc, source), {
|
|
344
|
+
dryRun: options.dryRun,
|
|
345
|
+
force: true
|
|
346
|
+
});
|
|
347
|
+
const grouped = groupOperationsByTag(doc);
|
|
348
|
+
const tags = Object.keys(grouped).sort();
|
|
349
|
+
const expectedServiceFiles = new Set(tags.map((tag) => `${toFileName(tag)}.service.ts`));
|
|
350
|
+
if (fs3.existsSync(servicesDir)) {
|
|
351
|
+
const existing = fs3.readdirSync(servicesDir);
|
|
352
|
+
existing.filter((file) => file.endsWith(".service.ts")).filter((file) => !expectedServiceFiles.has(file)).forEach((staleFile) => {
|
|
353
|
+
const stalePath = path4.join(servicesDir, staleFile);
|
|
354
|
+
if (options.dryRun) {
|
|
355
|
+
logger.dryRun(stalePath);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
fs3.unlinkSync(stalePath);
|
|
359
|
+
logger.updated(stalePath, "removed stale generated file");
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
tags.forEach((tag) => {
|
|
363
|
+
writeFileIfAllowed(path4.join(servicesDir, `${toFileName(tag)}.service.ts`), buildTagServiceFile(tag, grouped[tag], source), {
|
|
364
|
+
dryRun: options.dryRun,
|
|
365
|
+
force: true
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
writeFileIfAllowed(path4.join(servicesDir, "index.ts"), buildServicesIndex(tags, source), {
|
|
369
|
+
dryRun: options.dryRun,
|
|
370
|
+
force: true
|
|
371
|
+
});
|
|
372
|
+
writeFileIfAllowed(path4.join(generatedDir, "services.ts"), buildRootServiceBarrel(tags, source), {
|
|
373
|
+
dryRun: options.dryRun,
|
|
374
|
+
force: true
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
async function runWebSync(options = {}) {
|
|
378
|
+
await syncOnce(options);
|
|
379
|
+
if (options.watch) {
|
|
380
|
+
logger.section("fragment-web", "web:sync --watch");
|
|
381
|
+
setInterval(() => {
|
|
382
|
+
syncOnce(options).catch((error) => {
|
|
383
|
+
logger.error(error instanceof Error ? error.message : "web:sync watch failed");
|
|
384
|
+
});
|
|
385
|
+
}, 5e3);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function registerWebSync(program) {
|
|
389
|
+
program.command("web:sync").description("Generate typed web API artifacts into src/generated").option("--url <url>", "Backend base URL").option("--watch", "Watch and resync").option("--dry-run", "Preview changes").action((opts) => {
|
|
390
|
+
runWebSync(opts).catch((error) => {
|
|
391
|
+
logger.error(error instanceof Error ? error.message : "web:sync failed");
|
|
392
|
+
process.exitCode = 1;
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/platform/cli/web/commands/make/component.ts
|
|
398
|
+
import path6 from "path";
|
|
399
|
+
|
|
400
|
+
// src/platform/cli/web/utils/names.ts
|
|
401
|
+
function normalizeName(input) {
|
|
402
|
+
return input.trim().replace(/([a-z\d])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
403
|
+
}
|
|
404
|
+
function toKebabCase(input) {
|
|
405
|
+
const normalized = normalizeName(input);
|
|
406
|
+
return normalized || "app";
|
|
407
|
+
}
|
|
408
|
+
function toPascalCase(input) {
|
|
409
|
+
return toKebabCase(input).split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/platform/cli/web/utils/resolve-paths.ts
|
|
413
|
+
import path5 from "path";
|
|
414
|
+
function domainOrName(input) {
|
|
415
|
+
return toKebabCase(input.domain || input.name);
|
|
416
|
+
}
|
|
417
|
+
function resolveOutputDir(input) {
|
|
418
|
+
const dom = domainOrName(input);
|
|
419
|
+
const leaf = toKebabCase(input.name);
|
|
420
|
+
switch (input.mode) {
|
|
421
|
+
case "layered":
|
|
422
|
+
switch (input.kind) {
|
|
423
|
+
case "component":
|
|
424
|
+
return path5.join("src", "components", leaf);
|
|
425
|
+
case "page":
|
|
426
|
+
return path5.join("src", "pages", leaf);
|
|
427
|
+
case "service":
|
|
428
|
+
return path5.join("src", "services");
|
|
429
|
+
case "store":
|
|
430
|
+
return path5.join("src", "stores");
|
|
431
|
+
case "guard":
|
|
432
|
+
return path5.join("src", "guards");
|
|
433
|
+
case "layout":
|
|
434
|
+
return path5.join("src", "layouts");
|
|
435
|
+
default:
|
|
436
|
+
throw new Error(`${input.kind} is not available in layered mode`);
|
|
437
|
+
}
|
|
438
|
+
case "module":
|
|
439
|
+
switch (input.kind) {
|
|
440
|
+
case "component":
|
|
441
|
+
return path5.join("src", "modules", dom, "components");
|
|
442
|
+
case "page":
|
|
443
|
+
case "service":
|
|
444
|
+
case "store":
|
|
445
|
+
return path5.join("src", "modules", dom);
|
|
446
|
+
case "guard":
|
|
447
|
+
return path5.join("src", "shared", "guards");
|
|
448
|
+
case "layout":
|
|
449
|
+
return path5.join("src", "shared", "layouts");
|
|
450
|
+
default:
|
|
451
|
+
throw new Error(`${input.kind} is not available in module mode`);
|
|
452
|
+
}
|
|
453
|
+
case "feature":
|
|
454
|
+
switch (input.kind) {
|
|
455
|
+
case "component":
|
|
456
|
+
return path5.join("src", "features", dom, "components");
|
|
457
|
+
case "page":
|
|
458
|
+
return path5.join("src", "features", dom, "pages");
|
|
459
|
+
case "service":
|
|
460
|
+
return path5.join("src", "features", dom, "services");
|
|
461
|
+
case "store":
|
|
462
|
+
return path5.join("src", "features", dom, "stores");
|
|
463
|
+
case "guard":
|
|
464
|
+
return path5.join("src", "shared", "guards");
|
|
465
|
+
case "layout":
|
|
466
|
+
return path5.join("src", "shared", "layouts");
|
|
467
|
+
default:
|
|
468
|
+
throw new Error(`${input.kind} is not available in feature mode`);
|
|
469
|
+
}
|
|
470
|
+
case "mvvm":
|
|
471
|
+
switch (input.kind) {
|
|
472
|
+
case "component":
|
|
473
|
+
case "page":
|
|
474
|
+
return path5.join("src", "app", dom, "view");
|
|
475
|
+
case "service":
|
|
476
|
+
case "store":
|
|
477
|
+
case "viewmodel":
|
|
478
|
+
return path5.join("src", "app", dom, "viewmodel");
|
|
479
|
+
case "model":
|
|
480
|
+
return path5.join("src", "app", dom, "model");
|
|
481
|
+
case "guard":
|
|
482
|
+
return path5.join("src", "shared", "guards");
|
|
483
|
+
case "layout":
|
|
484
|
+
return path5.join("src", "shared", "layouts");
|
|
485
|
+
default:
|
|
486
|
+
throw new Error(`${input.kind} is not available in mvvm mode`);
|
|
487
|
+
}
|
|
488
|
+
default:
|
|
489
|
+
return path5.join("src");
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/platform/cli/web/commands/make/component.ts
|
|
494
|
+
function parseState(state) {
|
|
495
|
+
if (!state) return [];
|
|
496
|
+
return state.split(",").map((it) => it.trim()).filter(Boolean).map((pair) => {
|
|
497
|
+
const [key, type] = pair.split(":").map((v) => v.trim());
|
|
498
|
+
return { key, type: type || "unknown" };
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
async function runMakeComponent(name, options = {}) {
|
|
502
|
+
const config2 = ensureWebConfig();
|
|
503
|
+
const mode = options.structure || config2.structure;
|
|
504
|
+
const domain = options.feature || options.module;
|
|
505
|
+
const outDir = resolveOutputDir({ mode, kind: "component", name, domain });
|
|
506
|
+
const fileName = `${toKebabCase(name)}.component.tsx`;
|
|
507
|
+
const className = `${toPascalCase(name)}Component`;
|
|
508
|
+
const stateFields = parseState(options.state);
|
|
509
|
+
const injected = (options.inject || "").split(",").map((v) => v.trim()).filter(Boolean);
|
|
510
|
+
const decoratorArgs = [
|
|
511
|
+
`tag: '${toKebabCase(name)}'`,
|
|
512
|
+
options.memo ? "memo: true" : "",
|
|
513
|
+
`displayName: '${className}'`
|
|
514
|
+
].filter(Boolean).join(", ");
|
|
515
|
+
const stateDecl = stateFields.map((field) => ` @State(undefined as ${field.type})
|
|
516
|
+
${field.key}!: ${field.type};`).join("\n\n");
|
|
517
|
+
const injectDecl = injected.map((token) => ` @Inject('${token}')
|
|
518
|
+
${toKebabCase(token).replace(/-([a-z])/g, (_, c) => c.toUpperCase())}: unknown;`).join("\n\n");
|
|
519
|
+
const content = `// Generated by: frg make:component ${name}
|
|
520
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
521
|
+
// fragment-web v0.1.0
|
|
522
|
+
import React from 'react';
|
|
523
|
+
import { Component, Inject, State } from 'fragment-web';
|
|
524
|
+
|
|
525
|
+
@Component({ ${decoratorArgs} })
|
|
526
|
+
export class ${className} {
|
|
527
|
+
${injectDecl ? `${injectDecl}
|
|
528
|
+
|
|
529
|
+
` : ""}${stateDecl ? `${stateDecl}
|
|
530
|
+
|
|
531
|
+
` : ""} render(): JSX.Element {
|
|
532
|
+
return <div>${toPascalCase(name)} component</div>;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
`;
|
|
536
|
+
writeFileIfAllowed(path6.join(process.cwd(), outDir, fileName), content, options);
|
|
537
|
+
}
|
|
538
|
+
function registerMakeComponent(program) {
|
|
539
|
+
program.command("make:component <name>").description("Generate a frontend component").option("--memo", "Enable component memo hint").option("--state <fields>", "CSV field:type list").option("--inject <services>", "CSV of injected service tokens").option("--structure <mode>", "Override structure mode").option("--module <name>", "Module name").option("--feature <name>", "Feature name").option("--force", "Overwrite existing files").option("--dry-run", "Preview changes").action((name, opts) => {
|
|
540
|
+
runMakeComponent(name, opts).catch((error) => {
|
|
541
|
+
logger.error(error instanceof Error ? error.message : "make:component failed");
|
|
542
|
+
process.exitCode = 1;
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// src/platform/cli/web/commands/make/page.ts
|
|
548
|
+
import path7 from "path";
|
|
549
|
+
function parseCsv(value) {
|
|
550
|
+
if (!value) return [];
|
|
551
|
+
return value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
552
|
+
}
|
|
553
|
+
async function runMakePage(name, options = {}) {
|
|
554
|
+
const config2 = ensureWebConfig();
|
|
555
|
+
const mode = options.structure || config2.structure;
|
|
556
|
+
const domain = options.feature || options.module;
|
|
557
|
+
const outDir = resolveOutputDir({ mode, kind: "page", name, domain });
|
|
558
|
+
const fileName = `${toKebabCase(name)}.page.tsx`;
|
|
559
|
+
const className = `${toPascalCase(name)}Page`;
|
|
560
|
+
const route = options.path || `/${toKebabCase(name)}`;
|
|
561
|
+
const guard = options.guard?.trim();
|
|
562
|
+
const params = parseCsv(options.params);
|
|
563
|
+
const query = (options.query || "").split(",").map((v) => v.trim()).filter(Boolean);
|
|
564
|
+
const paramsDecl = params.map((p) => ` @Param('${p}')
|
|
565
|
+
${p}!: string;`).join("\n\n");
|
|
566
|
+
const queryDecl = query.map((q) => {
|
|
567
|
+
const [key, def] = q.split(":").map((v) => v.trim());
|
|
568
|
+
return ` @Query('${key}'${def ? `, '${def}'` : ""})
|
|
569
|
+
${key}!: string;`;
|
|
570
|
+
}).join("\n\n");
|
|
571
|
+
const content = `// Generated by: frg make:page ${name}
|
|
572
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
573
|
+
// fragment-web v0.1.0
|
|
574
|
+
import React from 'react';
|
|
575
|
+
import { Guard, Page, Param, Query } from 'fragment-web';
|
|
576
|
+
${guard ? `import { ${guard} } from '../guards/${toKebabCase(guard)}.guard';
|
|
577
|
+
` : ""}
|
|
578
|
+
@Page({ path: '${route}', title: '${toPascalCase(name)}' })
|
|
579
|
+
${guard ? `@Guard(${guard})
|
|
580
|
+
` : ""}export class ${className} {
|
|
581
|
+
${paramsDecl ? `${paramsDecl}
|
|
582
|
+
|
|
583
|
+
` : ""}${queryDecl ? `${queryDecl}
|
|
584
|
+
|
|
585
|
+
` : ""} render(): JSX.Element {
|
|
586
|
+
return <div>${toPascalCase(name)} page</div>;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
`;
|
|
590
|
+
writeFileIfAllowed(path7.join(process.cwd(), outDir, fileName), content, options);
|
|
591
|
+
}
|
|
592
|
+
function registerMakePage(program) {
|
|
593
|
+
program.command("make:page <name>").description("Generate a frontend page").option("--path <path>", "Route path").option("--guard <guard>", "Guard class name").option("--params <keys>", "CSV route param names").option("--query <pairs>", "CSV queryKey[:default] list").option("--structure <mode>", "Override structure mode").option("--module <name>", "Module name").option("--feature <name>", "Feature name").option("--force", "Overwrite existing files").option("--dry-run", "Preview changes").action((name, opts) => {
|
|
594
|
+
runMakePage(name, opts).catch((error) => {
|
|
595
|
+
logger.error(error instanceof Error ? error.message : "make:page failed");
|
|
596
|
+
process.exitCode = 1;
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// src/platform/cli/web/commands/make/service.ts
|
|
602
|
+
import fs8 from "fs";
|
|
603
|
+
import path12 from "path";
|
|
604
|
+
|
|
605
|
+
// src/shared/config.utils.ts
|
|
606
|
+
import * as fs6 from "fs";
|
|
607
|
+
import * as path10 from "path";
|
|
608
|
+
|
|
609
|
+
// src/shared/env.utils.ts
|
|
610
|
+
import * as fs4 from "fs";
|
|
611
|
+
import * as path8 from "path";
|
|
612
|
+
import * as dotenv from "dotenv";
|
|
613
|
+
var EnvUtils = class {
|
|
614
|
+
static loadEnvironment(cwd = process.cwd()) {
|
|
615
|
+
if (this.loaded) return;
|
|
616
|
+
const envPath = path8.join(cwd, ".env");
|
|
617
|
+
if (fs4.existsSync(envPath)) {
|
|
618
|
+
dotenv.config({ path: envPath, override: true });
|
|
619
|
+
}
|
|
620
|
+
this.loaded = true;
|
|
621
|
+
}
|
|
622
|
+
static resolveMode(cwd = process.cwd()) {
|
|
623
|
+
if (this.resolvedMode) return this.resolvedMode;
|
|
624
|
+
const projectRoot = path8.resolve(cwd).replace(/\\/g, "/");
|
|
625
|
+
const entryArg = process.argv[1] ? path8.resolve(process.argv[1]) : "";
|
|
626
|
+
const entryPath = entryArg.replace(/\\/g, "/");
|
|
627
|
+
const entryInsideProject = entryPath.length > 0 && (entryPath === projectRoot || entryPath.startsWith(`${projectRoot}/`));
|
|
628
|
+
if (entryInsideProject) {
|
|
629
|
+
if (entryPath.includes("/dist/")) {
|
|
630
|
+
this.resolvedMode = "production";
|
|
631
|
+
return this.resolvedMode;
|
|
632
|
+
}
|
|
633
|
+
if (entryPath.includes("/src/") || entryPath.endsWith(".ts")) {
|
|
634
|
+
this.resolvedMode = "development";
|
|
635
|
+
return this.resolvedMode;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
const hasSrc = fs4.existsSync(path8.join(projectRoot, "src"));
|
|
639
|
+
const hasDist = fs4.existsSync(path8.join(projectRoot, "dist"));
|
|
640
|
+
if (hasSrc && !hasDist) {
|
|
641
|
+
this.resolvedMode = "development";
|
|
642
|
+
return this.resolvedMode;
|
|
643
|
+
}
|
|
644
|
+
if (!hasSrc && hasDist) {
|
|
645
|
+
this.resolvedMode = "production";
|
|
646
|
+
return this.resolvedMode;
|
|
647
|
+
}
|
|
648
|
+
if (hasSrc && hasDist) {
|
|
649
|
+
this.resolvedMode = "development";
|
|
650
|
+
return this.resolvedMode;
|
|
651
|
+
}
|
|
652
|
+
this.resolvedMode = "production";
|
|
653
|
+
return this.resolvedMode;
|
|
654
|
+
}
|
|
655
|
+
static getString(key, defaultValue) {
|
|
656
|
+
this.loadEnvironment();
|
|
657
|
+
const value = process.env[key];
|
|
658
|
+
return value !== void 0 ? value : defaultValue ?? "";
|
|
659
|
+
}
|
|
660
|
+
static getBoolean(key, defaultValue = false) {
|
|
661
|
+
this.loadEnvironment();
|
|
662
|
+
const value = process.env[key];
|
|
663
|
+
if (value === void 0) return defaultValue;
|
|
664
|
+
return ["true", "1", "yes", "on"].includes(value.toLowerCase());
|
|
665
|
+
}
|
|
666
|
+
static getNumber(key, defaultValue = 0) {
|
|
667
|
+
this.loadEnvironment();
|
|
668
|
+
const value = process.env[key];
|
|
669
|
+
if (value === void 0) return defaultValue;
|
|
670
|
+
const num = Number(value);
|
|
671
|
+
return isNaN(num) ? defaultValue : num;
|
|
672
|
+
}
|
|
673
|
+
static getJson(key, defaultValue) {
|
|
674
|
+
this.loadEnvironment();
|
|
675
|
+
const value = process.env[key];
|
|
676
|
+
if (value === void 0) return defaultValue;
|
|
677
|
+
try {
|
|
678
|
+
return JSON.parse(value);
|
|
679
|
+
} catch {
|
|
680
|
+
console.warn(`\u26A0\uFE0F Invalid JSON in env var ${key}, using default`);
|
|
681
|
+
return defaultValue;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Check if running in development mode.
|
|
686
|
+
* Mode is inferred from runtime/project layout, not from env file variants.
|
|
687
|
+
*/
|
|
688
|
+
static isDevelopmentMode() {
|
|
689
|
+
this.loadEnvironment();
|
|
690
|
+
return this.resolveMode() === "development";
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Get current environment mode ('development' or 'production')
|
|
694
|
+
*/
|
|
695
|
+
static getEnvironmentMode() {
|
|
696
|
+
this.loadEnvironment();
|
|
697
|
+
return this.resolveMode();
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
EnvUtils.loaded = false;
|
|
701
|
+
EnvUtils.resolvedMode = null;
|
|
702
|
+
|
|
703
|
+
// src/shared/tsconfig.utils.ts
|
|
704
|
+
import * as fs5 from "fs";
|
|
705
|
+
import * as path9 from "path";
|
|
706
|
+
var TsConfigUtils = class {
|
|
707
|
+
static loadConfig() {
|
|
708
|
+
if (this.configCache) return this.configCache;
|
|
709
|
+
for (const file of this.DEFAULT_PATHS) {
|
|
710
|
+
const fullPath = path9.join(process.cwd(), file);
|
|
711
|
+
if (fs5.existsSync(fullPath)) {
|
|
712
|
+
try {
|
|
713
|
+
this.configCache = JSON.parse(fs5.readFileSync(fullPath, "utf8"));
|
|
714
|
+
if (!this.configCache) return {};
|
|
715
|
+
return this.configCache;
|
|
716
|
+
} catch {
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
this.configCache = {};
|
|
721
|
+
return this.configCache;
|
|
722
|
+
}
|
|
723
|
+
static exists() {
|
|
724
|
+
return this.DEFAULT_PATHS.some(
|
|
725
|
+
(f) => fs5.existsSync(path9.join(process.cwd(), f))
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
static hasDecoratorSupport() {
|
|
729
|
+
const config2 = this.loadConfig();
|
|
730
|
+
const opts = config2.compilerOptions || {};
|
|
731
|
+
return !!opts.experimentalDecorators && !!opts.emitDecoratorMetadata;
|
|
732
|
+
}
|
|
733
|
+
static getRootDir() {
|
|
734
|
+
const config2 = this.loadConfig();
|
|
735
|
+
const rootDir = config2.compilerOptions?.rootDir || "src";
|
|
736
|
+
return path9.isAbsolute(rootDir) ? rootDir : path9.join(process.cwd(), rootDir);
|
|
737
|
+
}
|
|
738
|
+
static getOutDir() {
|
|
739
|
+
const config2 = this.loadConfig();
|
|
740
|
+
const outDir = config2.compilerOptions?.outDir || "dist";
|
|
741
|
+
return path9.isAbsolute(outDir) ? outDir : path9.join(process.cwd(), outDir);
|
|
742
|
+
}
|
|
743
|
+
static getIncludePatterns() {
|
|
744
|
+
const config2 = this.loadConfig();
|
|
745
|
+
return config2.include || ["src/**/*"];
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
TsConfigUtils.configCache = null;
|
|
749
|
+
TsConfigUtils.DEFAULT_PATHS = ["tsconfig.json"];
|
|
750
|
+
|
|
751
|
+
// src/shared/config.utils.ts
|
|
752
|
+
var ConfigUtils = class {
|
|
753
|
+
static loadConfig() {
|
|
754
|
+
if (this.configCache) return this.configCache;
|
|
755
|
+
EnvUtils.loadEnvironment();
|
|
756
|
+
const configPath = path10.join(process.cwd(), "fragment.json");
|
|
757
|
+
const cachePath = path10.join(process.cwd(), "fragment.cache.json");
|
|
758
|
+
const sourcePath = fs6.existsSync(cachePath) ? cachePath : configPath;
|
|
759
|
+
if (!fs6.existsSync(sourcePath)) {
|
|
760
|
+
this.configCache = {};
|
|
761
|
+
return this.configCache;
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
this.configCache = JSON.parse(fs6.readFileSync(sourcePath, "utf8")) || {};
|
|
765
|
+
if (!this.configCache) return {};
|
|
766
|
+
return this.configCache;
|
|
767
|
+
} catch (error) {
|
|
768
|
+
console.error("\u274C Error loading fragment config:", error);
|
|
769
|
+
this.configCache = {};
|
|
770
|
+
return this.configCache;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
static interpolateEnvVars(value) {
|
|
774
|
+
if (typeof value === "string") {
|
|
775
|
+
return value.replace(
|
|
776
|
+
/\$\{([^}]+)\}/g,
|
|
777
|
+
(_match, expression) => {
|
|
778
|
+
const [rawKey, ...fallbackParts] = String(expression).split(":");
|
|
779
|
+
const key = rawKey.trim();
|
|
780
|
+
const fallback = fallbackParts.length > 0 ? fallbackParts.join(":").trim() : void 0;
|
|
781
|
+
return EnvUtils.getString(key, fallback) || "";
|
|
782
|
+
}
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
if (Array.isArray(value)) {
|
|
786
|
+
return value.map((item) => this.interpolateEnvVars(item));
|
|
787
|
+
}
|
|
788
|
+
if (value !== null && typeof value === "object") {
|
|
789
|
+
const result = {};
|
|
790
|
+
for (const key in value) {
|
|
791
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
792
|
+
result[key] = this.interpolateEnvVars(value[key]);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return result;
|
|
796
|
+
}
|
|
797
|
+
return value;
|
|
798
|
+
}
|
|
799
|
+
static getDatabaseConfig() {
|
|
800
|
+
const config2 = this.loadConfig();
|
|
801
|
+
if (!config2.database) return {};
|
|
802
|
+
let dbConfig = this.interpolateEnvVars(config2.database);
|
|
803
|
+
const isProduction = EnvUtils.getEnvironmentMode() === "production";
|
|
804
|
+
const rootDir = TsConfigUtils.getRootDir().replace(/\\/g, "/");
|
|
805
|
+
const outDir = TsConfigUtils.getOutDir().replace(/\\/g, "/");
|
|
806
|
+
if (Array.isArray(dbConfig.entities)) {
|
|
807
|
+
dbConfig.entities = dbConfig.entities.map(
|
|
808
|
+
(pattern) => this.toRuntimePathPattern(pattern, isProduction, rootDir, outDir)
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
if (Array.isArray(dbConfig.migrations)) {
|
|
812
|
+
dbConfig.migrations = dbConfig.migrations.map(
|
|
813
|
+
(pattern) => this.toRuntimePathPattern(pattern, isProduction, rootDir, outDir)
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
return dbConfig;
|
|
817
|
+
}
|
|
818
|
+
static toRuntimePathPattern(pattern, isProduction, rootDir, outDir) {
|
|
819
|
+
if (typeof pattern !== "string") return pattern;
|
|
820
|
+
const normalized = pattern.replace(/\\/g, "/");
|
|
821
|
+
if (!isProduction) return normalized;
|
|
822
|
+
const jsPattern = normalized.replace(/\.ts$/i, ".js");
|
|
823
|
+
const normalizedRoot = rootDir.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
824
|
+
const normalizedOut = outDir.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
825
|
+
const relativeRoot = path10.relative(process.cwd(), normalizedRoot).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
826
|
+
const prefixCandidates = Array.from(
|
|
827
|
+
new Set(
|
|
828
|
+
[
|
|
829
|
+
`${normalizedRoot}/`,
|
|
830
|
+
relativeRoot ? `${relativeRoot}/` : "",
|
|
831
|
+
relativeRoot ? `./${relativeRoot}/` : "",
|
|
832
|
+
"src/",
|
|
833
|
+
"./src/"
|
|
834
|
+
].filter(Boolean)
|
|
835
|
+
)
|
|
836
|
+
);
|
|
837
|
+
for (const prefix of prefixCandidates) {
|
|
838
|
+
if (jsPattern.startsWith(prefix)) {
|
|
839
|
+
return `${normalizedOut}/${jsPattern.slice(prefix.length)}`;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return jsPattern;
|
|
843
|
+
}
|
|
844
|
+
static getDatabaseProperty(property, defaultValue) {
|
|
845
|
+
const dbConfig = this.getDatabaseConfig();
|
|
846
|
+
return dbConfig[property] !== void 0 ? dbConfig[property] : defaultValue;
|
|
847
|
+
}
|
|
848
|
+
static getFullConfig() {
|
|
849
|
+
return this.loadConfig();
|
|
850
|
+
}
|
|
851
|
+
static getResolvedConfig() {
|
|
852
|
+
const config2 = this.getFullConfig();
|
|
853
|
+
return this.interpolateEnvVars(config2);
|
|
854
|
+
}
|
|
855
|
+
static isDatabaseEnabled() {
|
|
856
|
+
return !!this.getFullConfig().database;
|
|
857
|
+
}
|
|
858
|
+
static getVerbose() {
|
|
859
|
+
return this.getFullConfig().verbose === true;
|
|
860
|
+
}
|
|
861
|
+
static getLoggingConfig() {
|
|
862
|
+
return this.getFullConfig().logging || {};
|
|
863
|
+
}
|
|
864
|
+
static getMailConfig() {
|
|
865
|
+
return this.getResolvedConfig().mail || {};
|
|
866
|
+
}
|
|
867
|
+
static getNotificationsConfig() {
|
|
868
|
+
return this.getResolvedConfig().notifications || {};
|
|
869
|
+
}
|
|
870
|
+
static getDocsConfig() {
|
|
871
|
+
return this.getResolvedConfig().docs || {};
|
|
872
|
+
}
|
|
873
|
+
static getMailers() {
|
|
874
|
+
const mail = this.getMailConfig();
|
|
875
|
+
if (mail.mailers) {
|
|
876
|
+
if (Array.isArray(mail.mailers)) {
|
|
877
|
+
return mail.mailers.map((entry) => ({
|
|
878
|
+
...entry,
|
|
879
|
+
name: entry.name || entry.qualifier
|
|
880
|
+
}));
|
|
881
|
+
}
|
|
882
|
+
return Object.entries(mail.mailers).map(([name, entry]) => ({
|
|
883
|
+
...entry,
|
|
884
|
+
name
|
|
885
|
+
}));
|
|
886
|
+
}
|
|
887
|
+
const hasSingleConfig = mail.driver || mail.host || mail.port || mail.username || mail.password || mail.from || mail.apiKey;
|
|
888
|
+
if (hasSingleConfig) {
|
|
889
|
+
const { mailers, default: defaultName, ...single } = mail;
|
|
890
|
+
return [
|
|
891
|
+
{
|
|
892
|
+
name: defaultName || "default",
|
|
893
|
+
...single
|
|
894
|
+
}
|
|
895
|
+
];
|
|
896
|
+
}
|
|
897
|
+
return [];
|
|
898
|
+
}
|
|
899
|
+
static getDefaultMailer() {
|
|
900
|
+
const mail = this.getMailConfig();
|
|
901
|
+
if (mail.default) return mail.default;
|
|
902
|
+
const mailers = this.getMailers();
|
|
903
|
+
if (mailers.length === 1) return mailers[0].name || mailers[0].qualifier;
|
|
904
|
+
return void 0;
|
|
905
|
+
}
|
|
906
|
+
static getMailerConfig(name) {
|
|
907
|
+
const mailers = this.getMailers();
|
|
908
|
+
if (mailers.length === 0) return void 0;
|
|
909
|
+
if (name) {
|
|
910
|
+
return mailers.find(
|
|
911
|
+
(mailer) => mailer.name === name || mailer.qualifier === name
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
const defaultName = this.getDefaultMailer();
|
|
915
|
+
if (defaultName) {
|
|
916
|
+
return mailers.find(
|
|
917
|
+
(mailer) => mailer.name === defaultName || mailer.qualifier === defaultName
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
if (mailers.length === 1) return mailers[0];
|
|
921
|
+
return void 0;
|
|
922
|
+
}
|
|
923
|
+
static getPlugins() {
|
|
924
|
+
const config2 = this.getFullConfig();
|
|
925
|
+
const list = config2.plugins || [];
|
|
926
|
+
return list.map((entry) => {
|
|
927
|
+
if (typeof entry === "string") {
|
|
928
|
+
return { name: entry };
|
|
929
|
+
}
|
|
930
|
+
return entry;
|
|
931
|
+
}).filter((entry) => entry && entry.enabled !== false);
|
|
932
|
+
}
|
|
933
|
+
static getPluginAutoQualify() {
|
|
934
|
+
return this.getFullConfig().pluginAutoQualify === true;
|
|
935
|
+
}
|
|
936
|
+
static getStructureType() {
|
|
937
|
+
const config2 = this.loadConfig();
|
|
938
|
+
return config2.structure?.type || "layered";
|
|
939
|
+
}
|
|
940
|
+
static updateConfig(updates) {
|
|
941
|
+
const current = this.loadConfig();
|
|
942
|
+
const merged = this.mergeDeep(current, updates);
|
|
943
|
+
this.writeConfig(merged);
|
|
944
|
+
return merged;
|
|
945
|
+
}
|
|
946
|
+
static setVerbose(verbose) {
|
|
947
|
+
return this.updateConfig({ verbose });
|
|
948
|
+
}
|
|
949
|
+
static clearCache() {
|
|
950
|
+
this.configCache = null;
|
|
951
|
+
}
|
|
952
|
+
static clearCacheFile() {
|
|
953
|
+
const cachePath = path10.join(process.cwd(), "fragment.cache.json");
|
|
954
|
+
if (fs6.existsSync(cachePath)) {
|
|
955
|
+
fs6.unlinkSync(cachePath);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
static writeConfig(config2) {
|
|
959
|
+
const configPath = path10.join(process.cwd(), "fragment.json");
|
|
960
|
+
const payload = JSON.stringify(config2, null, 2);
|
|
961
|
+
fs6.writeFileSync(configPath, payload, "utf8");
|
|
962
|
+
this.configCache = config2;
|
|
963
|
+
}
|
|
964
|
+
static mergeDeep(target, source) {
|
|
965
|
+
const output = { ...target };
|
|
966
|
+
for (const [key, value] of Object.entries(source)) {
|
|
967
|
+
if (value === void 0) continue;
|
|
968
|
+
if (value && typeof value === "object" && !Array.isArray(value) && typeof target[key] === "object" && target[key] !== null) {
|
|
969
|
+
output[key] = this.mergeDeep(target[key], value);
|
|
970
|
+
} else {
|
|
971
|
+
output[key] = value;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return output;
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
ConfigUtils.configCache = null;
|
|
978
|
+
|
|
979
|
+
// src/platform/cli/scaffold/generate/component-generator.ts
|
|
980
|
+
import * as fs7 from "fs-extra";
|
|
981
|
+
import * as path11 from "path";
|
|
982
|
+
|
|
983
|
+
// src/shared/errors.ts
|
|
984
|
+
var FragmentError = class extends Error {
|
|
985
|
+
constructor(message, statusCode, details) {
|
|
986
|
+
super(message);
|
|
987
|
+
this.name = this.constructor.name;
|
|
988
|
+
this.statusCode = statusCode;
|
|
989
|
+
this.details = details;
|
|
990
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
var GenerateTypeError = class extends FragmentError {
|
|
994
|
+
constructor(message = "Unknown generate type", details) {
|
|
995
|
+
super(message, 400, details);
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
// src/platform/cli/scaffold/naming.ts
|
|
1000
|
+
var NamingUtils = class {
|
|
1001
|
+
static capitalize(value) {
|
|
1002
|
+
if (!value) return value;
|
|
1003
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
1004
|
+
}
|
|
1005
|
+
static camelCase(value) {
|
|
1006
|
+
if (!value) return value;
|
|
1007
|
+
const normalized = this.normalize(value);
|
|
1008
|
+
return normalized.charAt(0).toLowerCase() + normalized.slice(1);
|
|
1009
|
+
}
|
|
1010
|
+
static pascalCase(value) {
|
|
1011
|
+
if (!value) return value;
|
|
1012
|
+
const normalized = this.normalize(value);
|
|
1013
|
+
return this.capitalize(normalized);
|
|
1014
|
+
}
|
|
1015
|
+
static kebabCase(value) {
|
|
1016
|
+
if (!value) return value;
|
|
1017
|
+
return value.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").replace(/-+/g, "-").toLowerCase();
|
|
1018
|
+
}
|
|
1019
|
+
static normalize(value) {
|
|
1020
|
+
return value.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[\-_]+/g, " ").split(" ").filter(Boolean).map((part) => this.capitalize(part.toLowerCase())).join("");
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
// src/platform/cli/scaffold/generate/component-generator.ts
|
|
1025
|
+
var ComponentGenerator = class {
|
|
1026
|
+
constructor(structureType = ConfigUtils.getStructureType()) {
|
|
1027
|
+
this.structureType = structureType;
|
|
1028
|
+
}
|
|
1029
|
+
async generate(type, name, options) {
|
|
1030
|
+
switch (type) {
|
|
1031
|
+
case "controller":
|
|
1032
|
+
await this.generateController(name, options);
|
|
1033
|
+
break;
|
|
1034
|
+
case "service":
|
|
1035
|
+
await this.generateService(
|
|
1036
|
+
name,
|
|
1037
|
+
options?.withRepository ?? false,
|
|
1038
|
+
options
|
|
1039
|
+
);
|
|
1040
|
+
break;
|
|
1041
|
+
case "resource":
|
|
1042
|
+
await this.generateResource(name, options);
|
|
1043
|
+
break;
|
|
1044
|
+
case "entity":
|
|
1045
|
+
await this.generateEntity(name);
|
|
1046
|
+
break;
|
|
1047
|
+
case "dto":
|
|
1048
|
+
await this.generateDto(name);
|
|
1049
|
+
break;
|
|
1050
|
+
case "repository":
|
|
1051
|
+
await this.generateRepository(name, options);
|
|
1052
|
+
break;
|
|
1053
|
+
case "middleware":
|
|
1054
|
+
await this.generateMiddleware(name);
|
|
1055
|
+
break;
|
|
1056
|
+
case "guard":
|
|
1057
|
+
await this.generateGuard(name);
|
|
1058
|
+
break;
|
|
1059
|
+
case "interceptor":
|
|
1060
|
+
await this.generateInterceptor(name);
|
|
1061
|
+
break;
|
|
1062
|
+
case "filter":
|
|
1063
|
+
await this.generateFilter(name);
|
|
1064
|
+
break;
|
|
1065
|
+
case "module":
|
|
1066
|
+
await this.generateModule(name);
|
|
1067
|
+
break;
|
|
1068
|
+
case "feature":
|
|
1069
|
+
await this.generateFeature(name);
|
|
1070
|
+
break;
|
|
1071
|
+
case "job":
|
|
1072
|
+
await this.generateJob(name, options);
|
|
1073
|
+
break;
|
|
1074
|
+
case "mail":
|
|
1075
|
+
await this.generateMail(name, options);
|
|
1076
|
+
break;
|
|
1077
|
+
case "notification":
|
|
1078
|
+
await this.generateNotification(name, options);
|
|
1079
|
+
break;
|
|
1080
|
+
default:
|
|
1081
|
+
throw new GenerateTypeError(`Unknown generate type: ${type}`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
getBasePath(subdir, options) {
|
|
1085
|
+
const srcDir = TsConfigUtils.getRootDir();
|
|
1086
|
+
let basePath = srcDir;
|
|
1087
|
+
switch (this.structureType) {
|
|
1088
|
+
case "module": {
|
|
1089
|
+
const moduleName = options?.module || "app";
|
|
1090
|
+
if (subdir && ["controllers", "services", "repositories"].includes(subdir)) {
|
|
1091
|
+
basePath = path11.join(
|
|
1092
|
+
srcDir,
|
|
1093
|
+
"modules",
|
|
1094
|
+
NamingUtils.kebabCase(moduleName),
|
|
1095
|
+
subdir
|
|
1096
|
+
);
|
|
1097
|
+
} else if (subdir && ["jobs", "mail", "notifications"].includes(subdir)) {
|
|
1098
|
+
basePath = path11.join(srcDir, "modules", subdir);
|
|
1099
|
+
} else if (subdir && ["guards", "interceptors", "filters", "middlewares"].includes(subdir)) {
|
|
1100
|
+
basePath = path11.join(srcDir, "common", subdir);
|
|
1101
|
+
} else if (subdir) {
|
|
1102
|
+
basePath = path11.join(srcDir, subdir);
|
|
1103
|
+
}
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
case "feature": {
|
|
1107
|
+
const featureName = options?.feature || "app";
|
|
1108
|
+
if (subdir && ["controllers", "services", "repositories"].includes(subdir)) {
|
|
1109
|
+
basePath = path11.join(
|
|
1110
|
+
srcDir,
|
|
1111
|
+
"features",
|
|
1112
|
+
NamingUtils.kebabCase(featureName)
|
|
1113
|
+
);
|
|
1114
|
+
} else if (subdir && ["jobs", "mail", "notifications"].includes(subdir)) {
|
|
1115
|
+
basePath = path11.join(srcDir, "features", subdir);
|
|
1116
|
+
} else if (subdir && ["guards", "interceptors", "filters", "middlewares"].includes(subdir)) {
|
|
1117
|
+
basePath = path11.join(srcDir, "shared", subdir);
|
|
1118
|
+
} else if (subdir) {
|
|
1119
|
+
basePath = path11.join(srcDir, subdir);
|
|
1120
|
+
}
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
case "layered":
|
|
1124
|
+
default: {
|
|
1125
|
+
if (subdir) {
|
|
1126
|
+
basePath = path11.join(srcDir, subdir);
|
|
1127
|
+
}
|
|
1128
|
+
break;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
return basePath;
|
|
1132
|
+
}
|
|
1133
|
+
async generateController(name, options) {
|
|
1134
|
+
const className = `${NamingUtils.pascalCase(name)}Controller`;
|
|
1135
|
+
const fileName = `${NamingUtils.kebabCase(name)}.controller.ts`;
|
|
1136
|
+
let filePath;
|
|
1137
|
+
let serviceImport = "";
|
|
1138
|
+
if (this.structureType === "feature") {
|
|
1139
|
+
filePath = path11.join(this.getBasePath("controllers", options), fileName);
|
|
1140
|
+
serviceImport = `import { ${NamingUtils.pascalCase(name)}Service } from './${NamingUtils.kebabCase(name)}.service';`;
|
|
1141
|
+
} else {
|
|
1142
|
+
filePath = path11.join(this.getBasePath("controllers", options), fileName);
|
|
1143
|
+
serviceImport = `import { ${NamingUtils.pascalCase(name)}Service } from '../services/${NamingUtils.kebabCase(name)}.service';`;
|
|
1144
|
+
}
|
|
1145
|
+
const content = `import { Controller, Get, Post, Put, Delete, Body, Param, Autowired } from 'fragment-ts';
|
|
1146
|
+
${serviceImport}
|
|
1147
|
+
|
|
1148
|
+
@Controller('/${NamingUtils.kebabCase(name)}')
|
|
1149
|
+
export class ${className} {
|
|
1150
|
+
// @Autowired()
|
|
1151
|
+
// private ${NamingUtils.camelCase(name)}Service!: ${NamingUtils.pascalCase(name)}Service;
|
|
1152
|
+
|
|
1153
|
+
@Get()
|
|
1154
|
+
findAll() {
|
|
1155
|
+
return [];
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
@Get('/:id')
|
|
1159
|
+
findOne(@Param('id') id: string) {
|
|
1160
|
+
return { id };
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
@Post()
|
|
1164
|
+
create(@Body() body: any) {
|
|
1165
|
+
return body;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
@Put('/:id')
|
|
1169
|
+
update(@Param('id') id: string, @Body() body: any) {
|
|
1170
|
+
return { id, ...body };
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
@Delete('/:id')
|
|
1174
|
+
delete(@Param('id') id: string) {
|
|
1175
|
+
return { deleted: true, id };
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
`;
|
|
1179
|
+
await fs7.ensureDir(path11.dirname(filePath));
|
|
1180
|
+
await fs7.writeFile(filePath, content);
|
|
1181
|
+
}
|
|
1182
|
+
async generateService(name, withRepository, options) {
|
|
1183
|
+
const className = `${NamingUtils.pascalCase(name)}Service`;
|
|
1184
|
+
const fileName = `${NamingUtils.kebabCase(name)}.service.ts`;
|
|
1185
|
+
let filePath;
|
|
1186
|
+
let imports = "import { Service, Autowired } from 'fragment-ts';\n";
|
|
1187
|
+
let repositoryProp = "";
|
|
1188
|
+
filePath = path11.join(this.getBasePath("services", options), fileName);
|
|
1189
|
+
if (withRepository) {
|
|
1190
|
+
const repoName = `${NamingUtils.pascalCase(name)}Repository`;
|
|
1191
|
+
let repoPath;
|
|
1192
|
+
if (this.structureType === "module") {
|
|
1193
|
+
repoPath = `../repositories/${NamingUtils.kebabCase(name)}.repository`;
|
|
1194
|
+
} else if (this.structureType === "feature") {
|
|
1195
|
+
repoPath = `./${NamingUtils.kebabCase(name)}.repository`;
|
|
1196
|
+
} else {
|
|
1197
|
+
repoPath = `../repositories/${NamingUtils.kebabCase(name)}.repository`;
|
|
1198
|
+
}
|
|
1199
|
+
imports += `import { ${repoName} } from '${repoPath}';
|
|
1200
|
+
`;
|
|
1201
|
+
repositoryProp = `
|
|
1202
|
+
@Autowired()
|
|
1203
|
+
private ${NamingUtils.camelCase(name)}Repository!: ${repoName};
|
|
1204
|
+
`;
|
|
1205
|
+
}
|
|
1206
|
+
const content = `${imports}
|
|
1207
|
+
@Service()
|
|
1208
|
+
export class ${className} {${repositoryProp}
|
|
1209
|
+
async findAll() {
|
|
1210
|
+
return [];
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
async findOne(id: string) {
|
|
1214
|
+
return { id };
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
async create(data: any) {
|
|
1218
|
+
return data;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
async update(id: string, data: any) {
|
|
1222
|
+
return { id, ...data };
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
async delete(id: string) {
|
|
1226
|
+
return { deleted: true, id };
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
`;
|
|
1230
|
+
await fs7.ensureDir(path11.dirname(filePath));
|
|
1231
|
+
await fs7.writeFile(filePath, content);
|
|
1232
|
+
if (withRepository) {
|
|
1233
|
+
await this.generateRepository(name, options);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
async generateResource(name, options) {
|
|
1237
|
+
await this.generateController(name, options);
|
|
1238
|
+
await this.generateService(name, true, options);
|
|
1239
|
+
await this.generateEntity(name);
|
|
1240
|
+
await this.generateDto(name);
|
|
1241
|
+
}
|
|
1242
|
+
async generateModule(name) {
|
|
1243
|
+
const srcDir = TsConfigUtils.getRootDir();
|
|
1244
|
+
const modulePath = path11.join(
|
|
1245
|
+
srcDir,
|
|
1246
|
+
"modules",
|
|
1247
|
+
NamingUtils.kebabCase(name)
|
|
1248
|
+
);
|
|
1249
|
+
await fs7.ensureDir(path11.join(modulePath, "controllers"));
|
|
1250
|
+
await fs7.ensureDir(path11.join(modulePath, "services"));
|
|
1251
|
+
const controllerContent = `import { Controller, Get, Autowired } from 'fragment-ts';
|
|
1252
|
+
import { ${NamingUtils.pascalCase(name)}Service } from '../services/${NamingUtils.kebabCase(name)}.service';
|
|
1253
|
+
|
|
1254
|
+
@Controller('/${NamingUtils.kebabCase(name)}')
|
|
1255
|
+
export class ${NamingUtils.pascalCase(name)}Controller {
|
|
1256
|
+
@Autowired()
|
|
1257
|
+
private ${NamingUtils.camelCase(name)}Service!: ${NamingUtils.pascalCase(name)}Service;
|
|
1258
|
+
|
|
1259
|
+
@Get()
|
|
1260
|
+
findAll() {
|
|
1261
|
+
return this.${NamingUtils.camelCase(name)}Service.findAll();
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
`;
|
|
1265
|
+
const serviceContent = `import { Service } from 'fragment-ts';
|
|
1266
|
+
|
|
1267
|
+
@Service()
|
|
1268
|
+
export class ${NamingUtils.pascalCase(name)}Service {
|
|
1269
|
+
async findAll() {
|
|
1270
|
+
return [];
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
`;
|
|
1274
|
+
await fs7.writeFile(
|
|
1275
|
+
path11.join(
|
|
1276
|
+
modulePath,
|
|
1277
|
+
"controllers",
|
|
1278
|
+
`${NamingUtils.kebabCase(name)}.controller.ts`
|
|
1279
|
+
),
|
|
1280
|
+
controllerContent
|
|
1281
|
+
);
|
|
1282
|
+
await fs7.writeFile(
|
|
1283
|
+
path11.join(
|
|
1284
|
+
modulePath,
|
|
1285
|
+
"services",
|
|
1286
|
+
`${NamingUtils.kebabCase(name)}.service.ts`
|
|
1287
|
+
),
|
|
1288
|
+
serviceContent
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
async generateFeature(name) {
|
|
1292
|
+
const srcDir = TsConfigUtils.getRootDir();
|
|
1293
|
+
const featurePath = path11.join(
|
|
1294
|
+
srcDir,
|
|
1295
|
+
"features",
|
|
1296
|
+
NamingUtils.kebabCase(name)
|
|
1297
|
+
);
|
|
1298
|
+
await fs7.ensureDir(featurePath);
|
|
1299
|
+
const controllerContent = `import { Controller, Get, Autowired } from 'fragment-ts';
|
|
1300
|
+
import { ${NamingUtils.pascalCase(name)}Service } from './${NamingUtils.kebabCase(name)}.service';
|
|
1301
|
+
|
|
1302
|
+
@Controller('/${NamingUtils.kebabCase(name)}')
|
|
1303
|
+
export class ${NamingUtils.pascalCase(name)}Controller {
|
|
1304
|
+
@Autowired()
|
|
1305
|
+
private ${NamingUtils.camelCase(name)}Service!: ${NamingUtils.pascalCase(name)}Service;
|
|
1306
|
+
|
|
1307
|
+
@Get()
|
|
1308
|
+
findAll() {
|
|
1309
|
+
return this.${NamingUtils.camelCase(name)}Service.findAll();
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
`;
|
|
1313
|
+
const serviceContent = `import { Service } from 'fragment-ts';
|
|
1314
|
+
|
|
1315
|
+
@Service()
|
|
1316
|
+
export class ${NamingUtils.pascalCase(name)}Service {
|
|
1317
|
+
async findAll() {
|
|
1318
|
+
return [];
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
`;
|
|
1322
|
+
await fs7.writeFile(
|
|
1323
|
+
path11.join(featurePath, `${NamingUtils.kebabCase(name)}.controller.ts`),
|
|
1324
|
+
controllerContent
|
|
1325
|
+
);
|
|
1326
|
+
await fs7.writeFile(
|
|
1327
|
+
path11.join(featurePath, `${NamingUtils.kebabCase(name)}.service.ts`),
|
|
1328
|
+
serviceContent
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
async generateEntity(name) {
|
|
1332
|
+
const className = NamingUtils.pascalCase(name);
|
|
1333
|
+
const fileName = `${NamingUtils.kebabCase(name)}.entity.ts`;
|
|
1334
|
+
const srcDir = TsConfigUtils.getRootDir();
|
|
1335
|
+
const filePath = path11.join(srcDir, "entities", fileName);
|
|
1336
|
+
const content = `import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'fragment-ts';
|
|
1337
|
+
|
|
1338
|
+
@Entity()
|
|
1339
|
+
export class ${className} {
|
|
1340
|
+
@PrimaryGeneratedColumn()
|
|
1341
|
+
id: number;
|
|
1342
|
+
|
|
1343
|
+
@Column()
|
|
1344
|
+
name: string;
|
|
1345
|
+
|
|
1346
|
+
@CreateDateColumn()
|
|
1347
|
+
createdAt: Date;
|
|
1348
|
+
|
|
1349
|
+
@UpdateDateColumn()
|
|
1350
|
+
updatedAt: Date;
|
|
1351
|
+
}
|
|
1352
|
+
`;
|
|
1353
|
+
await fs7.ensureDir(path11.dirname(filePath));
|
|
1354
|
+
await fs7.writeFile(filePath, content);
|
|
1355
|
+
}
|
|
1356
|
+
async generateDto(name) {
|
|
1357
|
+
const className = `Create${NamingUtils.pascalCase(name)}Dto`;
|
|
1358
|
+
const fileName = `create-${NamingUtils.kebabCase(name)}.dto.ts`;
|
|
1359
|
+
const srcDir = TsConfigUtils.getRootDir();
|
|
1360
|
+
const filePath = path11.join(srcDir, "dto", fileName);
|
|
1361
|
+
const content = `export class ${className} {
|
|
1362
|
+
name: string;
|
|
1363
|
+
}
|
|
1364
|
+
`;
|
|
1365
|
+
await fs7.ensureDir(path11.dirname(filePath));
|
|
1366
|
+
await fs7.writeFile(filePath, content);
|
|
1367
|
+
}
|
|
1368
|
+
async generateRepository(name, options) {
|
|
1369
|
+
const className = `${NamingUtils.pascalCase(name)}Repository`;
|
|
1370
|
+
const entityName = NamingUtils.pascalCase(name);
|
|
1371
|
+
const fileName = `${NamingUtils.kebabCase(name)}.repository.ts`;
|
|
1372
|
+
let filePath;
|
|
1373
|
+
let entityImport;
|
|
1374
|
+
if (this.structureType === "module") {
|
|
1375
|
+
filePath = path11.join(this.getBasePath("repositories", options), fileName);
|
|
1376
|
+
entityImport = `import { ${entityName} } from '../../../entities/${NamingUtils.kebabCase(name)}.entity';`;
|
|
1377
|
+
} else if (this.structureType === "feature") {
|
|
1378
|
+
filePath = path11.join(this.getBasePath("repositories", options), fileName);
|
|
1379
|
+
entityImport = `import { ${entityName} } from '../../entities/${NamingUtils.kebabCase(name)}.entity';`;
|
|
1380
|
+
} else {
|
|
1381
|
+
filePath = path11.join(this.getBasePath("repositories", options), fileName);
|
|
1382
|
+
entityImport = `import { ${entityName} } from '../entities/${NamingUtils.kebabCase(name)}.entity';`;
|
|
1383
|
+
}
|
|
1384
|
+
const content = `import { Repository, InjectRepository } from 'fragment-ts';
|
|
1385
|
+
${entityImport}
|
|
1386
|
+
import { Repository as TypeOrmRepository } from 'fragment-ts';
|
|
1387
|
+
|
|
1388
|
+
@Repository()
|
|
1389
|
+
export class ${className} {
|
|
1390
|
+
@InjectRepository(${entityName})
|
|
1391
|
+
private repo!: TypeOrmRepository<${entityName}>;
|
|
1392
|
+
|
|
1393
|
+
async findAll(): Promise<${entityName}[]> {
|
|
1394
|
+
return this.repo.find();
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
async findById(id: number): Promise<${entityName} | null> {
|
|
1398
|
+
return this.repo.findOneBy({ id });
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
async create(data: Partial<${entityName}>): Promise<${entityName}> {
|
|
1402
|
+
const entity = this.repo.create(data);
|
|
1403
|
+
return this.repo.save(entity);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
async update(id: number, data: Partial<${entityName}>): Promise<${entityName} | null> {
|
|
1407
|
+
await this.repo.update(id, data);
|
|
1408
|
+
return this.findById(id);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
async delete(id: number): Promise<boolean> {
|
|
1412
|
+
const result = await this.repo.delete(id);
|
|
1413
|
+
return result.affected > 0;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
`;
|
|
1417
|
+
await fs7.ensureDir(path11.dirname(filePath));
|
|
1418
|
+
await fs7.writeFile(filePath, content);
|
|
1419
|
+
}
|
|
1420
|
+
async generateMiddleware(name) {
|
|
1421
|
+
const className = `${NamingUtils.pascalCase(name)}Middleware`;
|
|
1422
|
+
const fileName = `${NamingUtils.kebabCase(name)}.middleware.ts`;
|
|
1423
|
+
const filePath = path11.join(this.getBasePath("middlewares"), fileName);
|
|
1424
|
+
const content = `import { Injectable, express } from 'fragment-ts';
|
|
1425
|
+
|
|
1426
|
+
@Injectable()
|
|
1427
|
+
export class ${className} {
|
|
1428
|
+
use(req: express.Request, res: express.Response, next: express.NextFunction): void {
|
|
1429
|
+
// Add your middleware logic here
|
|
1430
|
+
next();
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
`;
|
|
1434
|
+
await fs7.ensureDir(path11.dirname(filePath));
|
|
1435
|
+
await fs7.writeFile(filePath, content);
|
|
1436
|
+
}
|
|
1437
|
+
async generateGuard(name) {
|
|
1438
|
+
const className = `${NamingUtils.pascalCase(name)}Guard`;
|
|
1439
|
+
const fileName = `${NamingUtils.kebabCase(name)}.guard.ts`;
|
|
1440
|
+
const filePath = path11.join(this.getBasePath("guards"), fileName);
|
|
1441
|
+
const content = `import { Injectable, express } from 'fragment-ts';
|
|
1442
|
+
|
|
1443
|
+
@Injectable()
|
|
1444
|
+
export class ${className} {
|
|
1445
|
+
canActivate(req: express.Request): boolean {
|
|
1446
|
+
// Add your authorization logic here
|
|
1447
|
+
// Return true to allow access, false to deny
|
|
1448
|
+
return true;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
`;
|
|
1452
|
+
await fs7.ensureDir(path11.dirname(filePath));
|
|
1453
|
+
await fs7.writeFile(filePath, content);
|
|
1454
|
+
}
|
|
1455
|
+
async generateInterceptor(name) {
|
|
1456
|
+
const className = `${NamingUtils.pascalCase(name)}Interceptor`;
|
|
1457
|
+
const fileName = `${NamingUtils.kebabCase(name)}.interceptor.ts`;
|
|
1458
|
+
const filePath = path11.join(this.getBasePath("interceptors"), fileName);
|
|
1459
|
+
const content = `import { Injectable, express } from 'fragment-ts';
|
|
1460
|
+
|
|
1461
|
+
@Injectable()
|
|
1462
|
+
export class ${className} {
|
|
1463
|
+
intercept(req: express.Request, res: express.Response, result: any): any {
|
|
1464
|
+
// Add your transformation logic here
|
|
1465
|
+
return result;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
`;
|
|
1469
|
+
await fs7.ensureDir(path11.dirname(filePath));
|
|
1470
|
+
await fs7.writeFile(filePath, content);
|
|
1471
|
+
}
|
|
1472
|
+
async generateFilter(name) {
|
|
1473
|
+
const className = `${NamingUtils.pascalCase(name)}ExceptionFilter`;
|
|
1474
|
+
const fileName = `${NamingUtils.kebabCase(name)}.exception.filter.ts`;
|
|
1475
|
+
const filePath = path11.join(this.getBasePath("filters"), fileName);
|
|
1476
|
+
const content = `import { Injectable, express } from 'fragment-ts';
|
|
1477
|
+
|
|
1478
|
+
@Injectable()
|
|
1479
|
+
export class ${className} {
|
|
1480
|
+
catch(exception: Error, req: express.Request, res: express.Response): void {
|
|
1481
|
+
// Add your error handling logic here
|
|
1482
|
+
res.status(500).json({
|
|
1483
|
+
statusCode: 500,
|
|
1484
|
+
error: 'Internal Server Error',
|
|
1485
|
+
message: exception.message || 'Something went wrong'
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
`;
|
|
1490
|
+
await fs7.ensureDir(path11.dirname(filePath));
|
|
1491
|
+
await fs7.writeFile(filePath, content);
|
|
1492
|
+
}
|
|
1493
|
+
async generateJob(name, options) {
|
|
1494
|
+
const className = `${NamingUtils.pascalCase(name)}Job`;
|
|
1495
|
+
const fileName = `${NamingUtils.kebabCase(name)}.job.ts`;
|
|
1496
|
+
const filePath = path11.join(this.getBasePath("jobs", options), fileName);
|
|
1497
|
+
const content = `import { Job, ScheduledJob } from 'fragment-ts';
|
|
1498
|
+
import type { JobContext } from 'fragment-ts';
|
|
1499
|
+
|
|
1500
|
+
@Job()
|
|
1501
|
+
export class ${className} extends ScheduledJob {
|
|
1502
|
+
protected async handle(payload: any, context: JobContext): Promise<void> {
|
|
1503
|
+
// Add your job logic here
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
`;
|
|
1507
|
+
await fs7.ensureDir(path11.dirname(filePath));
|
|
1508
|
+
await fs7.writeFile(filePath, content);
|
|
1509
|
+
}
|
|
1510
|
+
async generateMail(name, options) {
|
|
1511
|
+
const className = `${NamingUtils.pascalCase(name)}Mailer`;
|
|
1512
|
+
const fileName = `${NamingUtils.kebabCase(name)}.mail.ts`;
|
|
1513
|
+
const filePath = path11.join(this.getBasePath("mail", options), fileName);
|
|
1514
|
+
const qualifier = NamingUtils.kebabCase(name);
|
|
1515
|
+
const content = `import { Mailer } from 'fragment-ts';
|
|
1516
|
+
import type { MailMessage, MailProvider, MailSendResult } from 'fragment-ts';
|
|
1517
|
+
|
|
1518
|
+
@Mailer({ name: '${qualifier}' })
|
|
1519
|
+
export class ${className} implements MailProvider {
|
|
1520
|
+
async send(message: MailMessage): Promise<MailSendResult | void> {
|
|
1521
|
+
// Implement provider send logic here
|
|
1522
|
+
return {
|
|
1523
|
+
id: 'example',
|
|
1524
|
+
accepted: Array.isArray(message.to) ? message.to.map(String) : [String(message.to)],
|
|
1525
|
+
rejected: [],
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
`;
|
|
1530
|
+
await fs7.ensureDir(path11.dirname(filePath));
|
|
1531
|
+
await fs7.writeFile(filePath, content);
|
|
1532
|
+
}
|
|
1533
|
+
async generateNotification(name, options) {
|
|
1534
|
+
const className = `${NamingUtils.pascalCase(name)}Notification`;
|
|
1535
|
+
const fileName = `${NamingUtils.kebabCase(name)}.notification.ts`;
|
|
1536
|
+
const filePath = path11.join(
|
|
1537
|
+
this.getBasePath("notifications", options),
|
|
1538
|
+
fileName
|
|
1539
|
+
);
|
|
1540
|
+
const content = `import type { MailMessage, MailNotification } from 'fragment-ts';
|
|
1541
|
+
|
|
1542
|
+
export class ${className} implements MailNotification {
|
|
1543
|
+
via(): string[] {
|
|
1544
|
+
return ['mail'];
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
toMail(): MailMessage {
|
|
1548
|
+
return {
|
|
1549
|
+
subject: '${NamingUtils.pascalCase(name)} Notification',
|
|
1550
|
+
html: '<p>Notification content</p>'
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
`;
|
|
1555
|
+
await fs7.ensureDir(path11.dirname(filePath));
|
|
1556
|
+
await fs7.writeFile(filePath, content);
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
|
|
1560
|
+
// src/platform/cli/web/commands/make/service.ts
|
|
1561
|
+
async function resolveTarget(options) {
|
|
1562
|
+
if (options.target) return options.target;
|
|
1563
|
+
const hasApi = fs8.existsSync(path12.join(process.cwd(), "fragment.json"));
|
|
1564
|
+
const hasWeb = fs8.existsSync(path12.join(process.cwd(), "fragment.web.json"));
|
|
1565
|
+
if (!(hasApi && hasWeb)) return "web";
|
|
1566
|
+
const inquirer = await import("inquirer");
|
|
1567
|
+
const answer = await inquirer.default.prompt([
|
|
1568
|
+
{
|
|
1569
|
+
type: "list",
|
|
1570
|
+
name: "target",
|
|
1571
|
+
message: "Both backend and frontend configs were found. Which service do you want to generate?",
|
|
1572
|
+
choices: [
|
|
1573
|
+
{ name: "Frontend (@FragmentService) in src/web", value: "web" },
|
|
1574
|
+
{ name: "Backend (@Service) in fragment-ts", value: "api" }
|
|
1575
|
+
],
|
|
1576
|
+
default: "web"
|
|
1577
|
+
}
|
|
1578
|
+
]);
|
|
1579
|
+
return answer.target;
|
|
1580
|
+
}
|
|
1581
|
+
async function runBackendService(name, options) {
|
|
1582
|
+
const structureType = ConfigUtils.getStructureType();
|
|
1583
|
+
const generator = new ComponentGenerator(structureType);
|
|
1584
|
+
await generator.generate("service", name, {
|
|
1585
|
+
module: options.module,
|
|
1586
|
+
feature: options.feature
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
async function runMakeService(name, options = {}) {
|
|
1590
|
+
const target = await resolveTarget(options);
|
|
1591
|
+
if (target === "api") {
|
|
1592
|
+
await runBackendService(name, options);
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
const config2 = ensureWebConfig();
|
|
1596
|
+
const mode = options.structure || config2.structure;
|
|
1597
|
+
const domain = options.feature || options.module;
|
|
1598
|
+
const outDir = resolveOutputDir({ mode, kind: "service", name, domain });
|
|
1599
|
+
const fileName = `${toKebabCase(name)}.service.ts`;
|
|
1600
|
+
const className = `${toPascalCase(name)}Service`;
|
|
1601
|
+
const baseUrl = (options.baseUrl || `/${toKebabCase(name)}`).replace(/\/$/, "");
|
|
1602
|
+
const methods = (options.methods || "findAll,findById,create,update,delete").split(",").map((v) => v.trim()).filter(Boolean);
|
|
1603
|
+
const body = methods.map((m) => {
|
|
1604
|
+
switch (m) {
|
|
1605
|
+
case "findAll":
|
|
1606
|
+
return ` async findAll(): Promise<unknown> {
|
|
1607
|
+
return FragmentFetch.get('${baseUrl}');
|
|
1608
|
+
}`;
|
|
1609
|
+
case "findById":
|
|
1610
|
+
return ` async findById(id: string): Promise<unknown> {
|
|
1611
|
+
return FragmentFetch.get(\`${baseUrl}/\${id}\`);
|
|
1612
|
+
}`;
|
|
1613
|
+
case "create":
|
|
1614
|
+
return ` async create(payload: unknown): Promise<unknown> {
|
|
1615
|
+
return FragmentFetch.post('${baseUrl}', payload);
|
|
1616
|
+
}`;
|
|
1617
|
+
case "update":
|
|
1618
|
+
return ` async update(id: string, payload: unknown): Promise<unknown> {
|
|
1619
|
+
return FragmentFetch.put(\`${baseUrl}/\${id}\`, payload);
|
|
1620
|
+
}`;
|
|
1621
|
+
case "delete":
|
|
1622
|
+
return ` async delete(id: string): Promise<unknown> {
|
|
1623
|
+
return FragmentFetch.delete(\`${baseUrl}/\${id}\`);
|
|
1624
|
+
}`;
|
|
1625
|
+
default:
|
|
1626
|
+
return ` async ${m}(): Promise<unknown> {
|
|
1627
|
+
return FragmentFetch.get('${baseUrl}');
|
|
1628
|
+
}`;
|
|
1629
|
+
}
|
|
1630
|
+
}).join("\n\n");
|
|
1631
|
+
const content = `// Generated by: frg make:service ${name}
|
|
1632
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1633
|
+
// fragment-web v0.1.0
|
|
1634
|
+
import { FragmentFetch, FragmentService } from 'fragment-web';
|
|
1635
|
+
|
|
1636
|
+
@FragmentService()
|
|
1637
|
+
export class ${className} {
|
|
1638
|
+
${body}
|
|
1639
|
+
}
|
|
1640
|
+
`;
|
|
1641
|
+
writeFileIfAllowed(path12.join(process.cwd(), outDir, fileName), content, options);
|
|
1642
|
+
}
|
|
1643
|
+
function registerMakeService(program) {
|
|
1644
|
+
program.command("make:service <name>").description("Generate a service (frontend or backend when both contexts exist)").option("--base-url <path>", "Base URL path for FragmentFetch calls").option("--methods <list>", "CSV of service methods").option("--target <target>", "web|api").option("--structure <mode>", "Override structure mode").option("--module <name>", "Module name").option("--feature <name>", "Feature name").option("--force", "Overwrite existing files").option("--dry-run", "Preview changes").action((name, opts) => {
|
|
1645
|
+
runMakeService(name, opts).catch((error) => {
|
|
1646
|
+
logger.error(error instanceof Error ? error.message : "make:service failed");
|
|
1647
|
+
process.exitCode = 1;
|
|
1648
|
+
});
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// src/platform/cli/web/commands/make/guard.ts
|
|
1653
|
+
import path13 from "path";
|
|
1654
|
+
async function runMakeGuard(name, options = {}) {
|
|
1655
|
+
const config2 = ensureWebConfig();
|
|
1656
|
+
const mode = options.structure || config2.structure;
|
|
1657
|
+
const outDir = resolveOutputDir({ mode, kind: "guard", name });
|
|
1658
|
+
const fileName = `${toKebabCase(name)}.guard.ts`;
|
|
1659
|
+
const className = `${toPascalCase(name)}Guard`;
|
|
1660
|
+
const redirect = options.redirect || "/login";
|
|
1661
|
+
const bodyByType = {
|
|
1662
|
+
auth: ` canActivate(): boolean {
|
|
1663
|
+
return Boolean(FragmentFetch.getToken());
|
|
1664
|
+
}`,
|
|
1665
|
+
role: ` canActivate(): boolean {
|
|
1666
|
+
// Replace with role claim checks from your auth provider
|
|
1667
|
+
return Boolean(FragmentFetch.getToken());
|
|
1668
|
+
}`,
|
|
1669
|
+
custom: ` canActivate(): boolean {
|
|
1670
|
+
return true;
|
|
1671
|
+
}`
|
|
1672
|
+
};
|
|
1673
|
+
const type = options.type || "custom";
|
|
1674
|
+
const content = `// Generated by: frg make:guard ${name}
|
|
1675
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1676
|
+
// fragment-web v0.1.0
|
|
1677
|
+
import { FragmentFetch } from 'fragment-web';
|
|
1678
|
+
|
|
1679
|
+
export class ${className} {
|
|
1680
|
+
${bodyByType[type]}
|
|
1681
|
+
|
|
1682
|
+
redirectTo(): string {
|
|
1683
|
+
return '${redirect}';
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
`;
|
|
1687
|
+
writeFileIfAllowed(path13.join(process.cwd(), outDir, fileName), content, options);
|
|
1688
|
+
}
|
|
1689
|
+
function registerMakeGuard(program) {
|
|
1690
|
+
program.command("make:guard <name>").description("Generate a frontend guard").option("--type <type>", "auth|role|custom").option("--redirect <path>", "Redirect path").option("--structure <mode>", "Override structure mode").option("--force", "Overwrite existing files").option("--dry-run", "Preview changes").action((name, opts) => {
|
|
1691
|
+
runMakeGuard(name, opts).catch((error) => {
|
|
1692
|
+
logger.error(error instanceof Error ? error.message : "make:guard failed");
|
|
1693
|
+
process.exitCode = 1;
|
|
1694
|
+
});
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// src/platform/cli/web/commands/make/layout.ts
|
|
1699
|
+
import path14 from "path";
|
|
1700
|
+
async function runMakeLayout(name, options = {}) {
|
|
1701
|
+
const config2 = ensureWebConfig();
|
|
1702
|
+
const mode = options.structure || config2.structure;
|
|
1703
|
+
const outDir = resolveOutputDir({ mode, kind: "layout", name });
|
|
1704
|
+
const fileName = `${toKebabCase(name)}.layout.tsx`;
|
|
1705
|
+
const className = `${toPascalCase(name)}Layout`;
|
|
1706
|
+
const slots = (options.slots || "header,footer").split(",").map((v) => v.trim()).filter(Boolean);
|
|
1707
|
+
const slotProps = slots.map((s) => `${s}?: React.ReactNode;`).join("\n ");
|
|
1708
|
+
const slotUsage = slots.map((s) => `{${s}}`).join("\n ");
|
|
1709
|
+
const content = `// Generated by: frg make:layout ${name}
|
|
1710
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1711
|
+
// fragment-web v0.1.0
|
|
1712
|
+
import React from 'react';
|
|
1713
|
+
|
|
1714
|
+
interface ${className}Props {
|
|
1715
|
+
children?: React.ReactNode;
|
|
1716
|
+
${slotProps}
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
export function ${className}({ children, ${slots.join(", ")} }: ${className}Props): JSX.Element {
|
|
1720
|
+
return <section>
|
|
1721
|
+
${slotUsage}
|
|
1722
|
+
{children}
|
|
1723
|
+
</section>;
|
|
1724
|
+
}
|
|
1725
|
+
`;
|
|
1726
|
+
writeFileIfAllowed(path14.join(process.cwd(), outDir, fileName), content, options);
|
|
1727
|
+
}
|
|
1728
|
+
function registerMakeLayout(program) {
|
|
1729
|
+
program.command("make:layout <name>").description("Generate a frontend layout").option("--slots <slots>", "CSV slot names").option("--structure <mode>", "Override structure mode").option("--force", "Overwrite existing files").option("--dry-run", "Preview changes").action((name, opts) => {
|
|
1730
|
+
runMakeLayout(name, opts).catch((error) => {
|
|
1731
|
+
logger.error(error instanceof Error ? error.message : "make:layout failed");
|
|
1732
|
+
process.exitCode = 1;
|
|
1733
|
+
});
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// src/platform/cli/web/commands/make/store.ts
|
|
1738
|
+
import path15 from "path";
|
|
1739
|
+
function parseFields(fields) {
|
|
1740
|
+
const raw = (fields || "loading:boolean=false").split(",").map((v) => v.trim()).filter(Boolean);
|
|
1741
|
+
return raw.map((item) => {
|
|
1742
|
+
const [left, defaultValue] = item.split("=").map((v) => v.trim());
|
|
1743
|
+
const [key, type] = left.split(":").map((v) => v.trim());
|
|
1744
|
+
return {
|
|
1745
|
+
key,
|
|
1746
|
+
type: type || "unknown",
|
|
1747
|
+
initial: defaultValue ?? (type === "number" ? "0" : type === "boolean" ? "false" : type === "string" ? "''" : "null")
|
|
1748
|
+
};
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
async function runMakeStore(name, options = {}) {
|
|
1752
|
+
const config2 = ensureWebConfig();
|
|
1753
|
+
const mode = options.structure || config2.structure;
|
|
1754
|
+
const adapter = options.adapter || config2.app.stateAdapter;
|
|
1755
|
+
const domain = options.feature || options.module;
|
|
1756
|
+
const outDir = resolveOutputDir({ mode, kind: "store", name, domain });
|
|
1757
|
+
const fileName = `${toKebabCase(name)}.store.ts`;
|
|
1758
|
+
const className = toPascalCase(name);
|
|
1759
|
+
const fields = parseFields(options.fields);
|
|
1760
|
+
const actions = (options.actions || "setState").split(",").map((v) => v.trim()).filter(Boolean);
|
|
1761
|
+
const contentByAdapter = {
|
|
1762
|
+
zustand: `// Generated by: frg make:store ${name}
|
|
1763
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1764
|
+
// fragment-web v0.1.0
|
|
1765
|
+
import { create } from 'zustand';
|
|
1766
|
+
|
|
1767
|
+
interface ${className}State {
|
|
1768
|
+
${fields.map((f) => ` ${f.key}: ${f.type};`).join("\n")}
|
|
1769
|
+
${actions.map((a) => ` ${a}: (payload?: unknown) => void;`).join("\n")}
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
export const use${className}Store = create<${className}State>((set) => ({
|
|
1773
|
+
${fields.map((f) => ` ${f.key}: ${f.initial},`).join("\n")}
|
|
1774
|
+
${actions.map((a) => ` ${a}: (payload) => set((s) => ({ ...s })),`).join("\n")}
|
|
1775
|
+
}));
|
|
1776
|
+
`,
|
|
1777
|
+
jotai: `// Generated by: frg make:store ${name}
|
|
1778
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1779
|
+
// fragment-web v0.1.0
|
|
1780
|
+
import { atom } from 'jotai';
|
|
1781
|
+
|
|
1782
|
+
export const ${toKebabCase(name).replace(/-/g, "_")}Store = atom({
|
|
1783
|
+
${fields.map((f) => ` ${f.key}: ${f.initial},`).join("\n")}
|
|
1784
|
+
});
|
|
1785
|
+
`,
|
|
1786
|
+
redux: `// Generated by: frg make:store ${name}
|
|
1787
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1788
|
+
// fragment-web v0.1.0
|
|
1789
|
+
export interface ${className}State {
|
|
1790
|
+
${fields.map((f) => ` ${f.key}: ${f.type};`).join("\n")}
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
export const initial${className}State: ${className}State = {
|
|
1794
|
+
${fields.map((f) => ` ${f.key}: ${f.initial},`).join("\n")}
|
|
1795
|
+
};
|
|
1796
|
+
`,
|
|
1797
|
+
mobx: `// Generated by: frg make:store ${name}
|
|
1798
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1799
|
+
// fragment-web v0.1.0
|
|
1800
|
+
import { makeAutoObservable } from 'mobx';
|
|
1801
|
+
|
|
1802
|
+
export class ${className}Store {
|
|
1803
|
+
${fields.map((f) => ` ${f.key}: ${f.type} = ${f.initial};`).join("\n")}
|
|
1804
|
+
|
|
1805
|
+
constructor() {
|
|
1806
|
+
makeAutoObservable(this);
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
${actions.map((a) => ` ${a}(payload?: unknown): void {}`).join("\n\n")}
|
|
1810
|
+
}
|
|
1811
|
+
`
|
|
1812
|
+
};
|
|
1813
|
+
writeFileIfAllowed(path15.join(process.cwd(), outDir, fileName), contentByAdapter[adapter], options);
|
|
1814
|
+
}
|
|
1815
|
+
function registerMakeStore(program) {
|
|
1816
|
+
program.command("make:store <name>").description("Generate a frontend store").option("--adapter <adapter>", "zustand|jotai|redux|mobx").option("--fields <fields>", "CSV key:type[=default] list").option("--actions <actions>", "CSV action names").option("--structure <mode>", "Override structure mode").option("--module <name>", "Module name").option("--feature <name>", "Feature name").option("--force", "Overwrite existing files").option("--dry-run", "Preview changes").action((name, opts) => {
|
|
1817
|
+
runMakeStore(name, opts).catch((error) => {
|
|
1818
|
+
logger.error(error instanceof Error ? error.message : "make:store failed");
|
|
1819
|
+
process.exitCode = 1;
|
|
1820
|
+
});
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// src/platform/cli/web/commands/make/resource.ts
|
|
1825
|
+
async function runMakeResource(name, options = {}) {
|
|
1826
|
+
await runMakeComponent(`${name}-card`, options);
|
|
1827
|
+
await runMakePage(`${name}-list`, { ...options, path: options.path || `/${name}` });
|
|
1828
|
+
await runMakeService(name, options);
|
|
1829
|
+
await runMakeStore(name, options);
|
|
1830
|
+
}
|
|
1831
|
+
function registerMakeResource(program) {
|
|
1832
|
+
program.command("make:resource <name>").description("Generate page + service + store + component in one shot").option("--path <path>", "List page route path").option("--methods <list>", "Service methods CSV").option("--adapter <adapter>", "Store adapter").option("--fields <fields>", "Store fields").option("--actions <actions>", "Store actions").option("--structure <mode>", "Override structure mode").option("--module <name>", "Module name").option("--feature <name>", "Feature name").option("--force", "Overwrite existing files").option("--dry-run", "Preview changes").action((name, opts) => {
|
|
1833
|
+
runMakeResource(name, opts).catch((error) => {
|
|
1834
|
+
logger.error(error instanceof Error ? error.message : "make:resource failed");
|
|
1835
|
+
process.exitCode = 1;
|
|
1836
|
+
});
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
// src/platform/cli/web/commands/create/module.ts
|
|
1841
|
+
import path16 from "path";
|
|
1842
|
+
async function runCreateModule(name, options = {}) {
|
|
1843
|
+
const config2 = ensureWebConfig();
|
|
1844
|
+
const mode = options.structure || config2.structure;
|
|
1845
|
+
if (mode !== "module") {
|
|
1846
|
+
throw new Error("create:module requires structure mode 'module' (or pass --structure module)");
|
|
1847
|
+
}
|
|
1848
|
+
await runMakePage(`${name}-list`, { ...options, structure: mode, module: name });
|
|
1849
|
+
await runMakePage(`${name}-detail`, { ...options, structure: mode, module: name });
|
|
1850
|
+
await runMakeService(name, { ...options, structure: mode, module: name });
|
|
1851
|
+
await runMakeStore(name, { ...options, structure: mode, module: name });
|
|
1852
|
+
await runMakeComponent(`${name}-card`, { ...options, structure: mode, module: name });
|
|
1853
|
+
writeFileIfAllowed(
|
|
1854
|
+
path16.join(process.cwd(), "src/modules", toKebabCase(name), "index.ts"),
|
|
1855
|
+
`// Generated by: frg create:module ${name}
|
|
1856
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1857
|
+
// fragment-web v0.1.0
|
|
1858
|
+
export * from './${toKebabCase(name)}-list.page';
|
|
1859
|
+
export * from './${toKebabCase(name)}-detail.page';
|
|
1860
|
+
export * from './${toKebabCase(name)}.service';
|
|
1861
|
+
export * from './${toKebabCase(name)}.store';
|
|
1862
|
+
export * from './components/${toKebabCase(name)}-card.component';
|
|
1863
|
+
`,
|
|
1864
|
+
options
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
function registerCreateModule(program) {
|
|
1868
|
+
program.command("create:module <name>").description("Generate a full module slice").option("--structure <mode>", "Override structure mode").option("--force", "Overwrite existing files").option("--dry-run", "Preview changes").action((name, opts) => {
|
|
1869
|
+
runCreateModule(name, opts).catch((error) => {
|
|
1870
|
+
logger.error(error instanceof Error ? error.message : "create:module failed");
|
|
1871
|
+
process.exitCode = 1;
|
|
1872
|
+
});
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
// src/platform/cli/web/commands/create/feature.ts
|
|
1877
|
+
import path17 from "path";
|
|
1878
|
+
async function runCreateFeature(name, options = {}) {
|
|
1879
|
+
const config2 = ensureWebConfig();
|
|
1880
|
+
const mode = options.structure || config2.structure;
|
|
1881
|
+
if (mode !== "feature") {
|
|
1882
|
+
throw new Error("create:feature requires structure mode 'feature' (or pass --structure feature)");
|
|
1883
|
+
}
|
|
1884
|
+
await runMakePage(`${name}-list`, { ...options, structure: mode, feature: name });
|
|
1885
|
+
await runMakeService(name, { ...options, structure: mode, feature: name });
|
|
1886
|
+
await runMakeStore(name, { ...options, structure: mode, feature: name });
|
|
1887
|
+
await runMakeComponent(`${name}-card`, { ...options, structure: mode, feature: name });
|
|
1888
|
+
writeFileIfAllowed(
|
|
1889
|
+
path17.join(process.cwd(), "src/features", toKebabCase(name), "index.ts"),
|
|
1890
|
+
`// Generated by: frg create:feature ${name}
|
|
1891
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1892
|
+
// fragment-web v0.1.0
|
|
1893
|
+
export * from './pages/${toKebabCase(name)}-list.page';
|
|
1894
|
+
export * from './services/${toKebabCase(name)}.service';
|
|
1895
|
+
export * from './stores/${toKebabCase(name)}.store';
|
|
1896
|
+
export * from './components/${toKebabCase(name)}-card.component';
|
|
1897
|
+
`,
|
|
1898
|
+
options
|
|
1899
|
+
);
|
|
1900
|
+
}
|
|
1901
|
+
function registerCreateFeature(program) {
|
|
1902
|
+
program.command("create:feature <name>").description("Generate a full feature slice").option("--structure <mode>", "Override structure mode").option("--force", "Overwrite existing files").option("--dry-run", "Preview changes").action((name, opts) => {
|
|
1903
|
+
runCreateFeature(name, opts).catch((error) => {
|
|
1904
|
+
logger.error(error instanceof Error ? error.message : "create:feature failed");
|
|
1905
|
+
process.exitCode = 1;
|
|
1906
|
+
});
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// src/platform/cli/web/commands/create/mvvm.ts
|
|
1911
|
+
import path18 from "path";
|
|
1912
|
+
async function runCreateMvvm(name, options = {}) {
|
|
1913
|
+
const config2 = ensureWebConfig();
|
|
1914
|
+
const mode = options.structure || config2.structure;
|
|
1915
|
+
if (mode !== "mvvm") {
|
|
1916
|
+
throw new Error("create:mvvm requires structure mode 'mvvm' (or pass --structure mvvm)");
|
|
1917
|
+
}
|
|
1918
|
+
const dom = toKebabCase(name);
|
|
1919
|
+
const className = toPascalCase(name);
|
|
1920
|
+
await runMakePage(`${name}-list`, { ...options, structure: mode, feature: name });
|
|
1921
|
+
await runMakeComponent(`${name}-card`, { ...options, structure: mode, feature: name });
|
|
1922
|
+
writeFileIfAllowed(
|
|
1923
|
+
path18.join(process.cwd(), "src/app", dom, "viewmodel", `${dom}.vm.ts`),
|
|
1924
|
+
`// Generated by: frg create:mvvm ${name}
|
|
1925
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1926
|
+
// fragment-web v0.1.0
|
|
1927
|
+
import { FragmentFetch, FragmentService } from 'fragment-web';
|
|
1928
|
+
|
|
1929
|
+
@FragmentService()
|
|
1930
|
+
export class ${className}Vm {
|
|
1931
|
+
async load(): Promise<unknown> {
|
|
1932
|
+
return FragmentFetch.get('/${dom}');
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
`,
|
|
1936
|
+
options
|
|
1937
|
+
);
|
|
1938
|
+
writeFileIfAllowed(
|
|
1939
|
+
path18.join(process.cwd(), "src/app", dom, "model", `${dom}.model.ts`),
|
|
1940
|
+
`// Generated by: frg create:mvvm ${name}
|
|
1941
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1942
|
+
// fragment-web v0.1.0
|
|
1943
|
+
export interface ${className}Model {
|
|
1944
|
+
id: string;
|
|
1945
|
+
}
|
|
1946
|
+
`,
|
|
1947
|
+
options
|
|
1948
|
+
);
|
|
1949
|
+
writeFileIfAllowed(
|
|
1950
|
+
path18.join(process.cwd(), "src/app", dom, "index.ts"),
|
|
1951
|
+
`// Generated by: frg create:mvvm ${name}
|
|
1952
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1953
|
+
// fragment-web v0.1.0
|
|
1954
|
+
export * from './model/${dom}.model';
|
|
1955
|
+
export * from './viewmodel/${dom}.vm';
|
|
1956
|
+
export * from './view/${dom}-list.page';
|
|
1957
|
+
export * from './view/${dom}-card.component';
|
|
1958
|
+
`,
|
|
1959
|
+
options
|
|
1960
|
+
);
|
|
1961
|
+
}
|
|
1962
|
+
function registerCreateMvvm(program) {
|
|
1963
|
+
program.command("create:mvvm <name>").description("Generate a full MVVM slice").option("--structure <mode>", "Override structure mode").option("--force", "Overwrite existing files").option("--dry-run", "Preview changes").action((name, opts) => {
|
|
1964
|
+
runCreateMvvm(name, opts).catch((error) => {
|
|
1965
|
+
logger.error(error instanceof Error ? error.message : "create:mvvm failed");
|
|
1966
|
+
process.exitCode = 1;
|
|
1967
|
+
});
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
// src/platform/cli/web/index.ts
|
|
1972
|
+
function removeCommandIfExists(registry, name) {
|
|
1973
|
+
const commands = registry.commands;
|
|
1974
|
+
if (!commands) return;
|
|
1975
|
+
const idx = commands.findIndex((cmd) => cmd.name() === name);
|
|
1976
|
+
if (idx >= 0) {
|
|
1977
|
+
commands.splice(idx, 1);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
function registerWebCommands(registry) {
|
|
1981
|
+
removeCommandIfExists(registry, "make:service");
|
|
1982
|
+
removeCommandIfExists(registry, "make:guard");
|
|
1983
|
+
removeCommandIfExists(registry, "make:resource");
|
|
1984
|
+
registerWebSync(registry);
|
|
1985
|
+
registerMakeComponent(registry);
|
|
1986
|
+
registerMakePage(registry);
|
|
1987
|
+
registerMakeService(registry);
|
|
1988
|
+
registerMakeGuard(registry);
|
|
1989
|
+
registerMakeLayout(registry);
|
|
1990
|
+
registerMakeStore(registry);
|
|
1991
|
+
registerMakeResource(registry);
|
|
1992
|
+
registerCreateModule(registry);
|
|
1993
|
+
registerCreateFeature(registry);
|
|
1994
|
+
registerCreateMvvm(registry);
|
|
1995
|
+
}
|
|
1996
|
+
export {
|
|
1997
|
+
registerWebCommands
|
|
1998
|
+
};
|