@sealcode/sealgen 0.15.35 → 0.15.37
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/.xunit +59 -57
- package/@types/src/forms/controls/form-control.d.ts +2 -1
- package/@types/src/forms/controls/table.d.ts +24 -5
- package/@types/src/forms/fields/table.d.ts +4 -2
- package/@types/src/templates/form.d.ts +5 -0
- package/@types/src/templates/shared/collection-create-form.d.ts +0 -2
- package/@types/src/templates/shared/handlers/boolean.d.ts +2 -0
- package/@types/src/templates/shared/handlers/date.d.ts +2 -0
- package/@types/src/templates/shared/handlers/deep-reverse-single-reference.d.ts +2 -0
- package/@types/src/templates/shared/handlers/default.d.ts +2 -0
- package/@types/src/templates/shared/handlers/enum.d.ts +2 -0
- package/@types/src/templates/shared/handlers/file.d.ts +2 -0
- package/@types/src/templates/shared/handlers/image.d.ts +2 -0
- package/@types/src/templates/shared/handlers/int.d.ts +2 -0
- package/@types/src/templates/shared/handlers/jdd.d.ts +2 -0
- package/@types/src/templates/shared/handlers/single-reference.d.ts +2 -0
- package/@types/src/templates/shared/handlers/structured-array.d.ts +2 -0
- package/@types/src/templates/shared/shared-crud-form-fields.d.ts +29 -6
- package/@types/src/utils/extract-fields-from-collection.d.ts +12 -2
- package/@types/src/utils/with-fallback.d.ts +1 -1
- package/lib/add-crud.js +5 -5
- package/lib/cli.js +2 -0
- package/lib/forms/controls/form-control.js +2 -2
- package/lib/forms/controls/multiple-photos.js +2 -1
- package/lib/forms/controls/single-reference.test.js +1 -1
- package/lib/forms/controls/table.js +114 -12
- package/lib/forms/fields/boolean.js +1 -1
- package/lib/forms/fields/field.js +4 -1
- package/lib/forms/fields/table.js +8 -3
- package/lib/forms/form.js +7 -6
- package/lib/page/default-list-filters.js +1 -0
- package/lib/templates/form.js +25 -16
- package/lib/templates/list.js +6 -167
- package/lib/templates/shared/collection-create-form.js +23 -83
- package/lib/templates/shared/collection-edit-form.js +53 -96
- package/lib/templates/shared/collection-list.js +21 -6
- package/lib/templates/shared/confirm-item-delete.js +7 -5
- package/lib/templates/shared/handlers/boolean.js +14 -0
- package/lib/templates/shared/handlers/date.js +8 -0
- package/lib/templates/shared/handlers/deep-reverse-single-reference.js +209 -0
- package/lib/templates/shared/handlers/default.js +50 -0
- package/lib/templates/shared/handlers/enum.js +19 -0
- package/lib/templates/shared/handlers/file.js +18 -0
- package/lib/templates/shared/handlers/image.js +32 -0
- package/lib/templates/shared/handlers/int.js +11 -0
- package/lib/templates/shared/handlers/jdd.js +10 -0
- package/lib/templates/shared/handlers/single-reference.js +12 -0
- package/lib/templates/shared/handlers/structured-array.js +33 -0
- package/lib/templates/shared/item-delete.js +5 -3
- package/lib/templates/shared/shared-crud-form-fields.js +72 -82
- package/lib/utils/extract-fields-from-collection.js +31 -5
- package/package.json +2 -1
- package/src/add-crud.ts +153 -0
- package/src/cli.ts +2 -0
- package/src/forms/controls/form-control.ts +3 -2
- package/src/forms/controls/multiple-photos.ts +1 -0
- package/src/forms/controls/single-reference.test.ts +1 -1
- package/src/forms/controls/table.ts +168 -31
- package/src/forms/fields/boolean.ts +1 -1
- package/src/forms/fields/field.ts +4 -1
- package/src/forms/fields/table.ts +25 -6
- package/src/forms/form.ts +13 -7
- package/src/page/default-list-filters.ts +1 -0
- package/src/templates/form.ts +24 -15
- package/src/templates/list.ts +6 -174
- package/src/templates/shared/collection-create-form.ts +121 -0
- package/src/templates/shared/collection-edit-form.ts +196 -0
- package/src/templates/shared/collection-list.ts +252 -0
- package/src/templates/shared/confirm-item-delete.ts +78 -0
- package/src/templates/shared/handlers/boolean.ts +18 -0
- package/src/templates/shared/handlers/date.ts +12 -0
- package/src/templates/shared/handlers/deep-reverse-single-reference.ts +244 -0
- package/src/templates/shared/handlers/default.ts +58 -0
- package/src/templates/shared/handlers/enum.ts +23 -0
- package/src/templates/shared/handlers/file.ts +22 -0
- package/src/templates/shared/handlers/image.ts +45 -0
- package/src/templates/shared/handlers/int.ts +15 -0
- package/src/templates/shared/handlers/jdd.ts +14 -0
- package/src/templates/shared/handlers/single-reference.ts +16 -0
- package/src/templates/shared/handlers/structured-array.ts +42 -0
- package/src/templates/shared/item-delete.ts +45 -0
- package/src/templates/shared/shared-crud-form-fields.ts +179 -0
- package/src/utils/extract-fields-from-collection.ts +45 -6
- package/src/utils/with-fallback.ts +1 -1
- package/src/utils/write-file.ts +8 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const int_handler = async (_, { name }, __, { is_required_expr, fields_var }) => ({
|
|
2
|
+
field: `${name}: new Fields.NumberField(${is_required_expr}, 0)`,
|
|
3
|
+
controls: `new Controls.Number(${fields_var}.${name}, { label: "${name}" })`,
|
|
4
|
+
initial_value_edit: {
|
|
5
|
+
parsed_value: `String(item.get("${name}"))`
|
|
6
|
+
}
|
|
7
|
+
});
|
|
8
|
+
export {
|
|
9
|
+
int_handler
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=int.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const single_reference_handler = async (_, { name }, __, { fields_var }) => {
|
|
2
|
+
return {
|
|
3
|
+
controls: `new Controls.SingleReferenceDropdown(${fields_var}.${name}, {
|
|
4
|
+
label: "${name}",
|
|
5
|
+
getLabel: (item)=>item.id
|
|
6
|
+
})`
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export {
|
|
10
|
+
single_reference_handler
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=single-reference.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const structured_array_handler = async (_, { name, subfields }, __, { fields_var }) => ({
|
|
2
|
+
field: `${name}: new Fields.Table({
|
|
3
|
+
${subfields.map(
|
|
4
|
+
(subfield) => `${subfield.name}: new Fields.SimpleFormField(true)`
|
|
5
|
+
).join(",\n")}
|
|
6
|
+
})`,
|
|
7
|
+
controls: `new Controls.Table(${fields_var}.${name}, {
|
|
8
|
+
label: "${name}",
|
|
9
|
+
render_fields: {
|
|
10
|
+
${subfields.map(
|
|
11
|
+
({
|
|
12
|
+
name: name2
|
|
13
|
+
}) => `"${name2}": (fctx, name, value) => /* HTML */ \`<input
|
|
14
|
+
name="\${name}"
|
|
15
|
+
value="\${value}"
|
|
16
|
+
form="\${fctx.form_id}"
|
|
17
|
+
autocomplete="off"
|
|
18
|
+
/>\``
|
|
19
|
+
).join(",\n")}
|
|
20
|
+
|
|
21
|
+
},
|
|
22
|
+
allow_adding: true,
|
|
23
|
+
allow_removing: true,
|
|
24
|
+
make_new_row: async () => ({
|
|
25
|
+
${subfields.map(({ name: name2 }) => `"${name2}": ""`).join(",\n")}
|
|
26
|
+
}),
|
|
27
|
+
})`,
|
|
28
|
+
fallback_value_sealious: `[]`
|
|
29
|
+
});
|
|
30
|
+
export {
|
|
31
|
+
structured_array_handler
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=structured-array.js.map
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { toKebabCase, toPascalCase } from "js-convert-case";
|
|
2
2
|
import { curryImportPath } from "../../utils/import-path.js";
|
|
3
|
+
import { formatWithPrettier } from "../../utils/prettier.js";
|
|
3
4
|
function itemDeleteTemplate(delete_action_name, collection_name, newfilefullpath, list_action_name) {
|
|
4
5
|
const importPath = curryImportPath(newfilefullpath);
|
|
5
|
-
|
|
6
|
+
const result = `import type { Context } from "koa";
|
|
6
7
|
import { Mountable } from "@sealcode/sealgen";
|
|
7
|
-
import Router from "@koa/router";
|
|
8
|
+
import type Router from "@koa/router";
|
|
8
9
|
|
|
9
10
|
import { ${toPascalCase(collection_name)} } from "${importPath(
|
|
10
11
|
"src/back/collections/collections.js"
|
|
@@ -25,7 +26,7 @@ export default new (class ${delete_action_name}Redirect extends Mountable {
|
|
|
25
26
|
|
|
26
27
|
mount(router: Router, path: string) {
|
|
27
28
|
router.get(path, async (ctx) => {
|
|
28
|
-
ctx.$app.collections["${toKebabCase(
|
|
29
|
+
await ctx.$app.collections["${toKebabCase(
|
|
29
30
|
collection_name
|
|
30
31
|
)}"].removeByID(ctx.$context, ctx.params.id);
|
|
31
32
|
ctx.status = 302;
|
|
@@ -34,6 +35,7 @@ export default new (class ${delete_action_name}Redirect extends Mountable {
|
|
|
34
35
|
}
|
|
35
36
|
})();
|
|
36
37
|
`;
|
|
38
|
+
return formatWithPrettier(result);
|
|
37
39
|
}
|
|
38
40
|
export {
|
|
39
41
|
itemDeleteTemplate
|
|
@@ -4,82 +4,53 @@ import extract_fields_from_collection from "../../utils/extract-fields-from-coll
|
|
|
4
4
|
import _locreq from "locreq";
|
|
5
5
|
import { curryImportPath } from "../../utils/import-path.js";
|
|
6
6
|
import { formatWithPrettier } from "../../utils/prettier.js";
|
|
7
|
+
import { default_handler } from "./handlers/default.js";
|
|
8
|
+
import { enum_handler } from "./handlers/enum.js";
|
|
9
|
+
import { image_handler } from "./handlers/image.js";
|
|
10
|
+
import { file_handler } from "./handlers/file.js";
|
|
11
|
+
import { int_handler } from "./handlers/int.js";
|
|
12
|
+
import { boolean_handler } from "./handlers/boolean.js";
|
|
13
|
+
import { jdd_handler } from "./handlers/jdd.js";
|
|
14
|
+
import { date_handler } from "./handlers/date.js";
|
|
15
|
+
import { deep_reverse_single_reference_handler } from "./handlers/deep-reverse-single-reference.js";
|
|
16
|
+
import { structured_array_handler } from "./handlers/structured-array.js";
|
|
7
17
|
const target_locreq = _locreq(process.cwd());
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return items.map((s) => ({
|
|
46
|
-
label: s.get("name"),
|
|
47
|
-
value: s.id,
|
|
48
|
-
}));
|
|
49
|
-
})`;
|
|
50
|
-
}
|
|
51
|
-
return `${name}: new Fields.CollectionField(${required_s}, ${toPascalCase(
|
|
52
|
-
collection_name
|
|
53
|
-
)}.fields.${name})`;
|
|
54
|
-
}
|
|
55
|
-
function renderFormControl(collection_name, { name, type, target, is_readonly }) {
|
|
56
|
-
const fields_var = `${toPascalCase(collection_name)}FormFields`;
|
|
57
|
-
if (is_readonly || type == "jdd") {
|
|
58
|
-
return "";
|
|
59
|
-
}
|
|
60
|
-
if (type == "int") {
|
|
61
|
-
return `new Controls.Number(${fields_var}.${name}, { label: "${name}" })`;
|
|
62
|
-
}
|
|
63
|
-
if (type == "deep-reverse-single-reference" && target) {
|
|
64
|
-
return `new Controls.CheckboxedListInput(${fields_var}.${name}, {
|
|
65
|
-
label: "${name}",
|
|
66
|
-
})`;
|
|
67
|
-
}
|
|
68
|
-
if (type == "boolean") {
|
|
69
|
-
return `new Controls.Checkbox(${fields_var}.${name}, { label: "${name}" })`;
|
|
70
|
-
}
|
|
71
|
-
if (type == "date") {
|
|
72
|
-
return `new Controls.SimpleInput(${fields_var}.${name}, { label: "${name}", type: "date"})`;
|
|
73
|
-
}
|
|
74
|
-
if (type == "enum") {
|
|
75
|
-
return `new Controls.Dropdown(${fields_var}.${name}, { label: "${name}"})`;
|
|
76
|
-
}
|
|
77
|
-
if (type == "image") {
|
|
78
|
-
return `new Controls.Photo(${fields_var}.${name}, imageRouter, {
|
|
79
|
-
label: "${name}",
|
|
80
|
-
})`;
|
|
81
|
-
}
|
|
82
|
-
return `new Controls.SimpleInput(${fields_var}.${name}, { label: "${name}"})`;
|
|
18
|
+
const field_handlers = {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
|
|
20
|
+
default: default_handler,
|
|
21
|
+
enum: enum_handler,
|
|
22
|
+
image: image_handler,
|
|
23
|
+
file: file_handler,
|
|
24
|
+
int: int_handler,
|
|
25
|
+
boolean: boolean_handler,
|
|
26
|
+
jdd: jdd_handler,
|
|
27
|
+
date: date_handler,
|
|
28
|
+
"deep-reverse-single-reference": deep_reverse_single_reference_handler,
|
|
29
|
+
"structured-array": structured_array_handler
|
|
30
|
+
};
|
|
31
|
+
async function getFieldHandler(collection_name, field, importPath) {
|
|
32
|
+
const defaults = await field_handlers.default(
|
|
33
|
+
collection_name,
|
|
34
|
+
field,
|
|
35
|
+
importPath,
|
|
36
|
+
{}
|
|
37
|
+
);
|
|
38
|
+
const result = {
|
|
39
|
+
...defaults,
|
|
40
|
+
...await field_handlers[field.type]?.(
|
|
41
|
+
collection_name,
|
|
42
|
+
field,
|
|
43
|
+
importPath,
|
|
44
|
+
defaults
|
|
45
|
+
) || {}
|
|
46
|
+
};
|
|
47
|
+
const result_initial_value_edit = result.initial_value_edit;
|
|
48
|
+
result.initial_value_edit = typeof result_initial_value_edit == "string" ? result_initial_value_edit : {
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
...defaults.initial_value_edit,
|
|
51
|
+
...result_initial_value_edit
|
|
52
|
+
};
|
|
53
|
+
return result;
|
|
83
54
|
}
|
|
84
55
|
async function sharedCrudFormFields(collection_name, newfilefullpath) {
|
|
85
56
|
const [uppercase_collection, collection_fields] = await Promise.all([
|
|
@@ -91,28 +62,47 @@ async function sharedCrudFormFields(collection_name, newfilefullpath) {
|
|
|
91
62
|
extract_fields_from_collection(collection_name)
|
|
92
63
|
]);
|
|
93
64
|
const importPath = curryImportPath(newfilefullpath);
|
|
65
|
+
const field_handler_results = await Promise.all(
|
|
66
|
+
collection_fields.map(async (field) => {
|
|
67
|
+
return getFieldHandler(collection_name, field, importPath);
|
|
68
|
+
})
|
|
69
|
+
);
|
|
94
70
|
const result = `import { Controls, Fields } from "@sealcode/sealgen";
|
|
95
71
|
import { ${uppercase_collection} } from "${importPath(
|
|
96
72
|
"src/back/collections/collections.js"
|
|
97
73
|
)}";
|
|
98
|
-
import { TheFileManager } from "${importPath("src/back/file-manager.js")}";
|
|
99
|
-
import { imageRouter } from "${importPath("src/back/image-router.js")}";
|
|
100
74
|
|
|
101
75
|
${Array.from(
|
|
102
|
-
new Set(
|
|
103
|
-
|
|
76
|
+
new Set(
|
|
77
|
+
field_handler_results.filter(
|
|
78
|
+
({ hide_field, hide_control }) => !hide_field && !hide_control
|
|
79
|
+
).map(({ imports }) => imports.shared || "")
|
|
80
|
+
)
|
|
81
|
+
).join("\n")}
|
|
82
|
+
|
|
104
83
|
|
|
105
|
-
|
|
106
|
-
|
|
84
|
+
|
|
85
|
+
export const ${toPascalCase(collection_name)}FormFields = <const>
|
|
86
|
+
{
|
|
87
|
+
${field_handler_results.filter(({ hide_field }) => !hide_field).map(({ field }) => field).filter((e) => e != "").join(",\n")}
|
|
107
88
|
};
|
|
108
89
|
|
|
109
90
|
export const ${toPascalCase(collection_name)}FormControls = [
|
|
110
|
-
${
|
|
91
|
+
${field_handler_results.filter(({ hide_control }) => !hide_control).map(({ controls }) => controls).filter((e) => e != "").join(",\n")}
|
|
111
92
|
];
|
|
93
|
+
|
|
94
|
+
${Array.from(
|
|
95
|
+
new Set(
|
|
96
|
+
field_handler_results.map(({ top_level_shared }) => top_level_shared)
|
|
97
|
+
)
|
|
98
|
+
).join("\n")}
|
|
99
|
+
|
|
112
100
|
`;
|
|
113
101
|
return formatWithPrettier(result);
|
|
114
102
|
}
|
|
115
103
|
export {
|
|
104
|
+
field_handlers,
|
|
105
|
+
getFieldHandler,
|
|
116
106
|
sharedCrudFormFields
|
|
117
107
|
};
|
|
118
108
|
//# sourceMappingURL=shared-crud-form-fields.js.map
|
|
@@ -3,6 +3,12 @@ import { exec } from "./utils.js";
|
|
|
3
3
|
const target_locreq = _locreq(process.cwd());
|
|
4
4
|
import { promises as fs } from "fs";
|
|
5
5
|
import { is, predicates } from "@sealcode/ts-predicates";
|
|
6
|
+
const counter = function* () {
|
|
7
|
+
let i = 0;
|
|
8
|
+
while (true) {
|
|
9
|
+
yield i++;
|
|
10
|
+
}
|
|
11
|
+
}();
|
|
6
12
|
async function extract_fields_from_collection(collection_name) {
|
|
7
13
|
const extractor_code = `import {default as the_app} from "./app.js";
|
|
8
14
|
const c = new the_app().collections["${collection_name}"];
|
|
@@ -10,15 +16,34 @@ const fields = [];
|
|
|
10
16
|
for (const field_name in c.fields){
|
|
11
17
|
const field = c.fields[field_name];
|
|
12
18
|
let type = field.typeName;
|
|
19
|
+
let target = null;
|
|
20
|
+
let referencing_collection = null;
|
|
21
|
+
let referencing_field = null;
|
|
22
|
+
let intermediary_field_that_points_there = null;
|
|
23
|
+
let is_readonly = false;
|
|
24
|
+
let is_required = field.required;
|
|
25
|
+
let subfields = [];
|
|
13
26
|
if(["derived-value", "cached-value"].includes(type)){
|
|
14
|
-
|
|
27
|
+
type = field.virtual_field.typeName;
|
|
28
|
+
}
|
|
29
|
+
if(["derived-value", "cached-value", "reverse-single-reference"].includes(field.typeName)){
|
|
30
|
+
is_readonly = true;
|
|
31
|
+
}
|
|
32
|
+
if(type == "deep-reverse-single-reference" || type =="reverse-single-reference"){
|
|
33
|
+
target = field.target_collection;
|
|
34
|
+
referencing_collection = field.referencing_collection;
|
|
35
|
+
referencing_field = field.referencing_field;
|
|
36
|
+
intermediary_field_that_points_there = field.intermediary_field_that_points_there
|
|
37
|
+
}
|
|
38
|
+
if( type == "structured-array" ) {
|
|
39
|
+
subfields = Object.keys(field.subfields).map(key=>({name: key}));
|
|
15
40
|
}
|
|
16
|
-
fields.push({name: field_name, type})
|
|
41
|
+
fields.push({ name: field_name, type, target, referencing_collection, referencing_field, intermediary_field_that_points_there, is_readonly, is_required, subfields })
|
|
17
42
|
}
|
|
18
|
-
console.log(JSON.stringify(fields));
|
|
43
|
+
console.log(JSON.stringify(fields));
|
|
19
44
|
`;
|
|
20
45
|
const extractor_code_path = target_locreq.resolve(
|
|
21
|
-
|
|
46
|
+
`dist/back/___extract_fields--${counter.next().value || 0}.js`
|
|
22
47
|
);
|
|
23
48
|
await fs.writeFile(extractor_code_path, extractor_code);
|
|
24
49
|
const { stdout } = await exec("node", [extractor_code_path]);
|
|
@@ -29,7 +54,8 @@ console.log(JSON.stringify(fields));
|
|
|
29
54
|
predicates.array(
|
|
30
55
|
predicates.shape({
|
|
31
56
|
name: predicates.string,
|
|
32
|
-
type: predicates.string
|
|
57
|
+
type: predicates.string,
|
|
58
|
+
is_readonly: predicates.boolean
|
|
33
59
|
})
|
|
34
60
|
)
|
|
35
61
|
)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sealcode/sealgen",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.37",
|
|
4
4
|
"description": "Module to automate adding routes and collections to a sealious application",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"@koa/router": "^12.0.1",
|
|
62
62
|
"@sealcode/file-manager": "^1.0.2",
|
|
63
63
|
"@sealcode/ts-predicates": "^0.4.3",
|
|
64
|
+
"chokidar": "^3.6.0",
|
|
64
65
|
"deepmerge": "^4.3.1",
|
|
65
66
|
"esbuild": "^0.20.0",
|
|
66
67
|
"escape-goat": "^4.0.0",
|
package/src/add-crud.ts
ADDED
|
@@ -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 addCRUD(
|
|
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)}.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
|
@@ -11,6 +11,7 @@ import { makeEnv } from "./make-env.js";
|
|
|
11
11
|
import yargs from "yargs/yargs";
|
|
12
12
|
import { getFonts } from "./get-fonts.js";
|
|
13
13
|
import { generateStimulusControllers } from "./generate-stimulus.js";
|
|
14
|
+
import { addCRUD } from "./add-crud.js";
|
|
14
15
|
|
|
15
16
|
const actions: Record<
|
|
16
17
|
string,
|
|
@@ -18,6 +19,7 @@ const actions: Record<
|
|
|
18
19
|
> = {
|
|
19
20
|
"add-collection": addCollection,
|
|
20
21
|
"add-route": addRoute,
|
|
22
|
+
"add-crud": addCRUD,
|
|
21
23
|
"generate-collections": generateCollections,
|
|
22
24
|
"generate-components": generateComponents,
|
|
23
25
|
"generate-routes": generateRoutes,
|
|
@@ -2,6 +2,7 @@ import Router from "@koa/router";
|
|
|
2
2
|
import { Context } from "koa";
|
|
3
3
|
import { FlatTemplatable } from "tempstream";
|
|
4
4
|
import { FormDataValue, FormMessage } from "../form-types.js";
|
|
5
|
+
import type { Form } from "../form.js";
|
|
5
6
|
|
|
6
7
|
export class FormControlContext {
|
|
7
8
|
constructor(
|
|
@@ -20,6 +21,6 @@ export abstract class FormControl {
|
|
|
20
21
|
): FlatTemplatable | Promise<FlatTemplatable>;
|
|
21
22
|
abstract role: "input" | "decoration" | "messages" | "submit";
|
|
22
23
|
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
24
|
-
mount(_router: Router) {}
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-explicit-any
|
|
25
|
+
mount(_router: Router, _form: Form<any, any>) {}
|
|
25
26
|
}
|
|
@@ -40,7 +40,7 @@ describe("single-reference.test", () => {
|
|
|
40
40
|
await test_app.addFile(
|
|
41
41
|
form_file_path,
|
|
42
42
|
await formTemplate("SomeForm", form_file_path, {
|
|
43
|
-
postimport: `import {Comments} from "../collections/collections.js";`,
|
|
43
|
+
postimport: `import {Comments} from "../collections/collections.js"; import {Fields} from "@sealcode/sealgen";`,
|
|
44
44
|
fields: `article: new Fields.CollectionField(true, Comments.fields.article),`,
|
|
45
45
|
controls: `new Controls.SingleReferenceDropdown(fields.article, {getLabel: item=>item.get("title")}),`,
|
|
46
46
|
validate_method: "",
|