@sealcode/sealgen 0.14.3 → 0.14.5

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.
@@ -16,7 +16,8 @@ class StatefulPage extends Mountable {
16
16
  this.wrapInForm(
17
17
  state,
18
18
  await original_render(ctx, state, inputs)
19
- )
19
+ ),
20
+ state
20
21
  );
21
22
  };
22
23
  }
@@ -123,7 +124,7 @@ class StatefulPage extends Mountable {
123
124
  options
124
125
  );
125
126
  } else if (target.indexOf(item) === -1) {
126
- destination.push(item);
127
+ destination[index] = item;
127
128
  }
128
129
  });
129
130
  return destination;
@@ -0,0 +1,21 @@
1
+ import { toKebabCase, toPascalCase } from "js-convert-case";
2
+ function jddComponentTemplate(controller_name) {
3
+ return `import { Controller } from "stimulus";
4
+
5
+ export default class ${toPascalCase(controller_name)} extends Controller {
6
+
7
+ connect() {
8
+ console.warn(
9
+ "Remove ${toKebabCase(
10
+ controller_name
11
+ )}.stimulus.ts file if your JDD component doesn't need any browser-side logic"
12
+ );
13
+ }
14
+ }
15
+
16
+ `;
17
+ }
18
+ export {
19
+ jddComponentTemplate
20
+ };
21
+ //# sourceMappingURL=stimulus-controller.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.14.3",
3
+ "version": "0.14.5",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
package/src/build.ts CHANGED
@@ -1,21 +1,16 @@
1
- import { build, BuildContext, context } from "esbuild";
2
1
  import { promises as fs } from "fs";
3
2
  import * as chokidar from "chokidar";
4
- import { default as glob } from "tiny-glob";
5
- import { generate_css_includes } from "./find-css-includes.js";
6
- import { sleep } from "./utils/sleep.js";
7
- import { generateCollections } from "./generate-collections.js";
8
- import { generateRoutes } from "./generate-routes.js";
9
- import { generateComponents } from "./generate-components.js";
10
3
  import { make_notifier } from "./notifier.js";
11
4
  import getPort from "get-port";
12
- import { removeNamedImports } from "../remove-named-imports.js";
13
- import md5 from "md5";
14
5
 
15
6
  import _locreq from "locreq";
16
- import path, { basename, extname } from "path";
17
- import { copyFile } from "fs/promises";
18
- import { FONTS_CONFIG_PATH, getFonts } from "./get-fonts.js";
7
+
8
+ import {
9
+ BackendTSBuilder,
10
+ CSSBuilder,
11
+ FontsBuilder,
12
+ FrontendTSBuilder,
13
+ } from "./builder.js";
19
14
  const target_locreq = _locreq(process.cwd());
20
15
 
21
16
  async function get_notifier_port() {
@@ -33,229 +28,37 @@ async function write_notifier_config(watch: boolean, port?: number) {
33
28
  );
34
29
  }
35
30
 
36
- function addSuffix(filename: string, suffix: string) {
37
- return filename.replace(/(\.[a-zA-Z0-9]+)?$/, suffix + "$1");
38
- }
39
-
40
- async function makeResourceURL(file_path: string) {
41
- const hash_base = file_path + (await fs.stat(file_path)).mtimeMs;
42
- const hash = md5(hash_base);
43
- const public_url =
44
- "dist/" + addSuffix(basename(file_path), "-" + hash.slice(0, 8));
45
- const public_dir = "public";
46
- await copyFile(
47
- file_path,
48
- target_locreq.resolve(path.resolve(public_dir, public_url))
49
- );
50
- return "/" + public_url;
51
- }
52
-
53
- async function build_ts() {
54
- await Promise.all([
55
- generateCollections(),
56
- generateRoutes(),
57
- generateComponents(),
58
- ]);
59
- const embeddable_file_extensions = ["svg", "png", "jpg", "jpeg", "webp"];
60
- const entryPoints = (
61
- await glob(
62
- `./src/back/**/*.{ts,tsx,${embeddable_file_extensions.join(",")}}`
63
- )
64
- ).filter((path) => !path.includes(".#"));
65
- try {
66
- await build({
67
- entryPoints,
68
- sourcemap: true,
69
- bundle: false,
70
- jsxFactory: "TempstreamJSX.createElement",
71
- outdir: "./dist/back",
72
- logLevel: "info",
73
- platform: "node",
74
- target: "es2022",
75
- format: "esm",
76
- loader: Object.fromEntries(
77
- embeddable_file_extensions.map((ext) => ["." + ext, "js"])
78
- ),
79
- plugins: [
80
- {
81
- name: "sealgen-rewrite-asset-imports",
82
- setup(build) {
83
- build.onLoad(
84
- { filter: /\.tsx?$/ },
85
- async (args: any) => {
86
- let contents = await fs.readFile(
87
- args.path,
88
- "utf8"
89
- );
90
- for (const ext of embeddable_file_extensions) {
91
- const regex = new RegExp(
92
- `^import (\\w+|{[^}]+}) from "([^"]+.${ext})";`,
93
- "gm"
94
- );
95
- contents = contents.replaceAll(
96
- regex,
97
- (line) => {
98
- return line.replace(
99
- `.${ext}"`,
100
- '.js"'
101
- );
102
- }
103
- );
104
- }
105
-
106
- contents = removeNamedImports(contents);
107
-
108
- return {
109
- contents,
110
- loader:
111
- extname(args.path) === ".tsx"
112
- ? "tsx"
113
- : "ts",
114
- };
115
- }
116
- );
117
- },
118
- },
119
- {
120
- name: "sealgen-load-assets",
121
- setup(build) {
122
- build.onLoad(
123
- {
124
- filter: new RegExp(
125
- `\\.(${embeddable_file_extensions.join(
126
- "|"
127
- )})$`
128
- ),
129
- },
130
- async (args: any) => {
131
- let contents = `export const url = "${await makeResourceURL(
132
- args.path
133
- )}";
134
-
135
- import fs from "fs";
136
- let buffer_promise = null;
137
-
138
- export const getBuffer = function(){
139
- if(buffer_promise) return buffer_promise;
140
- buffer_promise = fs.promises.readFile("${args.path}");
141
- return buffer_promise;
142
- }
143
-
144
- export const getContent = async function(){
145
- const buffer = await getBuffer();
146
- return buffer.toString("utf-8");
147
- }
148
-
149
- export const getBase64 = async function(){
150
- const buffer = await getBuffer();
151
- return buffer.toString("base64");
152
- }
153
-
154
- export default { getContent, url, getBase64, getBuffer };
155
- `;
156
- return {
157
- contents,
158
- };
159
- }
160
- );
161
- },
162
- },
163
- ],
164
- });
165
- } catch (e) {
166
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
167
- console.error(e.message);
168
- }
169
- }
170
-
171
- async function complex_build(watch: boolean): Promise<void> {
172
- const loader = Object.fromEntries(
173
- (<const>["png", "svg", "jpg", "gif", "jpeg", "otf", "ttf"]).map(
174
- (ext) => <const>["." + ext, "file"]
175
- )
176
- );
177
- let css_build: BuildContext = await context({
178
- entryPoints: ["./src/main.css"],
179
- sourcemap: true,
180
- bundle: true,
181
- outdir: "./public/dist",
182
- logLevel: "info",
183
- // plugins: [sassPlugin()],
184
- loader,
185
- });
186
- let ongoing_fonts = false;
187
- let ongoing_ts = false;
188
- let ongoing_css = false;
189
- let ongoing_ts_promise: Promise<any> | null = null;
31
+ async function build(watch: boolean): Promise<void> {
32
+ const fonts_builder = new FontsBuilder();
33
+ const backend_ts_builder = new BackendTSBuilder();
34
+ const frontend_ts_builder = new FrontendTSBuilder();
35
+ const css_builder = new CSSBuilder();
36
+ const builders = [
37
+ fonts_builder,
38
+ backend_ts_builder,
39
+ css_builder,
40
+ frontend_ts_builder,
41
+ ];
42
+ await Promise.all(builders.map((b) => b.build()));
190
43
  if (watch) {
191
44
  const watcher = chokidar.watch("src", { ignoreInitial: true });
192
45
  const port = await get_notifier_port();
193
46
  const notifier = make_notifier(port);
194
47
  await write_notifier_config(watch, port);
195
- watcher.on("all", async (_, path) => {
196
- if (!css_build) return;
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
- }
206
- if (path.endsWith(".css") && !path.endsWith("/includes.css")) {
207
- if (ongoing_css) {
208
- return;
209
- }
210
- ongoing_css = true;
211
- // refresh the list of all css files in includes.css
212
- await generate_css_includes();
213
- try {
214
- await css_build?.rebuild?.();
215
- console.log(`Built main.css [on ${path}]`);
216
- } catch (e) {
217
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
218
- console.error(e.message);
219
- await sleep(200);
220
- css_build
221
- ?.rebuild?.()
222
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
223
- .catch((e) => console.error(e?.message));
48
+ watcher.on("all", async (_, file_path) => {
49
+ console.log("Detected a change!", file_path);
50
+ builders.forEach((builder) => {
51
+ const owns_a_file = builder.ownsFile(file_path);
52
+ if (owns_a_file) {
53
+ builder.build(notifier);
224
54
  }
225
- if (ongoing_ts_promise) {
226
- await ongoing_ts_promise;
227
- }
228
- notifier("css");
229
- ongoing_css = false;
230
- }
231
- if (
232
- (path.endsWith(".ts") || path.endsWith(".tsx")) &&
233
- !path.endsWith("src/back/collections/collections.ts") &&
234
- !path.endsWith("src/back/routes/routes.ts") &&
235
- !path.endsWith("src/back/routes/urls.ts")
236
- ) {
237
- if (ongoing_ts) {
238
- return;
239
- }
240
- ongoing_ts = true;
241
- ongoing_ts_promise = build_ts();
242
- await ongoing_ts_promise;
243
- notifier("ts");
244
- ongoing_ts_promise = null;
245
- console.log(`Finished TS build [on ${path}]`);
246
- ongoing_ts = false;
247
- }
55
+ });
248
56
  });
249
57
  } else {
250
58
  await write_notifier_config(watch);
251
- }
252
- await generate_css_includes();
253
-
254
- await Promise.all([css_build.rebuild(), build_ts()]);
255
- if (watch) {
256
- css_build?.rebuild?.();
257
- } else {
258
- css_build.dispose();
59
+ builders.forEach((builder) => {
60
+ builder.dispose();
61
+ });
259
62
  }
260
63
  }
261
64
 
@@ -265,14 +68,7 @@ export async function buildProject({
265
68
  watch: boolean;
266
69
  }): Promise<void> {
267
70
  try {
268
- await build({
269
- entryPoints: ["./src/front/index.ts"],
270
- sourcemap: true,
271
- outfile: "./public/dist/bundle.js",
272
- logLevel: "info",
273
- bundle: true,
274
- });
275
- await complex_build(watch);
71
+ await build(watch);
276
72
  } catch (e) {
277
73
  console.log("CAUGHT!");
278
74
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
package/src/builder.ts ADDED
@@ -0,0 +1,176 @@
1
+ import { build, BuildContext, context } from "esbuild";
2
+ import glob from "tiny-glob";
3
+ import { embeddable_file_extensions } from "./embeddable-file-extensions.js";
4
+ import { load_assets_plugin } from "./esbuild-plugins/load-assets.js";
5
+ import { rewrite_asset_imports_plugin } from "./esbuild-plugins/rewrite-asset-imports.js";
6
+ import { generate_css_includes } from "./find-css-includes.js";
7
+ import { generateCollections } from "./generate-collections.js";
8
+ import { generateComponents } from "./generate-components.js";
9
+ import { generateRoutes } from "./generate-routes.js";
10
+ import { generateStimulusControllers } from "./generate-stimulus.js";
11
+ import { FONTS_CONFIG_PATH, getFonts } from "./get-fonts.js";
12
+
13
+ export abstract class Builder {
14
+ abstract ownsFile(file_path: string): boolean;
15
+ abstract getName(): string;
16
+ abstract _build(): Promise<void>;
17
+ abstract dispose(): Promise<void>;
18
+
19
+ constructor() {}
20
+
21
+ public ongoing_build: Promise<void> | null = null;
22
+
23
+ public build(notifier?: (message: string) => void) {
24
+ if (!this.ongoing_build) {
25
+ const build = this._build()
26
+ .catch((err) => {
27
+ console.error(err);
28
+ })
29
+ .then(() => {
30
+ notifier?.(this.getName());
31
+ this.ongoing_build = null;
32
+ });
33
+ this.ongoing_build = build;
34
+ }
35
+ return this.ongoing_build;
36
+ }
37
+ }
38
+
39
+ export class CSSBuilder extends Builder {
40
+ getName(): string {
41
+ return "css";
42
+ }
43
+
44
+ private context_promise: Promise<BuildContext> | null;
45
+
46
+ ownsFile(file_path: string) {
47
+ return (
48
+ file_path.endsWith(".css") && !file_path.endsWith("/includes.css")
49
+ );
50
+ }
51
+
52
+ async dispose(): Promise<void> {
53
+ return (await this.getBuildContext()).dispose();
54
+ }
55
+
56
+ async getBuildContext(): Promise<BuildContext> {
57
+ if (!this.context_promise) {
58
+ const loader = Object.fromEntries(
59
+ (<const>["png", "svg", "jpg", "gif", "jpeg", "otf", "ttf"]).map(
60
+ (ext) => <const>["." + ext, "file"]
61
+ )
62
+ );
63
+ const context_promise = context({
64
+ entryPoints: ["./src/main.css"],
65
+ sourcemap: true,
66
+ bundle: true,
67
+ outdir: "./public/dist",
68
+ logLevel: "info",
69
+ // plugins: [sassPlugin()],
70
+ loader,
71
+ });
72
+ this.context_promise = context_promise;
73
+ return context_promise;
74
+ } else {
75
+ return this.context_promise;
76
+ }
77
+ }
78
+
79
+ async _build() {
80
+ const context = await this.getBuildContext();
81
+ await generate_css_includes();
82
+ await context.rebuild();
83
+ }
84
+ }
85
+
86
+ export class FontsBuilder extends Builder {
87
+ getName(): string {
88
+ return "fonts";
89
+ }
90
+
91
+ ownsFile(file_path: string) {
92
+ return file_path == FONTS_CONFIG_PATH;
93
+ }
94
+
95
+ async dispose(): Promise<void> {}
96
+
97
+ async _build() {
98
+ return getFonts();
99
+ }
100
+ }
101
+
102
+ export class BackendTSBuilder extends Builder {
103
+ getName(): string {
104
+ return "backend-ts";
105
+ }
106
+
107
+ ownsFile(file_path: string) {
108
+ return (
109
+ (file_path.endsWith(".ts") || file_path.endsWith(".tsx")) &&
110
+ !file_path.endsWith("src/back/collections/collections.ts") &&
111
+ !file_path.endsWith("src/back/routes/routes.ts") &&
112
+ !file_path.endsWith("src/back/routes/urls.ts") &&
113
+ !file_path.endsWith("stimulus.ts") &&
114
+ !file_path.startsWith("src/front")
115
+ );
116
+ }
117
+
118
+ async dispose(): Promise<void> {}
119
+
120
+ async _build(): Promise<void> {
121
+ await Promise.all([
122
+ generateCollections(),
123
+ generateRoutes(),
124
+ generateComponents(),
125
+ ]);
126
+ const entryPoints = (
127
+ await glob(
128
+ `./src/back/**/*.{ts,tsx,${embeddable_file_extensions.join(
129
+ ","
130
+ )}}`
131
+ )
132
+ ).filter((path) => !path.includes(".#"));
133
+ await build({
134
+ entryPoints,
135
+ sourcemap: true,
136
+ bundle: false,
137
+ jsxFactory: "TempstreamJSX.createElement",
138
+ outdir: "./dist/back",
139
+ logLevel: "info",
140
+ platform: "node",
141
+ target: "es2022",
142
+ format: "esm",
143
+ loader: Object.fromEntries(
144
+ embeddable_file_extensions.map((ext) => ["." + ext, "js"])
145
+ ),
146
+ plugins: [rewrite_asset_imports_plugin, load_assets_plugin],
147
+ });
148
+ }
149
+ }
150
+
151
+ export class FrontendTSBuilder extends Builder {
152
+ getName(): string {
153
+ return "frontend-ts";
154
+ }
155
+
156
+ ownsFile(file_path: string) {
157
+ return (
158
+ file_path.startsWith("src/front") ||
159
+ file_path.endsWith(".stimulus.ts")
160
+ );
161
+ }
162
+
163
+ async dispose(): Promise<void> {}
164
+
165
+ async _build(): Promise<void> {
166
+ await generateStimulusControllers();
167
+ await build({
168
+ entryPoints: ["./src/front/index.ts"],
169
+ sourcemap: true,
170
+ outfile: "./public/dist/bundle.js",
171
+ logLevel: "info",
172
+ bundle: true,
173
+ minify: true,
174
+ });
175
+ }
176
+ }
package/src/cli.ts CHANGED
@@ -10,6 +10,7 @@ import { generateRoutes } from "./generate-routes.js";
10
10
  import { makeEnv } from "./make-env.js";
11
11
  import yargs from "yargs/yargs";
12
12
  import { getFonts } from "./get-fonts.js";
13
+ import { generateStimulusControllers } from "./generate-stimulus.js";
13
14
 
14
15
  const actions: Record<
15
16
  string,
@@ -32,7 +33,8 @@ const actions: Record<
32
33
  },
33
34
  "make-env": makeEnv,
34
35
  "add-component": addJDDComponent,
35
- "get-fonts": getFonts,
36
+ "get-fonts": getFonts,
37
+ "generate-stimulus": generateStimulusControllers,
36
38
  };
37
39
 
38
40
  void (async function () {
@@ -0,0 +1 @@
1
+ export const embeddable_file_extensions = ["svg", "png", "jpg", "jpeg", "webp"];
@@ -0,0 +1,68 @@
1
+ import { copyFile } from "fs/promises";
2
+ import { promises as fs } from "fs";
3
+ import md5 from "md5";
4
+ import path, { basename } from "node:path";
5
+
6
+ import _locreq from "locreq";
7
+ import { embeddable_file_extensions } from "../embeddable-file-extensions.js";
8
+ const target_locreq = _locreq(process.cwd());
9
+
10
+ function addSuffix(filename: string, suffix: string) {
11
+ return filename.replace(/(\.[a-zA-Z0-9]+)?$/, suffix + "$1");
12
+ }
13
+
14
+ async function makeResourceURL(file_path: string) {
15
+ const hash_base = file_path + (await fs.stat(file_path)).mtimeMs;
16
+ const hash = md5(hash_base);
17
+ const public_url =
18
+ "dist/" + addSuffix(basename(file_path), "-" + hash.slice(0, 8));
19
+ const public_dir = "public";
20
+ await copyFile(
21
+ file_path,
22
+ target_locreq.resolve(path.resolve(public_dir, public_url))
23
+ );
24
+ return "/" + public_url;
25
+ }
26
+
27
+ export const load_assets_plugin = {
28
+ name: "sealgen-load-assets",
29
+ setup(build: any) {
30
+ build.onLoad(
31
+ {
32
+ filter: new RegExp(
33
+ `\\.(${embeddable_file_extensions.join("|")})$`
34
+ ),
35
+ },
36
+ async (args: any) => {
37
+ let contents = `export const url = "${await makeResourceURL(
38
+ args.path
39
+ )}";
40
+
41
+ import fs from "fs";
42
+ let buffer_promise = null;
43
+
44
+ export const getBuffer = function(){
45
+ if(buffer_promise) return buffer_promise;
46
+ buffer_promise = fs.promises.readFile("${args.path}");
47
+ return buffer_promise;
48
+ }
49
+
50
+ export const getContent = async function(){
51
+ const buffer = await getBuffer();
52
+ return buffer.toString("utf-8");
53
+ }
54
+
55
+ export const getBase64 = async function(){
56
+ const buffer = await getBuffer();
57
+ return buffer.toString("base64");
58
+ }
59
+
60
+ export default { getContent, url, getBase64, getBuffer };
61
+ `;
62
+ return {
63
+ contents,
64
+ };
65
+ }
66
+ );
67
+ },
68
+ };
@@ -0,0 +1,29 @@
1
+ import { promises as fs } from "fs";
2
+ import { extname } from "node:path";
3
+ import { embeddable_file_extensions } from "../embeddable-file-extensions.js";
4
+ import { removeNamedImports } from "../../remove-named-imports.js";
5
+
6
+ export const rewrite_asset_imports_plugin = {
7
+ name: "sealgen-rewrite-asset-imports",
8
+ setup(build: any) {
9
+ build.onLoad({ filter: /\.tsx?$/ }, async (args: any) => {
10
+ let contents = await fs.readFile(args.path, "utf8");
11
+ for (const ext of embeddable_file_extensions) {
12
+ const regex_assets = new RegExp(
13
+ `^import (\\w+|{[^}]+}) from "([^"]+.${ext})";`,
14
+ "gm"
15
+ );
16
+ contents = contents.replaceAll(regex_assets, (line) => {
17
+ return line.replace(`.${ext}"`, '.js"');
18
+ });
19
+ }
20
+
21
+ contents = removeNamedImports(contents);
22
+
23
+ return {
24
+ contents,
25
+ loader: extname(args.path) === ".tsx" ? "tsx" : "ts",
26
+ };
27
+ });
28
+ },
29
+ };
@@ -6,21 +6,23 @@ import { walkDir } from "./utils/walk.js";
6
6
  const target_locreq = _locreq(process.cwd());
7
7
 
8
8
  export async function generate_css_includes(): Promise<void> {
9
- const files = await Promise.all(
10
- (await walkDir(target_locreq.resolve("src/")))
11
- .filter(
12
- (f) =>
13
- f.endsWith(".css") &&
14
- !f.endsWith("/main.css") &&
15
- !f.endsWith("\\main.css") &&
16
- !f.endsWith("/includes.css") &&
17
- !f.endsWith("\\includes.css") &&
18
- !f.includes(".#")
19
- )
20
- .map((fullpath) =>
21
- relative(target_locreq.resolve("src/"), fullpath)
22
- )
23
- );
9
+ const files = (
10
+ await Promise.all(
11
+ (await walkDir(target_locreq.resolve("src/")))
12
+ .filter(
13
+ (f) =>
14
+ f.endsWith(".css") &&
15
+ !f.endsWith("/main.css") &&
16
+ !f.endsWith("\\main.css") &&
17
+ !f.endsWith("/includes.css") &&
18
+ !f.endsWith("\\includes.css") &&
19
+ !f.includes(".#")
20
+ )
21
+ .map((fullpath) =>
22
+ relative(target_locreq.resolve("src/"), fullpath)
23
+ )
24
+ )
25
+ ).sort();
24
26
 
25
27
  await fs.writeFile(
26
28
  target_locreq.resolve("src/includes.css"),
@@ -13,9 +13,9 @@ const target_locreq = _locreq(process.cwd());
13
13
  export async function generateComponents(): Promise<void> {
14
14
  const components_dir = target_locreq.resolve("src/back/jdd-components");
15
15
  await mkdir(components_dir, { recursive: true });
16
- const component_names = (await recursive(components_dir)).filter((c) =>
17
- c.endsWith(".jdd.tsx")
18
- );
16
+ const component_names = (await recursive(components_dir))
17
+ .filter((c) => c.endsWith(".jdd.tsx"))
18
+ .sort();
19
19
  const component_data = component_names
20
20
  .map(
21
21
  (p) =>