@_tc/template-core 0.0.3 → 0.1.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.
Files changed (176) hide show
  1. package/.skills/tc-generator/SKILL.md +62 -0
  2. package/.skills/tc-generator/reference/example.md +357 -0
  3. package/.skills/tc-generator/reference/model-schema.md +326 -0
  4. package/.skills/tc-generator/reference/project-template/app/controller/product.js +73 -0
  5. package/.skills/tc-generator/reference/project-template/app/router/product.js +9 -0
  6. package/.skills/tc-generator/reference/project-template/config/config.default.js +3 -0
  7. package/.skills/tc-generator/reference/project-template/index.js +17 -0
  8. package/.skills/tc-generator/reference/project-template/model/product/mode.js +179 -0
  9. package/.skills/tc-generator/reference/project-template/model/product/project/default.js +4 -0
  10. package/.skills/tc-generator/reference/project-template/package.json +10 -0
  11. package/README.md +239 -17
  12. package/cjs/_virtual/_rolldown/runtime.js +1 -1
  13. package/cjs/app/controller/base.js +1 -1
  14. package/cjs/app/controller/project.js +1 -1
  15. package/cjs/app/controller/view.js +1 -1
  16. package/cjs/app/extend/$fetch.js +1 -1
  17. package/cjs/app/extend/db.js +1 -1
  18. package/cjs/app/extend/generateErrorMessage.js +1 -1
  19. package/cjs/app/extend/logger.js +1 -1
  20. package/cjs/app/extend/parsingParamsOnUrl.js +1 -1
  21. package/cjs/app/middleware/api-sign-verify.js +1 -1
  22. package/cjs/app/middleware/error-handle.js +1 -1
  23. package/cjs/app/middleware/project-handler.js +1 -1
  24. package/cjs/app/middleware.js +1 -1
  25. package/cjs/app/router/project.js +1 -1
  26. package/cjs/app/router/view.js +1 -1
  27. package/cjs/app/router-schema/project.js +1 -1
  28. package/cjs/app/service/base.js +1 -1
  29. package/cjs/app/service/project.js +1 -1
  30. package/cjs/app/type.js +0 -1
  31. package/cjs/app/typings.js +0 -1
  32. package/cjs/app/view/entry.tpl +27 -28
  33. package/cjs/bundler/defaultAlias.js +1 -1
  34. package/cjs/bundler/dev.js +1 -1
  35. package/cjs/bundler/index.js +1 -1
  36. package/cjs/bundler/prod.js +1 -1
  37. package/cjs/bundler/state.js +1 -1
  38. package/cjs/config/config.default.js +1 -0
  39. package/cjs/index.js +1 -1
  40. package/cjs/packages/common/LRUCache.js +1 -1
  41. package/cjs/packages/common/array/index.js +1 -1
  42. package/cjs/packages/common/cache/LRUCache.js +1 -1
  43. package/cjs/packages/common/cache/index.js +1 -1
  44. package/cjs/packages/common/guards/index.js +1 -1
  45. package/cjs/packages/common/http/index.js +1 -1
  46. package/cjs/packages/common/i18n/default.js +1 -1
  47. package/cjs/packages/common/i18n/en-US.js +1 -1
  48. package/cjs/packages/common/i18n/index.js +1 -1
  49. package/cjs/packages/common/i18n/locales.js +1 -1
  50. package/cjs/packages/common/i18n/types.js +0 -1
  51. package/cjs/packages/common/index.js +1 -1
  52. package/cjs/packages/common/log/index.js +1 -1
  53. package/cjs/packages/common/number/index.js +1 -1
  54. package/cjs/packages/common/object/filterEmpty.js +1 -1
  55. package/cjs/packages/common/object/filtereEmpty.js +1 -1
  56. package/cjs/packages/common/object/index.js +1 -1
  57. package/cjs/packages/common/string/index.js +1 -1
  58. package/cjs/packages/common/types/index.js +0 -1
  59. package/cjs/packages/core/env.js +1 -1
  60. package/cjs/packages/core/index.js +1 -1
  61. package/cjs/packages/core/loader/config.js +1 -1
  62. package/cjs/packages/core/loader/controller.js +1 -1
  63. package/cjs/packages/core/loader/extend.js +1 -1
  64. package/cjs/packages/core/loader/middleware.js +1 -1
  65. package/cjs/packages/core/loader/router-schema.js +1 -1
  66. package/cjs/packages/core/loader/service.js +1 -1
  67. package/cjs/packages/core/paths.js +1 -1
  68. package/cjs/packages/core/types.js +0 -1
  69. package/cjs/packages/utils/getAllFnReturnValue.js +0 -1
  70. package/cjs/packages/utils/index.js +1 -1
  71. package/esm/_virtual/_rolldown/runtime.js +2 -15
  72. package/esm/app/controller/view.js +13 -11
  73. package/esm/app/middleware/api-sign-verify.js +9 -11
  74. package/esm/app/middleware.js +6 -6
  75. package/esm/app/view/entry.tpl +27 -28
  76. package/esm/config/config.default.js +9 -0
  77. package/esm/index.js +1 -1
  78. package/esm/packages/core/index.js +24 -26
  79. package/esm/packages/core/loader/config.js +11 -10
  80. package/esm/packages/core/loader/extend.js +13 -8
  81. package/fe/frontend/apps/dash/Dashboard.js +100 -1
  82. package/fe/frontend/apps/dash/dash.entry.js +2 -2
  83. package/fe/frontend/apps/ui-components/index.js +8 -0
  84. package/fe/frontend/apps/ui-components/ui-components.entry.d.ts +2 -0
  85. package/fe/frontend/src/api/baseInfo.d.ts +2 -2
  86. package/fe/frontend/src/common/fetchErrorShow.js +2 -1
  87. package/fe/frontend/src/common/generateMenuData.js +2 -1
  88. package/fe/frontend/src/common/importComponent.js +5 -2
  89. package/fe/frontend/src/common/language.d.ts +2 -1
  90. package/fe/frontend/src/common/language.js +7 -5
  91. package/fe/frontend/src/common/logFn/index.js +6 -4
  92. package/fe/frontend/src/common/request.js +6 -4
  93. package/fe/frontend/src/components/BasePage/HeaderView.d.ts +2 -1
  94. package/fe/frontend/src/components/BasePage/HeaderView.js +9 -4
  95. package/fe/frontend/src/defaultPages/Iframe/index.js +1 -7
  96. package/fe/frontend/src/defaultPages/NotFoundPage/index.d.ts +3 -0
  97. package/fe/frontend/src/defaultPages/NotFoundPage/index.js +7 -0
  98. package/fe/frontend/src/defaultPages/SchemaPage/components/CallCom/DetailPanel.js +3 -2
  99. package/fe/frontend/src/defaultPages/SchemaPage/components/CallCom/PopFrom.js +8 -2
  100. package/fe/frontend/src/defaultPages/SchemaPage/components/SchemaTable/index.js +4 -3
  101. package/fe/frontend/src/defaultPages/SchemaPage/index.css +0 -55
  102. package/fe/frontend/src/defaultPages/SchemaPage/index.js +3 -1
  103. package/fe/frontend/src/defaultPages/SchemaPage/utils/validator.d.ts +5 -1
  104. package/fe/frontend/src/defaultPages/SchemaPage/utils/validator.js +142 -7
  105. package/fe/frontend/src/defaultPages/SidebarSlotPage/index.js +2 -1
  106. package/fe/frontend/src/defaultPages/SidebarSlotPageTmp.js +1 -1
  107. package/fe/frontend/src/defaultPages/SlotPage/index.js +2 -1
  108. package/fe/frontend/src/hooks/useCurrentMenuData.js +1 -1
  109. package/fe/frontend/src/index.d.ts +4 -0
  110. package/fe/frontend/src/index.js +4 -0
  111. package/fe/frontend/src/language/en-US.d.ts +76 -0
  112. package/fe/frontend/src/language/en-US.js +75 -0
  113. package/fe/frontend/src/language/index.d.ts +66 -0
  114. package/fe/frontend/src/language/index.js +69 -0
  115. package/fe/frontend/src/language/zh-CN.d.ts +76 -0
  116. package/fe/frontend/src/language/zh-CN.js +75 -0
  117. package/fe/frontend/src/typing/window.d.ts +2 -0
  118. package/fe/model/types/data/button.d.ts +2 -2
  119. package/fe/model/types/data/button.js +1 -1
  120. package/fe/model/types/model.d.ts +19 -12
  121. package/fe/packages/ui/react/components/Card/Card.d.ts +10 -0
  122. package/fe/packages/ui/react/components/Card/Card.js +8 -0
  123. package/fe/packages/ui/react/components/Card/index.d.ts +2 -0
  124. package/fe/packages/ui/react/components/Card/index.js +1 -0
  125. package/fe/packages/ui/react/components/DataTable/index.js +7 -3
  126. package/fe/packages/ui/react/components/ImagePreview/ImagePreview.js +3 -3
  127. package/fe/packages/ui/react/components/Layout/Layout.d.ts +11 -0
  128. package/fe/packages/ui/react/components/Layout/Layout.js +8 -0
  129. package/fe/packages/ui/react/components/Layout/index.d.ts +2 -0
  130. package/fe/packages/ui/react/components/Layout/index.js +1 -0
  131. package/fe/packages/ui/react/components/Loading/Loading.d.ts +9 -0
  132. package/fe/packages/ui/react/components/Loading/Loading.js +13 -0
  133. package/fe/packages/ui/react/components/Loading/index.d.ts +2 -0
  134. package/fe/packages/ui/react/components/Loading/index.js +1 -0
  135. package/fe/packages/ui/react/components/Menu/Menu.js +3 -2
  136. package/fe/packages/ui/react/components/Notification/Notification.d.ts +14 -0
  137. package/fe/packages/ui/react/components/Notification/Notification.js +29 -0
  138. package/fe/packages/ui/react/components/Notification/index.d.ts +2 -0
  139. package/fe/packages/ui/react/components/Notification/index.js +1 -0
  140. package/fe/packages/ui/react/components/Radio/Radio.d.ts +9 -0
  141. package/fe/packages/ui/react/components/Radio/Radio.js +20 -0
  142. package/fe/packages/ui/react/components/Radio/RadioGroup.d.ts +19 -0
  143. package/fe/packages/ui/react/components/Radio/RadioGroup.js +21 -0
  144. package/fe/packages/ui/react/components/Radio/index.d.ts +3 -0
  145. package/fe/packages/ui/react/components/Radio/index.js +2 -0
  146. package/fe/packages/ui/react/components/Search/Search.d.ts +1 -1
  147. package/fe/packages/ui/react/components/Search/Search.js +1 -5
  148. package/fe/packages/ui/react/components/Tabs/Tabs.d.ts +16 -0
  149. package/fe/packages/ui/react/components/Tabs/Tabs.js +19 -0
  150. package/fe/packages/ui/react/components/Tabs/index.d.ts +2 -0
  151. package/fe/packages/ui/react/components/Tabs/index.js +1 -0
  152. package/fe/packages/ui/react/components/hooks/useInputController.js +0 -1
  153. package/fe/packages/ui/react/components/index.d.ts +6 -0
  154. package/fe/packages/ui/react/components/index.js +6 -0
  155. package/fe/packages/ui/react/components/table/table.d.ts +3 -0
  156. package/fe/packages/ui/react/components/table/table.js +6 -6
  157. package/fe/packages/ui/react/components/testPage/index.js +54 -11
  158. package/fe/packages/ui/react/index.css +4 -0
  159. package/model/index.d.ts +1 -1
  160. package/model/types/data/button.d.ts +2 -2
  161. package/model/types/index.d.ts +1 -1
  162. package/model/types/model.d.ts +19 -12
  163. package/package.json +15 -16
  164. package/types/config/config.default.d.ts +2 -1
  165. package/types/index.d.ts +1 -1
  166. package/types/packages/core/index.d.ts +3 -3
  167. package/types/packages/core/loader/extend.d.ts +1 -1
  168. package/types/packages/core/paths.d.ts +4 -7
  169. package/types/packages/core/types.d.ts +31 -6
  170. package/cjs/app/data/signKey.js +0 -1
  171. package/esm/app/data/signKey.js +0 -8
  172. package/fe/frontend/apps/testPage/index.js +0 -6
  173. package/fe/frontend/apps/testPage/testPage.entry.d.ts +0 -2
  174. package/types/app/data/signKey.d.ts +0 -1
  175. /package/fe/frontend/apps/{testPage → ui-components}/index.d.ts +0 -0
  176. /package/fe/frontend/apps/{testPage/testPage.entry.js → ui-components/ui-components.entry.js} +0 -0
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: tc-generator
3
+ description: Generate TemplateCore business projects, modules, model configurations, Schema CRUD pages, controllers, routers, and project overrides that follow this repository's Koa + React + model-driven conventions. Use when the user asks to create or modify TC/TemplateCore projects, admin modules, model/mode.js files, schemaConfig CRUD pages, menu structures, or app/controller and app/router files.
4
+ ---
5
+
6
+ # TC Generator
7
+
8
+ Use this skill to generate or update TemplateCore business code.
9
+
10
+ ## Quick Workflow
11
+
12
+ 1. Identify the target business root. In this repository, a generated project usually uses:
13
+ - `index.js`
14
+ - `config/config.default.js`
15
+ - `app/controller/*.js`
16
+ - `app/router/*.js`
17
+ - `model/{modelKey}/mode.js`
18
+ - optional `model/{modelKey}/project/{projectKey}.js`
19
+ 2. Read only the reference needed:
20
+ - Model/menu/schema rules: `reference/model-schema.md`
21
+ - Runnable skeleton: `reference/project-template/`
22
+ - Complete product CRUD example: `reference/example.md`
23
+ 3. Generate the smallest working slice first: config, entry, controller, router, model.
24
+ 4. Keep keys stable. Array merge and project overrides depend on `key`.
25
+ 5. Prefer CommonJS `.js` for generated business examples unless the existing business project already uses TypeScript.
26
+
27
+ ## TemplateCore Conventions
28
+
29
+ - Start the app with `serverStart({ name, baseDir })` from `@_tc/template-core`.
30
+ - Start the built-in frontend with `buildFE("dev")` from `@_tc/template-core/bundler` when the user needs the admin UI.
31
+ - Business code lives under the user's `baseDir`, not inside TemplateCore framework folders.
32
+ - Backend loaders map filenames to camelCase fields:
33
+ - `app/controller/product.js` -> `app.controller.product`
34
+ - `app/service/user-profile.js` -> `app.service.userProfile`
35
+ - Controller and service files export a factory: `module.exports = (app) => class Xxx {}`.
36
+ - Router files export a function: `module.exports = (app, router) => { ... }`.
37
+ - Schema pages expect REST-like endpoints:
38
+ - `GET {api}/list` for table data
39
+ - `GET {api}` for detail/edit fetch
40
+ - `POST {api}` for create
41
+ - `PUT {api}` for update
42
+ - `DELETE {api}` for remove
43
+
44
+ ## Output Rules
45
+
46
+ - Make generated code directly runnable.
47
+ - Include realistic in-memory data only for demos or scaffolds.
48
+ - Use `$i18n::...` only when also adding or pointing to language resources; otherwise write plain labels.
49
+ - For Schema CRUD, include `tableOption`, `createFormOption`, `editFormOption`, and `detailPanelOption` only where the field should appear.
50
+ - For edit/detail components, set `fetchKey` to the primary key field.
51
+ - For remove buttons, use `$schema::{field}` to pull values from the current row.
52
+
53
+ ## Validation
54
+
55
+ When editing an existing project, verify with the closest available command:
56
+
57
+ ```bash
58
+ pnpm type-check
59
+ pnpm dev
60
+ ```
61
+
62
+ For generated standalone CommonJS examples, at minimum inspect paths and imports against the current package name.
@@ -0,0 +1,357 @@
1
+ # Complete Product CRUD Example
2
+
3
+ This example creates a minimal TemplateCore business project with a product Schema CRUD page.
4
+
5
+ ## Structure
6
+
7
+ ```text
8
+ my-admin/
9
+ ├── app/
10
+ │ ├── controller/
11
+ │ │ └── product.js
12
+ │ └── router/
13
+ │ └── product.js
14
+ ├── config/
15
+ │ └── config.default.js
16
+ ├── model/
17
+ │ └── product/
18
+ │ ├── mode.js
19
+ │ └── project/
20
+ │ └── default.js
21
+ └── index.js
22
+ ```
23
+
24
+ ## index.js
25
+
26
+ ```js
27
+ const { join } = require("path");
28
+ const { serverStart } = require("@_tc/template-core");
29
+ const { buildFE } = require("@_tc/template-core/bundler");
30
+
31
+ async function main() {
32
+ await serverStart({
33
+ name: "my-admin",
34
+ baseDir: join(__dirname, "."),
35
+ });
36
+
37
+ await buildFE("dev");
38
+ }
39
+
40
+ main().catch((error) => {
41
+ console.error(error);
42
+ process.exit(1);
43
+ });
44
+ ```
45
+
46
+ ## config/config.default.js
47
+
48
+ ```js
49
+ module.exports = {
50
+ port: 9000,
51
+ };
52
+ ```
53
+
54
+ ## app/controller/product.js
55
+
56
+ ```js
57
+ const { baseFn } = require("@_tc/template-core");
58
+
59
+ const products = [
60
+ {
61
+ product_id: "1",
62
+ product_name: "Product A",
63
+ price: 100,
64
+ inventory: 30,
65
+ status: "enabled",
66
+ create_time: "2026-05-18 10:00:00",
67
+ },
68
+ {
69
+ product_id: "2",
70
+ product_name: "Product B",
71
+ price: 199,
72
+ inventory: 12,
73
+ status: "disabled",
74
+ create_time: "2026-05-18 11:00:00",
75
+ },
76
+ ];
77
+
78
+ module.exports = (app) => {
79
+ const BaseController = baseFn.baseControllerFn(app);
80
+
81
+ return class ProductController extends BaseController {
82
+ list = async (ctx) => {
83
+ const { product_name, status } = ctx.request.query;
84
+ const page = Number(ctx.request.query.page || 1);
85
+ const pageSize = Number(ctx.request.query.pageSize || 10);
86
+
87
+ const filtered = products.filter((item) => {
88
+ if (product_name && !item.product_name.includes(product_name)) return false;
89
+ if (status && item.status !== status) return false;
90
+ return true;
91
+ });
92
+
93
+ this.success(ctx, {
94
+ data: filtered.slice((page - 1) * pageSize, page * pageSize),
95
+ page,
96
+ pageSize,
97
+ total: filtered.length,
98
+ });
99
+ };
100
+
101
+ detail = async (ctx) => {
102
+ const item = products.find((product) => {
103
+ return product.product_id === ctx.request.query.product_id;
104
+ });
105
+
106
+ this.success(ctx, item || null);
107
+ };
108
+
109
+ create = async (ctx) => {
110
+ const item = {
111
+ ...ctx.request.body,
112
+ product_id: `${Date.now()}`,
113
+ create_time: new Date().toISOString(),
114
+ };
115
+
116
+ products.unshift(item);
117
+ this.success(ctx, item);
118
+ };
119
+
120
+ update = async (ctx) => {
121
+ const data = ctx.request.body;
122
+ const item = products.find((product) => product.product_id === data.product_id);
123
+
124
+ if (item) Object.assign(item, data);
125
+ this.success(ctx, item || data);
126
+ };
127
+
128
+ remove = async (ctx) => {
129
+ const index = products.findIndex((product) => {
130
+ return product.product_id === ctx.request.query.product_id;
131
+ });
132
+
133
+ if (index !== -1) products.splice(index, 1);
134
+ this.success(ctx, {});
135
+ };
136
+ };
137
+ };
138
+ ```
139
+
140
+ ## app/router/product.js
141
+
142
+ ```js
143
+ module.exports = (app, router) => {
144
+ const { product } = app.controller;
145
+
146
+ router.get("/api/product/list", product.list);
147
+ router.get("/api/product", product.detail);
148
+ router.post("/api/product", product.create);
149
+ router.put("/api/product", product.update);
150
+ router.delete("/api/product", product.remove);
151
+ };
152
+ ```
153
+
154
+ ## model/product/mode.js
155
+
156
+ ```js
157
+ module.exports = {
158
+ mode: "MB",
159
+ name: "Product Admin",
160
+ desc: "Product management example",
161
+ icon: "",
162
+ homePage: "/_sidebar_/product",
163
+ menu: [
164
+ {
165
+ key: "product",
166
+ name: "Products",
167
+ menuType: "module",
168
+ moduleType: "schema",
169
+ schemaConfig: {
170
+ api: "/api/product",
171
+ schema: {
172
+ type: "object",
173
+ properties: {
174
+ product_id: {
175
+ type: "string",
176
+ label: "Product ID",
177
+ tableOption: {
178
+ width: "180",
179
+ },
180
+ editFormOption: {
181
+ comType: "input",
182
+ disabled: true,
183
+ },
184
+ detailPanelOption: {},
185
+ },
186
+ product_name: {
187
+ type: "string",
188
+ label: "Product Name",
189
+ minLength: 2,
190
+ tableOption: {
191
+ width: "220",
192
+ },
193
+ searchOption: {
194
+ comType: "input",
195
+ },
196
+ createFormOption: {
197
+ comType: "input",
198
+ },
199
+ editFormOption: {
200
+ comType: "input",
201
+ },
202
+ detailPanelOption: {},
203
+ },
204
+ price: {
205
+ type: "number",
206
+ label: "Price",
207
+ tableOption: {
208
+ width: "120",
209
+ toFixed: 2,
210
+ },
211
+ createFormOption: {
212
+ comType: "inputNumber",
213
+ },
214
+ editFormOption: {
215
+ comType: "inputNumber",
216
+ },
217
+ detailPanelOption: {},
218
+ },
219
+ inventory: {
220
+ type: "number",
221
+ label: "Inventory",
222
+ tableOption: {
223
+ width: "120",
224
+ },
225
+ createFormOption: {
226
+ comType: "inputNumber",
227
+ default: 0,
228
+ },
229
+ editFormOption: {
230
+ comType: "inputNumber",
231
+ },
232
+ detailPanelOption: {},
233
+ },
234
+ status: {
235
+ type: "string",
236
+ label: "Status",
237
+ tableOption: {
238
+ width: "120",
239
+ },
240
+ searchOption: {
241
+ comType: "select",
242
+ options: [
243
+ { label: "Enabled", value: "enabled" },
244
+ { label: "Disabled", value: "disabled" },
245
+ ],
246
+ },
247
+ createFormOption: {
248
+ comType: "select",
249
+ default: "enabled",
250
+ options: [
251
+ { label: "Enabled", value: "enabled" },
252
+ { label: "Disabled", value: "disabled" },
253
+ ],
254
+ },
255
+ editFormOption: {
256
+ comType: "select",
257
+ options: [
258
+ { label: "Enabled", value: "enabled" },
259
+ { label: "Disabled", value: "disabled" },
260
+ ],
261
+ },
262
+ detailPanelOption: {
263
+ options: [
264
+ { label: "Enabled", value: "enabled" },
265
+ { label: "Disabled", value: "disabled" },
266
+ ],
267
+ },
268
+ },
269
+ create_time: {
270
+ type: "string",
271
+ label: "Created At",
272
+ tableOption: {
273
+ width: "180",
274
+ },
275
+ detailPanelOption: {},
276
+ },
277
+ },
278
+ required: ["product_name", "price"],
279
+ },
280
+ componentConfig: {
281
+ createForm: {
282
+ title: "Create Product",
283
+ saveBtnText: "Create",
284
+ },
285
+ editForm: {
286
+ title: "Edit Product",
287
+ saveBtnText: "Save",
288
+ fetchKey: "product_id",
289
+ },
290
+ detailPanel: {
291
+ title: "Product Detail",
292
+ fetchKey: "product_id",
293
+ },
294
+ },
295
+ tableConfig: {
296
+ headerButtons: [
297
+ {
298
+ label: "Create Product",
299
+ eventKey: "callComponent",
300
+ variant: "primary",
301
+ eventOption: {
302
+ comName: "createForm",
303
+ },
304
+ },
305
+ ],
306
+ rowButtons: [
307
+ {
308
+ label: "Edit",
309
+ eventKey: "callComponent",
310
+ eventOption: {
311
+ comName: "editForm",
312
+ },
313
+ },
314
+ {
315
+ label: "Detail",
316
+ eventKey: "callComponent",
317
+ eventOption: {
318
+ comName: "detailPanel",
319
+ },
320
+ },
321
+ {
322
+ label: "Delete",
323
+ eventKey: "remove",
324
+ eventOption: {
325
+ params: {
326
+ product_id: "$schema::product_id",
327
+ },
328
+ },
329
+ },
330
+ ],
331
+ },
332
+ },
333
+ },
334
+ ],
335
+ };
336
+ ```
337
+
338
+ ## model/product/project/default.js
339
+
340
+ ```js
341
+ module.exports = {
342
+ name: "Default Product Project",
343
+ desc: "Project-level overrides can be added here.",
344
+ };
345
+ ```
346
+
347
+ ## Run
348
+
349
+ ```bash
350
+ node index.js
351
+ ```
352
+
353
+ Open:
354
+
355
+ ```text
356
+ http://localhost:9000/dash?projk=default
357
+ ```