@sealcode/sealgen 0.17.6 → 0.17.7

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.
@@ -0,0 +1,3 @@
1
+ export declare function addVerboseCRUD(params: Partial<{
2
+ [key in "collection" | "url"]: string;
3
+ }>, app_directory?: string): Promise<void>;
@@ -30,11 +30,11 @@ export type FieldHandlerResult = {
30
30
  fallback_value_sealious: string;
31
31
  main_value_sealious: string;
32
32
  };
33
- export type FieldHandler<Defaults extends Partial<FieldHandlerResult>, Result extends Partial<FieldHandlerResult>> = (collection_name: string, field: ExtractedFieldInfo, importPath: (path: string) => string, defaults: Defaults) => Promise<Result>;
33
+ export type FieldHandler<Defaults extends Partial<FieldHandlerResult>, Result extends Partial<FieldHandlerResult>> = (collection_name: string, field: ExtractedFieldInfo, importPath: (path: string) => string, defaults: Defaults, fields_var?: string) => Promise<Result>;
34
34
  export declare const field_handlers: Record<Exclude<string, "default">, FieldHandler<FieldHandlerResult, Partial<FieldHandlerResult>> | undefined> & {
35
35
  default: FieldHandler<Record<string, never>, FieldHandlerResult>;
36
36
  };
37
- export declare function getFieldHandler(collection_name: string, field: ExtractedFieldInfo, importPath: (path: string) => string): Promise<{
37
+ export declare function getFieldHandler(collection_name: string, field: ExtractedFieldInfo, importPath: (path: string) => string, fields_var?: string): Promise<{
38
38
  imports: Partial<{
39
39
  shared: string;
40
40
  edit: string;
package/lib/add-crud.js CHANGED
@@ -3,15 +3,16 @@ import { escape_url_params } from "./utils/escape-url-params.js";
3
3
  import prompts from "prompts";
4
4
  import { toKebabCase, toPascalCase } from "js-convert-case";
5
5
  import { listCollections } from "./utils/list-collections.js";
6
- import { collectionListTemplate } from "./templates/shared/collection-list.js";
6
+ import {
7
+ makeDefaultDisplayFields,
8
+ makeDefaultFilterFields
9
+ } from "./templates/shared/collection-list.js";
7
10
  import { writeFile } from "./utils/write-file.js";
8
- import { importPath } from "./utils/import-path.js";
9
- import { sharedCrudFormFields } from "./templates/shared/shared-crud-form-fields.js";
11
+ import { extractCollectionClassname } from "./generate-collections.js";
12
+ import extract_fields_from_collection from "./utils/extract-fields-from-collection.js";
13
+ import { getFieldHandler } from "./templates/shared/shared-crud-form-fields.js";
10
14
  import { generateRoutes } from "./generate-routes.js";
11
- import { createItemFormTemplate } from "./templates/shared/collection-create-form.js";
12
- import { editItemFormTemplate } from "./templates/shared/collection-edit-form.js";
13
- import { askItemDeleteTemplate } from "./templates/shared/confirm-item-delete.js";
14
- import { itemDeleteTemplate } from "./templates/shared/item-delete.js";
15
+ import { formatWithPrettier } from "./utils/prettier.js";
15
16
  async function addCRUD(params, app_directory = process.cwd()) {
16
17
  const target_locreq = _locreq(app_directory);
17
18
  prompts.override(params);
@@ -28,7 +29,7 @@ async function addCRUD(params, app_directory = process.cwd()) {
28
29
  {
29
30
  type: "text",
30
31
  name: "url",
31
- message: "Enter a full absolute path for the new route (for example: /admin/users/:id/edit): ",
32
+ message: "Enter a full absolute path for the new route (for example: /admin/users/): ",
32
33
  validate: (s) => s.trim()[0] == "/" ? true : "Should start with a '/'",
33
34
  initial: function(_, { collection }) {
34
35
  return "/" + toKebabCase(collection);
@@ -37,92 +38,88 @@ async function addCRUD(params, app_directory = process.cwd()) {
37
38
  ]);
38
39
  const collection_name = response.collection;
39
40
  const url = response.url;
40
- const list_action_name = toPascalCase(collection_name) + "CRUDList";
41
- const create_action_name = toPascalCase(collection_name) + "CRUDCreate";
42
- const edit_action_name = toPascalCase(collection_name) + "CRUDEdit";
43
- const delete_ask_action_name = toPascalCase(collection_name) + "CRUDDeleteAsk";
44
- const delete_action_name = toPascalCase(collection_name) + "CRUDDelete";
45
41
  const list_path = target_locreq.resolve(
46
- `src/back/routes/${escape_url_params(url)}.list.tsx`
42
+ `src/back/routes/${escape_url_params(url)}/index.page.ts`
47
43
  );
48
- const list_content = await collectionListTemplate(
49
- collection_name,
50
- list_action_name,
51
- list_path,
52
- {
53
- post_import_js: `import { ${create_action_name}URL, ${edit_action_name}URL, ${delete_ask_action_name}URL } from "${importPath(
54
- list_path,
55
- "src/back/routes/urls.ts"
56
- )}";`,
57
- post_header_html: `<a href={${create_action_name}URL}> Create </a>`,
58
- render_item: (collection_name2) => ` async renderItem(ctx: Context, item: CollectionItem<typeof ${toPascalCase(
59
- collection_name2
60
- )}>) {
61
- return <tr>
62
- {displayFields.map(({ field, format }) => {
63
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
64
- const value = item.get(field as any);
65
- return <td>{format ? format(value, item) : value}</td>;
66
- })}
67
- <td><div class="sealious-list__actions">
68
- <a href={${edit_action_name}URL(item.id)}>Edit</a>
69
- <a href={${delete_ask_action_name}URL(item.id)}>Delete</a>
70
- </div>
71
- </td>
72
- </tr>;
73
- }`
74
- }
75
- );
76
- await writeFile(list_path, list_content);
77
- const fields_path = target_locreq.resolve(
78
- `src/back/routes/${escape_url_params(url)}/shared.ts`
79
- );
80
- const fields_content = await sharedCrudFormFields(
81
- collection_name,
82
- fields_path
83
- );
84
- await writeFile(fields_path, fields_content);
85
- const create_path = target_locreq.resolve(
86
- `src/back/routes/${escape_url_params(url)}/create.form.ts`
87
- );
88
- const create_content = await createItemFormTemplate(
89
- create_action_name,
90
- create_path,
91
- collection_name,
92
- list_action_name
93
- );
94
- await writeFile(create_path, create_content);
95
- const edit_path = target_locreq.resolve(
96
- `src/back/routes/${escape_url_params(url)}/[id]/edit.form.ts`
97
- );
98
- const edit_content = await editItemFormTemplate(
99
- edit_action_name,
100
- edit_path,
101
- collection_name,
102
- list_action_name
103
- );
104
- await writeFile(edit_path, edit_content);
105
- const delete_ask_path = target_locreq.resolve(
106
- `src/back/routes/${escape_url_params(url)}/[id]/delete-ask.page.tsx`
107
- );
108
- const delete_ask_content = await askItemDeleteTemplate(
109
- delete_ask_action_name,
110
- collection_name,
111
- delete_ask_path,
112
- list_action_name,
113
- delete_action_name
114
- );
115
- await writeFile(delete_ask_path, delete_ask_content);
116
- const delete_path = target_locreq.resolve(
117
- `src/back/routes/${escape_url_params(url)}/[id]/delete.page.tsx`
118
- );
119
- const delete_content = await itemDeleteTemplate(
120
- delete_action_name,
121
- collection_name,
122
- delete_ask_path,
123
- list_action_name
44
+ const [uppercase_collection, collection_fields] = await Promise.all([
45
+ extractCollectionClassname(
46
+ target_locreq.resolve(
47
+ "src/back/collections/" + collection_name + ".ts"
48
+ )
49
+ ),
50
+ extract_fields_from_collection(collection_name)
51
+ ]);
52
+ const field_handler_results = await Promise.all(
53
+ collection_fields.map(async (field) => {
54
+ return getFieldHandler(
55
+ collection_name,
56
+ field,
57
+ (p) => p,
58
+ "edit_fields"
59
+ );
60
+ })
124
61
  );
125
- await writeFile(delete_path, delete_content);
62
+ const list_content = `import { CRUD } from "@sealcode/crud-ui";
63
+ import type { CollectionItem } from "sealious";
64
+ import type { ListFilterRender } from "@sealcode/sealgen";
65
+ import { Controls, DefaultListFilters, Fields } from "@sealcode/sealgen";
66
+ import { ${toPascalCase(
67
+ collection_name
68
+ )} } from "src/back/collections/collections.js";
69
+ import html from "src/back/html.js";
70
+
71
+ export const actionName = "${toPascalCase(collection_name)}CRUD";
72
+
73
+ const edit_fields = <const>
74
+ {
75
+ ${field_handler_results.filter(({ hide_field }) => !hide_field).map(({ field }) => field).filter((e) => e != "").join(",\n")}
76
+ };
77
+
78
+
79
+ const edit_controls = [
80
+ ${field_handler_results.filter(({ hide_control }) => !hide_control).map(({ controls }) => controls).filter((e) => e != "").join(",\n")}
81
+ ]
82
+
83
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
84
+ const fields_for_display = [
85
+ ${makeDefaultDisplayFields(collection_fields)}
86
+ ] as {
87
+ field: keyof (typeof ${toPascalCase(collection_name)})["fields"];
88
+ label: string;
89
+ format?: (value: unknown, item: CollectionItem<typeof ${toPascalCase(
90
+ collection_name
91
+ )}>) => string | Promise<string>;
92
+ }[];
93
+
94
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
95
+ const fields_for_filters = [
96
+ ${makeDefaultFilterFields(collection_fields)}
97
+ ] as {
98
+ field: keyof (typeof ${uppercase_collection})["fields"];
99
+ render?: ListFilterRender;
100
+ prepareValue?: (filter_value: unknown)=>unknown; // set this function to change what filter value is passed to Sealious
101
+ }[];
102
+
103
+ export default new CRUD({
104
+ collection: ${toPascalCase(collection_name)},
105
+ nice_collection_name: "${toPascalCase(collection_name)}",
106
+ fields_for_display,
107
+ fields_for_filters,
108
+ html,
109
+ list_title: "${toPascalCase(collection_name)} list",
110
+ edit_title: "Edit",
111
+ edit_button_text: "Edit",
112
+ delete_button_text: "Delete",
113
+ back_to_list_button_text: "\u2190 Back to ${toPascalCase(collection_name)} list",
114
+ edit_fields,
115
+ edit_controls,
116
+ form_value_to_sealious_value: {},
117
+ sealious_value_to_form_value: {},
118
+ post_edit: async () => {},
119
+ post_create: async () => {},
120
+ });
121
+ `;
122
+ await writeFile(list_path, await formatWithPrettier(list_content));
126
123
  await generateRoutes();
127
124
  }
128
125
  export {
@@ -0,0 +1,131 @@
1
+ import _locreq from "locreq";
2
+ import { escape_url_params } from "./utils/escape-url-params.js";
3
+ import prompts from "prompts";
4
+ import { toKebabCase, toPascalCase } from "js-convert-case";
5
+ import { listCollections } from "./utils/list-collections.js";
6
+ import { collectionListTemplate } from "./templates/shared/collection-list.js";
7
+ import { writeFile } from "./utils/write-file.js";
8
+ import { importPath } from "./utils/import-path.js";
9
+ import { sharedCrudFormFields } from "./templates/shared/shared-crud-form-fields.js";
10
+ import { generateRoutes } from "./generate-routes.js";
11
+ import { createItemFormTemplate } from "./templates/shared/collection-create-form.js";
12
+ import { editItemFormTemplate } from "./templates/shared/collection-edit-form.js";
13
+ import { askItemDeleteTemplate } from "./templates/shared/confirm-item-delete.js";
14
+ import { itemDeleteTemplate } from "./templates/shared/item-delete.js";
15
+ async function addVerboseCRUD(params, app_directory = process.cwd()) {
16
+ const target_locreq = _locreq(app_directory);
17
+ prompts.override(params);
18
+ const response = await prompts([
19
+ {
20
+ type: "autocomplete",
21
+ name: "collection",
22
+ message: "Which sealious collection do you like to add CRUD forms for?",
23
+ choices: (await listCollections()).map((collection) => ({
24
+ title: collection,
25
+ value: collection
26
+ }))
27
+ },
28
+ {
29
+ type: "text",
30
+ name: "url",
31
+ message: "Enter a full absolute path for the new route (for example: /admin/users/:id/edit): ",
32
+ validate: (s) => s.trim()[0] == "/" ? true : "Should start with a '/'",
33
+ initial: function(_, { collection }) {
34
+ return "/" + toKebabCase(collection);
35
+ }
36
+ }
37
+ ]);
38
+ const collection_name = response.collection;
39
+ const url = response.url;
40
+ const list_action_name = toPascalCase(collection_name) + "CRUDList";
41
+ const create_action_name = toPascalCase(collection_name) + "CRUDCreate";
42
+ const edit_action_name = toPascalCase(collection_name) + "CRUDEdit";
43
+ const delete_ask_action_name = toPascalCase(collection_name) + "CRUDDeleteAsk";
44
+ const delete_action_name = toPascalCase(collection_name) + "CRUDDelete";
45
+ const list_path = target_locreq.resolve(
46
+ `src/back/routes/${escape_url_params(url)}/index.list.tsx`
47
+ );
48
+ const list_content = await collectionListTemplate(
49
+ collection_name,
50
+ list_action_name,
51
+ list_path,
52
+ {
53
+ post_import_js: `import { ${create_action_name}URL, ${edit_action_name}URL, ${delete_ask_action_name}URL } from "${importPath(
54
+ list_path,
55
+ "src/back/routes/urls.ts"
56
+ )}";`,
57
+ post_header_html: `<a href={${create_action_name}URL}> Create </a>`,
58
+ render_item: (collection_name2) => ` async renderItem(ctx: Context, item: CollectionItem<typeof ${toPascalCase(
59
+ collection_name2
60
+ )}>) {
61
+ return <tr>
62
+ {displayFields.map(({ field, format }) => {
63
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
64
+ const value = item.get(field as any);
65
+ return <td>{format ? format(value, item) : value}</td>;
66
+ })}
67
+ <td><div class="sealious-list__actions">
68
+ <a href={${edit_action_name}URL(item.id)}>Edit</a>
69
+ <a href={${delete_ask_action_name}URL(item.id)}>Delete</a>
70
+ </div>
71
+ </td>
72
+ </tr>;
73
+ }`
74
+ }
75
+ );
76
+ await writeFile(list_path, list_content);
77
+ const fields_path = target_locreq.resolve(
78
+ `src/back/routes/${escape_url_params(url)}/shared.ts`
79
+ );
80
+ const fields_content = await sharedCrudFormFields(
81
+ collection_name,
82
+ fields_path
83
+ );
84
+ await writeFile(fields_path, fields_content);
85
+ const create_path = target_locreq.resolve(
86
+ `src/back/routes/${escape_url_params(url)}/create.form.ts`
87
+ );
88
+ const create_content = await createItemFormTemplate(
89
+ create_action_name,
90
+ create_path,
91
+ collection_name,
92
+ list_action_name
93
+ );
94
+ await writeFile(create_path, create_content);
95
+ const edit_path = target_locreq.resolve(
96
+ `src/back/routes/${escape_url_params(url)}/[id]/edit.form.ts`
97
+ );
98
+ const edit_content = await editItemFormTemplate(
99
+ edit_action_name,
100
+ edit_path,
101
+ collection_name,
102
+ list_action_name
103
+ );
104
+ await writeFile(edit_path, edit_content);
105
+ const delete_ask_path = target_locreq.resolve(
106
+ `src/back/routes/${escape_url_params(url)}/[id]/delete-ask.page.tsx`
107
+ );
108
+ const delete_ask_content = await askItemDeleteTemplate(
109
+ delete_ask_action_name,
110
+ collection_name,
111
+ delete_ask_path,
112
+ list_action_name,
113
+ delete_action_name
114
+ );
115
+ await writeFile(delete_ask_path, delete_ask_content);
116
+ const delete_path = target_locreq.resolve(
117
+ `src/back/routes/${escape_url_params(url)}/[id]/delete.page.tsx`
118
+ );
119
+ const delete_content = await itemDeleteTemplate(
120
+ delete_action_name,
121
+ collection_name,
122
+ delete_ask_path,
123
+ list_action_name
124
+ );
125
+ await writeFile(delete_path, delete_content);
126
+ await generateRoutes();
127
+ }
128
+ export {
129
+ addVerboseCRUD
130
+ };
131
+ //# sourceMappingURL=add-verbose-crud.js.map
package/lib/cli.js CHANGED
@@ -9,10 +9,12 @@ import { generateRoutes } from "./generate-routes.js";
9
9
  import { makeEnv } from "./make-env.js";
10
10
  import yargs from "yargs/yargs";
11
11
  import { getFonts } from "./get-fonts.js";
12
+ import { addVerboseCRUD } from "./add-verbose-crud.js";
12
13
  import { addCRUD } from "./add-crud.js";
13
14
  const actions = {
14
15
  "add-collection": addCollection,
15
16
  "add-route": addRoute,
17
+ "add-verbose-crud": addVerboseCRUD,
16
18
  "add-crud": addCRUD,
17
19
  "generate-collections": generateCollections,
18
20
  "generate-components": generateComponents,
@@ -1,9 +1,8 @@
1
1
  import { toPascalCase } from "js-convert-case";
2
- const default_handler = async (collection_name, { name, is_readonly, is_required }) => {
2
+ const default_handler = async (collection_name, { name, is_readonly, is_required }, _, __, fields_var = `${toPascalCase(collection_name)}FormFields`) => {
3
3
  const required_s = `${toPascalCase(
4
4
  collection_name
5
5
  )}.fields.${name}.required`;
6
- const fields_var = `${toPascalCase(collection_name)}FormFields`;
7
6
  const fallback_value_initial = `""`;
8
7
  const fallback_value_sealious = `""`;
9
8
  const main_value_initial = `item.get("${name}")`;
@@ -30,12 +30,13 @@ const field_handlers = {
30
30
  "structured-array": structured_array_handler,
31
31
  "single-reference": single_reference_handler
32
32
  };
33
- async function getFieldHandler(collection_name, field, importPath) {
33
+ async function getFieldHandler(collection_name, field, importPath, fields_var) {
34
34
  const defaults = await field_handlers.default(
35
35
  collection_name,
36
36
  field,
37
37
  importPath,
38
- {}
38
+ {},
39
+ fields_var
39
40
  );
40
41
  const result = {
41
42
  ...defaults,
@@ -43,7 +44,8 @@ async function getFieldHandler(collection_name, field, importPath) {
43
44
  collection_name,
44
45
  field,
45
46
  importPath,
46
- defaults
47
+ defaults,
48
+ fields_var
47
49
  ) || {}
48
50
  };
49
51
  const result_initial_value_edit = result.initial_value_edit;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.17.6",
3
+ "version": "0.17.7",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
package/src/add-crud.ts CHANGED
@@ -3,15 +3,16 @@ import { escape_url_params } from "./utils/escape-url-params.js";
3
3
  import prompts from "prompts";
4
4
  import { toKebabCase, toPascalCase } from "js-convert-case";
5
5
  import { listCollections } from "./utils/list-collections.js";
6
- import { collectionListTemplate } from "./templates/shared/collection-list.js";
6
+ import {
7
+ makeDefaultDisplayFields,
8
+ makeDefaultFilterFields,
9
+ } from "./templates/shared/collection-list.js";
7
10
  import { writeFile } from "./utils/write-file.js";
8
- import { importPath } from "./utils/import-path.js";
9
- import { sharedCrudFormFields } from "./templates/shared/shared-crud-form-fields.js";
11
+ import { extractCollectionClassname } from "./generate-collections.js";
12
+ import extract_fields_from_collection from "./utils/extract-fields-from-collection.js";
13
+ import { getFieldHandler } from "./templates/shared/shared-crud-form-fields.js";
10
14
  import { generateRoutes } from "./generate-routes.js";
11
- import { createItemFormTemplate } from "./templates/shared/collection-create-form.js";
12
- import { editItemFormTemplate } from "./templates/shared/collection-edit-form.js";
13
- import { askItemDeleteTemplate } from "./templates/shared/confirm-item-delete.js";
14
- import { itemDeleteTemplate } from "./templates/shared/item-delete.js";
15
+ import { formatWithPrettier } from "./utils/prettier.js";
15
16
 
16
17
  export async function addCRUD(
17
18
  params: Partial<{ [key in "collection" | "url"]: string }>,
@@ -36,7 +37,7 @@ export async function addCRUD(
36
37
  type: "text",
37
38
  name: "url",
38
39
  message:
39
- "Enter a full absolute path for the new route (for example: /admin/users/:id/edit): ",
40
+ "Enter a full absolute path for the new route (for example: /admin/users/): ",
40
41
  validate: (s: string) =>
41
42
  s.trim()[0] == "/" ? true : "Should start with a '/'",
42
43
  initial: function (_, { collection }: { collection: string }) {
@@ -47,107 +48,100 @@ export async function addCRUD(
47
48
 
48
49
  const collection_name = response.collection as string;
49
50
  const url = response.url as string;
50
- const list_action_name = toPascalCase(collection_name) + "CRUDList";
51
- const create_action_name = toPascalCase(collection_name) + "CRUDCreate";
52
- const edit_action_name = toPascalCase(collection_name) + "CRUDEdit";
53
- const delete_ask_action_name =
54
- toPascalCase(collection_name) + "CRUDDeleteAsk";
55
- const delete_action_name = toPascalCase(collection_name) + "CRUDDelete";
56
51
 
57
52
  /* create the list endpoint */
58
53
  const list_path = target_locreq.resolve(
59
- `src/back/routes/${escape_url_params(url)}.list.tsx`
54
+ `src/back/routes/${escape_url_params(url)}/index.page.ts`
60
55
  );
61
- const list_content = await collectionListTemplate(
62
- collection_name,
63
- list_action_name,
64
- list_path,
65
- {
66
- post_import_js: `import { ${create_action_name}URL, ${edit_action_name}URL, ${delete_ask_action_name}URL } from "${importPath(
67
- list_path,
68
- "src/back/routes/urls.ts"
69
- )}";`,
70
- post_header_html: `<a href={${create_action_name}URL}> Create </a>`,
71
- render_item: (
72
- collection_name: string
73
- ) => ` async renderItem(ctx: Context, item: CollectionItem<typeof ${toPascalCase(
74
- collection_name
75
- )}>) {
76
- return <tr>
77
- {displayFields.map(({ field, format }) => {
78
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
79
- const value = item.get(field as any);
80
- return <td>{format ? format(value, item) : value}</td>;
81
- })}
82
- <td><div class="sealious-list__actions">
83
- <a href={${edit_action_name}URL(item.id)}>Edit</a>
84
- <a href={${delete_ask_action_name}URL(item.id)}>Delete</a>
85
- </div>
86
- </td>
87
- </tr>;
88
- }`,
89
- }
90
- );
91
- await writeFile(list_path, list_content);
56
+ const [uppercase_collection, collection_fields] = await Promise.all([
57
+ extractCollectionClassname(
58
+ target_locreq.resolve(
59
+ "src/back/collections/" + collection_name + ".ts"
60
+ )
61
+ ),
62
+ extract_fields_from_collection(collection_name),
63
+ ]);
92
64
 
93
- /* create the shared fields file */
94
- const fields_path = target_locreq.resolve(
95
- `src/back/routes/${escape_url_params(url)}/shared.ts`
96
- );
97
- const fields_content = await sharedCrudFormFields(
98
- collection_name,
99
- fields_path
65
+ const field_handler_results = await Promise.all(
66
+ collection_fields.map(async (field) => {
67
+ return getFieldHandler(
68
+ collection_name,
69
+ field,
70
+ (p) => p,
71
+ "edit_fields"
72
+ );
73
+ })
100
74
  );
101
- await writeFile(fields_path, fields_content);
102
75
 
103
- /* create the create endpoint */
104
- const create_path = target_locreq.resolve(
105
- `src/back/routes/${escape_url_params(url)}/create.form.ts`
106
- );
107
- const create_content = await createItemFormTemplate(
108
- create_action_name,
109
- create_path,
110
- collection_name,
111
- list_action_name
112
- );
113
- await writeFile(create_path, create_content);
76
+ const list_content = `import { CRUD } from "@sealcode/crud-ui";
77
+ import type { CollectionItem } from "sealious";
78
+ import type { ListFilterRender } from "@sealcode/sealgen";
79
+ import { Controls, DefaultListFilters, Fields } from "@sealcode/sealgen";
80
+ import { ${toPascalCase(
81
+ collection_name
82
+ )} } from "src/back/collections/collections.js";
83
+ import html from "src/back/html.js";
114
84
 
115
- /* create the edit endpoint */
116
- const edit_path = target_locreq.resolve(
117
- `src/back/routes/${escape_url_params(url)}/[id]/edit.form.ts`
118
- );
119
- const edit_content = await editItemFormTemplate(
120
- edit_action_name,
121
- edit_path,
122
- collection_name,
123
- list_action_name
124
- );
125
- await writeFile(edit_path, edit_content);
85
+ export const actionName = "${toPascalCase(collection_name)}CRUD";
126
86
 
127
- /* create the confirm delete endpoint */
128
- const delete_ask_path = target_locreq.resolve(
129
- `src/back/routes/${escape_url_params(url)}/[id]/delete-ask.page.tsx`
130
- );
131
- const delete_ask_content = await askItemDeleteTemplate(
132
- delete_ask_action_name,
133
- collection_name,
134
- delete_ask_path,
135
- list_action_name,
136
- delete_action_name
137
- );
138
- await writeFile(delete_ask_path, delete_ask_content);
87
+ const edit_fields = <const>
88
+ {
89
+ ${field_handler_results
90
+ .filter(({ hide_field }) => !hide_field)
91
+ .map(({ field }) => field)
92
+ .filter((e) => e != "")
93
+ .join(",\n")}
94
+ };
139
95
 
140
- /* create the actual delete endpoint */
141
- const delete_path = target_locreq.resolve(
142
- `src/back/routes/${escape_url_params(url)}/[id]/delete.page.tsx`
143
- );
144
- const delete_content = await itemDeleteTemplate(
145
- delete_action_name,
146
- collection_name,
147
- delete_ask_path,
148
- list_action_name
149
- );
150
- await writeFile(delete_path, delete_content);
96
+
97
+ const edit_controls = [
98
+ ${field_handler_results
99
+ .filter(({ hide_control }) => !hide_control)
100
+ .map(({ controls }) => controls)
101
+ .filter((e) => e != "")
102
+ .join(",\n")}
103
+ ]
104
+
105
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
106
+ const fields_for_display = [
107
+ ${makeDefaultDisplayFields(collection_fields)}
108
+ ] as {
109
+ field: keyof (typeof ${toPascalCase(collection_name)})["fields"];
110
+ label: string;
111
+ format?: (value: unknown, item: CollectionItem<typeof ${toPascalCase(
112
+ collection_name
113
+ )}>) => string | Promise<string>;
114
+ }[];
115
+
116
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
117
+ const fields_for_filters = [
118
+ ${makeDefaultFilterFields(collection_fields)}
119
+ ] as {
120
+ field: keyof (typeof ${uppercase_collection})["fields"];
121
+ render?: ListFilterRender;
122
+ prepareValue?: (filter_value: unknown)=>unknown; // set this function to change what filter value is passed to Sealious
123
+ }[];
124
+
125
+ export default new CRUD({
126
+ collection: ${toPascalCase(collection_name)},
127
+ nice_collection_name: "${toPascalCase(collection_name)}",
128
+ fields_for_display,
129
+ fields_for_filters,
130
+ html,
131
+ list_title: "${toPascalCase(collection_name)} list",
132
+ edit_title: "Edit",
133
+ edit_button_text: "Edit",
134
+ delete_button_text: "Delete",
135
+ back_to_list_button_text: "← Back to ${toPascalCase(collection_name)} list",
136
+ edit_fields,
137
+ edit_controls,
138
+ form_value_to_sealious_value: {},
139
+ sealious_value_to_form_value: {},
140
+ post_edit: async () => {},
141
+ post_create: async () => {},
142
+ });
143
+ `;
144
+ await writeFile(list_path, await formatWithPrettier(list_content));
151
145
 
152
146
  await generateRoutes();
153
147
  }
@@ -0,0 +1,153 @@
1
+ import _locreq from "locreq";
2
+ import { escape_url_params } from "./utils/escape-url-params.js";
3
+ import prompts from "prompts";
4
+ import { toKebabCase, toPascalCase } from "js-convert-case";
5
+ import { listCollections } from "./utils/list-collections.js";
6
+ import { collectionListTemplate } from "./templates/shared/collection-list.js";
7
+ import { writeFile } from "./utils/write-file.js";
8
+ import { importPath } from "./utils/import-path.js";
9
+ import { sharedCrudFormFields } from "./templates/shared/shared-crud-form-fields.js";
10
+ import { generateRoutes } from "./generate-routes.js";
11
+ import { createItemFormTemplate } from "./templates/shared/collection-create-form.js";
12
+ import { editItemFormTemplate } from "./templates/shared/collection-edit-form.js";
13
+ import { askItemDeleteTemplate } from "./templates/shared/confirm-item-delete.js";
14
+ import { itemDeleteTemplate } from "./templates/shared/item-delete.js";
15
+
16
+ export async function addVerboseCRUD(
17
+ params: Partial<{ [key in "collection" | "url"]: string }>,
18
+ app_directory: string = process.cwd()
19
+ ) {
20
+ const target_locreq = _locreq(app_directory);
21
+ prompts.override(params);
22
+ const response = await prompts([
23
+ {
24
+ type: "autocomplete",
25
+ name: "collection",
26
+ message:
27
+ "Which sealious collection do you like to add CRUD forms for?",
28
+ choices: (
29
+ await listCollections()
30
+ ).map((collection) => ({
31
+ title: collection,
32
+ value: collection,
33
+ })),
34
+ },
35
+ {
36
+ type: "text",
37
+ name: "url",
38
+ message:
39
+ "Enter a full absolute path for the new route (for example: /admin/users/:id/edit): ",
40
+ validate: (s: string) =>
41
+ s.trim()[0] == "/" ? true : "Should start with a '/'",
42
+ initial: function (_, { collection }: { collection: string }) {
43
+ return "/" + toKebabCase(collection);
44
+ },
45
+ },
46
+ ]);
47
+
48
+ const collection_name = response.collection as string;
49
+ const url = response.url as string;
50
+ const list_action_name = toPascalCase(collection_name) + "CRUDList";
51
+ const create_action_name = toPascalCase(collection_name) + "CRUDCreate";
52
+ const edit_action_name = toPascalCase(collection_name) + "CRUDEdit";
53
+ const delete_ask_action_name =
54
+ toPascalCase(collection_name) + "CRUDDeleteAsk";
55
+ const delete_action_name = toPascalCase(collection_name) + "CRUDDelete";
56
+
57
+ /* create the list endpoint */
58
+ const list_path = target_locreq.resolve(
59
+ `src/back/routes/${escape_url_params(url)}/index.list.tsx`
60
+ );
61
+ const list_content = await collectionListTemplate(
62
+ collection_name,
63
+ list_action_name,
64
+ list_path,
65
+ {
66
+ post_import_js: `import { ${create_action_name}URL, ${edit_action_name}URL, ${delete_ask_action_name}URL } from "${importPath(
67
+ list_path,
68
+ "src/back/routes/urls.ts"
69
+ )}";`,
70
+ post_header_html: `<a href={${create_action_name}URL}> Create </a>`,
71
+ render_item: (
72
+ collection_name: string
73
+ ) => ` async renderItem(ctx: Context, item: CollectionItem<typeof ${toPascalCase(
74
+ collection_name
75
+ )}>) {
76
+ return <tr>
77
+ {displayFields.map(({ field, format }) => {
78
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
79
+ const value = item.get(field as any);
80
+ return <td>{format ? format(value, item) : value}</td>;
81
+ })}
82
+ <td><div class="sealious-list__actions">
83
+ <a href={${edit_action_name}URL(item.id)}>Edit</a>
84
+ <a href={${delete_ask_action_name}URL(item.id)}>Delete</a>
85
+ </div>
86
+ </td>
87
+ </tr>;
88
+ }`,
89
+ }
90
+ );
91
+ await writeFile(list_path, list_content);
92
+
93
+ /* create the shared fields file */
94
+ const fields_path = target_locreq.resolve(
95
+ `src/back/routes/${escape_url_params(url)}/shared.ts`
96
+ );
97
+ const fields_content = await sharedCrudFormFields(
98
+ collection_name,
99
+ fields_path
100
+ );
101
+ await writeFile(fields_path, fields_content);
102
+
103
+ /* create the create endpoint */
104
+ const create_path = target_locreq.resolve(
105
+ `src/back/routes/${escape_url_params(url)}/create.form.ts`
106
+ );
107
+ const create_content = await createItemFormTemplate(
108
+ create_action_name,
109
+ create_path,
110
+ collection_name,
111
+ list_action_name
112
+ );
113
+ await writeFile(create_path, create_content);
114
+
115
+ /* create the edit endpoint */
116
+ const edit_path = target_locreq.resolve(
117
+ `src/back/routes/${escape_url_params(url)}/[id]/edit.form.ts`
118
+ );
119
+ const edit_content = await editItemFormTemplate(
120
+ edit_action_name,
121
+ edit_path,
122
+ collection_name,
123
+ list_action_name
124
+ );
125
+ await writeFile(edit_path, edit_content);
126
+
127
+ /* create the confirm delete endpoint */
128
+ const delete_ask_path = target_locreq.resolve(
129
+ `src/back/routes/${escape_url_params(url)}/[id]/delete-ask.page.tsx`
130
+ );
131
+ const delete_ask_content = await askItemDeleteTemplate(
132
+ delete_ask_action_name,
133
+ collection_name,
134
+ delete_ask_path,
135
+ list_action_name,
136
+ delete_action_name
137
+ );
138
+ await writeFile(delete_ask_path, delete_ask_content);
139
+
140
+ /* create the actual delete endpoint */
141
+ const delete_path = target_locreq.resolve(
142
+ `src/back/routes/${escape_url_params(url)}/[id]/delete.page.tsx`
143
+ );
144
+ const delete_content = await itemDeleteTemplate(
145
+ delete_action_name,
146
+ collection_name,
147
+ delete_ask_path,
148
+ list_action_name
149
+ );
150
+ await writeFile(delete_path, delete_content);
151
+
152
+ await generateRoutes();
153
+ }
package/src/cli.ts CHANGED
@@ -9,6 +9,7 @@ import { generateRoutes } from "./generate-routes.js";
9
9
  import { makeEnv } from "./make-env.js";
10
10
  import yargs from "yargs/yargs";
11
11
  import { getFonts } from "./get-fonts.js";
12
+ import { addVerboseCRUD } from "./add-verbose-crud.js";
12
13
  import { addCRUD } from "./add-crud.js";
13
14
 
14
15
  const actions: Record<
@@ -17,6 +18,7 @@ const actions: Record<
17
18
  > = {
18
19
  "add-collection": addCollection,
19
20
  "add-route": addRoute,
21
+ "add-verbose-crud": addVerboseCRUD,
20
22
  "add-crud": addCRUD,
21
23
  "generate-collections": generateCollections,
22
24
  "generate-components": generateComponents,
@@ -7,11 +7,16 @@ import {
7
7
  export const default_handler: FieldHandler<
8
8
  Record<string, never>,
9
9
  FieldHandlerResult
10
- > = async (collection_name, { name, is_readonly, is_required }) => {
10
+ > = async (
11
+ collection_name,
12
+ { name, is_readonly, is_required },
13
+ _,
14
+ __,
15
+ fields_var = `${toPascalCase(collection_name)}FormFields`
16
+ ) => {
11
17
  const required_s = `${toPascalCase(
12
18
  collection_name
13
19
  )}.fields.${name}.required`;
14
- const fields_var = `${toPascalCase(collection_name)}FormFields`;
15
20
  const fallback_value_initial = `""`;
16
21
  const fallback_value_sealious = `""`;
17
22
 
@@ -60,7 +60,8 @@ export type FieldHandler<
60
60
  collection_name: string,
61
61
  field: ExtractedFieldInfo,
62
62
  importPath: (path: string) => string,
63
- defaults: Defaults
63
+ defaults: Defaults,
64
+ fields_var?: string
64
65
  ) => Promise<Result>;
65
66
 
66
67
  export const field_handlers: Record<
@@ -86,13 +87,15 @@ export const field_handlers: Record<
86
87
  export async function getFieldHandler(
87
88
  collection_name: string,
88
89
  field: ExtractedFieldInfo,
89
- importPath: (path: string) => string
90
+ importPath: (path: string) => string,
91
+ fields_var?: string
90
92
  ) {
91
93
  const defaults = await field_handlers.default(
92
94
  collection_name,
93
95
  field,
94
96
  importPath,
95
- {}
97
+ {},
98
+ fields_var
96
99
  );
97
100
  const result = {
98
101
  ...defaults,
@@ -100,7 +103,8 @@ export async function getFieldHandler(
100
103
  collection_name,
101
104
  field,
102
105
  importPath,
103
- defaults
106
+ defaults,
107
+ fields_var
104
108
  )) || {}),
105
109
  };
106
110
  const result_initial_value_edit = result.initial_value_edit;