@sealcode/sealgen 0.15.29 → 0.15.31

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.
Files changed (39) hide show
  1. package/.xunit +54 -43
  2. package/@types/src/add-list-route.test.d.ts +1 -0
  3. package/@types/src/forms/controls/controls.d.ts +1 -0
  4. package/@types/src/forms/controls/single-reference.d.ts +13 -0
  5. package/@types/src/forms/controls/single-reference.test.d.ts +1 -0
  6. package/@types/src/page/default-list-filters.d.ts +14 -3
  7. package/@types/src/page/list.d.ts +1 -1
  8. package/@types/src/templates/collection.d.ts +1 -1
  9. package/@types/src/templates/form.d.ts +8 -1
  10. package/@types/src/test_utils/test-on-real-app.d.ts +4 -1
  11. package/lib/add-collection.js +5 -1
  12. package/lib/add-list-route.test.js +38 -0
  13. package/lib/add-route.test.js +1 -1
  14. package/lib/forms/controls/controls.js +1 -0
  15. package/lib/forms/controls/single-reference.js +26 -0
  16. package/lib/forms/controls/single-reference.test.js +51 -0
  17. package/lib/page/default-list-filters.js +40 -33
  18. package/lib/page/list.js +6 -6
  19. package/lib/page/sealious-list.js +5 -0
  20. package/lib/page/sealious-list.test.js +1 -1
  21. package/lib/templates/collection.js +4 -3
  22. package/lib/templates/form.js +32 -22
  23. package/lib/templates/list.js +31 -12
  24. package/lib/test_utils/test-on-real-app.js +33 -6
  25. package/package.json +2 -1
  26. package/src/add-collection.ts +5 -1
  27. package/src/add-list-route.test.ts +45 -0
  28. package/src/add-route.test.ts +1 -1
  29. package/src/forms/controls/controls.ts +1 -0
  30. package/src/forms/controls/single-reference.test.ts +62 -0
  31. package/src/forms/controls/single-reference.ts +36 -0
  32. package/src/page/default-list-filters.ts +45 -33
  33. package/src/page/list.ts +8 -8
  34. package/src/page/sealious-list.test.ts +1 -1
  35. package/src/page/sealious-list.ts +5 -0
  36. package/src/templates/collection.ts +8 -3
  37. package/src/templates/form.ts +34 -22
  38. package/src/templates/list.ts +31 -12
  39. package/src/test_utils/test-on-real-app.ts +35 -3
@@ -2,10 +2,39 @@ import Prettier from "prettier";
2
2
  import { curryImportPath } from "../utils/import-path.js";
3
3
  import { getPrettierConfig } from "../utils/prettier.js";
4
4
 
5
+ const defaults = {
6
+ postimport: ``,
7
+ fields: `
8
+ email: new Fields.CollectionField(true, Users.fields.email),
9
+ password: new Fields.SimpleFormField(true),`,
10
+ controls: `
11
+ new Controls.SimpleInput(fields.email, { label: "Email:", type: "email" }),
12
+ new Controls.SimpleInput(fields.password, {
13
+ label: "Password:",
14
+ type: "password",
15
+ }),`,
16
+ validate_method: `async validateValues(
17
+ ctx: Context,
18
+ data: Record<string, FormDataValue>
19
+ ): Promise<{ valid: boolean; error: string }> {
20
+ // just a silly example, please remove this with better logic
21
+ const { parsed: email } = await this.fields.email.getValue(ctx, data);
22
+ const { parsed: password } = await this.fields.password.getValue(ctx, data);
23
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
24
+ if ((password || "").length > ((email as string) || "").length) {
25
+ return { valid: false, error: "Password cannot be longer than email" };
26
+ } else {
27
+ return { valid: true, error: "" };
28
+ }
29
+ }`,
30
+ };
31
+
5
32
  export async function formTemplate(
6
33
  action_name: string,
7
- newfilefullpath: string
34
+ newfilefullpath: string,
35
+ _params: Partial<typeof defaults> = defaults
8
36
  ): Promise<string> {
37
+ const params = { ...defaults, ..._params };
9
38
  const rel = curryImportPath(newfilefullpath);
10
39
  const content = `import type { Context } from "koa";
11
40
  import type { FormData, FormDataValue } from "@sealcode/sealgen";
@@ -13,11 +42,12 @@ import { Form, Fields, Controls, fieldsToShape } from "@sealcode/sealgen";
13
42
  import html from "${rel("src/back/html.js")}";
14
43
  import { Users } from "${rel("src/back/collections/collections.js")}";
15
44
 
45
+ ${params.postimport}
46
+
16
47
  export const actionName = "${action_name}";
17
48
 
18
49
  const fields = {
19
- email: new Fields.CollectionField(true, Users.fields.email),
20
- password: new Fields.SimpleFormField(true),
50
+ ${params.fields}
21
51
  };
22
52
 
23
53
  export const ${action_name}Shape = fieldsToShape(fields);
@@ -27,27 +57,9 @@ export default new (class ${action_name}Form extends Form<typeof fields, void> {
27
57
  fields = fields;
28
58
 
29
59
  controls = [
30
- new Controls.SimpleInput(fields.email, { label: "Email:", type: "email" }),
31
- new Controls.SimpleInput(fields.password, {
32
- label: "Password:",
33
- type: "password",
34
- }),
60
+ ${params.controls}
35
61
  ];
36
62
 
37
- async validateValues(
38
- ctx: Context,
39
- data: Record<string, FormDataValue>
40
- ): Promise<{ valid: boolean; error: string }> {
41
- // just a silly example, please remove this with better logic
42
- const { parsed: email } = await this.fields.email.getValue(ctx, data);
43
- const { parsed: password } = await this.fields.password.getValue(ctx, data);
44
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
45
- if ((password || "").length > ((email as string) || "").length) {
46
- return { valid: false, error: "Password cannot be longer than email" };
47
- } else {
48
- return { valid: true, error: "" };
49
- }
50
- }
51
63
 
52
64
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
53
65
  async canAccess(_: Context) {
@@ -61,7 +61,7 @@ const filterFields = [
61
61
  ${collection_fields
62
62
  .map(
63
63
  (field) =>
64
- ` { field: "${field.name}", render: ${
64
+ ` { field: "${field.name}", ...${
65
65
  DefaultListFilters[field.type]
66
66
  ? `DefaultListFilters["${field.type}"]`
67
67
  : `DefaultListFilters.fallback`
@@ -71,6 +71,7 @@ ${collection_fields
71
71
  ] as {
72
72
  field: keyof (typeof ${uppercase_collection})["fields"];
73
73
  render?: ListFilterRender;
74
+ prepareValue?: (filter_value: unknown)=>unknown; // set this function to change what filter value is passed to Sealious
74
75
  }[];
75
76
 
76
77
  const displayFields = [
@@ -101,6 +102,7 @@ ${collection_fields
101
102
  format: async (value: FilePointer) => {
102
103
  return imageRouter.image(await value.getPath(), {
103
104
  container: { width: 45, height: 45 },
105
+ alt: "",
104
106
  });
105
107
  },
106
108
  }`;
@@ -123,23 +125,19 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
123
125
  async renderFilters(ctx: Context): Promise<FlatTemplatable> {
124
126
  const query_params = qs.parse(ctx.search.slice(1));
125
127
  query_params.page = "1";
126
- const filter_values = await this.getFilterValues(ctx);
128
+ const filter_values = await super.getFilterValues(ctx);
127
129
  return <form>
128
130
  {Object.entries(query_params).map(([key, value]) => {
129
131
  if (key == "filter") {
130
132
  return "";
131
133
  }
132
- return <input
133
- type="hidden"
134
- name={key}
135
- value={value}
136
- />;
134
+ // this is necessary to not lose any query params when the user changes the filter values
135
+ return <input type="hidden" name={key} value={value}/>;
137
136
  })}
138
137
  {filterFields.map(({ field, render }) => {
139
138
  if (!render) {
140
139
  render = DefaultListFilters.fallback;
141
140
  }
142
-
143
141
  return (
144
142
  render(filter_values[field] || "", this.collection.fields[field]) ||
145
143
  ""
@@ -147,6 +145,21 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
147
145
  })}
148
146
  <input type="submit" />
149
147
  </form>;
148
+ }
149
+
150
+ async getFilterValues(ctx: Context) {
151
+ // adding opportunity to adjust the values for a given field filter before it's sent to Sealious
152
+ const values = await super.getFilterValues(ctx);
153
+ for (const filterField of filterFields) {
154
+ const key = filterField.field as keyof typeof values;
155
+ if (key in values) {
156
+ const prepare_fn = filterField.prepareValue;
157
+ if (prepare_fn) {
158
+ values[key] = prepare_fn(values[key]) as any;
159
+ }
160
+ }
161
+ }
162
+ return values;
150
163
  }
151
164
 
152
165
  async renderItem(ctx: Context, item: CollectionItem<typeof ${uppercase_collection}>) {
@@ -157,6 +170,15 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
157
170
  return <td>{format ? format(value) : value}</td>;
158
171
  })}
159
172
  </tr>;
173
+ }
174
+
175
+ renderListContainer(ctx: Context, content: Templatable): FlatTemplatable {
176
+ return (
177
+ <table class="sealious-list ${toKebabCase(action_name)}-table">
178
+ {this.renderTableHead(ctx, displayFields)}
179
+ <tbody>{content}</tbody>
180
+ </table>
181
+ );
160
182
  }
161
183
 
162
184
  async render(ctx: Context) {
@@ -165,10 +187,7 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
165
187
  "${action_name}",
166
188
  <div class="sealious-list-wrapper ${toKebabCase(action_name)}--wrapper">
167
189
  <h2>${action_name} List</h2>
168
- <table class="sealious-list">
169
- {this.renderTableHead(ctx, displayFields)}
170
- <tbody>{super.render(ctx)}</tbody>
171
- </table>
190
+ {super.render(ctx)}
172
191
  </div>
173
192
  );
174
193
  }
@@ -5,6 +5,8 @@ import { spawn } from "child_process";
5
5
  import getPort from "get-port";
6
6
  import waitPort from "wait-port";
7
7
  import { promises as fs } from "fs";
8
+ import path from "node:path";
9
+ import chalk from "chalk";
8
10
 
9
11
  import _locreq from "locreq";
10
12
  import { module_dirname } from "../utils/module-dirname.js";
@@ -43,6 +45,7 @@ export class RealAppTest {
43
45
  console.log(
44
46
  "\tstarting the database...(if timeouts here, run 'docker pull mongo:4.4-bionic' to solve)"
45
47
  );
48
+ await test_app.runSealgenCommand("build");
46
49
  test_app.app_port = await getPort();
47
50
  return test_app;
48
51
  }
@@ -83,6 +86,11 @@ export class RealAppTest {
83
86
  output: "silent",
84
87
  timeout: 6000,
85
88
  });
89
+ console.log(
90
+ chalk.yellow(
91
+ `App is listening on http://localhost:${this.app_port}`
92
+ )
93
+ );
86
94
  }
87
95
 
88
96
  async close() {
@@ -105,6 +113,11 @@ export class RealAppTest {
105
113
  this.app_path
106
114
  } && ${callSealgenBin()} ${command} && ${callSealgenBin()} build`
107
115
  );
116
+ await this.build();
117
+ }
118
+
119
+ async build() {
120
+ await execAsync(`cd ${this.app_path} && ${callSealgenBin()} build`);
108
121
  }
109
122
 
110
123
  async throwIfItKillsTheApp<T>(cb: () => Promise<T>): Promise<T> {
@@ -134,7 +147,7 @@ export class RealAppTest {
134
147
  });
135
148
  }
136
149
 
137
- async httpGet(path: string): Promise<{ status: number; response: string }> {
150
+ async httpGET(path: string): Promise<{ status: number; response: string }> {
138
151
  return this.throwIfItKillsTheApp(async () => {
139
152
  const ret = await fetch(`http://localhost:${this.app_port}${path}`);
140
153
  const status = ret.status;
@@ -147,6 +160,7 @@ export class RealAppTest {
147
160
  path: string,
148
161
  body: Record<string, unknown>
149
162
  ): Promise<{ status: number; response: string }> {
163
+ console.debug("POST", path, body);
150
164
  return this.throwIfItKillsTheApp(async () => {
151
165
  const ret = await fetch(
152
166
  `http://localhost:${this.app_port}${path}`,
@@ -159,9 +173,27 @@ export class RealAppTest {
159
173
  const status = ret.status;
160
174
  const response = await ret.text();
161
175
 
162
- console.log("request done!");
163
- console.log({ response });
176
+ console.debug("request done!");
177
+ console.debug({ response });
164
178
  return { status, response };
165
179
  });
166
180
  }
181
+
182
+ async addFile(target_path: string, content: string) {
183
+ console.log(
184
+ chalk.yellow("Inserting file at path"),
185
+ chalk.green(target_path),
186
+ "with content\n",
187
+ content,
188
+ "\n\n\n\n=====\n"
189
+ );
190
+ await fs.writeFile(path.resolve(this.app_path, target_path), content);
191
+ await this.build();
192
+ }
193
+
194
+ fullURL(path: string): string {
195
+ return `http://localhost:${this.app_port}${
196
+ path.startsWith("/") ? "" : "/"
197
+ }${path}`;
198
+ }
167
199
  }