@sealcode/sealgen 0.10.6 → 0.11.0

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.
@@ -25,7 +25,7 @@ export declare class StructuredArray<Subfields extends Record<string, Field>> ex
25
25
  getActionURL(form_id?: string, action?: Record<string, unknown>): string;
26
26
  getDefaultItemBody(): Promise<{}>;
27
27
  makeSubfield(_field_name: string): FormField;
28
- renderItemControl(ctx: Context, form_id: string, control: FormControl | UnboundControlStorage, item: FieldsetOutput<Subfields>, index: number): Promise<(string | number | import("stream").Readable | null) | (string | number | import("stream").Readable | null)[] | Promise<string | number | import("stream").Readable | null>[] | import("tempstream").Slot>;
28
+ renderItemControl(ctx: Context, form_id: string, control: FormControl | UnboundControlStorage, item: FieldsetOutput<Subfields>, index: number): Promise<(string | number | import("stream").Readable | null) | (string | number | import("stream").Readable | null)[] | Promise<string | number | import("stream").Readable | null>[]>;
29
29
  renderItem(ctx: Context, form_id: string, item: FieldsetOutput<Subfields>, index: number): Promise<import("stream").Readable>;
30
30
  getItems(ctx: Context, raw_values: Record<string, FormDataValue>): Promise<FieldsetOutput<Subfields>[]>;
31
31
  _render(ctx: Context, form_id: string, raw_values: Record<string, FormDataValue>): Promise<import("stream").Readable>;
@@ -17,7 +17,7 @@ export declare class Multiform extends MountableWithFields {
17
17
  getSubformsToRender(ctx: Context): Array<readonly [string, Form<any, unknown>]>;
18
18
  renderSubform(ctx: Context, sub_form_name: string, show_field_errors: boolean): Promise<FlatTemplatable>;
19
19
  getSubformRawValues(ctx: Context, sub_form_name: string): Promise<Record<string, FormDataValue>>;
20
- render(ctx: Context, messages: FormMessage[], prerenderedForms: Record<string, FlatTemplatable | undefined> | undefined, show_field_errors: boolean): Promise<FlatTemplatable>;
20
+ render(ctx: Context, messages: FormMessage[], prerenderedForms: Record<string, import("tempstream/@types/stringify").Stringifiable | undefined> | undefined, show_field_errors: boolean): Promise<FlatTemplatable>;
21
21
  makeBottomScript(): string;
22
22
  mount(router: Router, path: string): void;
23
23
  }
package/lib/add-route.js CHANGED
@@ -45,7 +45,10 @@ async function addRoute(params, app_directory = process.cwd()) {
45
45
  },
46
46
  ]);
47
47
  const { action, url, mode } = response;
48
- const file_path = target_locreq.resolve(`src/back/routes/${(0, escape_url_params_1.escape_url_params)(url)}.${mode}.ts`);
48
+ const extension = ["list", "lpr", "page", "sreact"].includes(mode)
49
+ ? "tsx"
50
+ : "ts";
51
+ const file_path = target_locreq.resolve(`src/back/routes/${(0, escape_url_params_1.escape_url_params)(url)}.${mode}.${extension}`);
49
52
  const test_file_path = target_locreq.resolve(`src/back/routes/${(0, escape_url_params_1.escape_url_params)(url)}.test.ts`);
50
53
  const template = templates_1.Templates[mode];
51
54
  if (!template) {
@@ -9,7 +9,10 @@ const util_1 = require("util");
9
9
  const axios_1 = __importDefault(require("axios"));
10
10
  const wait_port_1 = __importDefault(require("wait-port"));
11
11
  const get_port_1 = __importDefault(require("get-port"));
12
+ const locreq_1 = __importDefault(require("locreq"));
13
+ const target_locreq = (0, locreq_1.default)(__dirname);
12
14
  const execAsync = (0, util_1.promisify)(child_process_1.exec);
15
+ const callSealgenBin = () => `node ${target_locreq.resolve("lib/cli.js")}`;
13
16
  describe("add route test", () => {
14
17
  let node_pid = 0;
15
18
  const app_path = "/tmp/sealious-minimal";
@@ -30,11 +33,11 @@ describe("add route test", () => {
30
33
  it("creates minimal sealious app", async function () {
31
34
  this.timeout(250000);
32
35
  console.log("\tcloning sealious-minimal...");
33
- await execAsync(`git clone https://hub.sealcode.org/source/sealious-minimal.git ${app_path} && cd ${app_path} && git checkout ce4f33309294`);
36
+ await execAsync(`git clone https://hub.sealcode.org/source/sealious-minimal.git ${app_path} && cd ${app_path} && git checkout d5abeb9514359dde4`);
34
37
  console.log("\tinstalling dependencies and sealgen...");
35
38
  await execAsync(`cd ${app_path} && npm ci && rm -rf node_modules/@sealcode/sealgen && ln -s $OLDPWD node_modules/@sealcode/sealgen`);
36
39
  console.log("\trunning add-route and building app...");
37
- await execAsync(`cd ${app_path} && npx sealgen add-route --url=/sample --action=Sample --mode=page && npm run build`);
40
+ await execAsync(`cd ${app_path} && ${callSealgenBin()} add-route --url=/sample --action=Sample --mode=page && ${callSealgenBin()} build`);
38
41
  console.log("\tstarting the database...(if timeouts here, run 'docker pull mongo:4.4-bionic' to solve)");
39
42
  const port = await (0, get_port_1.default)();
40
43
  await execAsync(`docker run -p ${port}:27017 --name ${container_name} -d mongo:4.4-bionic`);
package/lib/build.js CHANGED
@@ -29,6 +29,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.buildProject = void 0;
30
30
  const esbuild_1 = require("esbuild");
31
31
  const fs_1 = require("fs");
32
+ const path_1 = require("path");
32
33
  const esbuild_sass_plugin_1 = require("esbuild-sass-plugin");
33
34
  const chokidar = __importStar(require("chokidar"));
34
35
  const tiny_glob_1 = __importDefault(require("tiny-glob"));
@@ -37,20 +38,22 @@ const sleep_1 = require("./utils/sleep");
37
38
  const generate_collections_1 = require("./generate-collections");
38
39
  const generate_routes_1 = require("./generate-routes");
39
40
  const generate_names = (function* generate_names() {
40
- let base_name = "____unique_import_";
41
+ const base_name = "____unique_import_";
41
42
  let count = 0;
42
43
  while (true) {
43
- yield base_name + count;
44
+ yield `${base_name}${count}`;
44
45
  count++;
45
46
  }
46
47
  })();
47
48
  async function build_ts() {
48
49
  await Promise.all([(0, generate_collections_1.generateCollections)(), (0, generate_routes_1.generateRoutes)()]);
49
- const entryPoints = (await (0, tiny_glob_1.default)("./src/back/**/*.ts")).filter((path) => !path.includes(".#"));
50
+ const entryPoints = (await (0, tiny_glob_1.default)("./src/back/**/*.{ts,tsx}")).filter((path) => !path.includes(".#"));
50
51
  try {
51
52
  await (0, esbuild_1.build)({
52
53
  entryPoints,
53
54
  sourcemap: true,
55
+ bundle: false,
56
+ jsxFactory: "TempstreamJSX.createElement",
54
57
  outdir: "./dist/back",
55
58
  logLevel: "info",
56
59
  platform: "node",
@@ -64,6 +67,7 @@ async function build_ts() {
64
67
  const named_and_default_regex = /^import\s+((\S+),\s+)?({\s*[^}]+})\s+from\s+("[^"]+");\s*$/gm;
65
68
  const old_content = await fs_1.promises.readFile(path, "utf-8");
66
69
  const contents = old_content.replace(named_and_default_regex, (_, default_import_name, __, all_named, module_path) => {
70
+ /* eslint-disable */
67
71
  const new_name = generate_names.next().value;
68
72
  const result = `import * as ${new_name} from ${module_path}${default_import_name
69
73
  ? `; const ${default_import_name.replace(",", "")} = ${new_name}.default || ${new_name};`
@@ -83,8 +87,12 @@ async function build_ts() {
83
87
  })
84
88
  .join(" ");
85
89
  return result;
90
+ /* eslint-enable */
86
91
  });
87
- return { contents, loader: "ts" };
92
+ return {
93
+ contents,
94
+ loader: (0, path_1.extname)(path) === ".tsx" ? "tsx" : "ts",
95
+ };
88
96
  });
89
97
  },
90
98
  },
@@ -92,6 +100,7 @@ async function build_ts() {
92
100
  });
93
101
  }
94
102
  catch (e) {
103
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
95
104
  console.error(e.message);
96
105
  }
97
106
  }
@@ -112,6 +121,7 @@ async function complex_build(watch) {
112
121
  console.log(`Built main.scss [on ${path}]`);
113
122
  }
114
123
  catch (e) {
124
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
115
125
  console.error(e.message);
116
126
  await (0, sleep_1.sleep)(200);
117
127
  scss_build
@@ -120,7 +130,7 @@ async function complex_build(watch) {
120
130
  .catch((e) => console.error(e?.message));
121
131
  }
122
132
  }
123
- if (path.endsWith(".ts") &&
133
+ if ((path.endsWith(".ts") || path.endsWith(".tsx")) &&
124
134
  !path.endsWith("src/back/collections/collections.ts") &&
125
135
  !path.endsWith("src/back/routes/routes.ts") &&
126
136
  !path.endsWith("src/back/routes/urls.ts")) {
@@ -107,8 +107,8 @@ async function general_form_validation_e2e_tests(js_enabled) {
107
107
  await page.getByPlaceholder("password").click();
108
108
  await page.getByPlaceholder("password").fill("test");
109
109
  await page.locator("#multiform_test_form2_submit").click();
110
- (0, utils_1.assertThrowsAsync)(async () => {
111
- page.getByText("Incorrect input").click({ timeout: 500 });
110
+ await (0, utils_1.assertThrowsAsync)(async () => {
111
+ await page.getByText("Incorrect input").click({ timeout: 500 });
112
112
  });
113
113
  }
114
114
  async function individual_field_validation_e2e_tests(javaScriptEnabled) {
@@ -66,7 +66,7 @@ function generateURL(actionName, url) {
66
66
  }
67
67
  async function generateRoutes() {
68
68
  const files = await Promise.all((await (0, walk_1.walkDir)(target_locreq.resolve("src/back/routes")))
69
- .filter((f) => Object.keys(templates_1.Templates).some((key) => f.endsWith(`.${key}.ts`)))
69
+ .filter((f) => Object.keys(templates_1.Templates).some((key) => f.endsWith(`.${key}.ts`) || f.endsWith(`.${key}.tsx`)))
70
70
  .map(async (fullpath) => ({
71
71
  fullpath,
72
72
  actionName: await extractActionName(fullpath),
@@ -29,8 +29,8 @@ const listTemplate = async (action_name, newfilefullpath) => {
29
29
  ]);
30
30
  const rel = (0, import_path_1.curryImportPath)(newfilefullpath);
31
31
  return `import { Context } from "koa";
32
- import { CollectionItem, Field } from "sealious";
33
- import { FlatTemplatable, tempstream } from "tempstream";
32
+ import { CollectionItem } from "sealious";
33
+ import { FlatTemplatable, TempstreamJSX } from "tempstream";
34
34
  import { ${uppercase_collection} } from "${rel("src/back/collections/collections.js")}";
35
35
  import html from "${rel("src/back/html.js")}";
36
36
  import {
@@ -51,7 +51,7 @@ ${collection_fields
51
51
  : `DefaultListFilters.fallback`} }`)
52
52
  .join(",\n")}
53
53
  ] as {
54
- field: keyof (typeof PatronEvents)["fields"];
54
+ field: keyof (typeof ${uppercase_collection})["fields"];
55
55
  render?: ListFilterRender;
56
56
  }[];
57
57
 
@@ -91,18 +91,18 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
91
91
  const query_params = qs.parse(ctx.search.slice(1));
92
92
  query_params.page = "1";
93
93
  const filter_values = await this.getFilterValues(ctx);
94
- return tempstream/* HTML */ \`<form>
95
- \${Object.entries(query_params).map(([key, value]) => {
94
+ return <form>
95
+ {Object.entries(query_params).map(([key, value]) => {
96
96
  if (key == "filter") {
97
97
  return "";
98
98
  }
99
- return /* HTML */ \`<input
99
+ return <input
100
100
  type="hidden"
101
101
  name="\${key}"
102
102
  value="\${value}"
103
- />\`;
103
+ />;
104
104
  })}
105
- \${filterFields.map(({ field, render }) => {
105
+ {filterFields.map(({ field, render }) => {
106
106
  if (!render) {
107
107
  render = DefaultListFilters.fallback;
108
108
  }
@@ -113,31 +113,29 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
113
113
  );
114
114
  })}
115
115
  <input type="submit" />
116
- </form>\`;
116
+ </form>;
117
117
  }
118
118
 
119
119
  async renderItem(_: Context, item: CollectionItem<typeof ${uppercase_collection}>) {
120
- return tempstream/* HTML */ \`<tr>
121
- \${displayFields.map(({ field, format }) => {
120
+ return <tr>
121
+ {displayFields.map(({ field, format }) => {
122
122
  const value = item.get(field as any);
123
- return tempstream\`<td>\${format ? format(value) : value}</td>\`;
123
+ return <td>{format ? format(value) : value}</td>;
124
124
  })}
125
- </tr>\`;
125
+ </tr>;
126
126
  }
127
127
 
128
128
  async render(ctx: Context) {
129
129
  return html(
130
130
  ctx,
131
131
  "${action_name}",
132
- tempstream/* HTML */ \`<div>
133
- <h2>${action_name}</h2>
132
+ <div>
133
+ <h2>${action_name} List</h2>
134
134
  <table class="sealious-list">
135
- \${this.renderTableHead(ctx, displayFields)}
136
- <tbody>
137
- \${super.render(ctx)}
138
- </tbody>
135
+ {this.renderTableHead(ctx, displayFields)}
136
+ <tbody>{super.render(ctx)}</tbody>
139
137
  </table>
140
- </div>\`
138
+ </div>
141
139
  );
142
140
  }
143
141
 
@@ -5,7 +5,7 @@ const import_path_1 = require("../utils/import-path");
5
5
  async function LongRunningProcessTemplate(action_name, newfilefullpath) {
6
6
  const rel = (0, import_path_1.curryImportPath)(newfilefullpath);
7
7
  return `import { Context } from "koa";
8
- import { tempstream } from "tempstream";
8
+ import { TempstreamJSX } from "tempstream";
9
9
  import { Page } from "@sealcode/sealgen";
10
10
  import html from "${rel("src/back/html.js")}";
11
11
  import { LongRunningProcess } from "sealious";
@@ -23,34 +23,38 @@ export default new (class PatroniteAutoImportStatusPage extends Page {
23
23
  async render(ctx: Context) {
24
24
  const lpr_id = ctx.params.id;
25
25
  const lpr_data = LongRunningProcess.getByID(ctx.$context, lpr_id);
26
+ const render_script = lpr_data.state !== "error" && lpr_data.state !== "finished";
27
+ const script = /* HTML */ \`<script>
28
+ setTimeout(() => {
29
+ document.location = document.location;
30
+ }, \${this.interval});
31
+ </script>\`;
32
+
26
33
  return html(
27
34
  ctx,
28
35
  "PatroniteAutoImportStatus",
29
- tempstream/* HTML */ \`\${lpr_data.then(
30
- (lpr_data) => tempstream/* HTML */ \`
31
- <div>status: \${lpr_data.state}</div>
32
- <div style="height: 20px; width: 100%">
33
- <div
34
- style="height: 100%; background-color: green; width: \${Math.round(
35
- lpr_data.progress * 100
36
- )}%"
37
- ></div>
38
- </div>
39
- <div>
40
- \${lpr_data.events.map(
41
- (e) => /* HTML */ \`<div>\${e.message}</div>\`
42
- )}
43
- </div>
44
- \${lpr_data.state !== "error" &&
45
- lpr_data.state !== "finished"
46
- ? /* HTML */ \`<script>
47
- setTimeout(() => {
48
- document.location = document.location;
49
- }, \${this.interval});
50
- </script>\`
51
- : ""}
52
- \`
53
- )}\`
36
+ <div>
37
+ {lpr_data.then((lpr_data) => {
38
+ return (
39
+ <div>
40
+ <div>status: {lpr_data.state}</div>
41
+ <div style="height: 20px; width: 100%">
42
+ <div
43
+ style="height: 100%; background-color: green; width: {Math.round(
44
+ lpr_data.progress * 100
45
+ )}%"
46
+ ></div>
47
+ </div>
48
+ <div>
49
+ {lpr_data.events.map((e) => (
50
+ <div>{e.message}</div>
51
+ ))}
52
+ </div>
53
+ {render_script ? { script } : ""}
54
+ </div>
55
+ );
56
+ })}
57
+ </div>
54
58
  );
55
59
  }
56
60
  })();
@@ -5,7 +5,7 @@ const import_path_1 = require("../utils/import-path");
5
5
  async function pageTemplate(action_name, newfilefullpath) {
6
6
  const rel = (0, import_path_1.curryImportPath)(newfilefullpath);
7
7
  return `import { Context } from "koa";
8
- import { tempstream } from "tempstream";
8
+ import { TempstreamJSX } from "tempstream";
9
9
  import { Page } from "@sealcode/sealgen";
10
10
  import html from "${rel("src/back/html.js")}";
11
11
 
@@ -18,7 +18,7 @@ export default new (class ${action_name}Page extends Page {
18
18
  }
19
19
 
20
20
  async render(ctx: Context) {
21
- return html(ctx, "${action_name}", tempstream/* HTML */ \`<div></div>\`);
21
+ return html(ctx, "${action_name}", <div></div>);
22
22
  }
23
23
  })();
24
24
  `;
@@ -5,9 +5,10 @@ const import_path_1 = require("../utils/import-path");
5
5
  function testTemplate(action_name, new_file_fullpath) {
6
6
  const rel = (0, import_path_1.curryImportPath)(new_file_fullpath);
7
7
  return `import { withProdApp } from "${rel("src/back/test_utils/with-prod-app.js")}";
8
- import { LONG_TEST_TIMEOUT, webhintURL } from "${rel("src/back/test_utils/webhint.js")}";
8
+ import { VERY_LONG_TEST_TIMEOUT, webhintURL } from "${rel("src/back/test_utils/webhint.js")}";
9
9
  import { ${action_name}URL } from "${rel("src/back/routes/urls.js")}";
10
- import { getBrowser } from "${rel("src/test_utils/browser-creator.js")}";
10
+ import { getBrowser } from "${rel("src/back/test_utils/browser-creator.js")}";
11
+ import { Browser, BrowserContext, Page } from "@playwright/test";
11
12
 
12
13
  describe("${action_name} webhint", () => {
13
14
  it(
@@ -4,14 +4,14 @@ exports.statefulPageTemplate = void 0;
4
4
  const import_path_1 = require("../utils/import-path");
5
5
  async function statefulPageTemplate(action_name, newfilefullpath) {
6
6
  const rel = (0, import_path_1.curryImportPath)(newfilefullpath);
7
- return `import { BaseContext } from "koa";
8
- import { tempstream, Templatable } from "tempstream";
7
+ return `import { TempstreamJSX, Templatable } from "tempstream";
8
+ import { BaseContext } from "koa";
9
9
  import { StatefulPage } from "@sealcode/sealgen";
10
10
  import html from "${rel("src/back/html.js")}";
11
11
 
12
12
  export const actionName = "${action_name}";
13
13
 
14
- const actions = <const>{
14
+ const actions = {
15
15
  add: (state: State, inputs: Record<string, string>) => {
16
16
  console.log({ inputs });
17
17
  return {
@@ -25,39 +25,43 @@ const actions = <const>{
25
25
  elements: state.elements.filter((_, index) => index != index_to_remove),
26
26
  };
27
27
  },
28
- };
28
+ } as const;
29
29
 
30
30
  type State = {
31
31
  elements: string[];
32
32
  };
33
33
 
34
- export default new (class ${action_name}Page extends StatefulPage<State, typeof actions> {
34
+ export default new (class ${action_name}Page extends StatefulPage<State, typeof actions> {
35
35
  actions = actions;
36
36
 
37
37
  getInitialState() {
38
38
  return { elements: ["one", "two", "three"] };
39
39
  }
40
40
 
41
- wrapInLayout(ctx: BaseContext, content: Templatable): Templatable {
41
+ wrapInLayout(ctx: BaseContext, content: Templatable): Templatable {
42
42
  return html(ctx, "${action_name}", content);
43
43
  }
44
44
 
45
45
  render(ctx: BaseContext, state: State, inputs: Record<string, string>) {
46
- return tempstream/* HTML */ \`<ul>
47
- \${state.elements.map(
48
- (e, index) => /* HTML */ \`<li>
49
- \${e} \${this.makeActionButton(state, "remove", index)}
50
- </li>\`
51
- )}
52
- </ul>
46
+ return (
53
47
  <div>
54
- <input
48
+ <ul>
49
+ {state.elements.map((e, index) => (
50
+ <li>
51
+ {e} {this.makeActionButton(state, "remove", index)}
52
+ </li>
53
+ ))}
54
+ </ul>
55
+ <div>
56
+ <input
55
57
  name="element_to_add"
56
58
  type="text"
57
- value="\${inputs.element_to_add || ""}"
59
+ value={inputs.element_to_add || ""}
58
60
  />
59
- \${this.makeActionButton(state, "add")}
60
- </div>\`;
61
+ {this.makeActionButton(state, "add")}
62
+ </div>
63
+ </div>
64
+ );
61
65
  }
62
66
  })();
63
67
  `;
@@ -8,7 +8,7 @@ const path_1 = require("path");
8
8
  const locreq_1 = __importDefault(require("locreq"));
9
9
  const target_locreq = (0, locreq_1.default)(process.cwd());
10
10
  function importPath(project_module_path, project_target_path) {
11
- let result = (0, path_1.relative)((0, path_1.dirname)(target_locreq.resolve(project_module_path)), target_locreq.resolve(project_target_path).replace(/.ts$/, ".js"));
11
+ let result = (0, path_1.relative)((0, path_1.dirname)(target_locreq.resolve(project_module_path)), target_locreq.resolve(project_target_path).replace(/.tsx?$/, ".js"));
12
12
  if (result[0] != ".") {
13
13
  result = "./" + result;
14
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.10.6",
3
+ "version": "0.11.0",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -55,7 +55,7 @@
55
55
  "merge": "^2.1.1",
56
56
  "prettier": "^2.7.1",
57
57
  "prompts": "^2.4.2",
58
- "tempstream": "^0.0.21",
58
+ "tempstream": "^0.3.0",
59
59
  "tiny-glob": "^0.2.9",
60
60
  "yargs": "^17.6.2"
61
61
  },
@@ -4,9 +4,13 @@ import { promisify } from "util";
4
4
  import axios from "axios";
5
5
  import waitPort from "wait-port";
6
6
  import getPort from "get-port";
7
+ import _locreq from "locreq";
8
+ const target_locreq = _locreq(__dirname);
7
9
 
8
10
  const execAsync = promisify(exec);
9
11
 
12
+ const callSealgenBin = () => `node ${target_locreq.resolve("lib/cli.js")}`;
13
+
10
14
  describe("add route test", () => {
11
15
  let node_pid = 0;
12
16
  const app_path = "/tmp/sealious-minimal";
@@ -27,7 +31,7 @@ describe("add route test", () => {
27
31
  this.timeout(250000);
28
32
  console.log("\tcloning sealious-minimal...");
29
33
  await execAsync(
30
- `git clone https://hub.sealcode.org/source/sealious-minimal.git ${app_path} && cd ${app_path} && git checkout ce4f33309294`
34
+ `git clone https://hub.sealcode.org/source/sealious-minimal.git ${app_path} && cd ${app_path} && git checkout d5abeb9514359dde4`
31
35
  );
32
36
  console.log("\tinstalling dependencies and sealgen...");
33
37
  await execAsync(
@@ -35,7 +39,7 @@ describe("add route test", () => {
35
39
  );
36
40
  console.log("\trunning add-route and building app...");
37
41
  await execAsync(
38
- `cd ${app_path} && npx sealgen add-route --url=/sample --action=Sample --mode=page && npm run build`
42
+ `cd ${app_path} && ${callSealgenBin()} add-route --url=/sample --action=Sample --mode=page && ${callSealgenBin()} build`
39
43
  );
40
44
  console.log(
41
45
  "\tstarting the database...(if timeouts here, run 'docker pull mongo:4.4-bionic' to solve)"
package/src/add-route.ts CHANGED
@@ -47,9 +47,12 @@ export async function addRoute(
47
47
  ]);
48
48
 
49
49
  const { action, url, mode } = response as Record<string, string>;
50
+ const extension = ["list", "lpr", "page", "sreact"].includes(mode)
51
+ ? "tsx"
52
+ : "ts";
50
53
 
51
54
  const file_path = target_locreq.resolve(
52
- `src/back/routes/${escape_url_params(url)}.${mode}.ts`
55
+ `src/back/routes/${escape_url_params(url)}.${mode}.${extension}`
53
56
  );
54
57
  const test_file_path = target_locreq.resolve(
55
58
  `src/back/routes/${escape_url_params(url)}.test.ts`
package/src/build.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { build, BuildResult } from "esbuild";
2
2
  import { promises as fs } from "fs";
3
+ import { extname } from "path";
3
4
  import { sassPlugin } from "esbuild-sass-plugin";
4
5
  import * as chokidar from "chokidar";
5
6
  import { default as glob } from "tiny-glob";
@@ -9,23 +10,25 @@ import { generateCollections } from "./generate-collections";
9
10
  import { generateRoutes } from "./generate-routes";
10
11
 
11
12
  const generate_names = (function* generate_names() {
12
- let base_name = "____unique_import_";
13
+ const base_name = "____unique_import_";
13
14
  let count = 0;
14
15
  while (true) {
15
- yield base_name + count;
16
+ yield `${base_name}${count}`;
16
17
  count++;
17
18
  }
18
19
  })();
19
20
 
20
21
  async function build_ts() {
21
22
  await Promise.all([generateCollections(), generateRoutes()]);
22
- const entryPoints = (await glob("./src/back/**/*.ts")).filter(
23
+ const entryPoints = (await glob("./src/back/**/*.{ts,tsx}")).filter(
23
24
  (path) => !path.includes(".#")
24
25
  );
25
26
  try {
26
27
  await build({
27
28
  entryPoints,
28
29
  sourcemap: true,
30
+ bundle: false,
31
+ jsxFactory: "TempstreamJSX.createElement",
29
32
  outdir: "./dist/back",
30
33
  logLevel: "info",
31
34
  platform: "node",
@@ -52,6 +55,7 @@ async function build_ts() {
52
55
  all_named,
53
56
  module_path
54
57
  ) => {
58
+ /* eslint-disable */
55
59
  const new_name =
56
60
  generate_names.next().value;
57
61
  const result =
@@ -92,15 +96,20 @@ async function build_ts() {
92
96
  })
93
97
  .join(" ");
94
98
  return result;
99
+ /* eslint-enable */
95
100
  }
96
101
  );
97
- return { contents, loader: "ts" };
102
+ return {
103
+ contents,
104
+ loader: extname(path) === ".tsx" ? "tsx" : "ts",
105
+ };
98
106
  });
99
107
  },
100
108
  },
101
109
  ],
102
110
  });
103
111
  } catch (e) {
112
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
104
113
  console.error(e.message);
105
114
  }
106
115
  }
@@ -119,6 +128,7 @@ async function complex_build(watch: boolean): Promise<void> {
119
128
  await scss_build?.rebuild?.();
120
129
  console.log(`Built main.scss [on ${path}]`);
121
130
  } catch (e) {
131
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
122
132
  console.error(e.message);
123
133
  await sleep(200);
124
134
  scss_build
@@ -128,7 +138,7 @@ async function complex_build(watch: boolean): Promise<void> {
128
138
  }
129
139
  }
130
140
  if (
131
- path.endsWith(".ts") &&
141
+ (path.endsWith(".ts") || path.endsWith(".tsx")) &&
132
142
  !path.endsWith("src/back/collections/collections.ts") &&
133
143
  !path.endsWith("src/back/routes/routes.ts") &&
134
144
  !path.endsWith("src/back/routes/urls.ts")
@@ -109,8 +109,8 @@ async function general_form_validation_e2e_tests(js_enabled: boolean) {
109
109
  await page.getByPlaceholder("password").click();
110
110
  await page.getByPlaceholder("password").fill("test");
111
111
  await page.locator("#multiform_test_form2_submit").click();
112
- assertThrowsAsync(async () => {
113
- page.getByText("Incorrect input").click({ timeout: 500 });
112
+ await assertThrowsAsync(async () => {
113
+ await page.getByText("Incorrect input").click({ timeout: 500 });
114
114
  });
115
115
  }
116
116
 
@@ -51,7 +51,10 @@ export async function generateRoutes(): Promise<void> {
51
51
  await walkDir(target_locreq.resolve("src/back/routes"))
52
52
  )
53
53
  .filter((f) =>
54
- Object.keys(Templates).some((key) => f.endsWith(`.${key}.ts`))
54
+ Object.keys(Templates).some(
55
+ (key) =>
56
+ f.endsWith(`.${key}.ts`) || f.endsWith(`.${key}.tsx`)
57
+ )
55
58
  )
56
59
  .map(async (fullpath) => ({
57
60
  fullpath,
@@ -33,8 +33,8 @@ export const listTemplate = async (
33
33
  ]);
34
34
  const rel = curryImportPath(newfilefullpath);
35
35
  return `import { Context } from "koa";
36
- import { CollectionItem, Field } from "sealious";
37
- import { FlatTemplatable, tempstream } from "tempstream";
36
+ import { CollectionItem } from "sealious";
37
+ import { FlatTemplatable, TempstreamJSX } from "tempstream";
38
38
  import { ${uppercase_collection} } from "${rel(
39
39
  "src/back/collections/collections.js"
40
40
  )}";
@@ -62,7 +62,7 @@ ${collection_fields
62
62
  )
63
63
  .join(",\n")}
64
64
  ] as {
65
- field: keyof (typeof PatronEvents)["fields"];
65
+ field: keyof (typeof ${uppercase_collection})["fields"];
66
66
  render?: ListFilterRender;
67
67
  }[];
68
68
 
@@ -102,18 +102,18 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
102
102
  const query_params = qs.parse(ctx.search.slice(1));
103
103
  query_params.page = "1";
104
104
  const filter_values = await this.getFilterValues(ctx);
105
- return tempstream/* HTML */ \`<form>
106
- \${Object.entries(query_params).map(([key, value]) => {
105
+ return <form>
106
+ {Object.entries(query_params).map(([key, value]) => {
107
107
  if (key == "filter") {
108
108
  return "";
109
109
  }
110
- return /* HTML */ \`<input
110
+ return <input
111
111
  type="hidden"
112
112
  name="\${key}"
113
113
  value="\${value}"
114
- />\`;
114
+ />;
115
115
  })}
116
- \${filterFields.map(({ field, render }) => {
116
+ {filterFields.map(({ field, render }) => {
117
117
  if (!render) {
118
118
  render = DefaultListFilters.fallback;
119
119
  }
@@ -124,31 +124,29 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
124
124
  );
125
125
  })}
126
126
  <input type="submit" />
127
- </form>\`;
127
+ </form>;
128
128
  }
129
129
 
130
130
  async renderItem(_: Context, item: CollectionItem<typeof ${uppercase_collection}>) {
131
- return tempstream/* HTML */ \`<tr>
132
- \${displayFields.map(({ field, format }) => {
131
+ return <tr>
132
+ {displayFields.map(({ field, format }) => {
133
133
  const value = item.get(field as any);
134
- return tempstream\`<td>\${format ? format(value) : value}</td>\`;
134
+ return <td>{format ? format(value) : value}</td>;
135
135
  })}
136
- </tr>\`;
136
+ </tr>;
137
137
  }
138
138
 
139
139
  async render(ctx: Context) {
140
140
  return html(
141
141
  ctx,
142
142
  "${action_name}",
143
- tempstream/* HTML */ \`<div>
144
- <h2>${action_name}</h2>
143
+ <div>
144
+ <h2>${action_name} List</h2>
145
145
  <table class="sealious-list">
146
- \${this.renderTableHead(ctx, displayFields)}
147
- <tbody>
148
- \${super.render(ctx)}
149
- </tbody>
146
+ {this.renderTableHead(ctx, displayFields)}
147
+ <tbody>{super.render(ctx)}</tbody>
150
148
  </table>
151
- </div>\`
149
+ </div>
152
150
  );
153
151
  }
154
152
 
@@ -6,7 +6,7 @@ export async function LongRunningProcessTemplate(
6
6
  ): Promise<string> {
7
7
  const rel = curryImportPath(newfilefullpath);
8
8
  return `import { Context } from "koa";
9
- import { tempstream } from "tempstream";
9
+ import { TempstreamJSX } from "tempstream";
10
10
  import { Page } from "@sealcode/sealgen";
11
11
  import html from "${rel("src/back/html.js")}";
12
12
  import { LongRunningProcess } from "sealious";
@@ -24,34 +24,38 @@ export default new (class PatroniteAutoImportStatusPage extends Page {
24
24
  async render(ctx: Context) {
25
25
  const lpr_id = ctx.params.id;
26
26
  const lpr_data = LongRunningProcess.getByID(ctx.$context, lpr_id);
27
+ const render_script = lpr_data.state !== "error" && lpr_data.state !== "finished";
28
+ const script = /* HTML */ \`<script>
29
+ setTimeout(() => {
30
+ document.location = document.location;
31
+ }, \${this.interval});
32
+ </script>\`;
33
+
27
34
  return html(
28
35
  ctx,
29
36
  "PatroniteAutoImportStatus",
30
- tempstream/* HTML */ \`\${lpr_data.then(
31
- (lpr_data) => tempstream/* HTML */ \`
32
- <div>status: \${lpr_data.state}</div>
33
- <div style="height: 20px; width: 100%">
34
- <div
35
- style="height: 100%; background-color: green; width: \${Math.round(
36
- lpr_data.progress * 100
37
- )}%"
38
- ></div>
39
- </div>
40
- <div>
41
- \${lpr_data.events.map(
42
- (e) => /* HTML */ \`<div>\${e.message}</div>\`
43
- )}
44
- </div>
45
- \${lpr_data.state !== "error" &&
46
- lpr_data.state !== "finished"
47
- ? /* HTML */ \`<script>
48
- setTimeout(() => {
49
- document.location = document.location;
50
- }, \${this.interval});
51
- </script>\`
52
- : ""}
53
- \`
54
- )}\`
37
+ <div>
38
+ {lpr_data.then((lpr_data) => {
39
+ return (
40
+ <div>
41
+ <div>status: {lpr_data.state}</div>
42
+ <div style="height: 20px; width: 100%">
43
+ <div
44
+ style="height: 100%; background-color: green; width: {Math.round(
45
+ lpr_data.progress * 100
46
+ )}%"
47
+ ></div>
48
+ </div>
49
+ <div>
50
+ {lpr_data.events.map((e) => (
51
+ <div>{e.message}</div>
52
+ ))}
53
+ </div>
54
+ {render_script ? { script } : ""}
55
+ </div>
56
+ );
57
+ })}
58
+ </div>
55
59
  );
56
60
  }
57
61
  })();
@@ -5,7 +5,7 @@ export async function pageTemplate(
5
5
  ): Promise<string> {
6
6
  const rel = curryImportPath(newfilefullpath);
7
7
  return `import { Context } from "koa";
8
- import { tempstream } from "tempstream";
8
+ import { TempstreamJSX } from "tempstream";
9
9
  import { Page } from "@sealcode/sealgen";
10
10
  import html from "${rel("src/back/html.js")}";
11
11
 
@@ -18,7 +18,7 @@ export default new (class ${action_name}Page extends Page {
18
18
  }
19
19
 
20
20
  async render(ctx: Context) {
21
- return html(ctx, "${action_name}", tempstream/* HTML */ \`<div></div>\`);
21
+ return html(ctx, "${action_name}", <div></div>);
22
22
  }
23
23
  })();
24
24
  `;
@@ -5,11 +5,12 @@ export function testTemplate(action_name: string, new_file_fullpath: string) {
5
5
  return `import { withProdApp } from "${rel(
6
6
  "src/back/test_utils/with-prod-app.js"
7
7
  )}";
8
- import { LONG_TEST_TIMEOUT, webhintURL } from "${rel(
8
+ import { VERY_LONG_TEST_TIMEOUT, webhintURL } from "${rel(
9
9
  "src/back/test_utils/webhint.js"
10
10
  )}";
11
11
  import { ${action_name}URL } from "${rel("src/back/routes/urls.js")}";
12
- import { getBrowser } from "${rel("src/test_utils/browser-creator.js")}";
12
+ import { getBrowser } from "${rel("src/back/test_utils/browser-creator.js")}";
13
+ import { Browser, BrowserContext, Page } from "@playwright/test";
13
14
 
14
15
  describe("${action_name} webhint", () => {
15
16
  it(
@@ -5,14 +5,14 @@ export async function statefulPageTemplate(
5
5
  newfilefullpath: string
6
6
  ): Promise<string> {
7
7
  const rel = curryImportPath(newfilefullpath);
8
- return `import { BaseContext } from "koa";
9
- import { tempstream, Templatable } from "tempstream";
8
+ return `import { TempstreamJSX, Templatable } from "tempstream";
9
+ import { BaseContext } from "koa";
10
10
  import { StatefulPage } from "@sealcode/sealgen";
11
11
  import html from "${rel("src/back/html.js")}";
12
12
 
13
13
  export const actionName = "${action_name}";
14
14
 
15
- const actions = <const>{
15
+ const actions = {
16
16
  add: (state: State, inputs: Record<string, string>) => {
17
17
  console.log({ inputs });
18
18
  return {
@@ -26,39 +26,43 @@ const actions = <const>{
26
26
  elements: state.elements.filter((_, index) => index != index_to_remove),
27
27
  };
28
28
  },
29
- };
29
+ } as const;
30
30
 
31
31
  type State = {
32
32
  elements: string[];
33
33
  };
34
34
 
35
- export default new (class ${action_name}Page extends StatefulPage<State, typeof actions> {
35
+ export default new (class ${action_name}Page extends StatefulPage<State, typeof actions> {
36
36
  actions = actions;
37
37
 
38
38
  getInitialState() {
39
39
  return { elements: ["one", "two", "three"] };
40
40
  }
41
41
 
42
- wrapInLayout(ctx: BaseContext, content: Templatable): Templatable {
42
+ wrapInLayout(ctx: BaseContext, content: Templatable): Templatable {
43
43
  return html(ctx, "${action_name}", content);
44
44
  }
45
45
 
46
46
  render(ctx: BaseContext, state: State, inputs: Record<string, string>) {
47
- return tempstream/* HTML */ \`<ul>
48
- \${state.elements.map(
49
- (e, index) => /* HTML */ \`<li>
50
- \${e} \${this.makeActionButton(state, "remove", index)}
51
- </li>\`
52
- )}
53
- </ul>
47
+ return (
54
48
  <div>
55
- <input
49
+ <ul>
50
+ {state.elements.map((e, index) => (
51
+ <li>
52
+ {e} {this.makeActionButton(state, "remove", index)}
53
+ </li>
54
+ ))}
55
+ </ul>
56
+ <div>
57
+ <input
56
58
  name="element_to_add"
57
59
  type="text"
58
- value="\${inputs.element_to_add || ""}"
60
+ value={inputs.element_to_add || ""}
59
61
  />
60
- \${this.makeActionButton(state, "add")}
61
- </div>\`;
62
+ {this.makeActionButton(state, "add")}
63
+ </div>
64
+ </div>
65
+ );
62
66
  }
63
67
  })();
64
68
  `;
@@ -8,7 +8,7 @@ export function importPath(
8
8
  ): string {
9
9
  let result = relative(
10
10
  dirname(target_locreq.resolve(project_module_path)),
11
- target_locreq.resolve(project_target_path).replace(/.ts$/, ".js")
11
+ target_locreq.resolve(project_target_path).replace(/.tsx?$/, ".js")
12
12
  );
13
13
  if (result[0] != ".") {
14
14
  result = "./" + result;