@sealcode/sealgen 0.12.12 → 0.13.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.
@@ -0,0 +1,2 @@
1
+ export declare const FONTS_CONFIG_PATH = "src/fonts.json";
2
+ export declare function getFonts(_?: Record<string, string | boolean>): Promise<void>;
@@ -8,9 +8,21 @@ export type StatefulPageActionDescription<ActionName> = {
8
8
  action: ActionName;
9
9
  label?: string;
10
10
  } | ActionName;
11
- export declare abstract class StatefulPage<State extends Record<string, unknown>, Actions extends Record<string, (state: State, inputs: Record<string, string>, ...args: unknown[]) => State>> extends Mountable {
11
+ export type StateAndMetadata<State, Actions> = {
12
+ state: State;
13
+ inputs: Record<string, string>;
14
+ action: keyof Actions;
15
+ action_args: string;
16
+ } | {
17
+ state: State;
18
+ inputs: Record<string, string>;
19
+ action: null;
20
+ action_args: null;
21
+ $: Record<string, unknown>;
22
+ };
23
+ export declare abstract class StatefulPage<State extends Record<string, unknown>, Actions extends Record<string, (state: State, inputs: Record<string, string>, ...args: unknown[]) => State | Promise<State>>> extends Mountable {
12
24
  abstract actions: Actions;
13
- abstract getInitialState(): State;
25
+ abstract getInitialState(): State | Promise<State>;
14
26
  abstract render(ctx: BaseContext, state: State, inputs: Record<string, string>): Templatable | Promise<Templatable>;
15
27
  canAccess(): Promise<{
16
28
  readonly canAccess: true;
@@ -24,22 +36,13 @@ export declare abstract class StatefulPage<State extends Record<string, unknown>
24
36
  label: string;
25
37
  };
26
38
  makeActionURL<ActionName extends keyof Actions>(action_description: StatefulPageActionDescription<ActionName>, ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>): string;
27
- makeActionButton<ActionName extends keyof Actions>(state: State, action_description: StatefulPageActionDescription<ActionName>, ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>): string;
39
+ makeActionButton<ActionName extends keyof Actions>(_state: State, action_description: StatefulPageActionDescription<ActionName>, ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>): string;
28
40
  makeActionCallback<ActionName extends keyof Actions>(action_description: {
29
41
  action: ActionName;
30
42
  label?: string;
31
43
  } | ActionName, ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>): string;
32
44
  rerender(): string;
33
- extractState(ctx: BaseContext): {
34
- state: State;
35
- inputs: Record<string, string>;
36
- action: keyof Actions;
37
- action_args: string;
38
- } | {
39
- state: State;
40
- inputs: Record<string, string>;
41
- action: null;
42
- action_args: null;
43
- };
45
+ preprocessRequestBody(values: Record<string, unknown>): Promise<StateAndMetadata<State, Actions>>;
46
+ extractState(ctx: BaseContext): Promise<StateAndMetadata<State, Actions>>;
44
47
  mount(router: Router, path: string): void;
45
48
  }
@@ -0,0 +1,4 @@
1
+ export declare const target_locreq: {
2
+ (module_path: string): unknown;
3
+ resolve: (module_path: string) => string;
4
+ };
package/dist/build.js CHANGED
@@ -14,6 +14,7 @@ import md5 from "md5";
14
14
  import _locreq from "locreq";
15
15
  import path, { basename, extname } from "path";
16
16
  import { copyFile } from "fs/promises";
17
+ import { FONTS_CONFIG_PATH, getFonts } from "./get-fonts.js";
17
18
  const target_locreq = _locreq(process.cwd());
18
19
  async function get_notifier_port() {
19
20
  return getPort({ port: 4e3 });
@@ -113,7 +114,6 @@ async function build_ts() {
113
114
  )
114
115
  },
115
116
  async (args) => {
116
- const mode = "url";
117
117
  let contents = `export const url = "${await makeResourceURL(
118
118
  args.path
119
119
  )}";
@@ -167,6 +167,7 @@ async function complex_build(watch) {
167
167
  // plugins: [sassPlugin()],
168
168
  loader
169
169
  });
170
+ let ongoing_fonts = false;
170
171
  let ongoing_ts = false;
171
172
  let ongoing_css = false;
172
173
  let ongoing_ts_promise = null;
@@ -180,6 +181,14 @@ async function complex_build(watch) {
180
181
  return;
181
182
  if (path2.includes(".#"))
182
183
  return;
184
+ if (path2 == FONTS_CONFIG_PATH) {
185
+ if (!ongoing_fonts) {
186
+ ongoing_fonts = true;
187
+ await getFonts();
188
+ ongoing_fonts = false;
189
+ }
190
+ return;
191
+ }
183
192
  if (path2.endsWith(".css") && !path2.endsWith("/includes.css")) {
184
193
  if (ongoing_css) {
185
194
  return;
package/dist/cli.js CHANGED
@@ -9,6 +9,7 @@ import { generateComponents } from "./generate-components.js";
9
9
  import { generateRoutes } from "./generate-routes.js";
10
10
  import { makeEnv } from "./make-env.js";
11
11
  import yargs from "yargs/yargs";
12
+ import { getFonts } from "./get-fonts.js";
12
13
  const actions = {
13
14
  "add-collection": addCollection,
14
15
  "add-route": addRoute,
@@ -24,7 +25,8 @@ const actions = {
24
25
  );
25
26
  },
26
27
  "make-env": makeEnv,
27
- "add-component": addJDDComponent
28
+ "add-component": addJDDComponent,
29
+ "get-fonts": getFonts
28
30
  };
29
31
  void async function() {
30
32
  const action = process.argv.at(2);
@@ -0,0 +1,27 @@
1
+ import { readFile } from "fs/promises";
2
+ import { constructURL, download } from "google-fonts-helper";
3
+ import { target_locreq } from "./target-locreq.js";
4
+ const FONTS_CONFIG_PATH = "src/fonts.json";
5
+ async function getFonts(_ = {}) {
6
+ const fonts_config_path = target_locreq.resolve(FONTS_CONFIG_PATH);
7
+ const fonts_config = JSON.parse(await readFile(fonts_config_path, "utf8"));
8
+ const fonts_url = constructURL(fonts_config.googleFonts);
9
+ if (!fonts_url) {
10
+ throw new Error("Invalid font config");
11
+ }
12
+ const downloader = download(fonts_url, {
13
+ base64: false,
14
+ overwriting: false,
15
+ outputDir: target_locreq.resolve("public/dist/fonts"),
16
+ stylePath: "fonts.css",
17
+ fontsDir: "./",
18
+ fontsPath: "/dist/fonts"
19
+ });
20
+ await downloader.execute();
21
+ console.log("Downloaded new fonts pack");
22
+ }
23
+ export {
24
+ FONTS_CONFIG_PATH,
25
+ getFonts
26
+ };
27
+ //# sourceMappingURL=get-fonts.js.map
@@ -1,4 +1,4 @@
1
- import { predicates, hasShape, is } from "@sealcode/ts-predicates";
1
+ import { predicates, hasShape, is, hasField } from "@sealcode/ts-predicates";
2
2
  import deepmerge from "deepmerge";
3
3
  import { tempstream } from "tempstream";
4
4
  import { from_base64, to_base64 } from "../utils/base64.js";
@@ -43,16 +43,18 @@ class StatefulPage extends Mountable {
43
43
  }
44
44
  makeActionURL(action_description, ...args) {
45
45
  const { action } = this.extractActionAndLabel(action_description);
46
- return `./?action=${action}&args=${to_base64(JSON.stringify(args))}`;
46
+ return `./?action=${action}&action_args=${to_base64(
47
+ JSON.stringify(args)
48
+ )}`;
47
49
  }
48
- makeActionButton(state, action_description, ...args) {
50
+ makeActionButton(_state, action_description, ...args) {
49
51
  const { label } = this.extractActionAndLabel(action_description);
50
52
  return (
51
53
  /* HTML */
52
54
  `
53
55
  <input
54
56
  type="submit"
55
- value=${label}
57
+ value="${label}"
56
58
  formaction="${this.makeActionURL(action_description, ...args)}"
57
59
  />
58
60
  `
@@ -67,12 +69,15 @@ class StatefulPage extends Mountable {
67
69
  rerender() {
68
70
  return "this.closest('form').requestSubmit()";
69
71
  }
70
- extractState(ctx) {
72
+ async preprocessRequestBody(values) {
73
+ return values;
74
+ }
75
+ async extractState(ctx) {
71
76
  if (!hasShape(
72
77
  {
73
78
  action: predicates.maybe(predicates.string),
74
79
  state: predicates.string,
75
- args: predicates.maybe(predicates.string),
80
+ action_args: predicates.maybe(predicates.string),
76
81
  $: predicates.maybe(predicates.object)
77
82
  },
78
83
  ctx.$body
@@ -80,43 +85,52 @@ class StatefulPage extends Mountable {
80
85
  console.error("Wrong data: ", ctx.$body);
81
86
  throw new Error("wrong formdata shape");
82
87
  }
88
+ const prepared_body = await this.preprocessRequestBody(ctx.$body);
83
89
  const inputs = Object.fromEntries(
84
- Object.entries(ctx.$body).filter(
90
+ Object.entries(prepared_body).filter(
85
91
  ([key]) => !["action", "state", "args", "$"].includes(key)
86
92
  )
87
93
  );
88
- const original_state = JSON.parse(from_base64(ctx.$body.state));
89
- const modified_state = deepmerge(original_state, ctx.$body.$ || {}, {
90
- arrayMerge: (_target, source, _options) => {
91
- return source;
94
+ const original_state_string = prepared_body.state;
95
+ const original_state = JSON.parse(
96
+ typeof original_state_string == "string" ? from_base64(original_state_string) : "{}"
97
+ );
98
+ const modified_state = deepmerge(
99
+ original_state,
100
+ hasField("$", prepared_body) ? prepared_body.$ : {},
101
+ {
102
+ arrayMerge: (_target, source, _options) => {
103
+ return source;
104
+ }
92
105
  }
93
- });
94
- if (ctx.$body.action && ctx.$body.args) {
106
+ );
107
+ if (prepared_body.action && prepared_body.action_args) {
95
108
  return {
96
109
  state: modified_state,
97
- action: ctx.$body.action,
110
+ action: prepared_body.action,
98
111
  inputs,
99
- action_args: ctx.$body.args
112
+ action_args: prepared_body.action_args
100
113
  };
101
114
  } else {
102
115
  return {
103
116
  state: modified_state,
104
117
  action: null,
118
+ inputs,
105
119
  action_args: null,
106
- inputs
120
+ $: hasField("$", prepared_body) ? prepared_body.$ : {}
107
121
  };
108
122
  }
109
123
  }
110
124
  mount(router, path) {
111
- router.get(path, (ctx) => {
112
- ctx.body = this.render(ctx, this.getInitialState(), {});
125
+ router.get(path, async (ctx) => {
126
+ ctx.body = this.render(ctx, await this.getInitialState(), {});
113
127
  });
114
- router.post(path, (ctx) => {
115
- const { action, state, inputs, action_args } = this.extractState(ctx);
128
+ router.post(path, async (ctx) => {
129
+ const { action, state, inputs, action_args } = await this.extractState(ctx);
116
130
  if (action) {
117
131
  ctx.body = this.render(
118
132
  ctx,
119
- this.actions[action](
133
+ await this.actions[action](
120
134
  state,
121
135
  inputs,
122
136
  ...JSON.parse(
@@ -0,0 +1,6 @@
1
+ import _locreq from "locreq";
2
+ const target_locreq = _locreq(process.cwd());
3
+ export {
4
+ target_locreq
5
+ };
6
+ //# sourceMappingURL=target-locreq.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.12.12",
3
+ "version": "0.13.0",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -55,6 +55,7 @@
55
55
  "@sealcode/ts-predicates": "^0.4.3",
56
56
  "deepmerge": "^4.3.1",
57
57
  "esbuild": "^0.20.0",
58
+ "google-fonts-helper": "^3.4.1",
58
59
  "js-convert-case": "^4.2.0",
59
60
  "locreq": "^3.0.0",
60
61
  "md5": "^2.3.0",
package/src/build.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { build, BuildContext, BuildResult, context } from "esbuild";
1
+ import { build, BuildContext, context } from "esbuild";
2
2
  import { promises as fs } from "fs";
3
3
  import * as chokidar from "chokidar";
4
4
  import { default as glob } from "tiny-glob";
@@ -14,7 +14,8 @@ import md5 from "md5";
14
14
 
15
15
  import _locreq from "locreq";
16
16
  import path, { basename, extname } from "path";
17
- import { copyFile, writeFile } from "fs/promises";
17
+ import { copyFile } from "fs/promises";
18
+ import { FONTS_CONFIG_PATH, getFonts } from "./get-fonts.js";
18
19
  const target_locreq = _locreq(process.cwd());
19
20
 
20
21
  async function get_notifier_port() {
@@ -127,7 +128,6 @@ async function build_ts() {
127
128
  ),
128
129
  },
129
130
  async (args: any) => {
130
- const mode = "url";
131
131
  let contents = `export const url = "${await makeResourceURL(
132
132
  args.path
133
133
  )}";
@@ -183,6 +183,7 @@ async function complex_build(watch: boolean): Promise<void> {
183
183
  // plugins: [sassPlugin()],
184
184
  loader,
185
185
  });
186
+ let ongoing_fonts = false;
186
187
  let ongoing_ts = false;
187
188
  let ongoing_css = false;
188
189
  let ongoing_ts_promise: Promise<any> | null = null;
@@ -194,6 +195,14 @@ async function complex_build(watch: boolean): Promise<void> {
194
195
  watcher.on("all", async (_, path) => {
195
196
  if (!css_build) return;
196
197
  if (path.includes(".#")) return;
198
+ if (path == FONTS_CONFIG_PATH) {
199
+ if (!ongoing_fonts) {
200
+ ongoing_fonts = true;
201
+ await getFonts();
202
+ ongoing_fonts = false;
203
+ }
204
+ return;
205
+ }
197
206
  if (path.endsWith(".css") && !path.endsWith("/includes.css")) {
198
207
  if (ongoing_css) {
199
208
  return;
package/src/cli.ts CHANGED
@@ -9,6 +9,7 @@ import { generateComponents } from "./generate-components.js";
9
9
  import { generateRoutes } from "./generate-routes.js";
10
10
  import { makeEnv } from "./make-env.js";
11
11
  import yargs from "yargs/yargs";
12
+ import { getFonts } from "./get-fonts.js";
12
13
 
13
14
  const actions: Record<
14
15
  string,
@@ -31,6 +32,7 @@ const actions: Record<
31
32
  },
32
33
  "make-env": makeEnv,
33
34
  "add-component": addJDDComponent,
35
+ "get-fonts": getFonts,
34
36
  };
35
37
 
36
38
  void (async function () {
@@ -0,0 +1,25 @@
1
+ import { readFile } from "fs/promises";
2
+ import { constructURL, download } from "google-fonts-helper";
3
+ import { target_locreq } from "./target-locreq.js";
4
+
5
+ export const FONTS_CONFIG_PATH = "src/fonts.json";
6
+
7
+ export async function getFonts(_: Record<string, string | boolean> = {}) {
8
+ const fonts_config_path = target_locreq.resolve(FONTS_CONFIG_PATH);
9
+
10
+ const fonts_config = JSON.parse(await readFile(fonts_config_path, "utf8"));
11
+ const fonts_url = constructURL(fonts_config.googleFonts);
12
+ if (!fonts_url) {
13
+ throw new Error("Invalid font config");
14
+ }
15
+ const downloader = download(fonts_url, {
16
+ base64: false,
17
+ overwriting: false,
18
+ outputDir: target_locreq.resolve("public/dist/fonts"),
19
+ stylePath: "fonts.css",
20
+ fontsDir: "./",
21
+ fontsPath: "/dist/fonts",
22
+ });
23
+ await downloader.execute();
24
+ console.log("Downloaded new fonts pack");
25
+ }
@@ -1,5 +1,5 @@
1
1
  import Router from "@koa/router";
2
- import { predicates, hasShape, is } from "@sealcode/ts-predicates";
2
+ import { predicates, hasShape, is, hasField } from "@sealcode/ts-predicates";
3
3
  import deepmerge, { ArrayMergeOptions } from "deepmerge";
4
4
  import { BaseContext } from "koa";
5
5
  import { Templatable, tempstream } from "tempstream";
@@ -14,6 +14,21 @@ export type StatefulPageActionDescription<ActionName> =
14
14
  }
15
15
  | ActionName;
16
16
 
17
+ export type StateAndMetadata<State, Actions> =
18
+ | {
19
+ state: State;
20
+ inputs: Record<string, string>;
21
+ action: keyof Actions;
22
+ action_args: string;
23
+ }
24
+ | {
25
+ state: State;
26
+ inputs: Record<string, string>;
27
+ action: null;
28
+ action_args: null;
29
+ $: Record<string, unknown>;
30
+ };
31
+
17
32
  export abstract class StatefulPage<
18
33
  State extends Record<string, unknown>,
19
34
  Actions extends Record<
@@ -22,11 +37,11 @@ export abstract class StatefulPage<
22
37
  state: State,
23
38
  inputs: Record<string, string>,
24
39
  ...args: unknown[]
25
- ) => State
40
+ ) => State | Promise<State>
26
41
  >
27
42
  > extends Mountable {
28
43
  abstract actions: Actions;
29
- abstract getInitialState(): State;
44
+ abstract getInitialState(): State | Promise<State>;
30
45
  abstract render(
31
46
  ctx: BaseContext,
32
47
  state: State,
@@ -87,11 +102,13 @@ export abstract class StatefulPage<
87
102
  ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>
88
103
  ) {
89
104
  const { action } = this.extractActionAndLabel(action_description);
90
- return `./?action=${action}&args=${to_base64(JSON.stringify(args))}`;
105
+ return `./?action=${action}&action_args=${to_base64(
106
+ JSON.stringify(args)
107
+ )}`;
91
108
  }
92
109
 
93
110
  makeActionButton<ActionName extends keyof Actions>(
94
- state: State,
111
+ _state: State,
95
112
  action_description: StatefulPageActionDescription<ActionName>,
96
113
  ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>
97
114
  ) {
@@ -99,7 +116,7 @@ export abstract class StatefulPage<
99
116
  return /* HTML */ `
100
117
  <input
101
118
  type="submit"
102
- value=${label}
119
+ value="${label}"
103
120
  formaction="${this.makeActionURL(action_description, ...args)}"
104
121
  />
105
122
  `;
@@ -124,25 +141,21 @@ export abstract class StatefulPage<
124
141
  return "this.closest('form').requestSubmit()";
125
142
  }
126
143
 
127
- extractState(ctx: BaseContext):
128
- | {
129
- state: State;
130
- inputs: Record<string, string>;
131
- action: keyof Actions;
132
- action_args: string;
133
- }
134
- | {
135
- state: State;
136
- inputs: Record<string, string>;
137
- action: null;
138
- action_args: null;
139
- } {
144
+ async preprocessRequestBody(
145
+ values: Record<string, unknown>
146
+ ): Promise<StateAndMetadata<State, Actions>> {
147
+ return values as StateAndMetadata<State, Actions>;
148
+ }
149
+
150
+ async extractState(
151
+ ctx: BaseContext
152
+ ): Promise<StateAndMetadata<State, Actions>> {
140
153
  if (
141
154
  !hasShape(
142
155
  {
143
156
  action: predicates.maybe(predicates.string),
144
157
  state: predicates.string,
145
- args: predicates.maybe(predicates.string),
158
+ action_args: predicates.maybe(predicates.string),
146
159
  $: predicates.maybe(predicates.object),
147
160
  },
148
161
  ctx.$body
@@ -151,55 +164,67 @@ export abstract class StatefulPage<
151
164
  console.error("Wrong data: ", ctx.$body);
152
165
  throw new Error("wrong formdata shape");
153
166
  }
167
+ // giving extending classes a change to modify body before furhter processing
168
+ const prepared_body = await this.preprocessRequestBody(ctx.$body);
154
169
  const inputs = Object.fromEntries(
155
- Object.entries(ctx.$body).filter(
170
+ Object.entries(prepared_body).filter(
156
171
  ([key]) => !["action", "state", "args", "$"].includes(key)
157
172
  )
158
173
  ) as Record<string, string>;
159
174
  // the "$" key is parsed as dot notation and overrides the state
160
175
 
161
- const original_state = JSON.parse(from_base64(ctx.$body.state));
162
-
163
- const modified_state = deepmerge(original_state, ctx.$body.$ || {}, {
164
- arrayMerge: (
165
- _target: any[],
166
- source: any[],
167
- _options: ArrayMergeOptions
168
- ) => {
169
- // https://github.com/TehShrike/deepmerge#user-content-arraymerge-example-combine-arrays
170
- return source;
171
- },
172
- }) as State;
176
+ const original_state_string = prepared_body.state;
177
+ const original_state = JSON.parse(
178
+ typeof original_state_string == "string"
179
+ ? from_base64(original_state_string)
180
+ : "{}"
181
+ );
182
+
183
+ const modified_state = deepmerge(
184
+ original_state,
185
+ hasField("$", prepared_body) ? prepared_body.$ : {},
186
+ {
187
+ arrayMerge: (
188
+ _target: any[],
189
+ source: any[],
190
+ _options: ArrayMergeOptions
191
+ ) => {
192
+ // https://github.com/TehShrike/deepmerge#user-content-arraymerge-example-combine-arrays
193
+ return source;
194
+ },
195
+ }
196
+ ) as State;
173
197
 
174
- if (ctx.$body.action && ctx.$body.args) {
198
+ if (prepared_body.action && prepared_body.action_args) {
175
199
  return {
176
200
  state: modified_state,
177
- action: ctx.$body.action as string,
201
+ action: prepared_body.action as string,
178
202
  inputs,
179
- action_args: ctx.$body.args,
203
+ action_args: prepared_body.action_args,
180
204
  };
181
205
  } else {
182
206
  return {
183
207
  state: modified_state,
184
208
  action: null,
185
- action_args: null,
186
209
  inputs,
210
+ action_args: null,
211
+ $: hasField("$", prepared_body) ? prepared_body.$ : {},
187
212
  };
188
213
  }
189
214
  }
190
215
 
191
216
  mount(router: Router, path: string) {
192
- router.get(path, (ctx) => {
193
- ctx.body = this.render(ctx, this.getInitialState(), {});
217
+ router.get(path, async (ctx) => {
218
+ ctx.body = this.render(ctx, await this.getInitialState(), {});
194
219
  });
195
220
 
196
- router.post(path, (ctx) => {
221
+ router.post(path, async (ctx) => {
197
222
  const { action, state, inputs, action_args } =
198
- this.extractState(ctx);
223
+ await this.extractState(ctx);
199
224
  if (action) {
200
225
  ctx.body = this.render(
201
226
  ctx,
202
- this.actions[action](
227
+ await this.actions[action](
203
228
  state,
204
229
  inputs,
205
230
  ...(JSON.parse(
@@ -0,0 +1,2 @@
1
+ import _locreq from "locreq";
2
+ export const target_locreq = _locreq(process.cwd());