@sealcode/sealgen 0.10.5 → 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.
- package/@types/forms/controls/array.d.ts +1 -1
- package/@types/forms/multiform.d.ts +1 -1
- package/@types/page/sealious-list.d.ts +1 -0
- package/lib/add-route.js +4 -1
- package/lib/add-route.test.js +5 -2
- package/lib/build.js +15 -5
- package/lib/forms/multiform.test.js +2 -2
- package/lib/generate-routes.js +1 -1
- package/lib/page/sealious-list.js +13 -0
- package/lib/templates/list.js +28 -41
- package/lib/templates/long-running-process.js +30 -26
- package/lib/templates/page.js +2 -2
- package/lib/templates/route-test.js +3 -2
- package/lib/templates/stateful-page.js +21 -17
- package/lib/utils/import-path.js +1 -1
- package/package.json +2 -2
- package/src/add-route.test.ts +6 -2
- package/src/add-route.ts +4 -1
- package/src/build.ts +15 -5
- package/src/forms/multiform.test.ts +2 -2
- package/src/generate-routes.ts +4 -1
- package/src/page/sealious-list.ts +14 -0
- package/src/templates/list.ts +28 -41
- package/src/templates/long-running-process.ts +30 -26
- package/src/templates/page.ts +2 -2
- package/src/templates/route-test.ts +3 -2
- package/src/templates/stateful-page.ts +21 -17
- package/src/utils/import-path.ts +1 -1
|
@@ -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>[]
|
|
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,
|
|
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
|
}
|
|
@@ -10,6 +10,7 @@ export declare abstract class SealiousItemListPage<C extends Collection, F exten
|
|
|
10
10
|
protected addFilter(ctx: Context, list: ItemList<C>): Promise<void>;
|
|
11
11
|
makeQuery(ctx: Context): Promise<ItemList<C>>;
|
|
12
12
|
getItems(ctx: Context, page: number, itemsPerPage: number): Promise<CollectionItem<C>[]>;
|
|
13
|
+
getFilterValues(ctx: Context): Promise<Record<string, string | undefined>>;
|
|
13
14
|
renderItem(_: Context, item: CollectionItem<C>, index: number): Promise<FlatTemplatable>;
|
|
14
15
|
canAccess: (ctx: Context) => Promise<{
|
|
15
16
|
canAccess: boolean;
|
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
|
|
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) {
|
package/lib/add-route.test.js
CHANGED
|
@@ -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
|
|
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} &&
|
|
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
|
-
|
|
41
|
+
const base_name = "____unique_import_";
|
|
41
42
|
let count = 0;
|
|
42
43
|
while (true) {
|
|
43
|
-
yield base_name
|
|
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 {
|
|
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) {
|
package/lib/generate-routes.js
CHANGED
|
@@ -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),
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.SealiousItemListPage = void 0;
|
|
4
7
|
const list_1 = require("./list");
|
|
5
8
|
const access_control_1 = require("./access-control");
|
|
9
|
+
const qs_1 = __importDefault(require("qs"));
|
|
10
|
+
const ts_predicates_1 = require("@sealcode/ts-predicates");
|
|
6
11
|
class SealiousItemListPage extends list_1.ListPage {
|
|
7
12
|
constructor(collection) {
|
|
8
13
|
super();
|
|
@@ -45,6 +50,14 @@ class SealiousItemListPage extends list_1.ListPage {
|
|
|
45
50
|
.fetch();
|
|
46
51
|
return items;
|
|
47
52
|
}
|
|
53
|
+
async getFilterValues(ctx) {
|
|
54
|
+
const query_params = qs_1.default.parse(ctx.search.slice(1));
|
|
55
|
+
const _filter_values = query_params?.filter || {};
|
|
56
|
+
let filter_values = (0, ts_predicates_1.is)(_filter_values, ts_predicates_1.predicates.record(ts_predicates_1.predicates.string, ts_predicates_1.predicates.string))
|
|
57
|
+
? _filter_values
|
|
58
|
+
: {};
|
|
59
|
+
return filter_values;
|
|
60
|
+
}
|
|
48
61
|
async renderItem(_, item, index) {
|
|
49
62
|
return `<div>${item.id}</div>`;
|
|
50
63
|
}
|
package/lib/templates/list.js
CHANGED
|
@@ -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
|
|
33
|
-
import { FlatTemplatable,
|
|
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
|
|
54
|
+
field: keyof (typeof ${uppercase_collection})["fields"];
|
|
55
55
|
render?: ListFilterRender;
|
|
56
56
|
}[];
|
|
57
57
|
|
|
@@ -87,38 +87,22 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
87
87
|
> {
|
|
88
88
|
fields = BaseListPageFields;
|
|
89
89
|
|
|
90
|
-
async render(ctx: Context) {
|
|
91
|
-
return html(
|
|
92
|
-
ctx,
|
|
93
|
-
"${action_name}",
|
|
94
|
-
tempstream/* HTML */ \`<div>
|
|
95
|
-
<h2>${action_name}</h2>
|
|
96
|
-
<table class="sealious-list">
|
|
97
|
-
\${this.renderTableHead(ctx, displayFields)}
|
|
98
|
-
<tbody>
|
|
99
|
-
\${super.render(ctx)}
|
|
100
|
-
</tbody>
|
|
101
|
-
</table>
|
|
102
|
-
</div>\`
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
90
|
async renderFilters(ctx: Context): Promise<FlatTemplatable> {
|
|
107
91
|
const query_params = qs.parse(ctx.search.slice(1));
|
|
108
92
|
query_params.page = "1";
|
|
109
93
|
const filter_values = await this.getFilterValues(ctx);
|
|
110
|
-
return
|
|
111
|
-
|
|
94
|
+
return <form>
|
|
95
|
+
{Object.entries(query_params).map(([key, value]) => {
|
|
112
96
|
if (key == "filter") {
|
|
113
97
|
return "";
|
|
114
98
|
}
|
|
115
|
-
return
|
|
99
|
+
return <input
|
|
116
100
|
type="hidden"
|
|
117
101
|
name="\${key}"
|
|
118
102
|
value="\${value}"
|
|
119
|
-
|
|
103
|
+
/>;
|
|
120
104
|
})}
|
|
121
|
-
|
|
105
|
+
{filterFields.map(({ field, render }) => {
|
|
122
106
|
if (!render) {
|
|
123
107
|
render = DefaultListFilters.fallback;
|
|
124
108
|
}
|
|
@@ -129,29 +113,32 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
129
113
|
);
|
|
130
114
|
})}
|
|
131
115
|
<input type="submit" />
|
|
132
|
-
</form
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async getFilterValues(ctx: Context) {
|
|
136
|
-
const query_params = qs.parse(ctx.search.slice(1));
|
|
137
|
-
const _filter_values = query_params?.filter || {};
|
|
138
|
-
let filter_values: Record<string, string | undefined> = is(
|
|
139
|
-
_filter_values,
|
|
140
|
-
predicates.record(predicates.string, predicates.string)
|
|
141
|
-
)
|
|
142
|
-
? _filter_values
|
|
143
|
-
: {};
|
|
144
|
-
return filter_values;
|
|
116
|
+
</form>;
|
|
145
117
|
}
|
|
146
118
|
|
|
147
119
|
async renderItem(_: Context, item: CollectionItem<typeof ${uppercase_collection}>) {
|
|
148
|
-
return
|
|
149
|
-
|
|
120
|
+
return <tr>
|
|
121
|
+
{displayFields.map(({ field, format }) => {
|
|
150
122
|
const value = item.get(field as any);
|
|
151
|
-
return
|
|
123
|
+
return <td>{format ? format(value) : value}</td>;
|
|
152
124
|
})}
|
|
153
|
-
</tr
|
|
125
|
+
</tr>;
|
|
154
126
|
}
|
|
127
|
+
|
|
128
|
+
async render(ctx: Context) {
|
|
129
|
+
return html(
|
|
130
|
+
ctx,
|
|
131
|
+
"${action_name}",
|
|
132
|
+
<div>
|
|
133
|
+
<h2>${action_name} List</h2>
|
|
134
|
+
<table class="sealious-list">
|
|
135
|
+
{this.renderTableHead(ctx, displayFields)}
|
|
136
|
+
<tbody>{super.render(ctx)}</tbody>
|
|
137
|
+
</table>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
155
142
|
})(${uppercase_collection});
|
|
156
143
|
`;
|
|
157
144
|
};
|
|
@@ -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 {
|
|
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
|
-
|
|
30
|
-
(lpr_data) =>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
style="height:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
})();
|
package/lib/templates/page.js
CHANGED
|
@@ -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 {
|
|
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}",
|
|
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 {
|
|
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 {
|
|
8
|
-
import {
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
<
|
|
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=
|
|
59
|
+
value={inputs.element_to_add || ""}
|
|
58
60
|
/>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
{this.makeActionButton(state, "add")}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
61
65
|
}
|
|
62
66
|
})();
|
|
63
67
|
`;
|
package/lib/utils/import-path.js
CHANGED
|
@@ -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(/.
|
|
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.
|
|
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
|
|
58
|
+
"tempstream": "^0.3.0",
|
|
59
59
|
"tiny-glob": "^0.2.9",
|
|
60
60
|
"yargs": "^17.6.2"
|
|
61
61
|
},
|
package/src/add-route.test.ts
CHANGED
|
@@ -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
|
|
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} &&
|
|
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}
|
|
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
|
-
|
|
13
|
+
const base_name = "____unique_import_";
|
|
13
14
|
let count = 0;
|
|
14
15
|
while (true) {
|
|
15
|
-
yield base_name
|
|
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 {
|
|
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
|
|
package/src/generate-routes.ts
CHANGED
|
@@ -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(
|
|
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,
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
} from "sealious";
|
|
10
10
|
import { FlatTemplatable } from "tempstream";
|
|
11
11
|
import { peopleWhoCan } from "./access-control";
|
|
12
|
+
import qs from "qs";
|
|
13
|
+
import { is, predicates } from "@sealcode/ts-predicates";
|
|
12
14
|
|
|
13
15
|
export abstract class SealiousItemListPage<
|
|
14
16
|
C extends Collection,
|
|
@@ -64,6 +66,18 @@ export abstract class SealiousItemListPage<
|
|
|
64
66
|
return items;
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
async getFilterValues(ctx: Context) {
|
|
70
|
+
const query_params = qs.parse(ctx.search.slice(1));
|
|
71
|
+
const _filter_values = query_params?.filter || {};
|
|
72
|
+
let filter_values: Record<string, string | undefined> = is(
|
|
73
|
+
_filter_values,
|
|
74
|
+
predicates.record(predicates.string, predicates.string)
|
|
75
|
+
)
|
|
76
|
+
? _filter_values
|
|
77
|
+
: {};
|
|
78
|
+
return filter_values;
|
|
79
|
+
}
|
|
80
|
+
|
|
67
81
|
async renderItem(
|
|
68
82
|
_: Context,
|
|
69
83
|
item: CollectionItem<C>,
|
package/src/templates/list.ts
CHANGED
|
@@ -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
|
|
37
|
-
import { FlatTemplatable,
|
|
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
|
|
65
|
+
field: keyof (typeof ${uppercase_collection})["fields"];
|
|
66
66
|
render?: ListFilterRender;
|
|
67
67
|
}[];
|
|
68
68
|
|
|
@@ -98,38 +98,22 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
98
98
|
> {
|
|
99
99
|
fields = BaseListPageFields;
|
|
100
100
|
|
|
101
|
-
async render(ctx: Context) {
|
|
102
|
-
return html(
|
|
103
|
-
ctx,
|
|
104
|
-
"${action_name}",
|
|
105
|
-
tempstream/* HTML */ \`<div>
|
|
106
|
-
<h2>${action_name}</h2>
|
|
107
|
-
<table class="sealious-list">
|
|
108
|
-
\${this.renderTableHead(ctx, displayFields)}
|
|
109
|
-
<tbody>
|
|
110
|
-
\${super.render(ctx)}
|
|
111
|
-
</tbody>
|
|
112
|
-
</table>
|
|
113
|
-
</div>\`
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
101
|
async renderFilters(ctx: Context): Promise<FlatTemplatable> {
|
|
118
102
|
const query_params = qs.parse(ctx.search.slice(1));
|
|
119
103
|
query_params.page = "1";
|
|
120
104
|
const filter_values = await this.getFilterValues(ctx);
|
|
121
|
-
return
|
|
122
|
-
|
|
105
|
+
return <form>
|
|
106
|
+
{Object.entries(query_params).map(([key, value]) => {
|
|
123
107
|
if (key == "filter") {
|
|
124
108
|
return "";
|
|
125
109
|
}
|
|
126
|
-
return
|
|
110
|
+
return <input
|
|
127
111
|
type="hidden"
|
|
128
112
|
name="\${key}"
|
|
129
113
|
value="\${value}"
|
|
130
|
-
|
|
114
|
+
/>;
|
|
131
115
|
})}
|
|
132
|
-
|
|
116
|
+
{filterFields.map(({ field, render }) => {
|
|
133
117
|
if (!render) {
|
|
134
118
|
render = DefaultListFilters.fallback;
|
|
135
119
|
}
|
|
@@ -140,29 +124,32 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
140
124
|
);
|
|
141
125
|
})}
|
|
142
126
|
<input type="submit" />
|
|
143
|
-
</form
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async getFilterValues(ctx: Context) {
|
|
147
|
-
const query_params = qs.parse(ctx.search.slice(1));
|
|
148
|
-
const _filter_values = query_params?.filter || {};
|
|
149
|
-
let filter_values: Record<string, string | undefined> = is(
|
|
150
|
-
_filter_values,
|
|
151
|
-
predicates.record(predicates.string, predicates.string)
|
|
152
|
-
)
|
|
153
|
-
? _filter_values
|
|
154
|
-
: {};
|
|
155
|
-
return filter_values;
|
|
127
|
+
</form>;
|
|
156
128
|
}
|
|
157
129
|
|
|
158
130
|
async renderItem(_: Context, item: CollectionItem<typeof ${uppercase_collection}>) {
|
|
159
|
-
return
|
|
160
|
-
|
|
131
|
+
return <tr>
|
|
132
|
+
{displayFields.map(({ field, format }) => {
|
|
161
133
|
const value = item.get(field as any);
|
|
162
|
-
return
|
|
134
|
+
return <td>{format ? format(value) : value}</td>;
|
|
163
135
|
})}
|
|
164
|
-
</tr
|
|
136
|
+
</tr>;
|
|
165
137
|
}
|
|
138
|
+
|
|
139
|
+
async render(ctx: Context) {
|
|
140
|
+
return html(
|
|
141
|
+
ctx,
|
|
142
|
+
"${action_name}",
|
|
143
|
+
<div>
|
|
144
|
+
<h2>${action_name} List</h2>
|
|
145
|
+
<table class="sealious-list">
|
|
146
|
+
{this.renderTableHead(ctx, displayFields)}
|
|
147
|
+
<tbody>{super.render(ctx)}</tbody>
|
|
148
|
+
</table>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
166
153
|
})(${uppercase_collection});
|
|
167
154
|
`;
|
|
168
155
|
};
|
|
@@ -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 {
|
|
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
|
-
|
|
31
|
-
(lpr_data) =>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
style="height:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
})();
|
package/src/templates/page.ts
CHANGED
|
@@ -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 {
|
|
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}",
|
|
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 {
|
|
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 {
|
|
9
|
-
import {
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
<
|
|
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=
|
|
60
|
+
value={inputs.element_to_add || ""}
|
|
59
61
|
/>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
{this.makeActionButton(state, "add")}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
62
66
|
}
|
|
63
67
|
})();
|
|
64
68
|
`;
|
package/src/utils/import-path.ts
CHANGED
|
@@ -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(/.
|
|
11
|
+
target_locreq.resolve(project_target_path).replace(/.tsx?$/, ".js")
|
|
12
12
|
);
|
|
13
13
|
if (result[0] != ".") {
|
|
14
14
|
result = "./" + result;
|