@sealcode/sealgen 0.17.5 → 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.
- package/@types/src/add-verbose-crud.d.ts +3 -0
- package/@types/src/templates/shared/collection-list.d.ts +1 -1
- package/@types/src/templates/shared/shared-crud-form-fields.d.ts +2 -2
- package/lib/add-crud.js +88 -91
- package/lib/add-verbose-crud.js +131 -0
- package/lib/cli.js +2 -0
- package/lib/forms/controls/file.test.js +0 -8
- package/lib/forms/controls/single-reference.test.js +1 -1
- package/lib/templates/form.js +1 -1
- package/lib/templates/page.js +1 -1
- package/lib/templates/shared/collection-list.js +11 -16
- package/lib/templates/shared/handlers/default.js +1 -2
- package/lib/templates/shared/shared-crud-form-fields.js +5 -3
- package/lib/test_utils/test-on-real-app.js +1 -1
- package/package.json +1 -1
- package/src/add-crud.ts +93 -99
- package/src/add-verbose-crud.ts +153 -0
- package/src/cli.ts +2 -0
- package/src/forms/controls/file.test.ts +0 -8
- package/src/forms/controls/single-reference.test.ts +1 -1
- package/src/templates/form.ts +1 -1
- package/src/templates/page.ts +1 -1
- package/src/templates/shared/collection-list.ts +11 -20
- package/src/templates/shared/handlers/default.ts +7 -2
- package/src/templates/shared/shared-crud-form-fields.ts +8 -4
- package/src/test_utils/test-on-real-app.ts +1 -1
|
@@ -12,5 +12,5 @@ declare const default_hooks: {
|
|
|
12
12
|
post_import_js: string;
|
|
13
13
|
render_item: (collection_name: string) => string;
|
|
14
14
|
};
|
|
15
|
-
export declare function collectionListTemplate(collection_name: string, action_name: string,
|
|
15
|
+
export declare function collectionListTemplate(collection_name: string, action_name: string, _: string, _hooks?: Partial<typeof default_hooks>): Promise<string>;
|
|
16
16
|
export {};
|
|
@@ -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 {
|
|
6
|
+
import {
|
|
7
|
+
makeDefaultDisplayFields,
|
|
8
|
+
makeDefaultFilterFields
|
|
9
|
+
} from "./templates/shared/collection-list.js";
|
|
7
10
|
import { writeFile } from "./utils/write-file.js";
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
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 {
|
|
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
|
|
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)}.
|
|
42
|
+
`src/back/routes/${escape_url_params(url)}/index.page.ts`
|
|
47
43
|
);
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -60,18 +60,10 @@ describe("file-control", () => {
|
|
|
60
60
|
browser = await getBrowser();
|
|
61
61
|
const context = await browser.newContext();
|
|
62
62
|
page = await context.newPage();
|
|
63
|
-
let screenshot = true;
|
|
64
|
-
(async function() {
|
|
65
|
-
while (screenshot) {
|
|
66
|
-
await page.screenshot({ path: "/home/kuba/Downloads/" + Date.now() + ".png" });
|
|
67
|
-
await sleep(100);
|
|
68
|
-
}
|
|
69
|
-
})();
|
|
70
63
|
await page.goto(`http://localhost:${port}`);
|
|
71
64
|
await page.getByRole("button", { name: "Wy\u015Blij" }).click();
|
|
72
65
|
await sleep(100);
|
|
73
66
|
assert(submit_called, "Submit not called - something prevented submit on click?");
|
|
74
|
-
screenshot = false;
|
|
75
67
|
});
|
|
76
68
|
}).timeout(1e4);
|
|
77
69
|
});
|
|
@@ -13,7 +13,7 @@ describe("single-reference.test", () => {
|
|
|
13
13
|
after(async () => {
|
|
14
14
|
await page.close();
|
|
15
15
|
});
|
|
16
|
-
it("creates minimal sealious app and adds
|
|
16
|
+
it("creates minimal sealious app and adds form using single-reference field that works", async function() {
|
|
17
17
|
const test_app = await RealAppTest.init();
|
|
18
18
|
await test_app.addFile(
|
|
19
19
|
"src/back/collections/articles.ts",
|
package/lib/templates/form.js
CHANGED
|
@@ -35,7 +35,7 @@ const defaults = {
|
|
|
35
35
|
return { canAccess: true, message: "" };
|
|
36
36
|
}`,
|
|
37
37
|
render: `async render(ctx: Context, data: FormData, show_field_errors: boolean) {
|
|
38
|
-
return html(ctx, "Form", await super.render(ctx, data, show_field_errors));
|
|
38
|
+
return html({ctx, title: "Form", description: "", body: await super.render(ctx, data, show_field_errors)});
|
|
39
39
|
}`,
|
|
40
40
|
get_initial_values: "",
|
|
41
41
|
other_methods: ""
|
package/lib/templates/page.js
CHANGED
|
@@ -4,7 +4,7 @@ async function pageTemplate(action_name, newfilefullpath) {
|
|
|
4
4
|
return `import type { Context } from "koa";
|
|
5
5
|
import { TempstreamJSX } from "tempstream";
|
|
6
6
|
import { Page } from "@sealcode/sealgen";
|
|
7
|
-
import html from "
|
|
7
|
+
import html from "src/back/html.js";
|
|
8
8
|
|
|
9
9
|
export const actionName = "${action_name}";
|
|
10
10
|
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { extractCollectionClassname } from "../../generate-collections.js";
|
|
2
2
|
import extract_fields_from_collection from "../../utils/extract-fields-from-collection.js";
|
|
3
3
|
import _locreq from "locreq";
|
|
4
|
-
import { curryImportPath } from "../../utils/import-path.js";
|
|
5
4
|
import { DefaultListFilters } from "../../page/default-list-filters.js";
|
|
6
5
|
import { toKebabCase, toPascalCase } from "js-convert-case";
|
|
7
6
|
import { formatWithPrettier } from "../../utils/prettier.js";
|
|
8
7
|
const target_locreq = _locreq(process.cwd());
|
|
9
|
-
function fieldImport({ type }
|
|
8
|
+
function fieldImport({ type }) {
|
|
10
9
|
if (type == "image") {
|
|
11
10
|
return `
|
|
12
11
|
import type { FilePointer } from "@sealcode/file-manager";
|
|
13
|
-
import { imageRouter } from "
|
|
12
|
+
import { imageRouter } from ""src/back/image-router.js";
|
|
14
13
|
`;
|
|
15
14
|
}
|
|
16
15
|
return "";
|
|
@@ -73,7 +72,7 @@ const default_hooks = {
|
|
|
73
72
|
</tr>;
|
|
74
73
|
}`
|
|
75
74
|
};
|
|
76
|
-
async function collectionListTemplate(collection_name, action_name,
|
|
75
|
+
async function collectionListTemplate(collection_name, action_name, _, _hooks = {}) {
|
|
77
76
|
const hooks = { ...default_hooks, ..._hooks };
|
|
78
77
|
const [uppercase_collection, collection_fields] = await Promise.all([
|
|
79
78
|
extractCollectionClassname(
|
|
@@ -83,20 +82,15 @@ async function collectionListTemplate(collection_name, action_name, newfilefullp
|
|
|
83
82
|
),
|
|
84
83
|
extract_fields_from_collection(collection_name)
|
|
85
84
|
]);
|
|
86
|
-
const importPath = curryImportPath(newfilefullpath);
|
|
87
85
|
const field_import_string = Array.from(
|
|
88
|
-
new Set(
|
|
89
|
-
collection_fields.map((field) => fieldImport(field, importPath))
|
|
90
|
-
)
|
|
86
|
+
new Set(collection_fields.map((field) => fieldImport(field)))
|
|
91
87
|
).join("\n");
|
|
92
88
|
const result = `import type { Context } from "koa";
|
|
93
89
|
import type { CollectionItem } from "sealious";
|
|
94
90
|
import type { FlatTemplatable, Templatable } from "tempstream";
|
|
95
91
|
import { TempstreamJSX, tempstream } from "tempstream";
|
|
96
|
-
import { ${uppercase_collection} } from "
|
|
97
|
-
|
|
98
|
-
)}";
|
|
99
|
-
import html from "${importPath(target_locreq.resolve("src/back/html.ts"))}";
|
|
92
|
+
import { ${uppercase_collection} } from "src/back/collections/collections.js";
|
|
93
|
+
import html from "src/back/html.js";
|
|
100
94
|
import type { ListFilterRender } from "@sealcode/sealgen";
|
|
101
95
|
import {
|
|
102
96
|
SealiousItemListPage,
|
|
@@ -203,16 +197,17 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
203
197
|
}
|
|
204
198
|
|
|
205
199
|
async render(ctx: Context) {
|
|
206
|
-
return html(
|
|
200
|
+
return html({
|
|
207
201
|
ctx,
|
|
208
|
-
"${action_name}",
|
|
209
|
-
|
|
202
|
+
title: "${action_name}",
|
|
203
|
+
description: "",
|
|
204
|
+
body: <div class="sealious-list-wrapper ${toKebabCase(action_name)}--wrapper">
|
|
210
205
|
${hooks.pre_header_html}
|
|
211
206
|
<h2>${action_name} List</h2>
|
|
212
207
|
${hooks.post_header_html}
|
|
213
208
|
{super.render(ctx)}
|
|
214
209
|
</div>
|
|
215
|
-
);
|
|
210
|
+
});
|
|
216
211
|
}
|
|
217
212
|
|
|
218
213
|
})(${uppercase_collection});
|
|
@@ -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;
|
|
@@ -10,7 +10,7 @@ import { module_dirname } from "../utils/module-dirname.js";
|
|
|
10
10
|
import EventEmitter from "events";
|
|
11
11
|
const locreq = _locreq(module_dirname(import.meta.url));
|
|
12
12
|
const callSealgenBin = () => `node ${locreq.resolve("lib/cli.js")}`;
|
|
13
|
-
const sealious_minimal_commit = "
|
|
13
|
+
const sealious_minimal_commit = "833e70c";
|
|
14
14
|
class RealAppTest {
|
|
15
15
|
constructor() {
|
|
16
16
|
this.emitter = new EventEmitter();
|
package/package.json
CHANGED
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 {
|
|
6
|
+
import {
|
|
7
|
+
makeDefaultDisplayFields,
|
|
8
|
+
makeDefaultFilterFields,
|
|
9
|
+
} from "./templates/shared/collection-list.js";
|
|
7
10
|
import { writeFile } from "./utils/write-file.js";
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
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 {
|
|
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
|
|
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)}.
|
|
54
|
+
`src/back/routes/${escape_url_params(url)}/index.page.ts`
|
|
60
55
|
);
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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,
|
|
@@ -69,19 +69,11 @@ describe("file-control", () => {
|
|
|
69
69
|
browser = await getBrowser();
|
|
70
70
|
const context = await browser.newContext();
|
|
71
71
|
page = await context.newPage();
|
|
72
|
-
let screenshot = true;
|
|
73
|
-
(async function () {
|
|
74
|
-
while (screenshot) {
|
|
75
|
-
await page.screenshot({ path: "/home/kuba/Downloads/" + Date.now() + ".png" });
|
|
76
|
-
await sleep(100);
|
|
77
|
-
}
|
|
78
|
-
})();
|
|
79
72
|
await page.goto(`http://localhost:${port}`);
|
|
80
73
|
|
|
81
74
|
await page.getByRole("button", { name: "Wyślij" }).click();
|
|
82
75
|
await sleep(100);
|
|
83
76
|
assert(submit_called, "Submit not called - something prevented submit on click?");
|
|
84
|
-
screenshot = false;
|
|
85
77
|
});
|
|
86
78
|
}).timeout(10000);
|
|
87
79
|
});
|
|
@@ -17,7 +17,7 @@ describe("single-reference.test", () => {
|
|
|
17
17
|
await page.close();
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
it("creates minimal sealious app and adds
|
|
20
|
+
it("creates minimal sealious app and adds form using single-reference field that works", async function () {
|
|
21
21
|
const test_app = await RealAppTest.init();
|
|
22
22
|
|
|
23
23
|
// add possiblity to modify templates (form and collection) and create test case
|
package/src/templates/form.ts
CHANGED
|
@@ -36,7 +36,7 @@ const defaults = {
|
|
|
36
36
|
return { canAccess: true, message: "" };
|
|
37
37
|
}`,
|
|
38
38
|
render: `async render(ctx: Context, data: FormData, show_field_errors: boolean) {
|
|
39
|
-
return html(ctx, "Form", await super.render(ctx, data, show_field_errors));
|
|
39
|
+
return html({ctx, title: "Form", description: "", body: await super.render(ctx, data, show_field_errors)});
|
|
40
40
|
}`,
|
|
41
41
|
get_initial_values: "",
|
|
42
42
|
other_methods: "",
|
package/src/templates/page.ts
CHANGED
|
@@ -7,7 +7,7 @@ export async function pageTemplate(
|
|
|
7
7
|
return `import type { Context } from "koa";
|
|
8
8
|
import { TempstreamJSX } from "tempstream";
|
|
9
9
|
import { Page } from "@sealcode/sealgen";
|
|
10
|
-
import html from "
|
|
10
|
+
import html from "src/back/html.js";
|
|
11
11
|
|
|
12
12
|
export const actionName = "${action_name}";
|
|
13
13
|
|
|
@@ -3,20 +3,16 @@ import extract_fields_from_collection, {
|
|
|
3
3
|
ExtractedFieldInfo,
|
|
4
4
|
} from "../../utils/extract-fields-from-collection.js";
|
|
5
5
|
import _locreq from "locreq";
|
|
6
|
-
import { curryImportPath } from "../../utils/import-path.js";
|
|
7
6
|
import { DefaultListFilters } from "../../page/default-list-filters.js";
|
|
8
7
|
import { toKebabCase, toPascalCase } from "js-convert-case";
|
|
9
8
|
import { formatWithPrettier } from "../../utils/prettier.js";
|
|
10
9
|
const target_locreq = _locreq(process.cwd());
|
|
11
10
|
|
|
12
|
-
function fieldImport(
|
|
13
|
-
{ type }: ExtractedFieldInfo,
|
|
14
|
-
importPath: (path: string) => string
|
|
15
|
-
): string {
|
|
11
|
+
function fieldImport({ type }: ExtractedFieldInfo): string {
|
|
16
12
|
if (type == "image") {
|
|
17
13
|
return `
|
|
18
14
|
import type { FilePointer } from "@sealcode/file-manager";
|
|
19
|
-
import { imageRouter } from "
|
|
15
|
+
import { imageRouter } from ""src/back/image-router.js";
|
|
20
16
|
`;
|
|
21
17
|
}
|
|
22
18
|
return "";
|
|
@@ -100,7 +96,7 @@ const default_hooks = {
|
|
|
100
96
|
export async function collectionListTemplate(
|
|
101
97
|
collection_name: string,
|
|
102
98
|
action_name: string,
|
|
103
|
-
|
|
99
|
+
_: string,
|
|
104
100
|
_hooks: Partial<typeof default_hooks> = {}
|
|
105
101
|
) {
|
|
106
102
|
const hooks = { ...default_hooks, ..._hooks };
|
|
@@ -112,22 +108,16 @@ export async function collectionListTemplate(
|
|
|
112
108
|
),
|
|
113
109
|
extract_fields_from_collection(collection_name),
|
|
114
110
|
]);
|
|
115
|
-
const importPath = curryImportPath(newfilefullpath);
|
|
116
|
-
|
|
117
111
|
const field_import_string = Array.from(
|
|
118
|
-
new Set(
|
|
119
|
-
collection_fields.map((field) => fieldImport(field, importPath))
|
|
120
|
-
)
|
|
112
|
+
new Set(collection_fields.map((field) => fieldImport(field)))
|
|
121
113
|
).join("\n");
|
|
122
114
|
|
|
123
115
|
const result = `import type { Context } from "koa";
|
|
124
116
|
import type { CollectionItem } from "sealious";
|
|
125
117
|
import type { FlatTemplatable, Templatable } from "tempstream";
|
|
126
118
|
import { TempstreamJSX, tempstream } from "tempstream";
|
|
127
|
-
import { ${uppercase_collection} } from "
|
|
128
|
-
|
|
129
|
-
)}";
|
|
130
|
-
import html from "${importPath(target_locreq.resolve("src/back/html.ts"))}";
|
|
119
|
+
import { ${uppercase_collection} } from "src/back/collections/collections.js";
|
|
120
|
+
import html from "src/back/html.js";
|
|
131
121
|
import type { ListFilterRender } from "@sealcode/sealgen";
|
|
132
122
|
import {
|
|
133
123
|
SealiousItemListPage,
|
|
@@ -234,16 +224,17 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
234
224
|
}
|
|
235
225
|
|
|
236
226
|
async render(ctx: Context) {
|
|
237
|
-
return html(
|
|
227
|
+
return html({
|
|
238
228
|
ctx,
|
|
239
|
-
"${action_name}",
|
|
240
|
-
|
|
229
|
+
title: "${action_name}",
|
|
230
|
+
description: "",
|
|
231
|
+
body: <div class="sealious-list-wrapper ${toKebabCase(action_name)}--wrapper">
|
|
241
232
|
${hooks.pre_header_html}
|
|
242
233
|
<h2>${action_name} List</h2>
|
|
243
234
|
${hooks.post_header_html}
|
|
244
235
|
{super.render(ctx)}
|
|
245
236
|
</div>
|
|
246
|
-
);
|
|
237
|
+
});
|
|
247
238
|
}
|
|
248
239
|
|
|
249
240
|
})(${uppercase_collection});
|
|
@@ -7,11 +7,16 @@ import {
|
|
|
7
7
|
export const default_handler: FieldHandler<
|
|
8
8
|
Record<string, never>,
|
|
9
9
|
FieldHandlerResult
|
|
10
|
-
> = async (
|
|
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;
|
|
@@ -15,7 +15,7 @@ const locreq = _locreq(module_dirname(import.meta.url));
|
|
|
15
15
|
|
|
16
16
|
export const callSealgenBin = () => `node ${locreq.resolve("lib/cli.js")}`;
|
|
17
17
|
|
|
18
|
-
const sealious_minimal_commit = "
|
|
18
|
+
const sealious_minimal_commit = "833e70c";
|
|
19
19
|
|
|
20
20
|
export class RealAppTest {
|
|
21
21
|
public node_pid: number;
|