@jskit-ai/crud-ui-generator 0.1.14 → 0.1.15

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 ADDED
@@ -0,0 +1,67 @@
1
+ # @jskit-ai/crud-ui-generator
2
+
3
+ Generate CRUD route trees from an explicit route root relative to `src/pages/...`.
4
+
5
+ ## Mental Model
6
+
7
+ This generator follows the same file-driven model as `@jskit-ai/ui-generator`.
8
+
9
+ - You point at one explicit route root relative to `src/pages/...`.
10
+ - The surface is derived from that path.
11
+ - The visible route URL is derived from that path.
12
+ - The generated list-page link placement is inferred from the nearest real parent subpages host when there is one.
13
+ - Explicit overrides still exist, but they are not required for the normal case.
14
+
15
+ ## Command
16
+
17
+ ```bash
18
+ npx jskit generate @jskit-ai/crud-ui-generator crud \
19
+ admin/catalog/index/products \
20
+ --resource-file packages/products/src/shared/productResource.js
21
+ ```
22
+
23
+ That generates:
24
+
25
+ - `src/pages/admin/catalog/index/products/index.vue`
26
+ - `src/pages/admin/catalog/index/products/[recordId]/index.vue`
27
+ - `src/pages/admin/catalog/index/products/new.vue`
28
+ - `src/pages/admin/catalog/index/products/[recordId]/edit.vue`
29
+ - `src/pages/admin/catalog/index/products/_components/...`
30
+
31
+ ## Defaults
32
+
33
+ From the explicit `target-root`, the generator derives:
34
+
35
+ - the owning surface
36
+ - the visible CRUD route
37
+ - the default list-page placement target
38
+ - the default link component token
39
+ - the default relative `to` for nested subpage links
40
+
41
+ The generated list-page link follows the same parent-host inference as `@jskit-ai/ui-generator page`.
42
+
43
+ If you want the detailed behavior, read:
44
+
45
+ - `npx jskit generate ui-generator page help`
46
+
47
+ That is where the parent-host, tab-link, and relative `to` behavior is explained.
48
+
49
+ ## Options
50
+
51
+ - `--resource-file`: required resource module path relative to the app root
52
+ - `--operations`: optional comma-separated subset of `list,view,new,edit`, defaulting to all four
53
+ - `--display-fields`: optional comma-separated subset of fields to render
54
+ - `--id-param`: optional route param name for record pages, default `recordId`
55
+ - `--link-placement`: optional link placement override for the generated list page
56
+ - `--namespace`: optional CRUD namespace override when the resource module does not expose `resource.resource`
57
+
58
+ ## Route Roots
59
+
60
+ Use the real route root you want in the app:
61
+
62
+ - index-route parent child: `admin/catalog/index/products`
63
+ - nested under a record view page: `admin/customers/[customerId]/index/pets`
64
+ - file-route parent child: `admin/customers/[customerId]/orders`
65
+ - top-level route: `admin/products`
66
+
67
+ There is no separate `surface`, `directory-prefix`, `route-path`, or `container` assembly step anymore.
@@ -1,22 +1,17 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/crud-ui-generator",
4
- version: "0.1.14",
4
+ version: "0.1.15",
5
5
  kind: "generator",
6
- description: "Generate app-local CRUD UI scaffolds from resource validators.",
6
+ description: "Generate CRUD route trees from resource validators at an explicit route root relative to src/pages/.",
7
7
  options: {
8
- namespace: {
8
+ "target-root": {
9
9
  required: true,
10
10
  inputType: "text",
11
11
  defaultValue: "",
12
- promptLabel: "UI namespace",
13
- promptHint: "Required slug (example: contacts-alt, customers-ui, tickets-view)."
14
- },
15
- surface: {
16
- required: true,
17
- inputType: "text",
18
- promptLabel: "Target surface",
19
- promptHint: "Must match an enabled surface id."
12
+ normalizationType: "pages-relative-target-root",
13
+ promptLabel: "Target route root",
14
+ promptHint: "Explicit route root relative to src/pages/ (example: admin/catalog/products)."
20
15
  },
21
16
  "resource-file": {
22
17
  required: true,
@@ -26,11 +21,11 @@ export default Object.freeze({
26
21
  promptHint: "Relative path from app root to the resource module."
27
22
  },
28
23
  operations: {
29
- required: true,
24
+ required: false,
30
25
  inputType: "text",
31
- defaultValue: "",
26
+ defaultValue: "list,view,new,edit",
32
27
  promptLabel: "Operations",
33
- promptHint: "Required comma-separated values from: list, view, new, edit."
28
+ promptHint: "Optional comma-separated values from: list, view, new, edit. Defaults to all four."
34
29
  },
35
30
  "display-fields": {
36
31
  required: false,
@@ -39,28 +34,6 @@ export default Object.freeze({
39
34
  promptLabel: "Display fields",
40
35
  promptHint: "Optional comma-separated field keys to render (must exist in selected operation schemas)."
41
36
  },
42
- "api-path": {
43
- required: false,
44
- inputType: "text",
45
- defaultFromOptionTemplate: "/${option:namespace|kebab}",
46
- promptLabel: "API path",
47
- promptHint: "Base API path without trailing id (defaults to /<namespace>, example: /contacts)."
48
- },
49
- "route-path": {
50
- required: false,
51
- inputType: "text",
52
- defaultFromOptionTemplate: "${option:namespace|kebab}",
53
- promptLabel: "Route path",
54
- promptHint: "List route path under the target surface (defaults to <namespace>, example: contacts)."
55
- },
56
- container: {
57
- required: false,
58
- inputType: "text",
59
- defaultValue: "",
60
- promptLabel: "Container host",
61
- promptHint:
62
- "Optional container host slug (example: practice). Routes are generated under this prefix and list menu placement defaults to <container>:sub-pages."
63
- },
64
37
  "id-param": {
65
38
  required: false,
66
39
  inputType: "text",
@@ -68,35 +41,26 @@ export default Object.freeze({
68
41
  promptLabel: "Route id param",
69
42
  promptHint: "Route param used by view and edit pages (default: recordId)."
70
43
  },
71
- "directory-prefix": {
44
+ force: {
72
45
  required: false,
73
- inputType: "text",
74
- defaultValue: "",
75
- promptLabel: "Page directory prefix",
76
- promptHint: "Optional subpath under the selected surface pages root (example: crm or ops/team-a)."
77
- },
78
- placement: {
79
- required: false,
80
- inputType: "text",
46
+ inputType: "flag",
81
47
  defaultValue: "",
82
- promptLabel: "Menu placement",
83
- promptHint: "Optional host:position target (defaults to ShellLayout default outlet)."
48
+ promptLabel: "Force overwrite",
49
+ promptHint: "Overwrite generated CRUD files if the target root already exists."
84
50
  },
85
- "placement-component-token": {
51
+ "link-placement": {
86
52
  required: false,
87
53
  inputType: "text",
88
54
  defaultValue: "",
89
- promptLabel: "Placement component token",
90
- promptHint:
91
- "Optional component token override for generated menu placement. Use local.main.ui.tab-link-item for routed tab links (auto-provisions src/components/TabLinkItem.vue + MainClientProvider registration)."
55
+ promptLabel: "Link placement",
56
+ promptHint: "Optional host:position override for the generated list-page link placement."
92
57
  },
93
- "placement-to": {
58
+ namespace: {
94
59
  required: false,
95
60
  inputType: "text",
96
61
  defaultValue: "",
97
- promptLabel: "Placement to",
98
- promptHint:
99
- "Optional explicit props.to value for generated menu placement (example: ./pets). Required when adding placement for dynamic directory-prefix/route-path values."
62
+ promptLabel: "Namespace override",
63
+ promptHint: "Optional CRUD namespace override when the resource export does not expose resource.resource."
100
64
  }
101
65
  },
102
66
  dependsOn: [],
@@ -115,6 +79,53 @@ export default Object.freeze({
115
79
  metadata: {
116
80
  generatorPrimarySubcommand: "crud",
117
81
  generatorSubcommands: {
82
+ crud: {
83
+ description: "Create CRUD pages at an explicit route root relative to src/pages/.",
84
+ longDescription: [
85
+ "CRUD generation follows the same page-placement model as `ui-generator page`.",
86
+ "That means the generated list page link uses the same nearest-parent-host inference, tab-link inference, and relative `props.to` inference as a normal generated page. If you want the detailed host behavior, read `jskit generate ui-generator page help`."
87
+ ],
88
+ positionalArgs: [
89
+ {
90
+ name: "target-root",
91
+ required: true,
92
+ descriptionKey: "crud-target-root"
93
+ }
94
+ ],
95
+ optionNames: ["resource-file", "operations", "display-fields", "id-param", "link-placement", "namespace", "force"],
96
+ requiredOptionNames: ["resource-file"],
97
+ createTarget: {
98
+ pathTemplate: "src/pages/${option:target-root|trim}",
99
+ label: "target root",
100
+ allowExistingEmptyDirectory: true
101
+ },
102
+ notes: [
103
+ "The target root is the real route root relative to src/pages/.... JSKIT derives the surface and route from that path.",
104
+ "Operations default to list,view,new,edit. For list-page placement behavior, use the same mental model as ui-generator page.",
105
+ "If the target root already exists and is not empty, rerun with --force to overwrite generated files."
106
+ ],
107
+ examples: [
108
+ {
109
+ label: "Common usage",
110
+ lines: [
111
+ "npx jskit generate crud-ui-generator crud \\",
112
+ " admin/catalog/index/products \\",
113
+ " --resource-file packages/products/src/shared/productResource.js"
114
+ ]
115
+ },
116
+ {
117
+ label: "More advanced usage",
118
+ lines: [
119
+ "npx jskit generate crud-ui-generator crud \\",
120
+ " admin/customers/[customerId]/index/pets \\",
121
+ " --resource-file packages/pets/src/shared/petResource.js \\",
122
+ " --id-param petId \\",
123
+ " --display-fields name,breedId,sex \\",
124
+ " --force"
125
+ ]
126
+ }
127
+ ]
128
+ },
118
129
  field: {
119
130
  entrypoint: "src/server/subcommands/addField.js",
120
131
  export: "runGeneratorSubcommand"
@@ -124,7 +135,7 @@ export default Object.freeze({
124
135
  surfaces: [
125
136
  {
126
137
  subpath: "./server/buildTemplateContext",
127
- summary: "Builds deterministic template context values from selected resource operation validators."
138
+ summary: "Builds deterministic CRUD UI template context values from the explicit route root and resource validators."
128
139
  }
129
140
  ],
130
141
  containerTokens: {
@@ -136,7 +147,7 @@ export default Object.freeze({
136
147
  mutations: {
137
148
  dependencies: {
138
149
  runtime: {
139
- "@jskit-ai/users-web": "0.1.46"
150
+ "@jskit-ai/users-web": "0.1.47"
140
151
  },
141
152
  dev: {}
142
153
  },
@@ -147,11 +158,10 @@ export default Object.freeze({
147
158
  files: [
148
159
  {
149
160
  from: "templates/src/pages/admin/ui-generator/ListElement.vue",
150
- toSurface: "${option:surface|lower}",
151
- toSurfacePath: "${option:directory-prefix|pathprefix}${option:container|pathprefix}${option:route-path|path}/index.vue",
161
+ to: "src/pages/${option:target-root|trim}/index.vue",
152
162
  reason: "Install generated list page.",
153
- category: "ui-generator",
154
- id: "ui-generator-page-list-${option:namespace|snake}",
163
+ category: "crud-ui-generator",
164
+ id: "crud-ui-page-list-${option:target-root|snake}",
155
165
  templateContext: {
156
166
  entrypoint: "src/server/buildTemplateContext.js",
157
167
  export: "buildUiTemplateContext"
@@ -163,12 +173,10 @@ export default Object.freeze({
163
173
  },
164
174
  {
165
175
  from: "templates/src/pages/admin/ui-generator/ViewElement.vue",
166
- toSurface: "${option:surface|lower}",
167
- toSurfacePath:
168
- "${option:directory-prefix|pathprefix}${option:container|pathprefix}${option:route-path|path}/[${option:id-param|trim}]/index.vue",
176
+ to: "src/pages/${option:target-root|trim}/[${option:id-param|trim}]/index.vue",
169
177
  reason: "Install generated view page.",
170
- category: "ui-generator",
171
- id: "ui-generator-page-view-${option:namespace|snake}",
178
+ category: "crud-ui-generator",
179
+ id: "crud-ui-page-view-${option:target-root|snake}",
172
180
  templateContext: {
173
181
  entrypoint: "src/server/buildTemplateContext.js",
174
182
  export: "buildUiTemplateContext"
@@ -180,11 +188,10 @@ export default Object.freeze({
180
188
  },
181
189
  {
182
190
  from: "templates/src/pages/admin/ui-generator/NewWrapperElement.vue",
183
- toSurface: "${option:surface|lower}",
184
- toSurfacePath: "${option:directory-prefix|pathprefix}${option:container|pathprefix}${option:route-path|path}/new.vue",
191
+ to: "src/pages/${option:target-root|trim}/new.vue",
185
192
  reason: "Install generated new page.",
186
- category: "ui-generator",
187
- id: "ui-generator-page-new-${option:namespace|snake}",
193
+ category: "crud-ui-generator",
194
+ id: "crud-ui-page-new-${option:target-root|snake}",
188
195
  templateContext: {
189
196
  entrypoint: "src/server/buildTemplateContext.js",
190
197
  export: "buildUiTemplateContext"
@@ -204,12 +211,10 @@ export default Object.freeze({
204
211
  },
205
212
  {
206
213
  from: "templates/src/pages/admin/ui-generator/EditWrapperElement.vue",
207
- toSurface: "${option:surface|lower}",
208
- toSurfacePath:
209
- "${option:directory-prefix|pathprefix}${option:container|pathprefix}${option:route-path|path}/[${option:id-param|trim}]/edit.vue",
214
+ to: "src/pages/${option:target-root|trim}/[${option:id-param|trim}]/edit.vue",
210
215
  reason: "Install generated edit page.",
211
- category: "ui-generator",
212
- id: "ui-generator-page-edit-${option:namespace|snake}",
216
+ category: "crud-ui-generator",
217
+ id: "crud-ui-page-edit-${option:target-root|snake}",
213
218
  templateContext: {
214
219
  entrypoint: "src/server/buildTemplateContext.js",
215
220
  export: "buildUiTemplateContext"
@@ -229,12 +234,10 @@ export default Object.freeze({
229
234
  },
230
235
  {
231
236
  from: "templates/src/pages/admin/ui-generator/AddEditForm.vue",
232
- toSurface: "${option:surface|lower}",
233
- toSurfacePath:
234
- "${option:directory-prefix|pathprefix}${option:container|pathprefix}${option:route-path|path}/_components/${option:namespace|singular|pascal|default(Record)}AddEditForm.vue",
237
+ to: "src/pages/${option:target-root|trim}/_components/CrudAddEditForm.vue",
235
238
  reason: "Install generated shared add/edit form component.",
236
- category: "ui-generator",
237
- id: "ui-generator-page-add-edit-form-${option:namespace|snake}",
239
+ category: "crud-ui-generator",
240
+ id: "crud-ui-page-add-edit-form-${option:target-root|snake}",
238
241
  templateContext: {
239
242
  entrypoint: "src/server/buildTemplateContext.js",
240
243
  export: "buildUiTemplateContext"
@@ -254,12 +257,10 @@ export default Object.freeze({
254
257
  },
255
258
  {
256
259
  from: "templates/src/pages/admin/ui-generator/AddEditFormFields.js",
257
- toSurface: "${option:surface|lower}",
258
- toSurfacePath:
259
- "${option:directory-prefix|pathprefix}${option:container|pathprefix}${option:route-path|path}/_components/${option:namespace|singular|pascal|default(Record)}AddEditFormFields.js",
260
+ to: "src/pages/${option:target-root|trim}/_components/CrudAddEditFormFields.js",
260
261
  reason: "Install generated shared add/edit form field definitions.",
261
- category: "ui-generator",
262
- id: "ui-generator-page-add-edit-form-fields-${option:namespace|snake}",
262
+ category: "crud-ui-generator",
263
+ id: "crud-ui-page-add-edit-form-fields-${option:target-root|snake}",
263
264
  templateContext: {
264
265
  entrypoint: "src/server/buildTemplateContext.js",
265
266
  export: "buildUiTemplateContext"
@@ -279,11 +280,10 @@ export default Object.freeze({
279
280
  },
280
281
  {
281
282
  from: "templates/src/pages/admin/ui-generator/NewElement.vue",
282
- toSurface: "${option:surface|lower}",
283
- toSurfacePath: "${option:directory-prefix|pathprefix}${option:container|pathprefix}${option:route-path|path}/new.vue",
283
+ to: "src/pages/${option:target-root|trim}/new.vue",
284
284
  reason: "Install generated new page.",
285
- category: "ui-generator",
286
- id: "ui-generator-page-new-standalone-${option:namespace|snake}",
285
+ category: "crud-ui-generator",
286
+ id: "crud-ui-page-new-standalone-${option:target-root|snake}",
287
287
  templateContext: {
288
288
  entrypoint: "src/server/buildTemplateContext.js",
289
289
  export: "buildUiTemplateContext"
@@ -303,12 +303,10 @@ export default Object.freeze({
303
303
  },
304
304
  {
305
305
  from: "templates/src/pages/admin/ui-generator/EditElement.vue",
306
- toSurface: "${option:surface|lower}",
307
- toSurfacePath:
308
- "${option:directory-prefix|pathprefix}${option:container|pathprefix}${option:route-path|path}/[${option:id-param|trim}]/edit.vue",
306
+ to: "src/pages/${option:target-root|trim}/[${option:id-param|trim}]/edit.vue",
309
307
  reason: "Install generated edit page.",
310
- category: "ui-generator",
311
- id: "ui-generator-page-edit-standalone-${option:namespace|snake}",
308
+ category: "crud-ui-generator",
309
+ id: "crud-ui-page-edit-standalone-${option:target-root|snake}",
312
310
  templateContext: {
313
311
  entrypoint: "src/server/buildTemplateContext.js",
314
312
  export: "buildUiTemplateContext"
@@ -332,52 +330,19 @@ export default Object.freeze({
332
330
  op: "append-text",
333
331
  file: "src/placement.js",
334
332
  position: "bottom",
335
- skipIfContains:
336
- "jskit:ui-generator.menu:${option:namespace|kebab}:${option:directory-prefix|path}:${option:container|path}:${option:route-path|path}",
333
+ skipIfContains: "__JSKIT_UI_MENU_MARKER__",
337
334
  value:
338
- "\n// jskit:ui-generator.menu:${option:namespace|kebab}:${option:directory-prefix|path}:${option:container|path}:${option:route-path|path}\n{\n addPlacement({\n id: \"ui-generator.${option:namespace|kebab}.menu\",\n host: \"__JSKIT_UI_MENU_PLACEMENT_HOST__\",\n position: \"__JSKIT_UI_MENU_PLACEMENT_POSITION__\",\n surfaces: [\"${option:surface|lower}\"],\n order: 155,\n componentToken: \"__JSKIT_UI_MENU_COMPONENT_TOKEN__\",\n props: {\n label: \"${option:namespace|plural|pascal}\",\n surface: \"${option:surface|lower}\",\n workspaceSuffix: \"__JSKIT_UI_MENU_WORKSPACE_SUFFIX__\",\n nonWorkspaceSuffix: \"__JSKIT_UI_MENU_NON_WORKSPACE_SUFFIX__\",\n__JSKIT_UI_MENU_TO_PROP_LINE__ },\n when: ({ auth }) => Boolean(auth?.authenticated)\n });\n}\n",
339
- reason: "Append generated UI menu placement.",
340
- category: "ui-generator",
341
- id: "ui-generator-placement-menu",
335
+ "\n// __JSKIT_UI_MENU_MARKER__\n{\n addPlacement({\n id: \"__JSKIT_UI_MENU_PLACEMENT_ID__\",\n host: \"__JSKIT_UI_MENU_PLACEMENT_HOST__\",\n position: \"__JSKIT_UI_MENU_PLACEMENT_POSITION__\",\n surfaces: [\"__JSKIT_UI_SURFACE_ID__\"],\n order: 155,\n componentToken: \"__JSKIT_UI_MENU_COMPONENT_TOKEN__\",\n props: {\n label: \"__JSKIT_UI_MENU_LABEL__\",\n surface: \"__JSKIT_UI_SURFACE_ID__\",\n workspaceSuffix: \"__JSKIT_UI_MENU_WORKSPACE_SUFFIX__\",\n nonWorkspaceSuffix: \"__JSKIT_UI_MENU_NON_WORKSPACE_SUFFIX__\",\n__JSKIT_UI_MENU_TO_PROP_LINE__ },\n when: ({ auth }) => Boolean(auth?.authenticated)\n });\n}\n",
336
+ reason: "Append generated CRUD list-page placement.",
337
+ category: "crud-ui-generator",
338
+ id: "crud-ui-placement-menu",
342
339
  templateContext: {
343
340
  entrypoint: "src/server/buildTemplateContext.js",
344
341
  export: "buildUiTemplateContext"
345
342
  },
346
343
  when: {
347
- all: [
348
- {
349
- option: "operations",
350
- in: ["list"]
351
- },
352
- {
353
- any: [
354
- {
355
- all: [
356
- {
357
- option: "route-path",
358
- notContains: "["
359
- },
360
- {
361
- option: "directory-prefix",
362
- notContains: "["
363
- }
364
- ]
365
- },
366
- {
367
- all: [
368
- {
369
- option: "placement",
370
- contains: ":"
371
- },
372
- {
373
- option: "placement-to",
374
- notEquals: ""
375
- }
376
- ]
377
- }
378
- ]
379
- }
380
- ]
344
+ option: "operations",
345
+ in: ["list"]
381
346
  }
382
347
  }
383
348
  ]
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@jskit-ai/crud-ui-generator",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
7
7
  },
8
8
  "dependencies": {
9
- "@jskit-ai/crud-core": "0.1.39",
10
- "@jskit-ai/kernel": "0.1.31"
9
+ "@jskit-ai/crud-core": "0.1.40",
10
+ "@jskit-ai/kernel": "0.1.32"
11
11
  },
12
12
  "exports": {
13
13
  "./server/buildTemplateContext": "./src/server/buildTemplateContext.js"