@voxgig/build 4.3.0 → 4.11.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 +159 -1
- 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 +174 -1
- 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 +37 -4
- 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 +3 -0
- package/tm/web/web/package.json.frag +4 -1
- package/tm/web/web/playwright.config.js.frag +6 -1
- package/tm/web/web/src/api.js.frag +92 -0
- package/tm/web/web/src/bus.js.frag +46 -23
- package/tm/web/web/src/cmp/admin.js.frag +292 -193
- 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
|
@@ -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/)
|
|
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.
|
|
31
34
|
{ frag: 'web/package.json.frag', out: 'web/package.json' },
|
|
32
35
|
{ frag: 'web/index.html.frag', out: 'web/index.html' },
|
|
33
36
|
{ frag: 'web/playwright.config.js.frag', out: 'web/playwright.config.js' },
|
|
34
37
|
{ frag: 'web/src/main.js.frag', out: 'web/src/main.js' },
|
|
35
38
|
{ frag: 'web/src/bus.js.frag', out: 'web/src/bus.js' },
|
|
39
|
+
{ frag: 'web/src/model.js.frag', out: 'web/src/model.js' },
|
|
40
|
+
{ frag: 'web/src/api.js.frag', out: 'web/src/api.js' },
|
|
41
|
+
{ frag: 'web/src/hooks.js.frag', out: 'web/src/hooks.js' },
|
|
42
|
+
{ frag: 'web/src/theme.js.frag', out: 'web/src/theme.js' },
|
|
43
|
+
{ frag: 'web/src/customise.js.frag', out: 'web/src/customise.js' },
|
|
44
|
+
{ frag: 'web/src/custom.css.frag', out: 'web/src/custom.css' },
|
|
36
45
|
{ frag: 'web/src/style.css.frag', out: 'web/src/style.css' },
|
|
37
46
|
{ frag: 'web/src/cmp/app.js.frag', out: 'web/src/cmp/app.js' },
|
|
47
|
+
{ frag: 'web/src/cmp/public.js.frag', out: 'web/src/cmp/public.js' },
|
|
38
48
|
{ frag: 'web/src/cmp/auth.js.frag', out: 'web/src/cmp/auth.js' },
|
|
49
|
+
{ frag: 'web/src/cmp/shell.js.frag', out: 'web/src/cmp/shell.js' },
|
|
39
50
|
{ frag: 'web/src/cmp/admin.js.frag', out: 'web/src/cmp/admin.js' },
|
|
51
|
+
{ frag: 'web/src/cmp/settings.js.frag', out: 'web/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: 'web/src/cmp/app.md' },
|
|
56
|
+
{ frag: 'web/src/cmp/public.md.frag', out: 'web/src/cmp/public.md' },
|
|
57
|
+
{ frag: 'web/src/cmp/auth.md.frag', out: 'web/src/cmp/auth.md' },
|
|
58
|
+
{ frag: 'web/src/cmp/shell.md.frag', out: 'web/src/cmp/shell.md' },
|
|
59
|
+
{ frag: 'web/src/cmp/admin.md.frag', out: 'web/src/cmp/admin.md' },
|
|
60
|
+
{ frag: 'web/src/cmp/settings.md.frag', out: 'web/src/cmp/settings.md' },
|
|
40
61
|
{ frag: 'web/e2e/smoke.spec.js.frag', out: 'web/e2e/smoke.spec.js' },
|
|
62
|
+
{ frag: 'web/AGENTS.md.frag', out: 'web/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
|
|
|
@@ -104,6 +194,89 @@ const web_gen = async (model: any, spec: {
|
|
|
104
194
|
created.push(outrel)
|
|
105
195
|
}
|
|
106
196
|
|
|
197
|
+
// Custom entity views: for every entity declaring `ux:{ view: 'custom' }`,
|
|
198
|
+
// generate a hand-coded view component (CREATE-ONCE, so edits survive) plus
|
|
199
|
+
// an always-regenerated index (views.js) that imports them.
|
|
200
|
+
const entzones = model.main.ent || {}
|
|
201
|
+
const customs: { zone: string, name: string, canon: string }[] = []
|
|
202
|
+
for (const zone of Object.keys(entzones)) {
|
|
203
|
+
for (const name of Object.keys(entzones[zone])) {
|
|
204
|
+
const def = entzones[zone][name]
|
|
205
|
+
if (def && def.ux && 'custom' === def.ux.view) {
|
|
206
|
+
customs.push({ zone, name, canon: zone + '/' + name })
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const c of customs) {
|
|
212
|
+
const vslots = Object.assign({}, slots, {
|
|
213
|
+
canon: c.canon,
|
|
214
|
+
zone: c.zone,
|
|
215
|
+
name: c.name,
|
|
216
|
+
tag: 'vg-view-' + c.zone + '-' + c.name,
|
|
217
|
+
className: 'VgView' + camelify(c.zone) + camelify(c.name),
|
|
218
|
+
Label: camelify(c.name),
|
|
219
|
+
})
|
|
220
|
+
// The component starter plus its doc sidecar, both create-once.
|
|
221
|
+
for (const ext of ['js', 'md']) {
|
|
222
|
+
const outrel = 'web/src/cmp/view/' + c.zone + '_' + c.name + '.' + ext
|
|
223
|
+
const dest = Path.join(spec.root, outrel)
|
|
224
|
+
if (Fs.existsSync(dest) && !spec.force) {
|
|
225
|
+
skipped.push(outrel)
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
const content = renderFragment(
|
|
229
|
+
loadFragment('web/src/cmp/view/custom-view.' + ext + '.frag', spec, 'web'), vslots)
|
|
230
|
+
Fs.mkdirSync(Path.dirname(dest), { recursive: true })
|
|
231
|
+
Fs.writeFileSync(dest, content)
|
|
232
|
+
created.push(outrel)
|
|
233
|
+
}
|
|
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 = 'web/src/views.js'
|
|
239
|
+
const viewsPath = Path.join(spec.root, viewsRel)
|
|
240
|
+
const viewsBody =
|
|
241
|
+
'// AUTO-GENERATED: imports every custom entity view (model entities declaring\n' +
|
|
242
|
+
'// ux:{view:\'custom\'}). Regenerated from the model — do not edit. The view\n' +
|
|
243
|
+
'// files it imports are hand-coded and create-once.\n\n' +
|
|
244
|
+
customs.map((c) => `import './cmp/view/${c.zone}_${c.name}.js'`).join('\n') + '\n'
|
|
245
|
+
const existingViews = Fs.existsSync(viewsPath) ? Fs.readFileSync(viewsPath, 'utf8') : null
|
|
246
|
+
if (existingViews !== viewsBody) {
|
|
247
|
+
Fs.mkdirSync(Path.join(spec.root, 'web/src'), { recursive: true })
|
|
248
|
+
Fs.writeFileSync(viewsPath, viewsBody)
|
|
249
|
+
created.push(viewsRel)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// theme.css — the design theme from the model (main.theme). Each mode's
|
|
253
|
+
// tokens become CSS variables under :root[data-theme-mode="<mode>"], with the
|
|
254
|
+
// default mode also on plain :root. Regenerated from the model; override
|
|
255
|
+
// tokens in custom.css and add modes via the theme:modes hook.
|
|
256
|
+
const theme = model.main && model.main.theme
|
|
257
|
+
if (theme && theme.modes && Object.keys(theme.modes).length) {
|
|
258
|
+
const defMode = theme.mode || Object.keys(theme.modes)[0]
|
|
259
|
+
const tokensCss = (tokens: any) => Object.keys(tokens).sort()
|
|
260
|
+
.map((k) => ` --vg-${k}: ${tokens[k]};`).join('\n')
|
|
261
|
+
const blocks = [':root,\n:root[data-theme-mode="' + defMode + '"] {\n' + tokensCss(theme.modes[defMode]) + '\n}']
|
|
262
|
+
for (const mode of Object.keys(theme.modes)) {
|
|
263
|
+
if (mode !== defMode) {
|
|
264
|
+
blocks.push(':root[data-theme-mode="' + mode + '"] {\n' + tokensCss(theme.modes[mode]) + '\n}')
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const themeBody =
|
|
268
|
+
'/* AUTO-GENERATED from the model theme (main.theme) — do not edit.\n' +
|
|
269
|
+
' Override tokens in custom.css; add modes via the theme:modes hook. */\n\n' +
|
|
270
|
+
blocks.join('\n\n') + '\n'
|
|
271
|
+
const themePath = Path.join(spec.root, 'web/src/theme.css')
|
|
272
|
+
const existingTheme = Fs.existsSync(themePath) ? Fs.readFileSync(themePath, 'utf8') : null
|
|
273
|
+
if (existingTheme !== themeBody) {
|
|
274
|
+
Fs.mkdirSync(Path.join(spec.root, 'web/src'), { recursive: true })
|
|
275
|
+
Fs.writeFileSync(themePath, themeBody)
|
|
276
|
+
created.push('web/src/theme.css')
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
107
280
|
return { created: created.sort(), skipped: skipped.sort() }
|
|
108
281
|
}
|
|
109
282
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voxgig/build",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.11.0",
|
|
4
4
|
"main": "dist/build.js",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"types": "dist/build.d.ts",
|
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"model-s01": "voxgig-model test/s01/model/model.aontu",
|
|
22
|
-
"test": "
|
|
23
|
-
"test-some": "
|
|
24
|
-
"test-watch": "
|
|
22
|
+
"test": "node --enable-source-maps --experimental-test-coverage --test-coverage-exclude='dist-test/test/**' --test-coverage-lines=95 --test-coverage-functions=85 --test-coverage-branches=78 --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage.lcov --test \"dist-test/test/**/*.test.js\"",
|
|
23
|
+
"test-some": "node --enable-source-maps --test --test-name-pattern=\"$npm_config_pattern\" \"dist-test/test/**/*.test.js\"",
|
|
24
|
+
"test-watch": "node --enable-source-maps --test --watch \"dist-test/test/**/*.test.js\"",
|
|
25
25
|
"watch": "tsc -w -d",
|
|
26
|
-
"build": "tsc -d",
|
|
27
|
-
"clean": "rm -rf node_modules yarn.lock package-lock.json",
|
|
26
|
+
"build": "tsc -d && tsc -p test && cp -r test/fixture test/richmodel.js dist-test/test/",
|
|
27
|
+
"clean": "rm -rf dist dist-test node_modules yarn.lock package-lock.json",
|
|
28
28
|
"reset": "npm run clean && npm i && npm run build && npm test",
|
|
29
29
|
"repo-tag": "REPO_VERSION=`node -e \"console.log(require('./package').version)\"` && echo TAG: v$REPO_VERSION && git commit -a -m v$REPO_VERSION && git push && git tag v$REPO_VERSION && git push --tags;",
|
|
30
30
|
"repo-publish": "npm run clean && npm i && npm run repo-publish-quick",
|
|
@@ -53,14 +53,11 @@
|
|
|
53
53
|
"js-yaml": "^4.1.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@types/jest": "^29.5.12",
|
|
57
56
|
"@types/js-yaml": "^4.0.9",
|
|
58
57
|
"@types/node": "26.1.2",
|
|
59
58
|
"@voxgig/model": "^9.2.4",
|
|
60
59
|
"@voxgig/system": "^1.2.0",
|
|
61
60
|
"esbuild": "^0.23.0",
|
|
62
|
-
"jest": "^29.7.0",
|
|
63
|
-
"ts-jest": "^29.2.2",
|
|
64
61
|
"typescript": "^5.5.3"
|
|
65
62
|
}
|
|
66
63
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Demo/seed data for local development. The web runner already seeds the
|
|
2
|
+
// predefined users; add your DOMAIN seed data here (projects, lists, items,
|
|
3
|
+
// ...). Runs once per boot against the in-memory store. Create-once —
|
|
4
|
+
// customise freely.
|
|
5
|
+
|
|
6
|
+
export async function seedDemo(
|
|
7
|
+
seneca: any,
|
|
8
|
+
usersByEmail: Record<string, any>,
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
// Nothing seeded by default. Example (uncomment and adapt to your model):
|
|
11
|
+
//
|
|
12
|
+
// const owner = Object.values(usersByEmail)[0]
|
|
13
|
+
// if (!owner) return
|
|
14
|
+
// const existing = await seneca.entity('proj/project').list$({})
|
|
15
|
+
// if (existing.length > 0) return
|
|
16
|
+
// const now = Date.now()
|
|
17
|
+
// const p = await seneca.entity('proj/project')
|
|
18
|
+
// .data$({ name: 'Example', owner_id: owner.id, t_c: now, t_m: now }).save$()
|
|
19
|
+
// await seneca.entity('proj/member')
|
|
20
|
+
// .data$({ project_id: p.id, user_id: owner.id, role: 'owner',
|
|
21
|
+
// owner_id: owner.id, t_c: now, t_m: now }).save$()
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// The strict-JSON REST API endpoint (model main.api). Maps
|
|
2
|
+
//
|
|
3
|
+
// GET <prefix>/<version>/<zone>/<name> -> list (query -> q)
|
|
4
|
+
// GET <prefix>/<version>/<zone>/<name>/<id> -> load
|
|
5
|
+
// POST <prefix>/<version>/<zone>/<name> -> create
|
|
6
|
+
// PUT <prefix>/<version>/<zone>/<name>/<id> -> update
|
|
7
|
+
// DELETE <prefix>/<version>/<zone>/<name>/<id> -> remove
|
|
8
|
+
//
|
|
9
|
+
// onto aim:api,on:ent messages (src/srv/api/on_ent.ts). Uniform semantic
|
|
10
|
+
// paths and result shapes so an SDK can be generated (sdkgen); the
|
|
11
|
+
// OpenAPI spec is generated from the model (gen/api/openapi.json).
|
|
12
|
+
//
|
|
13
|
+
// Authentication: API access keys - Authorization: Bearer vk_... - looked
|
|
14
|
+
// up by sha-256 hash against sys/apikey and resolved to the owning user,
|
|
15
|
+
// which becomes the request principal (so project-membership access
|
|
16
|
+
// applies exactly as in the web app).
|
|
17
|
+
|
|
18
|
+
import Crypto from 'node:crypto'
|
|
19
|
+
import Path from 'node:path'
|
|
20
|
+
|
|
21
|
+
// Result 'why' -> HTTP status.
|
|
22
|
+
const STATUS: Record<string, number> = {
|
|
23
|
+
'not-authenticated': 401,
|
|
24
|
+
'forbidden': 403,
|
|
25
|
+
'unknown-entity': 404,
|
|
26
|
+
'not-found': 404,
|
|
27
|
+
'unknown-op': 405,
|
|
28
|
+
'read-only': 405,
|
|
29
|
+
'invalid-data': 400,
|
|
30
|
+
'project-required': 400,
|
|
31
|
+
'invalid-args': 400,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function apiHandler(seneca: any, model: any) {
|
|
35
|
+
const api = (model.main && model.main.api) || {}
|
|
36
|
+
const version = api.version || 'v1'
|
|
37
|
+
|
|
38
|
+
return async function handle(req: any, res: any) {
|
|
39
|
+
res.type('application/json')
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// The generated OpenAPI spec (unauthenticated; from gen/api/).
|
|
43
|
+
if ('GET' === req.method && '/openapi.json' === req.path) {
|
|
44
|
+
return res.sendFile(Path.join(
|
|
45
|
+
__dirname, '..', '..', '..', 'gen', 'api', 'openapi.json'))
|
|
46
|
+
}
|
|
47
|
+
// The same spec as YAML - the format most codegen/SDK tools want.
|
|
48
|
+
if ('GET' === req.method && '/openapi.yaml' === req.path) {
|
|
49
|
+
res.type('application/yaml')
|
|
50
|
+
return res.sendFile(Path.join(
|
|
51
|
+
__dirname, '..', '..', '..', 'gen', 'api', 'openapi.yaml'))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Path (relative to the mount prefix): /<version>/<zone>/<name>[/<id>]
|
|
55
|
+
const parts = String(req.path || '').split('/').filter((p: string) => '' !== p)
|
|
56
|
+
if (version !== parts[0] || parts.length < 3 || 4 < parts.length) {
|
|
57
|
+
return fail(res, 404, 'not-found', 'unknown route')
|
|
58
|
+
}
|
|
59
|
+
const ent = parts[1] + '/' + parts[2]
|
|
60
|
+
const id = parts[3]
|
|
61
|
+
|
|
62
|
+
const user = await authenticate(seneca, req)
|
|
63
|
+
if (null == user) {
|
|
64
|
+
return fail(res, 401, 'not-authenticated',
|
|
65
|
+
'provide a valid API key: Authorization: Bearer <key>')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const method = String(req.method || 'GET').toUpperCase()
|
|
69
|
+
let op: string | null = null
|
|
70
|
+
let body = undefined
|
|
71
|
+
|
|
72
|
+
if ('GET' === method) {
|
|
73
|
+
op = null == id ? 'list' : 'load'
|
|
74
|
+
}
|
|
75
|
+
else if ('POST' === method && null == id) {
|
|
76
|
+
op = 'create'
|
|
77
|
+
body = req.body
|
|
78
|
+
}
|
|
79
|
+
else if ('PUT' === method && null != id) {
|
|
80
|
+
op = 'update'
|
|
81
|
+
body = req.body
|
|
82
|
+
}
|
|
83
|
+
else if ('DELETE' === method && null != id) {
|
|
84
|
+
op = 'remove'
|
|
85
|
+
}
|
|
86
|
+
if (null == op) {
|
|
87
|
+
return fail(res, 405, 'unknown-op', 'method not supported on this path')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const out = await seneca.post('aim:api,on:ent', {
|
|
91
|
+
op,
|
|
92
|
+
ent,
|
|
93
|
+
id,
|
|
94
|
+
data: body,
|
|
95
|
+
q: 'list' === op ? Object.assign({}, req.query) : undefined,
|
|
96
|
+
custom$: { principal: { user } },
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
if (!out.ok) {
|
|
100
|
+
return fail(res, STATUS[out.why] || 500, out.why || 'failed',
|
|
101
|
+
out.message, out.details)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const status = 'create' === op ? 201 : 200
|
|
105
|
+
const result: any = {}
|
|
106
|
+
if (null != out.items) {
|
|
107
|
+
result.items = out.items
|
|
108
|
+
}
|
|
109
|
+
if (null != out.item) {
|
|
110
|
+
result.item = out.item
|
|
111
|
+
}
|
|
112
|
+
if ('remove' === op) {
|
|
113
|
+
result.ok = true
|
|
114
|
+
result.id = id
|
|
115
|
+
}
|
|
116
|
+
return res.status(status).send(result)
|
|
117
|
+
}
|
|
118
|
+
catch (e: any) {
|
|
119
|
+
return fail(res, 500, 'error', 'internal error')
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function fail(res: any, status: number, code: string, message?: string, details?: any) {
|
|
125
|
+
const error: any = { code }
|
|
126
|
+
if (message) {
|
|
127
|
+
error.message = message
|
|
128
|
+
}
|
|
129
|
+
if (details) {
|
|
130
|
+
error.details = details
|
|
131
|
+
}
|
|
132
|
+
return res.status(status).send({ error })
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Resolve the Bearer API key to its owning user (the principal), or null.
|
|
136
|
+
async function authenticate(seneca: any, req: any): Promise<any> {
|
|
137
|
+
const header = String(req.headers['authorization'] || '')
|
|
138
|
+
const m = header.match(/^Bearer\s+(\S+)$/i)
|
|
139
|
+
if (null == m) {
|
|
140
|
+
return null
|
|
141
|
+
}
|
|
142
|
+
const hash = Crypto.createHash('sha256').update(m[1]).digest('hex')
|
|
143
|
+
const keys = await seneca.entity('sys/apikey').list$({ hash })
|
|
144
|
+
const key = keys.find((k: any) => !k.revoked)
|
|
145
|
+
if (null == key) {
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
const user = await seneca.entity('sys/user').load$(key.user_id)
|
|
149
|
+
if (null == user) {
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
const d = user.data$(false)
|
|
153
|
+
delete d.pass
|
|
154
|
+
delete d.salt
|
|
155
|
+
return d
|
|
156
|
+
}
|
|
157
|
+
|
|
@@ -18,6 +18,8 @@ import Seneca from 'seneca'
|
|
|
18
18
|
import { Local } from '@voxgig/system'
|
|
19
19
|
|
|
20
20
|
import { basic, base } from '../shared/basic'
|
|
21
|
+
import { seedDemo } from '../shared/seed'
|
|
22
|
+
import { apiHandler } from './api'
|
|
21
23
|
|
|
22
24
|
import Pkg from '../../../package.json'
|
|
23
25
|
import Model from '../../../model/model.json'
|
|
@@ -48,12 +50,25 @@ async function run() {
|
|
|
48
50
|
|
|
49
51
|
basic(seneca)
|
|
50
52
|
|
|
53
|
+
// Dev REPL (@seneca/repl): poke the running system with messages -
|
|
54
|
+
// npx seneca-repl telnet://localhost:<port.repl>
|
|
55
|
+
// Disable with REPL=false; override the port with REPL_PORT.
|
|
56
|
+
if ('false' !== process.env.REPL) {
|
|
57
|
+
seneca.use('repl', {
|
|
58
|
+
port: parseInt(process.env.REPL_PORT || '', 10) ||
|
|
59
|
+
(Model as any).main.conf.port.repl,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
51
63
|
seneca
|
|
52
64
|
.use('gateway', {
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
// THE BROWSER SURFACE. Only aim:web is reachable from a browser:
|
|
66
|
+
// every message the SPA may send is declared in the model as an
|
|
67
|
+
// aim:web PROXY that forwards to the real service message. Service
|
|
68
|
+
// namespaces (aim:auth, aim:ent, ...) stay internal, so a browser
|
|
69
|
+
// cannot post one directly. API-key clients have their own proxy
|
|
70
|
+
// layer (aim:api behind the REST router, see ./api.ts).
|
|
71
|
+
allow: { 'aim:web': true },
|
|
57
72
|
})
|
|
58
73
|
.use('gateway-express', {
|
|
59
74
|
// gateway-express sets/clears the auth cookie when a result carries
|
|
@@ -89,13 +104,21 @@ async function run() {
|
|
|
89
104
|
await seneca.ready()
|
|
90
105
|
|
|
91
106
|
// Seed the predefined users (idempotent per boot: in-memory store).
|
|
107
|
+
const usersByEmail: Record<string, any> = {}
|
|
92
108
|
for (const u of USERS) {
|
|
93
109
|
const res = await seneca.post('sys:user,register:user', u)
|
|
94
110
|
if (!res.ok) {
|
|
95
111
|
console.log('SEED-USER-FAILED', u.email, res.why)
|
|
96
112
|
}
|
|
113
|
+
const got = await seneca.post('sys:user,get:user', { email: u.email })
|
|
114
|
+
if (got.ok && got.user) {
|
|
115
|
+
usersByEmail[u.email] = got.user
|
|
116
|
+
}
|
|
97
117
|
}
|
|
98
118
|
|
|
119
|
+
// Seed demo projects/todolists/items (collaborative, one shared project).
|
|
120
|
+
await seedDemo(seneca, usersByEmail)
|
|
121
|
+
|
|
99
122
|
const app = Express()
|
|
100
123
|
// web/ is a sibling of backend/; from dist/env/local go up to the
|
|
101
124
|
// project root, then web/dist.
|
|
@@ -106,6 +129,15 @@ async function run() {
|
|
|
106
129
|
.use(Express.json())
|
|
107
130
|
.use(new (CookieParser as any)())
|
|
108
131
|
.post('/seneca', seneca.export('gateway-express/handler'))
|
|
132
|
+
|
|
133
|
+
// The strict-JSON REST API (model main.api), if active. Mounted with
|
|
134
|
+
// use() so req.path inside the handler is relative to the prefix.
|
|
135
|
+
const apiconf = (Model as any).main.api
|
|
136
|
+
if (apiconf && false !== apiconf.active) {
|
|
137
|
+
app.use(apiconf.prefix || '/api', apiHandler(seneca, Model))
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
app
|
|
109
141
|
.get('/model.json',
|
|
110
142
|
(_req: any, res: any) => res.sendFile(
|
|
111
143
|
Path.join(__dirname, '..', '..', '..', 'model', 'model.json')))
|
|
@@ -118,3 +150,4 @@ async function run() {
|
|
|
118
150
|
version: Pkg.version,
|
|
119
151
|
})
|
|
120
152
|
}
|
|
153
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Which entities the REST API exposes (model main.api). Application
|
|
2
|
+
// entities are exposed by default; the sys zone never is; per-entity
|
|
3
|
+
// overrides live under main.api.ent.
|
|
4
|
+
export function exposedCanon(model: any, canon: string): boolean {
|
|
5
|
+
const [zone, name] = String(canon).split('/')
|
|
6
|
+
const def = model && model.main && model.main.ent &&
|
|
7
|
+
model.main.ent[zone] && model.main.ent[zone][name]
|
|
8
|
+
if (null == def || 'sys' === zone) {
|
|
9
|
+
return false
|
|
10
|
+
}
|
|
11
|
+
const api = (model.main.api || {})
|
|
12
|
+
if (false === api.active) {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
const conf = (api.ent || {})[canon]
|
|
16
|
+
return !(conf && false === conf.active)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// All exposed canons, sorted (used by generators and get:info).
|
|
20
|
+
export function exposedCanons(model: any): string[] {
|
|
21
|
+
const out: string[] = []
|
|
22
|
+
const zones = (model && model.main && model.main.ent) || {}
|
|
23
|
+
for (const zone of Object.keys(zones)) {
|
|
24
|
+
for (const name of Object.keys(zones[zone])) {
|
|
25
|
+
const canon = zone + '/' + name
|
|
26
|
+
if (exposedCanon(model, canon)) {
|
|
27
|
+
out.push(canon)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return out.sort()
|
|
32
|
+
}
|
|
33
|
+
|