@sealcode/sealgen 0.15.34 → 0.15.36
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/controls.d.ts +1 -0
- package/@types/src/forms/controls/form-control.d.ts +2 -1
- package/@types/src/forms/controls/table.d.ts +42 -0
- package/@types/src/forms/fields/fields.d.ts +1 -0
- package/@types/src/forms/fields/table.d.ts +14 -0
- package/@types/src/templates/form.d.ts +5 -0
- package/@types/src/templates/shared/collection-edit-form.d.ts +1 -0
- package/@types/src/templates/shared/collection-list.d.ts +1 -0
- package/@types/src/templates/shared/confirm-item-delete.d.ts +1 -0
- 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/item-delete.d.ts +1 -0
- package/@types/src/templates/shared/shared-crud-form-fields.d.ts +67 -0
- 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 +131 -0
- package/lib/cli.js +2 -0
- package/lib/forms/controls/controls.js +1 -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 +164 -0
- package/lib/forms/fields/boolean.js +1 -1
- package/lib/forms/fields/field.js +4 -1
- package/lib/forms/fields/fields.js +1 -0
- package/lib/forms/fields/table.js +63 -0
- 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 +86 -0
- package/lib/templates/shared/collection-edit-form.js +157 -0
- package/lib/templates/shared/collection-list.js +226 -0
- package/lib/templates/shared/confirm-item-delete.js +74 -0
- 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 +43 -0
- package/lib/templates/shared/shared-crud-form-fields.js +108 -0
- package/lib/utils/extract-fields-from-collection.js +31 -5
- package/lib/utils/write-file.js +11 -0
- package/package.json +2 -1
- package/src/add-crud.ts +153 -0
- package/src/cli.ts +2 -0
- package/src/forms/controls/controls.ts +1 -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 +208 -0
- package/src/forms/fields/boolean.ts +1 -1
- package/src/forms/fields/field.ts +4 -1
- package/src/forms/fields/fields.ts +1 -0
- package/src/forms/fields/table.ts +91 -0
- 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,50 @@
|
|
|
1
|
+
import { toPascalCase } from "js-convert-case";
|
|
2
|
+
const default_handler = async (collection_name, { name, is_readonly, is_required }) => {
|
|
3
|
+
const required_s = `${toPascalCase(
|
|
4
|
+
collection_name
|
|
5
|
+
)}.fields.${name}.required`;
|
|
6
|
+
const fields_var = `${toPascalCase(collection_name)}FormFields`;
|
|
7
|
+
const fallback_value_initial = `""`;
|
|
8
|
+
const fallback_value_sealious = `""`;
|
|
9
|
+
const main_value_initial = `item.get("${name}")`;
|
|
10
|
+
const main_value_sealious = `data["${name}"]`;
|
|
11
|
+
const parsed_value = main_value_initial;
|
|
12
|
+
return {
|
|
13
|
+
is_required,
|
|
14
|
+
name,
|
|
15
|
+
post_create: "",
|
|
16
|
+
imports: { shared: "", edit: "" },
|
|
17
|
+
hide_field: is_readonly,
|
|
18
|
+
field: `${name}: new Fields.CollectionField(${required_s}, ${toPascalCase(
|
|
19
|
+
collection_name
|
|
20
|
+
)}.fields.${name})`,
|
|
21
|
+
hide_control: is_readonly,
|
|
22
|
+
controls: `new Controls.SimpleInput(${fields_var}.${name}, { label: "${name}"})`,
|
|
23
|
+
is_required_expr: `${toPascalCase(
|
|
24
|
+
collection_name
|
|
25
|
+
)}.fields.${name}.required`,
|
|
26
|
+
fields_var,
|
|
27
|
+
top_level_shared: "",
|
|
28
|
+
hide_initial_value_edit: is_readonly,
|
|
29
|
+
initial_value_edit: {
|
|
30
|
+
fallback_value: fallback_value_initial,
|
|
31
|
+
main_value: main_value_initial,
|
|
32
|
+
parsed_value
|
|
33
|
+
},
|
|
34
|
+
hide_initial_value_create: is_readonly,
|
|
35
|
+
initial_value_create: "",
|
|
36
|
+
pre_edit: "",
|
|
37
|
+
post_edit: "",
|
|
38
|
+
pre_create: "",
|
|
39
|
+
main_value_sealious,
|
|
40
|
+
fallback_value_sealious,
|
|
41
|
+
sealious_value: (result) => {
|
|
42
|
+
return result.is_required ? `${result.name}: ${result.main_value_sealious}` : `${result.name}: ${result.main_value_sealious} != null ? ${result.main_value_sealious} : ${result.fallback_value_sealious}`;
|
|
43
|
+
},
|
|
44
|
+
hide_sealious_value: is_readonly
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
export {
|
|
48
|
+
default_handler
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=default.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { toPascalCase } from "js-convert-case";
|
|
2
|
+
const enum_handler = async (collection_name, { name }, _, { fields_var }) => ({
|
|
3
|
+
// this assertion and import is only necessary because required fields
|
|
4
|
+
// in Sealious lose the type information. Once this is fixed, we can remove this import here
|
|
5
|
+
imports: { shared: `import type { FieldTypes } from "sealious";` },
|
|
6
|
+
field: `${name}: new Fields.PickFromListField(
|
|
7
|
+
${toPascalCase(collection_name)}.fields["${name}"].required,
|
|
8
|
+
async (ctx) =>
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
10
|
+
(${toPascalCase(collection_name)}.fields["${name}"])
|
|
11
|
+
.getAllowedValues(ctx.$app)
|
|
12
|
+
.map((value) => ({ value, label: value }))
|
|
13
|
+
)`,
|
|
14
|
+
controls: `new Controls.Dropdown(${fields_var}.${name}, { label: "${name}"})`
|
|
15
|
+
});
|
|
16
|
+
export {
|
|
17
|
+
enum_handler
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=enum.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { toCamelCase } from "js-convert-case";
|
|
2
|
+
const file_handler = async (_, { name, is_required }, __, { is_required_expr }) => {
|
|
3
|
+
const tempname = toCamelCase("prepared-" + name);
|
|
4
|
+
return {
|
|
5
|
+
field: `${name}: new Fields.File(${is_required_expr}, TheFileManager)`,
|
|
6
|
+
initial_value_edit: `${name}: {old: item.get("${name}")}`,
|
|
7
|
+
pre_edit: `const ${tempname} = data.${name}.new || data.${name}.old;
|
|
8
|
+
if(!${tempname}){
|
|
9
|
+
throw new Error("Missing field: ${name}")
|
|
10
|
+
}`,
|
|
11
|
+
fallback_value_sealious: "null",
|
|
12
|
+
...is_required ? { main_value_sealious: tempname } : {}
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export {
|
|
16
|
+
file_handler
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { toCamelCase } from "js-convert-case";
|
|
2
|
+
const image_handler = async (_, { name, is_required }, importPath, { is_required_expr, fields_var }) => {
|
|
3
|
+
const tempname = toCamelCase("prepared-" + name);
|
|
4
|
+
return {
|
|
5
|
+
imports: {
|
|
6
|
+
shared: `import { imageRouter } from "${importPath(
|
|
7
|
+
"src/back/image-router.js"
|
|
8
|
+
)}";
|
|
9
|
+
import { TheFileManager } from "${importPath(
|
|
10
|
+
"src/back/file-manager.js"
|
|
11
|
+
)}";`
|
|
12
|
+
},
|
|
13
|
+
field: `${name}: new Fields.File(${is_required_expr}, TheFileManager)`,
|
|
14
|
+
controls: `new Controls.Photo(${fields_var}.${name}, imageRouter, {
|
|
15
|
+
label: "${name}",
|
|
16
|
+
})`,
|
|
17
|
+
initial_value_edit: `{old: item.get("${name}")}`,
|
|
18
|
+
pre_edit: `const ${tempname} = data.${name}.new || data.${name}.old ` + (is_required ? `; if(!${tempname}){
|
|
19
|
+
throw new Error("Missing field: ${name}")
|
|
20
|
+
}` : " || undefined;"),
|
|
21
|
+
pre_create: `const ${tempname} = data.${name}.new || data.${name}.old;
|
|
22
|
+
if(!${tempname}){
|
|
23
|
+
throw new Error("Missing field: ${name}")
|
|
24
|
+
}`,
|
|
25
|
+
fallback_value_sealious: "null",
|
|
26
|
+
sealious_value: () => `${name}: ${tempname}`
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export {
|
|
30
|
+
image_handler
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=image.js.map
|
|
@@ -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
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { toKebabCase, toPascalCase } from "js-convert-case";
|
|
2
|
+
import { curryImportPath } from "../../utils/import-path.js";
|
|
3
|
+
import { formatWithPrettier } from "../../utils/prettier.js";
|
|
4
|
+
function itemDeleteTemplate(delete_action_name, collection_name, newfilefullpath, list_action_name) {
|
|
5
|
+
const importPath = curryImportPath(newfilefullpath);
|
|
6
|
+
const result = `import type { Context } from "koa";
|
|
7
|
+
import { Mountable } from "@sealcode/sealgen";
|
|
8
|
+
import type Router from "@koa/router";
|
|
9
|
+
|
|
10
|
+
import { ${toPascalCase(collection_name)} } from "${importPath(
|
|
11
|
+
"src/back/collections/collections.js"
|
|
12
|
+
)}";
|
|
13
|
+
|
|
14
|
+
import { ${list_action_name}URL } from "${importPath(
|
|
15
|
+
"src/back/routes/urls.js"
|
|
16
|
+
)}";
|
|
17
|
+
|
|
18
|
+
export const actionName = "${delete_action_name}";
|
|
19
|
+
|
|
20
|
+
export default new (class ${delete_action_name}Redirect extends Mountable {
|
|
21
|
+
canAccess = async (ctx: Context) => {
|
|
22
|
+
const policy = ${toPascalCase(collection_name)}.getPolicy("edit");
|
|
23
|
+
const response = await policy.check(ctx.$context);
|
|
24
|
+
return { canAccess: response?.allowed || false, message: response?.reason || "" };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
mount(router: Router, path: string) {
|
|
28
|
+
router.get(path, async (ctx) => {
|
|
29
|
+
await ctx.$app.collections["${toKebabCase(
|
|
30
|
+
collection_name
|
|
31
|
+
)}"].removeByID(ctx.$context, ctx.params.id);
|
|
32
|
+
ctx.status = 302;
|
|
33
|
+
ctx.redirect(${list_action_name}URL);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
})();
|
|
37
|
+
`;
|
|
38
|
+
return formatWithPrettier(result);
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
itemDeleteTemplate
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=item-delete.js.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { toPascalCase } from "js-convert-case";
|
|
2
|
+
import { extractCollectionClassname } from "../../generate-collections.js";
|
|
3
|
+
import extract_fields_from_collection from "../../utils/extract-fields-from-collection.js";
|
|
4
|
+
import _locreq from "locreq";
|
|
5
|
+
import { curryImportPath } from "../../utils/import-path.js";
|
|
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";
|
|
17
|
+
const target_locreq = _locreq(process.cwd());
|
|
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;
|
|
54
|
+
}
|
|
55
|
+
async function sharedCrudFormFields(collection_name, newfilefullpath) {
|
|
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
|
+
]);
|
|
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
|
+
);
|
|
70
|
+
const result = `import { Controls, Fields } from "@sealcode/sealgen";
|
|
71
|
+
import { ${uppercase_collection} } from "${importPath(
|
|
72
|
+
"src/back/collections/collections.js"
|
|
73
|
+
)}";
|
|
74
|
+
|
|
75
|
+
${Array.from(
|
|
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
|
+
|
|
83
|
+
|
|
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")}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const ${toPascalCase(collection_name)}FormControls = [
|
|
91
|
+
${field_handler_results.filter(({ hide_control }) => !hide_control).map(({ controls }) => controls).filter((e) => e != "").join(",\n")}
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
${Array.from(
|
|
95
|
+
new Set(
|
|
96
|
+
field_handler_results.map(({ top_level_shared }) => top_level_shared)
|
|
97
|
+
)
|
|
98
|
+
).join("\n")}
|
|
99
|
+
|
|
100
|
+
`;
|
|
101
|
+
return formatWithPrettier(result);
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
field_handlers,
|
|
105
|
+
getFieldHandler,
|
|
106
|
+
sharedCrudFormFields
|
|
107
|
+
};
|
|
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
|
)) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
async function writeFile(path, content) {
|
|
4
|
+
await fs.mkdir(resolve(path, "../"), { recursive: true });
|
|
5
|
+
await fs.writeFile(path, content);
|
|
6
|
+
console.log(`${path} created`);
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
writeFile
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=write-file.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sealcode/sealgen",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.36",
|
|
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: "",
|