@voxgig/build 4.4.0 → 4.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -2
- package/build.ts +20 -0
- package/dist/api/api_gen.d.ts +7 -0
- package/dist/api/api_gen.js +370 -0
- package/dist/api/api_gen.js.map +1 -0
- package/dist/build.d.ts +16 -1
- package/dist/build.js +17 -1
- package/dist/build.js.map +1 -1
- package/dist/doc/doc_gen.d.ts +8 -0
- package/dist/doc/doc_gen.js +397 -0
- package/dist/doc/doc_gen.js.map +1 -0
- package/dist/env/web/web_gen.js +197 -11
- package/dist/env/web/web_gen.js.map +1 -1
- package/dist/shape/ent.js +4 -2
- package/dist/shape/ent.js.map +1 -1
- package/env/web/web_gen.ts +219 -13
- package/package.json +6 -9
- package/tm/web/backend/env/shared/seed.ts.frag +23 -0
- package/tm/web/backend/env/web/api.ts.frag +157 -0
- package/tm/web/backend/env/web/web.ts.frag +43 -8
- package/tm/web/backend/srv/api/api-srv.ts.frag +4 -0
- package/tm/web/backend/srv/api/expose.ts.frag +33 -0
- package/tm/web/backend/srv/api/get_info.ts.frag +6 -0
- package/tm/web/backend/srv/api/on_ent.ts.frag +124 -0
- package/tm/web/backend/srv/auth/apikey_util.ts.frag +10 -0
- package/tm/web/backend/srv/auth/auth-srv.ts.frag +1 -0
- package/tm/web/backend/srv/auth/change_pass.ts.frag +12 -0
- package/tm/web/backend/srv/auth/create_apikey.ts.frag +33 -0
- package/tm/web/backend/srv/auth/get_info.ts.frag +1 -0
- package/tm/web/backend/srv/auth/list_apikey.ts.frag +15 -0
- package/tm/web/backend/srv/auth/load_auth.ts.frag +10 -1
- package/tm/web/backend/srv/auth/remind_pass.ts.frag +35 -0
- package/tm/web/backend/srv/auth/revoke_apikey.ts.frag +23 -0
- package/tm/web/backend/srv/auth/signin_user.ts.frag +12 -2
- package/tm/web/backend/srv/auth/signout_user.ts.frag +11 -1
- package/tm/web/backend/srv/auth/update_user.ts.frag +15 -0
- package/tm/web/backend/srv/auth/user_util.ts.frag +21 -0
- package/tm/web/backend/srv/auth/web_change_pass.ts.frag +16 -0
- package/tm/web/backend/srv/auth/web_create_apikey.ts.frag +15 -0
- package/tm/web/backend/srv/auth/web_list_apikey.ts.frag +11 -0
- package/tm/web/backend/srv/auth/web_load_auth.ts.frag +1 -0
- package/tm/web/backend/srv/auth/web_remind_pass.ts.frag +7 -0
- package/tm/web/backend/srv/auth/web_revoke_apikey.ts.frag +14 -0
- package/tm/web/backend/srv/auth/web_signin_user.ts.frag +1 -0
- package/tm/web/backend/srv/auth/web_signout_user.ts.frag +11 -3
- package/tm/web/backend/srv/auth/web_update_user.ts.frag +11 -0
- package/tm/web/backend/srv/ent/access.ts.frag +179 -0
- package/tm/web/backend/srv/ent/cmd_list.ts.frag +86 -0
- package/tm/web/backend/srv/ent/cmd_load.ts.frag +56 -0
- package/tm/web/backend/srv/ent/cmd_remove.ts.frag +60 -0
- package/tm/web/backend/srv/ent/cmd_save.ts.frag +141 -0
- package/tm/web/backend/srv/ent/ent-srv.ts.frag +4 -0
- package/tm/web/backend/srv/ent/get_info.ts.frag +6 -0
- package/tm/web/backend/srv/ent/web_cmd_list.ts.frag +17 -0
- package/tm/web/backend/srv/ent/web_cmd_load.ts.frag +17 -0
- package/tm/web/backend/srv/ent/web_cmd_remove.ts.frag +17 -0
- package/tm/web/backend/srv/ent/web_cmd_save.ts.frag +17 -0
- package/tm/web/backend/test/unit/env/web/api-router.test.ts.frag +315 -0
- package/tm/web/backend/test/unit/env/web/surface.test.ts.frag +109 -0
- package/tm/web/backend/test/unit/srv/api/api.setup.ts.frag +162 -0
- package/tm/web/backend/test/unit/srv/api/api.test.ts.frag +225 -0
- package/tm/web/backend/test/unit/srv/auth/apikey.test.ts.frag +152 -0
- package/tm/web/backend/test/unit/srv/auth/auth.setup.ts.frag +40 -0
- package/tm/web/backend/test/unit/srv/auth/session.test.ts.frag +189 -0
- package/tm/web/backend/test/unit/srv/ent/ent.setup.ts.frag +53 -0
- package/tm/web/backend/test/unit/srv/ent/proxy.test.ts.frag +109 -0
- package/tm/web/docs/explanation/web-architecture.md.frag +50 -0
- package/tm/web/docs/how-to/add-a-custom-entity-view.md.frag +53 -0
- package/tm/web/docs/how-to/change-the-theme.md.frag +63 -0
- package/tm/web/docs/how-to/customise-the-web-app.md.frag +68 -0
- package/tm/web/docs/how-to/use-the-api.md.frag +53 -0
- package/tm/web/docs/reference/web-app.md.frag +98 -0
- package/tm/web/web/AGENTS.md.frag +52 -0
- package/tm/web/web/e2e/smoke.spec.js.frag +13 -11
- package/tm/web/web/index.html.frag +1 -0
- package/tm/web/web/package.json.frag +1 -0
- package/tm/web/web/playwright.config.js.frag +2 -1
- package/tm/web/web/src/api.js.frag +92 -0
- package/tm/web/web/src/bus.js.frag +14 -8
- package/tm/web/web/src/cmp/admin.js.frag +293 -212
- package/tm/web/web/src/cmp/admin.md.frag +51 -0
- package/tm/web/web/src/cmp/app.js.frag +12 -15
- package/tm/web/web/src/cmp/app.md.frag +28 -0
- package/tm/web/web/src/cmp/auth.js.frag +69 -41
- package/tm/web/web/src/cmp/auth.md.frag +36 -0
- package/tm/web/web/src/cmp/public.js.frag +58 -0
- package/tm/web/web/src/cmp/public.md.frag +26 -0
- package/tm/web/web/src/cmp/settings.js.frag +178 -0
- package/tm/web/web/src/cmp/settings.md.frag +30 -0
- package/tm/web/web/src/cmp/shell.js.frag +231 -0
- package/tm/web/web/src/cmp/shell.md.frag +49 -0
- package/tm/web/web/src/cmp/view/custom-view.js.frag +47 -0
- package/tm/web/web/src/cmp/view/custom-view.md.frag +31 -0
- package/tm/web/web/src/custom.css.frag +8 -0
- package/tm/web/web/src/customise.js.frag +30 -0
- package/tm/web/web/src/hooks.js.frag +79 -0
- package/tm/web/web/src/main.js.frag +17 -4
- package/tm/web/web/src/model.js.frag +162 -0
- package/tm/web/web/src/style.css.frag +135 -21
- package/tm/web/web/src/theme.js.frag +82 -0
package/dist/env/web/web_gen.js
CHANGED
|
@@ -26,20 +26,52 @@ const generate_1 = require("../lambda/generate");
|
|
|
26
26
|
// the project root, which holds backend/ and web/). $$slots$$ in the
|
|
27
27
|
// output path are rendered too.
|
|
28
28
|
const WEB_FILES = [
|
|
29
|
-
// Frontend SPA (web/)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{ frag: 'web/
|
|
34
|
-
{ frag: 'web/
|
|
35
|
-
{ frag: 'web/
|
|
36
|
-
{ frag: 'web/src/
|
|
37
|
-
{ frag: 'web/src/
|
|
38
|
-
{ frag: 'web/src/
|
|
39
|
-
{ frag: 'web/
|
|
29
|
+
// Frontend SPA (web/) — a model-driven enterprise app: public site + login,
|
|
30
|
+
// an app shell (nav / project selector / user menu / collapsible entity
|
|
31
|
+
// menu), and generic CRUD with relationship navigation. All runtime
|
|
32
|
+
// model-driven (model.js reads /model.json), so it fits any entity graph.
|
|
33
|
+
{ frag: 'web/package.json.frag', out: '$$fe$$/package.json' },
|
|
34
|
+
{ frag: 'web/index.html.frag', out: '$$fe$$/index.html' },
|
|
35
|
+
{ frag: 'web/playwright.config.js.frag', out: '$$fe$$/playwright.config.js' },
|
|
36
|
+
{ frag: 'web/src/main.js.frag', out: '$$fe$$/src/main.js' },
|
|
37
|
+
{ frag: 'web/src/bus.js.frag', out: '$$fe$$/src/bus.js' },
|
|
38
|
+
{ frag: 'web/src/model.js.frag', out: '$$fe$$/src/model.js' },
|
|
39
|
+
{ frag: 'web/src/api.js.frag', out: '$$fe$$/src/api.js' },
|
|
40
|
+
{ frag: 'web/src/hooks.js.frag', out: '$$fe$$/src/hooks.js' },
|
|
41
|
+
{ frag: 'web/src/theme.js.frag', out: '$$fe$$/src/theme.js' },
|
|
42
|
+
{ frag: 'web/src/customise.js.frag', out: '$$fe$$/src/customise.js' },
|
|
43
|
+
{ frag: 'web/src/custom.css.frag', out: '$$fe$$/src/custom.css' },
|
|
44
|
+
{ frag: 'web/src/style.css.frag', out: '$$fe$$/src/style.css' },
|
|
45
|
+
{ frag: 'web/src/cmp/app.js.frag', out: '$$fe$$/src/cmp/app.js' },
|
|
46
|
+
{ frag: 'web/src/cmp/public.js.frag', out: '$$fe$$/src/cmp/public.js' },
|
|
47
|
+
{ frag: 'web/src/cmp/auth.js.frag', out: '$$fe$$/src/cmp/auth.js' },
|
|
48
|
+
{ frag: 'web/src/cmp/shell.js.frag', out: '$$fe$$/src/cmp/shell.js' },
|
|
49
|
+
{ frag: 'web/src/cmp/admin.js.frag', out: '$$fe$$/src/cmp/admin.js' },
|
|
50
|
+
{ frag: 'web/src/cmp/settings.js.frag', out: '$$fe$$/src/cmp/settings.js' },
|
|
51
|
+
// Per-component doc sidecars (mermaid structure + message diagrams),
|
|
52
|
+
// create-once next to each component.
|
|
53
|
+
{ frag: 'web/src/cmp/app.md.frag', out: '$$fe$$/src/cmp/app.md' },
|
|
54
|
+
{ frag: 'web/src/cmp/public.md.frag', out: '$$fe$$/src/cmp/public.md' },
|
|
55
|
+
{ frag: 'web/src/cmp/auth.md.frag', out: '$$fe$$/src/cmp/auth.md' },
|
|
56
|
+
{ frag: 'web/src/cmp/shell.md.frag', out: '$$fe$$/src/cmp/shell.md' },
|
|
57
|
+
{ frag: 'web/src/cmp/admin.md.frag', out: '$$fe$$/src/cmp/admin.md' },
|
|
58
|
+
{ frag: 'web/src/cmp/settings.md.frag', out: '$$fe$$/src/cmp/settings.md' },
|
|
59
|
+
{ frag: 'web/e2e/smoke.spec.js.frag', out: '$$fe$$/e2e/smoke.spec.js' },
|
|
60
|
+
{ frag: 'web/AGENTS.md.frag', out: '$$fe$$/AGENTS.md' },
|
|
61
|
+
// Project docs (Diátaxis) for the web app, alongside the scaffold's
|
|
62
|
+
// backend docs (@voxgig/create-system DocsPart). Create-once like all
|
|
63
|
+
// WEB_FILES: the project owns its docs after generation.
|
|
64
|
+
{ frag: 'docs/how-to/customise-the-web-app.md.frag', out: 'docs/how-to/customise-the-web-app.md' },
|
|
65
|
+
{ frag: 'docs/how-to/use-the-api.md.frag', out: 'docs/how-to/use-the-api.md' },
|
|
66
|
+
{ frag: 'docs/how-to/change-the-theme.md.frag', out: 'docs/how-to/change-the-theme.md' },
|
|
67
|
+
{ frag: 'docs/how-to/add-a-custom-entity-view.md.frag', out: 'docs/how-to/add-a-custom-entity-view.md' },
|
|
68
|
+
{ frag: 'docs/reference/web-app.md.frag', out: 'docs/reference/web-app.md' },
|
|
69
|
+
{ frag: 'docs/explanation/web-architecture.md.frag', out: 'docs/explanation/web-architecture.md' },
|
|
40
70
|
// Backend web runner + auth service (backend/src/...)
|
|
41
71
|
{ frag: 'backend/env/web/web.ts.frag', out: 'backend/src/env/web/web.ts' },
|
|
72
|
+
{ frag: 'backend/env/shared/seed.ts.frag', out: 'backend/src/env/shared/seed.ts' },
|
|
42
73
|
{ frag: 'backend/srv/auth/auth-srv.ts.frag', out: 'backend/src/srv/auth/auth-srv.ts' },
|
|
74
|
+
{ frag: 'backend/srv/auth/user_util.ts.frag', out: 'backend/src/srv/auth/user_util.ts' },
|
|
43
75
|
{ frag: 'backend/srv/auth/get_info.ts.frag', out: 'backend/src/srv/auth/get_info.ts' },
|
|
44
76
|
{ frag: 'backend/srv/auth/signin_user.ts.frag', out: 'backend/src/srv/auth/signin_user.ts' },
|
|
45
77
|
{ frag: 'backend/srv/auth/signout_user.ts.frag', out: 'backend/src/srv/auth/signout_user.ts' },
|
|
@@ -47,6 +79,56 @@ const WEB_FILES = [
|
|
|
47
79
|
{ frag: 'backend/srv/auth/web_signin_user.ts.frag', out: 'backend/src/srv/auth/web_signin_user.ts' },
|
|
48
80
|
{ frag: 'backend/srv/auth/web_signout_user.ts.frag', out: 'backend/src/srv/auth/web_signout_user.ts' },
|
|
49
81
|
{ frag: 'backend/srv/auth/web_load_auth.ts.frag', out: 'backend/src/srv/auth/web_load_auth.ts' },
|
|
82
|
+
// Auth settings/security extensions.
|
|
83
|
+
{ frag: 'backend/srv/auth/change_pass.ts.frag', out: 'backend/src/srv/auth/change_pass.ts' },
|
|
84
|
+
{ frag: 'backend/srv/auth/web_change_pass.ts.frag', out: 'backend/src/srv/auth/web_change_pass.ts' },
|
|
85
|
+
{ frag: 'backend/srv/auth/update_user.ts.frag', out: 'backend/src/srv/auth/update_user.ts' },
|
|
86
|
+
{ frag: 'backend/srv/auth/web_update_user.ts.frag', out: 'backend/src/srv/auth/web_update_user.ts' },
|
|
87
|
+
{ frag: 'backend/srv/auth/remind_pass.ts.frag', out: 'backend/src/srv/auth/remind_pass.ts' },
|
|
88
|
+
{ frag: 'backend/srv/auth/web_remind_pass.ts.frag', out: 'backend/src/srv/auth/web_remind_pass.ts' },
|
|
89
|
+
// API access keys (Settings & security + REST API auth).
|
|
90
|
+
{ frag: 'backend/srv/auth/apikey_util.ts.frag', out: 'backend/src/srv/auth/apikey_util.ts' },
|
|
91
|
+
{ frag: 'backend/srv/auth/create_apikey.ts.frag', out: 'backend/src/srv/auth/create_apikey.ts' },
|
|
92
|
+
{ frag: 'backend/srv/auth/list_apikey.ts.frag', out: 'backend/src/srv/auth/list_apikey.ts' },
|
|
93
|
+
{ frag: 'backend/srv/auth/revoke_apikey.ts.frag', out: 'backend/src/srv/auth/revoke_apikey.ts' },
|
|
94
|
+
{ frag: 'backend/srv/auth/web_create_apikey.ts.frag', out: 'backend/src/srv/auth/web_create_apikey.ts' },
|
|
95
|
+
{ frag: 'backend/srv/auth/web_list_apikey.ts.frag', out: 'backend/src/srv/auth/web_list_apikey.ts' },
|
|
96
|
+
{ frag: 'backend/srv/auth/web_revoke_apikey.ts.frag', out: 'backend/src/srv/auth/web_revoke_apikey.ts' },
|
|
97
|
+
// The strict-JSON REST API (model main.api): api service + HTTP mapping.
|
|
98
|
+
// valid_gen.ts (request validation shapes) is REGENERATED by api_gen.
|
|
99
|
+
{ frag: 'backend/srv/api/api-srv.ts.frag', out: 'backend/src/srv/api/api-srv.ts' },
|
|
100
|
+
{ frag: 'backend/srv/api/get_info.ts.frag', out: 'backend/src/srv/api/get_info.ts' },
|
|
101
|
+
{ frag: 'backend/srv/api/on_ent.ts.frag', out: 'backend/src/srv/api/on_ent.ts' },
|
|
102
|
+
{ frag: 'backend/srv/api/expose.ts.frag', out: 'backend/src/srv/api/expose.ts' },
|
|
103
|
+
{ frag: 'backend/env/web/api.ts.frag', out: 'backend/src/env/web/api.ts' },
|
|
104
|
+
// Unit tests for what EnvWeb generates (node:test). They are
|
|
105
|
+
// MODEL-DRIVEN - the entity under test and its payloads come from the
|
|
106
|
+
// model - so they hold for any entity graph, and they stand in for
|
|
107
|
+
// other services with mock messages rather than booting them.
|
|
108
|
+
{ frag: 'backend/test/unit/srv/api/api.setup.ts.frag', out: 'backend/test/unit/srv/api/api.setup.ts' },
|
|
109
|
+
{ frag: 'backend/test/unit/srv/api/api.test.ts.frag', out: 'backend/test/unit/srv/api/api.test.ts' },
|
|
110
|
+
{ frag: 'backend/test/unit/srv/auth/auth.setup.ts.frag', out: 'backend/test/unit/srv/auth/auth.setup.ts' },
|
|
111
|
+
{ frag: 'backend/test/unit/srv/auth/apikey.test.ts.frag', out: 'backend/test/unit/srv/auth/apikey.test.ts' },
|
|
112
|
+
{ frag: 'backend/test/unit/srv/auth/session.test.ts.frag', out: 'backend/test/unit/srv/auth/session.test.ts' },
|
|
113
|
+
{ frag: 'backend/test/unit/env/web/api-router.test.ts.frag', out: 'backend/test/unit/env/web/api-router.test.ts' },
|
|
114
|
+
{ frag: 'backend/test/unit/env/web/surface.test.ts.frag', out: 'backend/test/unit/env/web/surface.test.ts' },
|
|
115
|
+
{ frag: 'backend/test/unit/srv/ent/ent.setup.ts.frag', out: 'backend/test/unit/srv/ent/ent.setup.ts' },
|
|
116
|
+
{ frag: 'backend/test/unit/srv/ent/proxy.test.ts.frag', out: 'backend/test/unit/srv/ent/proxy.test.ts' },
|
|
117
|
+
// Generic entity service (parameterised CRUD, membership-scoped).
|
|
118
|
+
{ frag: 'backend/srv/ent/ent-srv.ts.frag', out: 'backend/src/srv/ent/ent-srv.ts' },
|
|
119
|
+
{ frag: 'backend/srv/ent/access.ts.frag', out: 'backend/src/srv/ent/access.ts' },
|
|
120
|
+
{ frag: 'backend/srv/ent/get_info.ts.frag', out: 'backend/src/srv/ent/get_info.ts' },
|
|
121
|
+
{ frag: 'backend/srv/ent/cmd_list.ts.frag', out: 'backend/src/srv/ent/cmd_list.ts' },
|
|
122
|
+
{ frag: 'backend/srv/ent/cmd_load.ts.frag', out: 'backend/src/srv/ent/cmd_load.ts' },
|
|
123
|
+
{ frag: 'backend/srv/ent/cmd_save.ts.frag', out: 'backend/src/srv/ent/cmd_save.ts' },
|
|
124
|
+
{ frag: 'backend/srv/ent/cmd_remove.ts.frag', out: 'backend/src/srv/ent/cmd_remove.ts' },
|
|
125
|
+
// Browser proxies: aim:web,on:ent,cmd:* -> aim:ent,cmd:* (the gateway
|
|
126
|
+
// accepts only aim:web, so the entity service is never posted directly
|
|
127
|
+
// from a browser).
|
|
128
|
+
{ frag: 'backend/srv/ent/web_cmd_list.ts.frag', out: 'backend/src/srv/ent/web_cmd_list.ts' },
|
|
129
|
+
{ frag: 'backend/srv/ent/web_cmd_load.ts.frag', out: 'backend/src/srv/ent/web_cmd_load.ts' },
|
|
130
|
+
{ frag: 'backend/srv/ent/web_cmd_save.ts.frag', out: 'backend/src/srv/ent/web_cmd_save.ts' },
|
|
131
|
+
{ frag: 'backend/srv/ent/web_cmd_remove.ts.frag', out: 'backend/src/srv/ent/web_cmd_remove.ts' },
|
|
50
132
|
];
|
|
51
133
|
exports.WEB_FILES = WEB_FILES;
|
|
52
134
|
// Default seeded users (local testing only), rendered into the runner.
|
|
@@ -55,12 +137,39 @@ const DEFAULT_USERS = [
|
|
|
55
137
|
{ name: 'Bob Example', email: 'bob@example.com', password: 'bob-pass-01' },
|
|
56
138
|
];
|
|
57
139
|
exports.DEFAULT_USERS = DEFAULT_USERS;
|
|
140
|
+
// The frontend folder name, from the model env entry (`web: { dir: ... }`).
|
|
141
|
+
// Defaults to 'web', so a project that says nothing keeps the layout it
|
|
142
|
+
// already has. Everything that names the folder - the WEB_FILES output
|
|
143
|
+
// paths, views.js, theme.css, and the static path the backend runner
|
|
144
|
+
// serves - goes through this one value, via the $$fe$$ slot.
|
|
145
|
+
//
|
|
146
|
+
// Only the FOLDER moves. The `aim:web` message namespace is unrelated and
|
|
147
|
+
// must never follow it: the gateway allow-list is the literal
|
|
148
|
+
// { 'aim:web': true }, and backend/src/env/web/ is the backend runner, not
|
|
149
|
+
// the SPA.
|
|
150
|
+
const DEFAULT_FE = 'web';
|
|
151
|
+
// Absent means 'web'. ANYTHING ELSE provided is validated rather than
|
|
152
|
+
// silently defaulted - `dir: ''` is a mistake, not a request for 'web', and
|
|
153
|
+
// defaulting it would generate the whole SPA into the project root.
|
|
154
|
+
function feDir(envdef) {
|
|
155
|
+
const dir = null == envdef ? undefined : envdef.dir;
|
|
156
|
+
if (null == dir) {
|
|
157
|
+
return DEFAULT_FE;
|
|
158
|
+
}
|
|
159
|
+
if ('string' !== typeof dir || '' === dir || dir.split('/').includes('..') ||
|
|
160
|
+
path_1.default.isAbsolute(dir)) {
|
|
161
|
+
throw new Error('EnvWeb: env web `dir` must be a non-empty relative folder name, got: ' +
|
|
162
|
+
JSON.stringify(dir));
|
|
163
|
+
}
|
|
164
|
+
return dir;
|
|
165
|
+
}
|
|
58
166
|
// web_gen(model, spec): generate the frontend + backend web pieces into
|
|
59
167
|
// the project. spec.root is the project root (parent of backend/); files
|
|
60
168
|
// that already exist are left untouched (create-once).
|
|
61
169
|
const web_gen = async (model, spec) => {
|
|
62
170
|
const core = (0, conf_1.CoreConfShape)(model.main.conf.core);
|
|
63
171
|
const envdef = spec.env || {};
|
|
172
|
+
const fe = feDir(envdef);
|
|
64
173
|
const users = envdef.users || DEFAULT_USERS;
|
|
65
174
|
const seed = users[0] || DEFAULT_USERS[0];
|
|
66
175
|
const port = envdef.port ||
|
|
@@ -68,6 +177,7 @@ const web_gen = async (model, spec) => {
|
|
|
68
177
|
const slots = {
|
|
69
178
|
name: core.name,
|
|
70
179
|
Name: (0, util_1.camelify)(core.name),
|
|
180
|
+
fe,
|
|
71
181
|
users: JSON.stringify(users, null, 2),
|
|
72
182
|
e2eport: envdef.e2eport || (port + 10),
|
|
73
183
|
seedEmail: seed.email,
|
|
@@ -87,6 +197,82 @@ const web_gen = async (model, spec) => {
|
|
|
87
197
|
fs_1.default.writeFileSync(dest, content);
|
|
88
198
|
created.push(outrel);
|
|
89
199
|
}
|
|
200
|
+
// Custom entity views: for every entity declaring `ux:{ view: 'custom' }`,
|
|
201
|
+
// generate a hand-coded view component (CREATE-ONCE, so edits survive) plus
|
|
202
|
+
// an always-regenerated index (views.js) that imports them.
|
|
203
|
+
const entzones = model.main.ent || {};
|
|
204
|
+
const customs = [];
|
|
205
|
+
for (const zone of Object.keys(entzones)) {
|
|
206
|
+
for (const name of Object.keys(entzones[zone])) {
|
|
207
|
+
const def = entzones[zone][name];
|
|
208
|
+
if (def && def.ux && 'custom' === def.ux.view) {
|
|
209
|
+
customs.push({ zone, name, canon: zone + '/' + name });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
for (const c of customs) {
|
|
214
|
+
const vslots = Object.assign({}, slots, {
|
|
215
|
+
canon: c.canon,
|
|
216
|
+
zone: c.zone,
|
|
217
|
+
name: c.name,
|
|
218
|
+
tag: 'vg-view-' + c.zone + '-' + c.name,
|
|
219
|
+
className: 'VgView' + (0, util_1.camelify)(c.zone) + (0, util_1.camelify)(c.name),
|
|
220
|
+
Label: (0, util_1.camelify)(c.name),
|
|
221
|
+
});
|
|
222
|
+
// The component starter plus its doc sidecar, both create-once.
|
|
223
|
+
for (const ext of ['js', 'md']) {
|
|
224
|
+
const outrel = fe + '/src/cmp/view/' + c.zone + '_' + c.name + '.' + ext;
|
|
225
|
+
const dest = path_1.default.join(spec.root, outrel);
|
|
226
|
+
if (fs_1.default.existsSync(dest) && !spec.force) {
|
|
227
|
+
skipped.push(outrel);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const content = (0, generate_1.renderFragment)((0, generate_1.loadFragment)('web/src/cmp/view/custom-view.' + ext + '.frag', spec, 'web'), vslots);
|
|
231
|
+
fs_1.default.mkdirSync(path_1.default.dirname(dest), { recursive: true });
|
|
232
|
+
fs_1.default.writeFileSync(dest, content);
|
|
233
|
+
created.push(outrel);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// views.js — the generated index of custom views. Regenerated whenever the
|
|
237
|
+
// set of custom views changes; a no-op run leaves it (and `created`) alone.
|
|
238
|
+
const viewsRel = fe + '/src/views.js';
|
|
239
|
+
const viewsPath = path_1.default.join(spec.root, viewsRel);
|
|
240
|
+
const viewsBody = '// AUTO-GENERATED: imports every custom entity view (model entities declaring\n' +
|
|
241
|
+
'// ux:{view:\'custom\'}). Regenerated from the model — do not edit. The view\n' +
|
|
242
|
+
'// files it imports are hand-coded and create-once.\n\n' +
|
|
243
|
+
customs.map((c) => `import './cmp/view/${c.zone}_${c.name}.js'`).join('\n') + '\n';
|
|
244
|
+
const existingViews = fs_1.default.existsSync(viewsPath) ? fs_1.default.readFileSync(viewsPath, 'utf8') : null;
|
|
245
|
+
if (existingViews !== viewsBody) {
|
|
246
|
+
fs_1.default.mkdirSync(path_1.default.join(spec.root, fe, 'src'), { recursive: true });
|
|
247
|
+
fs_1.default.writeFileSync(viewsPath, viewsBody);
|
|
248
|
+
created.push(viewsRel);
|
|
249
|
+
}
|
|
250
|
+
// theme.css — the design theme from the model (main.theme). Each mode's
|
|
251
|
+
// tokens become CSS variables under :root[data-theme-mode="<mode>"], with the
|
|
252
|
+
// default mode also on plain :root. Regenerated from the model; override
|
|
253
|
+
// tokens in custom.css and add modes via the theme:modes hook.
|
|
254
|
+
const theme = model.main && model.main.theme;
|
|
255
|
+
if (theme && theme.modes && Object.keys(theme.modes).length) {
|
|
256
|
+
const defMode = theme.mode || Object.keys(theme.modes)[0];
|
|
257
|
+
const tokensCss = (tokens) => Object.keys(tokens).sort()
|
|
258
|
+
.map((k) => ` --vg-${k}: ${tokens[k]};`).join('\n');
|
|
259
|
+
const blocks = [':root,\n:root[data-theme-mode="' + defMode + '"] {\n' + tokensCss(theme.modes[defMode]) + '\n}'];
|
|
260
|
+
for (const mode of Object.keys(theme.modes)) {
|
|
261
|
+
if (mode !== defMode) {
|
|
262
|
+
blocks.push(':root[data-theme-mode="' + mode + '"] {\n' + tokensCss(theme.modes[mode]) + '\n}');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const themeBody = '/* AUTO-GENERATED from the model theme (main.theme) — do not edit.\n' +
|
|
266
|
+
' Override tokens in custom.css; add modes via the theme:modes hook. */\n\n' +
|
|
267
|
+
blocks.join('\n\n') + '\n';
|
|
268
|
+
const themePath = path_1.default.join(spec.root, fe, 'src/theme.css');
|
|
269
|
+
const existingTheme = fs_1.default.existsSync(themePath) ? fs_1.default.readFileSync(themePath, 'utf8') : null;
|
|
270
|
+
if (existingTheme !== themeBody) {
|
|
271
|
+
fs_1.default.mkdirSync(path_1.default.join(spec.root, fe, 'src'), { recursive: true });
|
|
272
|
+
fs_1.default.writeFileSync(themePath, themeBody);
|
|
273
|
+
created.push(fe + '/src/theme.css');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
90
276
|
return { created: created.sort(), skipped: skipped.sort() };
|
|
91
277
|
};
|
|
92
278
|
exports.web_gen = web_gen;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web_gen.js","sourceRoot":"","sources":["../../../env/web/web_gen.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"web_gen.js","sourceRoot":"","sources":["../../../env/web/web_gen.ts"],"names":[],"mappings":"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"}
|
package/dist/shape/ent.js
CHANGED
|
@@ -3,7 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.EntShape = void 0;
|
|
4
4
|
const gubu_1 = require("gubu");
|
|
5
5
|
const { Open, Skip } = gubu_1.Gubu;
|
|
6
|
-
|
|
6
|
+
// Open: entities may carry extra attributes beyond the deployment-relevant
|
|
7
|
+
// ones below (e.g. `ux` for frontend view hints), which this generator ignores.
|
|
8
|
+
const EntShape = (0, gubu_1.Gubu)(Open({
|
|
7
9
|
id: {
|
|
8
10
|
field: 'id'
|
|
9
11
|
},
|
|
@@ -23,6 +25,6 @@ const EntShape = (0, gubu_1.Gubu)({
|
|
|
23
25
|
active: false
|
|
24
26
|
}),
|
|
25
27
|
custom: Skip(String),
|
|
26
|
-
}, { prefix: 'Entity' });
|
|
28
|
+
}), { prefix: 'Entity' });
|
|
27
29
|
exports.EntShape = EntShape;
|
|
28
30
|
//# sourceMappingURL=ent.js.map
|
package/dist/shape/ent.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ent.js","sourceRoot":"","sources":["../../shape/ent.ts"],"names":[],"mappings":";;;AACA,+BAA2B;AAE3B,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,WAAI,CAAA;AAG3B,MAAM,QAAQ,GAAG,IAAA,WAAI,EAAC;
|
|
1
|
+
{"version":3,"file":"ent.js","sourceRoot":"","sources":["../../shape/ent.ts"],"names":[],"mappings":";;;AACA,+BAA2B;AAE3B,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,WAAI,CAAA;AAG3B,2EAA2E;AAC3E,gFAAgF;AAChF,MAAM,QAAQ,GAAG,IAAA,WAAI,EAAC,IAAI,CAAC;IACzB,EAAE,EAAE;QACF,KAAK,EAAE,IAAI;KACZ;IACD,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,EACrB,CAAC;IACF,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;IACf,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,EACrB,CAAC;IACF,QAAQ,EAAE,IAAI,CAAC;QACb,IAAI,EAAE,EAAE;KACT,CAAC;IACF,MAAM,EAAE,IAAI,CAAC;QACX,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,EAAE;QACV,MAAM,EAAE,EAAE;KACX,CAAC;IACF,KAAK,EAAE,IAAI,CAAC;QACV,MAAM,EAAE,KAAK;KACd,CAAC;IACF,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;CACrB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;AAIvB,4BAAQ"}
|
package/env/web/web_gen.ts
CHANGED
|
@@ -27,21 +27,55 @@ import { loadFragment, renderFragment } from '../lambda/generate'
|
|
|
27
27
|
// the project root, which holds backend/ and web/). $$slots$$ in the
|
|
28
28
|
// output path are rendered too.
|
|
29
29
|
const WEB_FILES: { frag: string, out: string }[] = [
|
|
30
|
-
// Frontend SPA (web/)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{ frag: 'web/
|
|
35
|
-
{ frag: 'web/
|
|
36
|
-
{ frag: 'web/
|
|
37
|
-
{ frag: 'web/src/
|
|
38
|
-
{ frag: 'web/src/
|
|
39
|
-
{ frag: 'web/src/
|
|
40
|
-
{ frag: 'web/
|
|
30
|
+
// Frontend SPA (web/) — a model-driven enterprise app: public site + login,
|
|
31
|
+
// an app shell (nav / project selector / user menu / collapsible entity
|
|
32
|
+
// menu), and generic CRUD with relationship navigation. All runtime
|
|
33
|
+
// model-driven (model.js reads /model.json), so it fits any entity graph.
|
|
34
|
+
{ frag: 'web/package.json.frag', out: '$$fe$$/package.json' },
|
|
35
|
+
{ frag: 'web/index.html.frag', out: '$$fe$$/index.html' },
|
|
36
|
+
{ frag: 'web/playwright.config.js.frag', out: '$$fe$$/playwright.config.js' },
|
|
37
|
+
{ frag: 'web/src/main.js.frag', out: '$$fe$$/src/main.js' },
|
|
38
|
+
{ frag: 'web/src/bus.js.frag', out: '$$fe$$/src/bus.js' },
|
|
39
|
+
{ frag: 'web/src/model.js.frag', out: '$$fe$$/src/model.js' },
|
|
40
|
+
{ frag: 'web/src/api.js.frag', out: '$$fe$$/src/api.js' },
|
|
41
|
+
{ frag: 'web/src/hooks.js.frag', out: '$$fe$$/src/hooks.js' },
|
|
42
|
+
{ frag: 'web/src/theme.js.frag', out: '$$fe$$/src/theme.js' },
|
|
43
|
+
{ frag: 'web/src/customise.js.frag', out: '$$fe$$/src/customise.js' },
|
|
44
|
+
{ frag: 'web/src/custom.css.frag', out: '$$fe$$/src/custom.css' },
|
|
45
|
+
{ frag: 'web/src/style.css.frag', out: '$$fe$$/src/style.css' },
|
|
46
|
+
{ frag: 'web/src/cmp/app.js.frag', out: '$$fe$$/src/cmp/app.js' },
|
|
47
|
+
{ frag: 'web/src/cmp/public.js.frag', out: '$$fe$$/src/cmp/public.js' },
|
|
48
|
+
{ frag: 'web/src/cmp/auth.js.frag', out: '$$fe$$/src/cmp/auth.js' },
|
|
49
|
+
{ frag: 'web/src/cmp/shell.js.frag', out: '$$fe$$/src/cmp/shell.js' },
|
|
50
|
+
{ frag: 'web/src/cmp/admin.js.frag', out: '$$fe$$/src/cmp/admin.js' },
|
|
51
|
+
{ frag: 'web/src/cmp/settings.js.frag', out: '$$fe$$/src/cmp/settings.js' },
|
|
52
|
+
|
|
53
|
+
// Per-component doc sidecars (mermaid structure + message diagrams),
|
|
54
|
+
// create-once next to each component.
|
|
55
|
+
{ frag: 'web/src/cmp/app.md.frag', out: '$$fe$$/src/cmp/app.md' },
|
|
56
|
+
{ frag: 'web/src/cmp/public.md.frag', out: '$$fe$$/src/cmp/public.md' },
|
|
57
|
+
{ frag: 'web/src/cmp/auth.md.frag', out: '$$fe$$/src/cmp/auth.md' },
|
|
58
|
+
{ frag: 'web/src/cmp/shell.md.frag', out: '$$fe$$/src/cmp/shell.md' },
|
|
59
|
+
{ frag: 'web/src/cmp/admin.md.frag', out: '$$fe$$/src/cmp/admin.md' },
|
|
60
|
+
{ frag: 'web/src/cmp/settings.md.frag', out: '$$fe$$/src/cmp/settings.md' },
|
|
61
|
+
{ frag: 'web/e2e/smoke.spec.js.frag', out: '$$fe$$/e2e/smoke.spec.js' },
|
|
62
|
+
{ frag: 'web/AGENTS.md.frag', out: '$$fe$$/AGENTS.md' },
|
|
63
|
+
|
|
64
|
+
// Project docs (Diátaxis) for the web app, alongside the scaffold's
|
|
65
|
+
// backend docs (@voxgig/create-system DocsPart). Create-once like all
|
|
66
|
+
// WEB_FILES: the project owns its docs after generation.
|
|
67
|
+
{ frag: 'docs/how-to/customise-the-web-app.md.frag', out: 'docs/how-to/customise-the-web-app.md' },
|
|
68
|
+
{ frag: 'docs/how-to/use-the-api.md.frag', out: 'docs/how-to/use-the-api.md' },
|
|
69
|
+
{ frag: 'docs/how-to/change-the-theme.md.frag', out: 'docs/how-to/change-the-theme.md' },
|
|
70
|
+
{ frag: 'docs/how-to/add-a-custom-entity-view.md.frag', out: 'docs/how-to/add-a-custom-entity-view.md' },
|
|
71
|
+
{ frag: 'docs/reference/web-app.md.frag', out: 'docs/reference/web-app.md' },
|
|
72
|
+
{ frag: 'docs/explanation/web-architecture.md.frag', out: 'docs/explanation/web-architecture.md' },
|
|
41
73
|
|
|
42
74
|
// Backend web runner + auth service (backend/src/...)
|
|
43
75
|
{ frag: 'backend/env/web/web.ts.frag', out: 'backend/src/env/web/web.ts' },
|
|
76
|
+
{ frag: 'backend/env/shared/seed.ts.frag', out: 'backend/src/env/shared/seed.ts' },
|
|
44
77
|
{ frag: 'backend/srv/auth/auth-srv.ts.frag', out: 'backend/src/srv/auth/auth-srv.ts' },
|
|
78
|
+
{ frag: 'backend/srv/auth/user_util.ts.frag', out: 'backend/src/srv/auth/user_util.ts' },
|
|
45
79
|
{ frag: 'backend/srv/auth/get_info.ts.frag', out: 'backend/src/srv/auth/get_info.ts' },
|
|
46
80
|
{ frag: 'backend/srv/auth/signin_user.ts.frag', out: 'backend/src/srv/auth/signin_user.ts' },
|
|
47
81
|
{ frag: 'backend/srv/auth/signout_user.ts.frag', out: 'backend/src/srv/auth/signout_user.ts' },
|
|
@@ -49,6 +83,62 @@ const WEB_FILES: { frag: string, out: string }[] = [
|
|
|
49
83
|
{ frag: 'backend/srv/auth/web_signin_user.ts.frag', out: 'backend/src/srv/auth/web_signin_user.ts' },
|
|
50
84
|
{ frag: 'backend/srv/auth/web_signout_user.ts.frag', out: 'backend/src/srv/auth/web_signout_user.ts' },
|
|
51
85
|
{ frag: 'backend/srv/auth/web_load_auth.ts.frag', out: 'backend/src/srv/auth/web_load_auth.ts' },
|
|
86
|
+
|
|
87
|
+
// Auth settings/security extensions.
|
|
88
|
+
{ frag: 'backend/srv/auth/change_pass.ts.frag', out: 'backend/src/srv/auth/change_pass.ts' },
|
|
89
|
+
{ frag: 'backend/srv/auth/web_change_pass.ts.frag', out: 'backend/src/srv/auth/web_change_pass.ts' },
|
|
90
|
+
{ frag: 'backend/srv/auth/update_user.ts.frag', out: 'backend/src/srv/auth/update_user.ts' },
|
|
91
|
+
{ frag: 'backend/srv/auth/web_update_user.ts.frag', out: 'backend/src/srv/auth/web_update_user.ts' },
|
|
92
|
+
{ frag: 'backend/srv/auth/remind_pass.ts.frag', out: 'backend/src/srv/auth/remind_pass.ts' },
|
|
93
|
+
{ frag: 'backend/srv/auth/web_remind_pass.ts.frag', out: 'backend/src/srv/auth/web_remind_pass.ts' },
|
|
94
|
+
|
|
95
|
+
// API access keys (Settings & security + REST API auth).
|
|
96
|
+
{ frag: 'backend/srv/auth/apikey_util.ts.frag', out: 'backend/src/srv/auth/apikey_util.ts' },
|
|
97
|
+
{ frag: 'backend/srv/auth/create_apikey.ts.frag', out: 'backend/src/srv/auth/create_apikey.ts' },
|
|
98
|
+
{ frag: 'backend/srv/auth/list_apikey.ts.frag', out: 'backend/src/srv/auth/list_apikey.ts' },
|
|
99
|
+
{ frag: 'backend/srv/auth/revoke_apikey.ts.frag', out: 'backend/src/srv/auth/revoke_apikey.ts' },
|
|
100
|
+
{ frag: 'backend/srv/auth/web_create_apikey.ts.frag', out: 'backend/src/srv/auth/web_create_apikey.ts' },
|
|
101
|
+
{ frag: 'backend/srv/auth/web_list_apikey.ts.frag', out: 'backend/src/srv/auth/web_list_apikey.ts' },
|
|
102
|
+
{ frag: 'backend/srv/auth/web_revoke_apikey.ts.frag', out: 'backend/src/srv/auth/web_revoke_apikey.ts' },
|
|
103
|
+
|
|
104
|
+
// The strict-JSON REST API (model main.api): api service + HTTP mapping.
|
|
105
|
+
// valid_gen.ts (request validation shapes) is REGENERATED by api_gen.
|
|
106
|
+
{ frag: 'backend/srv/api/api-srv.ts.frag', out: 'backend/src/srv/api/api-srv.ts' },
|
|
107
|
+
{ frag: 'backend/srv/api/get_info.ts.frag', out: 'backend/src/srv/api/get_info.ts' },
|
|
108
|
+
{ frag: 'backend/srv/api/on_ent.ts.frag', out: 'backend/src/srv/api/on_ent.ts' },
|
|
109
|
+
{ frag: 'backend/srv/api/expose.ts.frag', out: 'backend/src/srv/api/expose.ts' },
|
|
110
|
+
{ frag: 'backend/env/web/api.ts.frag', out: 'backend/src/env/web/api.ts' },
|
|
111
|
+
|
|
112
|
+
// Unit tests for what EnvWeb generates (node:test). They are
|
|
113
|
+
// MODEL-DRIVEN - the entity under test and its payloads come from the
|
|
114
|
+
// model - so they hold for any entity graph, and they stand in for
|
|
115
|
+
// other services with mock messages rather than booting them.
|
|
116
|
+
{ frag: 'backend/test/unit/srv/api/api.setup.ts.frag', out: 'backend/test/unit/srv/api/api.setup.ts' },
|
|
117
|
+
{ frag: 'backend/test/unit/srv/api/api.test.ts.frag', out: 'backend/test/unit/srv/api/api.test.ts' },
|
|
118
|
+
{ frag: 'backend/test/unit/srv/auth/auth.setup.ts.frag', out: 'backend/test/unit/srv/auth/auth.setup.ts' },
|
|
119
|
+
{ frag: 'backend/test/unit/srv/auth/apikey.test.ts.frag', out: 'backend/test/unit/srv/auth/apikey.test.ts' },
|
|
120
|
+
{ frag: 'backend/test/unit/srv/auth/session.test.ts.frag', out: 'backend/test/unit/srv/auth/session.test.ts' },
|
|
121
|
+
{ frag: 'backend/test/unit/env/web/api-router.test.ts.frag', out: 'backend/test/unit/env/web/api-router.test.ts' },
|
|
122
|
+
{ frag: 'backend/test/unit/env/web/surface.test.ts.frag', out: 'backend/test/unit/env/web/surface.test.ts' },
|
|
123
|
+
{ frag: 'backend/test/unit/srv/ent/ent.setup.ts.frag', out: 'backend/test/unit/srv/ent/ent.setup.ts' },
|
|
124
|
+
{ frag: 'backend/test/unit/srv/ent/proxy.test.ts.frag', out: 'backend/test/unit/srv/ent/proxy.test.ts' },
|
|
125
|
+
|
|
126
|
+
// Generic entity service (parameterised CRUD, membership-scoped).
|
|
127
|
+
{ frag: 'backend/srv/ent/ent-srv.ts.frag', out: 'backend/src/srv/ent/ent-srv.ts' },
|
|
128
|
+
{ frag: 'backend/srv/ent/access.ts.frag', out: 'backend/src/srv/ent/access.ts' },
|
|
129
|
+
{ frag: 'backend/srv/ent/get_info.ts.frag', out: 'backend/src/srv/ent/get_info.ts' },
|
|
130
|
+
{ frag: 'backend/srv/ent/cmd_list.ts.frag', out: 'backend/src/srv/ent/cmd_list.ts' },
|
|
131
|
+
{ frag: 'backend/srv/ent/cmd_load.ts.frag', out: 'backend/src/srv/ent/cmd_load.ts' },
|
|
132
|
+
{ frag: 'backend/srv/ent/cmd_save.ts.frag', out: 'backend/src/srv/ent/cmd_save.ts' },
|
|
133
|
+
{ frag: 'backend/srv/ent/cmd_remove.ts.frag', out: 'backend/src/srv/ent/cmd_remove.ts' },
|
|
134
|
+
|
|
135
|
+
// Browser proxies: aim:web,on:ent,cmd:* -> aim:ent,cmd:* (the gateway
|
|
136
|
+
// accepts only aim:web, so the entity service is never posted directly
|
|
137
|
+
// from a browser).
|
|
138
|
+
{ frag: 'backend/srv/ent/web_cmd_list.ts.frag', out: 'backend/src/srv/ent/web_cmd_list.ts' },
|
|
139
|
+
{ frag: 'backend/srv/ent/web_cmd_load.ts.frag', out: 'backend/src/srv/ent/web_cmd_load.ts' },
|
|
140
|
+
{ frag: 'backend/srv/ent/web_cmd_save.ts.frag', out: 'backend/src/srv/ent/web_cmd_save.ts' },
|
|
141
|
+
{ frag: 'backend/srv/ent/web_cmd_remove.ts.frag', out: 'backend/src/srv/ent/web_cmd_remove.ts' },
|
|
52
142
|
]
|
|
53
143
|
|
|
54
144
|
|
|
@@ -59,19 +149,51 @@ const DEFAULT_USERS = [
|
|
|
59
149
|
]
|
|
60
150
|
|
|
61
151
|
|
|
152
|
+
// The frontend folder name, from the model env entry (`web: { dir: ... }`).
|
|
153
|
+
// Defaults to 'web', so a project that says nothing keeps the layout it
|
|
154
|
+
// already has. Everything that names the folder - the WEB_FILES output
|
|
155
|
+
// paths, views.js, theme.css, and the static path the backend runner
|
|
156
|
+
// serves - goes through this one value, via the $$fe$$ slot.
|
|
157
|
+
//
|
|
158
|
+
// Only the FOLDER moves. The `aim:web` message namespace is unrelated and
|
|
159
|
+
// must never follow it: the gateway allow-list is the literal
|
|
160
|
+
// { 'aim:web': true }, and backend/src/env/web/ is the backend runner, not
|
|
161
|
+
// the SPA.
|
|
162
|
+
const DEFAULT_FE = 'web'
|
|
163
|
+
|
|
164
|
+
// Absent means 'web'. ANYTHING ELSE provided is validated rather than
|
|
165
|
+
// silently defaulted - `dir: ''` is a mistake, not a request for 'web', and
|
|
166
|
+
// defaulting it would generate the whole SPA into the project root.
|
|
167
|
+
function feDir(envdef: any): string {
|
|
168
|
+
const dir = null == envdef ? undefined : envdef.dir
|
|
169
|
+
if (null == dir) {
|
|
170
|
+
return DEFAULT_FE
|
|
171
|
+
}
|
|
172
|
+
if ('string' !== typeof dir || '' === dir || dir.split('/').includes('..') ||
|
|
173
|
+
Path.isAbsolute(dir)) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
'EnvWeb: env web `dir` must be a non-empty relative folder name, got: ' +
|
|
176
|
+
JSON.stringify(dir))
|
|
177
|
+
}
|
|
178
|
+
return dir
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
62
182
|
// web_gen(model, spec): generate the frontend + backend web pieces into
|
|
63
183
|
// the project. spec.root is the project root (parent of backend/); files
|
|
64
184
|
// that already exist are left untouched (create-once).
|
|
65
185
|
const web_gen = async (model: any, spec: {
|
|
66
|
-
root: string // project root (holds backend/ and
|
|
186
|
+
root: string // project root (holds backend/ and the frontend folder)
|
|
67
187
|
tm?: string // project tm/web folder (fragment shadowing)
|
|
68
|
-
env?: any // the model env entry (config: port, users)
|
|
188
|
+
env?: any // the model env entry (config: port, users, dir)
|
|
69
189
|
force?: boolean // overwrite existing files (default: create-once)
|
|
70
190
|
}) => {
|
|
71
191
|
|
|
72
192
|
const core = CoreConfShape(model.main.conf.core)
|
|
73
193
|
const envdef = spec.env || {}
|
|
74
194
|
|
|
195
|
+
const fe = feDir(envdef)
|
|
196
|
+
|
|
75
197
|
const users = envdef.users || DEFAULT_USERS
|
|
76
198
|
const seed = users[0] || DEFAULT_USERS[0]
|
|
77
199
|
const port = envdef.port ||
|
|
@@ -80,6 +202,7 @@ const web_gen = async (model: any, spec: {
|
|
|
80
202
|
const slots: Record<string, any> = {
|
|
81
203
|
name: core.name,
|
|
82
204
|
Name: camelify(core.name),
|
|
205
|
+
fe,
|
|
83
206
|
users: JSON.stringify(users, null, 2),
|
|
84
207
|
e2eport: envdef.e2eport || (port + 10),
|
|
85
208
|
seedEmail: seed.email,
|
|
@@ -104,6 +227,89 @@ const web_gen = async (model: any, spec: {
|
|
|
104
227
|
created.push(outrel)
|
|
105
228
|
}
|
|
106
229
|
|
|
230
|
+
// Custom entity views: for every entity declaring `ux:{ view: 'custom' }`,
|
|
231
|
+
// generate a hand-coded view component (CREATE-ONCE, so edits survive) plus
|
|
232
|
+
// an always-regenerated index (views.js) that imports them.
|
|
233
|
+
const entzones = model.main.ent || {}
|
|
234
|
+
const customs: { zone: string, name: string, canon: string }[] = []
|
|
235
|
+
for (const zone of Object.keys(entzones)) {
|
|
236
|
+
for (const name of Object.keys(entzones[zone])) {
|
|
237
|
+
const def = entzones[zone][name]
|
|
238
|
+
if (def && def.ux && 'custom' === def.ux.view) {
|
|
239
|
+
customs.push({ zone, name, canon: zone + '/' + name })
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
for (const c of customs) {
|
|
245
|
+
const vslots = Object.assign({}, slots, {
|
|
246
|
+
canon: c.canon,
|
|
247
|
+
zone: c.zone,
|
|
248
|
+
name: c.name,
|
|
249
|
+
tag: 'vg-view-' + c.zone + '-' + c.name,
|
|
250
|
+
className: 'VgView' + camelify(c.zone) + camelify(c.name),
|
|
251
|
+
Label: camelify(c.name),
|
|
252
|
+
})
|
|
253
|
+
// The component starter plus its doc sidecar, both create-once.
|
|
254
|
+
for (const ext of ['js', 'md']) {
|
|
255
|
+
const outrel = fe + '/src/cmp/view/' + c.zone + '_' + c.name + '.' + ext
|
|
256
|
+
const dest = Path.join(spec.root, outrel)
|
|
257
|
+
if (Fs.existsSync(dest) && !spec.force) {
|
|
258
|
+
skipped.push(outrel)
|
|
259
|
+
continue
|
|
260
|
+
}
|
|
261
|
+
const content = renderFragment(
|
|
262
|
+
loadFragment('web/src/cmp/view/custom-view.' + ext + '.frag', spec, 'web'), vslots)
|
|
263
|
+
Fs.mkdirSync(Path.dirname(dest), { recursive: true })
|
|
264
|
+
Fs.writeFileSync(dest, content)
|
|
265
|
+
created.push(outrel)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// views.js — the generated index of custom views. Regenerated whenever the
|
|
270
|
+
// set of custom views changes; a no-op run leaves it (and `created`) alone.
|
|
271
|
+
const viewsRel = fe + '/src/views.js'
|
|
272
|
+
const viewsPath = Path.join(spec.root, viewsRel)
|
|
273
|
+
const viewsBody =
|
|
274
|
+
'// AUTO-GENERATED: imports every custom entity view (model entities declaring\n' +
|
|
275
|
+
'// ux:{view:\'custom\'}). Regenerated from the model — do not edit. The view\n' +
|
|
276
|
+
'// files it imports are hand-coded and create-once.\n\n' +
|
|
277
|
+
customs.map((c) => `import './cmp/view/${c.zone}_${c.name}.js'`).join('\n') + '\n'
|
|
278
|
+
const existingViews = Fs.existsSync(viewsPath) ? Fs.readFileSync(viewsPath, 'utf8') : null
|
|
279
|
+
if (existingViews !== viewsBody) {
|
|
280
|
+
Fs.mkdirSync(Path.join(spec.root, fe, 'src'), { recursive: true })
|
|
281
|
+
Fs.writeFileSync(viewsPath, viewsBody)
|
|
282
|
+
created.push(viewsRel)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// theme.css — the design theme from the model (main.theme). Each mode's
|
|
286
|
+
// tokens become CSS variables under :root[data-theme-mode="<mode>"], with the
|
|
287
|
+
// default mode also on plain :root. Regenerated from the model; override
|
|
288
|
+
// tokens in custom.css and add modes via the theme:modes hook.
|
|
289
|
+
const theme = model.main && model.main.theme
|
|
290
|
+
if (theme && theme.modes && Object.keys(theme.modes).length) {
|
|
291
|
+
const defMode = theme.mode || Object.keys(theme.modes)[0]
|
|
292
|
+
const tokensCss = (tokens: any) => Object.keys(tokens).sort()
|
|
293
|
+
.map((k) => ` --vg-${k}: ${tokens[k]};`).join('\n')
|
|
294
|
+
const blocks = [':root,\n:root[data-theme-mode="' + defMode + '"] {\n' + tokensCss(theme.modes[defMode]) + '\n}']
|
|
295
|
+
for (const mode of Object.keys(theme.modes)) {
|
|
296
|
+
if (mode !== defMode) {
|
|
297
|
+
blocks.push(':root[data-theme-mode="' + mode + '"] {\n' + tokensCss(theme.modes[mode]) + '\n}')
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const themeBody =
|
|
301
|
+
'/* AUTO-GENERATED from the model theme (main.theme) — do not edit.\n' +
|
|
302
|
+
' Override tokens in custom.css; add modes via the theme:modes hook. */\n\n' +
|
|
303
|
+
blocks.join('\n\n') + '\n'
|
|
304
|
+
const themePath = Path.join(spec.root, fe, 'src/theme.css')
|
|
305
|
+
const existingTheme = Fs.existsSync(themePath) ? Fs.readFileSync(themePath, 'utf8') : null
|
|
306
|
+
if (existingTheme !== themeBody) {
|
|
307
|
+
Fs.mkdirSync(Path.join(spec.root, fe, 'src'), { recursive: true })
|
|
308
|
+
Fs.writeFileSync(themePath, themeBody)
|
|
309
|
+
created.push(fe + '/src/theme.css')
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
107
313
|
return { created: created.sort(), skipped: skipped.sort() }
|
|
108
314
|
}
|
|
109
315
|
|