@sealcode/sealgen 0.14.4 → 0.14.6

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,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.4",
3
+ "version": "0.14.6",
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
+ };
@@ -0,0 +1,52 @@
1
+ import _locreq from "locreq";
2
+ import * as prettier from "prettier";
3
+ import { promises as fs } from "fs";
4
+ import { getPrettierConfig } from "./utils/prettier.js";
5
+ import recursive from "recursive-readdir";
6
+ import path, { dirname } from "path";
7
+ import { toKebabCase, toPascalCase } from "js-convert-case";
8
+
9
+ const target_locreq = _locreq(process.cwd());
10
+
11
+ export async function generateStimulusControllers(): Promise<void> {
12
+ const output_file = "src/front/controllers.ts";
13
+ const stimulus_search_dir = target_locreq.resolve("src/");
14
+ const controller_names = (await recursive(stimulus_search_dir))
15
+ .filter((c) => c.endsWith(".stimulus.ts"))
16
+ .sort();
17
+ const controller_data = controller_names
18
+ .map(
19
+ (p) =>
20
+ "./" +
21
+ path
22
+ .relative(target_locreq.resolve(dirname(output_file)), p)
23
+ .replace(/\.[tj]sx?$/, ".js")
24
+ )
25
+ .map((relative_path) => {
26
+ const name = relative_path.match(/\/([^\/]+).stimulus/)?.[1];
27
+ return { name, relative_path };
28
+ });
29
+
30
+ const content = `// DO NOT EDIT! This file is generated automaticaly with 'npm run generate-stimulus'
31
+
32
+ import * as Turbo from "@hotwired/turbo";
33
+ import { Application } from "stimulus";
34
+ const application = Application.start();
35
+ ${controller_data
36
+ .map(
37
+ (c) => `
38
+ import { default as ${toPascalCase(c.name)} } from "${c.relative_path}";
39
+ application.register("${toKebabCase(c.name)}", ${toPascalCase(c.name)});`
40
+ )
41
+ .join("\n")}
42
+
43
+ export { Turbo };
44
+ `;
45
+
46
+ await fs.writeFile(
47
+ target_locreq.resolve(output_file),
48
+ prettier.format(content, await getPrettierConfig())
49
+ );
50
+ // eslint-disable-next-line no-console
51
+ console.log(`Successfuly generated new ${output_file} file`);
52
+ }
package/src/index.ts CHANGED
@@ -1,14 +1,3 @@
1
- import { App, Context as SealiousContext } from "sealious";
2
- import { FormDataValue } from "./forms/form-types.js";
3
-
4
- declare module "koa" {
5
- interface BaseContext {
6
- $context: SealiousContext;
7
- $app: App;
8
- $body: Record<string, FormDataValue>;
9
- }
10
- }
11
-
12
1
  export * as Controls from "./forms/controls/controls.js";
13
2
  export * from "./forms/fields/field.js";
14
3
  export * as Fields from "./forms/fields/fields.js";