@sealcode/sealgen 0.17.9 → 0.17.10

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,7 @@
1
+ import { Builder } from "./builder.js";
2
+ export declare class BackendTSBuilder extends Builder {
3
+ getName(): string;
4
+ ownsFile(file_path: string): boolean;
5
+ dispose(): Promise<void>;
6
+ _build(): Promise<void>;
7
+ }
@@ -0,0 +1,11 @@
1
+ export declare abstract class Builder {
2
+ project_dir: string;
3
+ style_dirs: string[];
4
+ abstract ownsFile(file_path: string): boolean;
5
+ abstract getName(): string;
6
+ abstract _build(): Promise<void>;
7
+ abstract dispose(): Promise<void>;
8
+ constructor(project_dir: string, style_dirs: string[]);
9
+ ongoing_build: Promise<void> | null;
10
+ build(notifier?: (message: string) => void): Promise<void>;
11
+ }
@@ -0,0 +1,10 @@
1
+ import { Builder } from "./builder.js";
2
+ export declare const COLORS_TS_PATH = "src/back/colors.ts";
3
+ export declare const COLORS_CSS_PATH = "src/colors.css";
4
+ export declare const COLORS_HTML_PATH = "src/colors.html";
5
+ export declare class ColorsBuilder extends Builder {
6
+ getName(): string;
7
+ ownsFile(file_path: string): boolean;
8
+ dispose(): Promise<void>;
9
+ _build(): Promise<void>;
10
+ }
@@ -0,0 +1,19 @@
1
+ import { Builder } from "./builder.js";
2
+ export declare class CSSClump {
3
+ project_dir: string;
4
+ name: string;
5
+ files: string[];
6
+ constructor(project_dir: string, name: string);
7
+ addFile(full_path: string): void;
8
+ build(): Promise<void>;
9
+ getEntrypointPath(): string;
10
+ makeEntrypoint(): Promise<void>;
11
+ static assignFileToClumps(file_path: string): string[];
12
+ static findCSSClumps(project_dir: string, style_dirs: string[]): Promise<CSSClump[]>;
13
+ }
14
+ export declare class CSSBuilder extends Builder {
15
+ getName(): string;
16
+ ownsFile(file_path: string): boolean;
17
+ dispose(): Promise<void>;
18
+ _build(): Promise<void>;
19
+ }
@@ -0,0 +1,7 @@
1
+ import { Builder } from "./builder.js";
2
+ export declare class FontsBuilder extends Builder {
3
+ getName(): string;
4
+ ownsFile(file_path: string): boolean;
5
+ dispose(): Promise<void>;
6
+ _build(): Promise<void>;
7
+ }
@@ -0,0 +1,11 @@
1
+ import { Builder } from "./builder.js";
2
+ export declare class FrontendTSBuilder extends Builder {
3
+ project_dir: string;
4
+ style_dirs: string[];
5
+ controller_dirs: string[];
6
+ constructor(project_dir: string, style_dirs: string[], controller_dirs: string[]);
7
+ getName(): string;
8
+ ownsFile(file_path: string): boolean;
9
+ dispose(): Promise<void>;
10
+ _build(): Promise<void>;
11
+ }
package/lib/build.js CHANGED
@@ -4,13 +4,12 @@ import { make_notifier } from "./notifier.js";
4
4
  import getPort from "get-port";
5
5
  import { resolve } from "node:path";
6
6
  import _locreq from "locreq";
7
- import {
8
- BackendTSBuilder,
9
- FontsBuilder,
10
- FrontendTSBuilder
11
- } from "./builder.js";
12
- import { CSSBuilder } from "./CSSBuilder.js";
13
7
  import { hasShape, predicates } from "@sealcode/ts-predicates";
8
+ import { FontsBuilder } from "./builders/fonts-builder.js";
9
+ import { BackendTSBuilder } from "./builders/backend-ts-builder.js";
10
+ import { FrontendTSBuilder } from "./builders/frontend-ts-builder.js";
11
+ import { CSSBuilder } from "./builders/css-builder.js";
12
+ import { ColorsBuilder } from "./builders/colors-builder.js";
14
13
  const target_locreq = _locreq(process.cwd());
15
14
  async function get_notifier_port() {
16
15
  return getPort({ port: 4e3 });
@@ -77,9 +76,11 @@ async function build(watch) {
77
76
  controller_dirs
78
77
  );
79
78
  const css_builder = new CSSBuilder(project_dir, style_dirs);
79
+ const colors_builder = new ColorsBuilder(project_dir, style_dirs);
80
80
  const builders = [
81
81
  fonts_builder,
82
82
  backend_ts_builder,
83
+ colors_builder,
83
84
  css_builder,
84
85
  frontend_ts_builder
85
86
  ];
@@ -0,0 +1,62 @@
1
+ import glob from "tiny-glob";
2
+ import { build } from "esbuild";
3
+ import { promises as fs } from "fs";
4
+ import { relative } from "node:path";
5
+ import { embeddable_file_extensions } from "../embeddable-file-extensions.js";
6
+ import { generateCollections } from "../generate-collections.js";
7
+ import { generateComponents } from "../generate-components.js";
8
+ import { generateRoutes } from "../generate-routes.js";
9
+ import { Builder } from "./builder.js";
10
+ import { rewrite_asset_imports_plugin } from "../esbuild-plugins/rewrite-asset-imports.js";
11
+ import { load_assets_plugin } from "../esbuild-plugins/load-assets.js";
12
+ class BackendTSBuilder extends Builder {
13
+ getName() {
14
+ return "backend-ts";
15
+ }
16
+ ownsFile(file_path) {
17
+ return (file_path.endsWith(".ts") || file_path.endsWith(".tsx")) && !file_path.endsWith("src/back/collections/collections.ts") && !file_path.endsWith("src/back/routes/routes.ts") && !file_path.endsWith("src/back/routes/urls.ts") && !file_path.endsWith("stimulus.ts") && !file_path.startsWith("src/front");
18
+ }
19
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
20
+ async dispose() {
21
+ }
22
+ async _build() {
23
+ await Promise.all([
24
+ generateCollections(),
25
+ generateRoutes(),
26
+ generateComponents()
27
+ ]);
28
+ const entryPoints = (await glob(
29
+ `./src/back/**/*.{ts,tsx,${embeddable_file_extensions.join(
30
+ ","
31
+ )}}`
32
+ )).filter((path) => !path.includes(".#"));
33
+ const { metafile } = await build({
34
+ entryPoints,
35
+ sourcemap: true,
36
+ bundle: false,
37
+ jsxFactory: "TempstreamJSX.createElement",
38
+ jsxFragment: "TempstreamJSX.Fragment",
39
+ outdir: "./dist/back",
40
+ logLevel: "info",
41
+ platform: "node",
42
+ target: "es2022",
43
+ format: "esm",
44
+ loader: Object.fromEntries(
45
+ embeddable_file_extensions.map((ext) => ["." + ext, "js"])
46
+ ),
47
+ plugins: [
48
+ rewrite_asset_imports_plugin(this.project_dir),
49
+ load_assets_plugin
50
+ ],
51
+ metafile: true
52
+ });
53
+ await fs.writeFile(
54
+ relative(this.project_dir, "dist") + "/back.meta.json",
55
+ JSON.stringify(metafile)
56
+ );
57
+ }
58
+ }
59
+ export {
60
+ BackendTSBuilder
61
+ };
62
+ //# sourceMappingURL=backend-ts-builder.js.map
@@ -0,0 +1,23 @@
1
+ class Builder {
2
+ constructor(project_dir, style_dirs) {
3
+ this.project_dir = project_dir;
4
+ this.style_dirs = style_dirs;
5
+ this.ongoing_build = null;
6
+ }
7
+ build(notifier) {
8
+ if (!this.ongoing_build) {
9
+ const build = this._build().catch((err) => {
10
+ console.error(err);
11
+ }).then(() => {
12
+ notifier?.(this.getName());
13
+ this.ongoing_build = null;
14
+ });
15
+ this.ongoing_build = build;
16
+ }
17
+ return this.ongoing_build;
18
+ }
19
+ }
20
+ export {
21
+ Builder
22
+ };
23
+ //# sourceMappingURL=builder.js.map
@@ -0,0 +1,202 @@
1
+ import { build } from "esbuild";
2
+ import tempfile from "tempfile";
3
+ import { promises as fs } from "fs";
4
+ import { resolve } from "path";
5
+ import { colord } from "colord";
6
+ import { formatWithPrettier } from "../utils/prettier.js";
7
+ import { Builder } from "./builder.js";
8
+ import { exec } from "../utils/exec.js";
9
+ const COLORS_TS_PATH = "src/back/colors.ts";
10
+ const COLORS_CSS_PATH = "src/colors.css";
11
+ const COLORS_HTML_PATH = "src/colors.html";
12
+ class ColorsBuilder extends Builder {
13
+ getName() {
14
+ return "colors";
15
+ }
16
+ ownsFile(file_path) {
17
+ return file_path == COLORS_TS_PATH;
18
+ }
19
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
20
+ async dispose() {
21
+ }
22
+ async _build() {
23
+ const outfile = tempfile({ extension: "mjs" });
24
+ await build({
25
+ entryPoints: [COLORS_TS_PATH],
26
+ bundle: true,
27
+ format: "esm",
28
+ outfile
29
+ });
30
+ await fs.appendFile(outfile, "console.log(JSON.stringify(colors))");
31
+ const output = await exec("node", [outfile]);
32
+ const colors = JSON.parse(output.stdout);
33
+ let css_colors = "";
34
+ let html_colors = "";
35
+ for (const group_name in colors) {
36
+ const sorted_colors = Object.entries(colors[group_name]).sort(
37
+ ([key1], [key2]) => key1 > key2 ? 1 : -1
38
+ );
39
+ html_colors += `<h2>${group_name}</h2><div class="group"><div class="base">`;
40
+ let last_color_name_base = "";
41
+ for (const [color_name, color] of sorted_colors) {
42
+ const color_name_elements = color_name.split("-");
43
+ const color_name_base = color_name_elements.filter((s) => !/^(light|dark)[0-9]$/.test(s)).join("-");
44
+ let index = 0;
45
+ const modifier = color_name_elements.at(-1);
46
+ let multiplier = 1;
47
+ let steps = 0;
48
+ if (modifier) {
49
+ if (modifier.startsWith("dark")) {
50
+ multiplier = -1;
51
+ }
52
+ }
53
+ if (modifier && modifier.match(/[0-9]$/) && modifier.match(/^(dark|light)[0-9]/)) {
54
+ steps = parseInt(modifier.at(-1));
55
+ }
56
+ index = multiplier * steps;
57
+ const is_new_base = color_name_base != last_color_name_base;
58
+ css_colors += `--color-${group_name}-${color_name}: ${color};`;
59
+ if (is_new_base) {
60
+ html_colors += `</div><h3>${color_name_base}</h3><div class="base" base="${color_name_base}">`;
61
+ }
62
+ if (color != "#000000" && color != "#ffffff") {
63
+ html_colors += /* HTML */
64
+ `<div
65
+ class="color-box ${colord(color).isDark() ? "is-dark" : ""} ${index == 0 ? "is-main" : ""}"
66
+ style="background-color: ${color}; order: ${index}"
67
+ data-color-name="${color_name}"
68
+ data-color-group="${group_name}"
69
+ ></div>`;
70
+ }
71
+ last_color_name_base = color_name_base;
72
+ }
73
+ html_colors += "</div></div>";
74
+ }
75
+ const css = await formatWithPrettier(
76
+ `/* DO NOT EDIT! This file is automatically generated by sealgen */
77
+ :root {
78
+ ${css_colors}
79
+ }`,
80
+ "css"
81
+ );
82
+ const html = await formatWithPrettier(
83
+ /* HTML */
84
+ `<!DOCTYPE html>
85
+ <html>
86
+ <head>
87
+ <style>
88
+ * {
89
+ font-family: sans-serif;
90
+ }
91
+
92
+ .base {
93
+ display: flex;
94
+ }
95
+
96
+ .color-box {
97
+ width: 100px;
98
+ height: 100px;
99
+ transition: all 50ms;
100
+ transition-timing-function: ease-in-out;
101
+ cursor: pointer;
102
+
103
+ &:hover {
104
+ transform: scale(1.1);
105
+ }
106
+
107
+ &.is-main {
108
+ height: 116px;
109
+ width: 116px;
110
+ margin-top: -8px;
111
+ box-shadow: 0px 0px 6px 2px white;
112
+ z-index: 1;
113
+ }
114
+ }
115
+
116
+ .color-box:after {
117
+ content: attr(data-color-name);
118
+ display: block;
119
+ color: black;
120
+ font-size: 14px;
121
+ width: 100%;
122
+ height: 100%;
123
+ padding: 10px;
124
+ opacity: 0.3;
125
+ }
126
+
127
+ .color-box.is-dark:after {
128
+ color: white;
129
+ }
130
+
131
+ @keyframes float-up {
132
+ from {
133
+ transform: translateY(0);
134
+ opacity: 1;
135
+ }
136
+
137
+ to {
138
+ transform: translateY(-20px);
139
+ opacity: 0;
140
+ }
141
+ }
142
+
143
+ .toast {
144
+ background-color: black;
145
+ position: absolute;
146
+ color: white;
147
+ border-radius: 8px;
148
+ padding: 8px;
149
+ animation: float-up 800ms;
150
+ animation-timing-function: ease-out;
151
+ z-index: 2;
152
+ }
153
+ </style>
154
+ </head>
155
+ <body>
156
+ ${html_colors}
157
+ </body>
158
+ <script>
159
+ document.addEventListener("click", (event) => {
160
+ navigator.clipboard.writeText(
161
+ \`var(--color-\${event.target.getAttribute(
162
+ "data-color-group"
163
+ )}-\${event.target.getAttribute(
164
+ "data-color-name"
165
+ )})\`
166
+ );
167
+ const toast = document.createElement("div");
168
+ document.body.appendChild(toast);
169
+ toast.classList.add("toast");
170
+ toast.textContent = "Copied!";
171
+ toast.style.setProperty(
172
+ "top",
173
+ event.clientY +
174
+ document.scrollingElement.scrollTop -
175
+ 40 +
176
+ "px"
177
+ );
178
+ toast.style.setProperty(
179
+ "left",
180
+ event.clientX + "px"
181
+ );
182
+ toast.addEventListener("animationend", () => {
183
+ toast.remove();
184
+ });
185
+ });
186
+ </script>
187
+ </html>`,
188
+ "html"
189
+ );
190
+ await Promise.all([
191
+ fs.writeFile(resolve(this.project_dir, COLORS_CSS_PATH), css),
192
+ fs.writeFile(resolve(this.project_dir, COLORS_HTML_PATH), html)
193
+ ]);
194
+ }
195
+ }
196
+ export {
197
+ COLORS_CSS_PATH,
198
+ COLORS_HTML_PATH,
199
+ COLORS_TS_PATH,
200
+ ColorsBuilder
201
+ };
202
+ //# sourceMappingURL=colors-builder.js.map
@@ -2,7 +2,7 @@ import { build } from "esbuild";
2
2
  import { resolve, relative, basename, dirname } from "node:path";
3
3
  import glob from "tiny-glob";
4
4
  import { promises as fs } from "fs";
5
- import { walkDir } from "./utils/walk.js";
5
+ import { walkDir } from "../utils/walk.js";
6
6
  import { Builder } from "./builder.js";
7
7
  class CSSClump {
8
8
  constructor(project_dir, name) {
@@ -140,4 +140,4 @@ export {
140
140
  CSSBuilder,
141
141
  CSSClump
142
142
  };
143
- //# sourceMappingURL=CSSBuilder.js.map
143
+ //# sourceMappingURL=css-builder.js.map
@@ -0,0 +1,20 @@
1
+ import { Builder } from "./builder.js";
2
+ import { FONTS_CONFIG_PATH, getFonts } from "../get-fonts.js";
3
+ class FontsBuilder extends Builder {
4
+ getName() {
5
+ return "fonts";
6
+ }
7
+ ownsFile(file_path) {
8
+ return file_path == FONTS_CONFIG_PATH;
9
+ }
10
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
11
+ async dispose() {
12
+ }
13
+ async _build() {
14
+ return getFonts();
15
+ }
16
+ }
17
+ export {
18
+ FontsBuilder
19
+ };
20
+ //# sourceMappingURL=fonts-builder.js.map
@@ -0,0 +1,43 @@
1
+ import { generateStimulusControllers } from "../generate-stimulus.js";
2
+ import { promises as fs } from "fs";
3
+ import { build } from "esbuild";
4
+ import { Builder } from "./builder.js";
5
+ import { relative } from "node:path";
6
+ class FrontendTSBuilder extends Builder {
7
+ constructor(project_dir, style_dirs, controller_dirs) {
8
+ super(project_dir, style_dirs);
9
+ this.project_dir = project_dir;
10
+ this.style_dirs = style_dirs;
11
+ this.controller_dirs = controller_dirs;
12
+ }
13
+ getName() {
14
+ return "frontend-ts";
15
+ }
16
+ ownsFile(file_path) {
17
+ return file_path.startsWith("src/front") || file_path.endsWith(".stimulus.ts");
18
+ }
19
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
20
+ async dispose() {
21
+ }
22
+ async _build() {
23
+ await generateStimulusControllers(this.controller_dirs);
24
+ const { metafile } = await build({
25
+ entryPoints: ["./src/front/index.ts"],
26
+ sourcemap: true,
27
+ outfile: "./public/dist/bundle.js",
28
+ logLevel: "info",
29
+ bundle: true,
30
+ minify: true,
31
+ metafile: true,
32
+ treeShaking: true
33
+ });
34
+ await fs.writeFile(
35
+ relative(this.project_dir, "dist") + "/front.meta.json",
36
+ JSON.stringify(metafile)
37
+ );
38
+ }
39
+ }
40
+ export {
41
+ FrontendTSBuilder
42
+ };
43
+ //# sourceMappingURL=frontend-ts-builder.js.map
package/lib/get-fonts.js CHANGED
@@ -2,10 +2,11 @@ import { readFile, writeFile } from "fs/promises";
2
2
  import { constructURL, download } from "google-fonts-helper";
3
3
  import { resolve } from "node:path";
4
4
  import { target_locreq } from "./target-locreq.js";
5
+ import JSON5 from "json5";
5
6
  const FONTS_CONFIG_PATH = "src/fonts.json";
6
7
  async function getFonts(_ = {}) {
7
8
  const fonts_config_path = target_locreq.resolve(FONTS_CONFIG_PATH);
8
- const fonts_config = JSON.parse(await readFile(fonts_config_path, "utf8"));
9
+ const fonts_config = JSON5.parse(await readFile(fonts_config_path, "utf8"));
9
10
  const fonts_url = constructURL(fonts_config.googleFonts);
10
11
  if (!fonts_url) {
11
12
  throw new Error("Invalid font config");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.17.9",
3
+ "version": "0.17.10",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
@@ -63,18 +63,21 @@
63
63
  "@sealcode/file-manager": "^1.0.2",
64
64
  "@sealcode/ts-predicates": "^0.4.3",
65
65
  "chokidar": "^3.6.0",
66
+ "colord": "^2.9.3",
66
67
  "deepmerge": "^4.3.1",
67
68
  "esbuild": "^0.20.0",
68
69
  "escape-goat": "^4.0.0",
69
70
  "google-fonts-helper": "^3.4.1",
70
71
  "is-what": "^4.1.16",
71
72
  "js-convert-case": "^4.2.0",
73
+ "json5": "^2.2.3",
72
74
  "locreq": "^3.0.0",
73
75
  "md5": "^2.3.0",
74
76
  "merge": "^2.1.1",
75
77
  "prettier": "^2.7.1",
76
78
  "prompts": "^2.4.2",
77
79
  "recursive-readdir": "^2.2.3",
80
+ "tempfile": "^5.0.0",
78
81
  "tempstream": "^0.4.4",
79
82
  "tiny-glob": "^0.2.9",
80
83
  "ws": "^8.16.0",
package/src/build.ts CHANGED
@@ -6,13 +6,12 @@ import { resolve } from "node:path";
6
6
 
7
7
  import _locreq from "locreq";
8
8
 
9
- import {
10
- BackendTSBuilder,
11
- FontsBuilder,
12
- FrontendTSBuilder,
13
- } from "./builder.js";
14
- import { CSSBuilder } from "./CSSBuilder.js";
15
9
  import { hasShape, predicates } from "@sealcode/ts-predicates";
10
+ import { FontsBuilder } from "./builders/fonts-builder.js";
11
+ import { BackendTSBuilder } from "./builders/backend-ts-builder.js";
12
+ import { FrontendTSBuilder } from "./builders/frontend-ts-builder.js";
13
+ import { CSSBuilder } from "./builders/css-builder.js";
14
+ import { ColorsBuilder } from "./builders/colors-builder.js";
16
15
  const target_locreq = _locreq(process.cwd());
17
16
 
18
17
  async function get_notifier_port() {
@@ -85,9 +84,11 @@ async function build(watch: boolean): Promise<void> {
85
84
  controller_dirs
86
85
  );
87
86
  const css_builder = new CSSBuilder(project_dir, style_dirs);
87
+ const colors_builder = new ColorsBuilder(project_dir, style_dirs);
88
88
  const builders = [
89
89
  fonts_builder,
90
90
  backend_ts_builder,
91
+ colors_builder,
91
92
  css_builder,
92
93
  frontend_ts_builder,
93
94
  ];
@@ -0,0 +1,73 @@
1
+ import glob from "tiny-glob";
2
+ import { build } from "esbuild";
3
+ import { promises as fs } from "fs";
4
+ import { relative } from "node:path";
5
+
6
+ import { embeddable_file_extensions } from "../embeddable-file-extensions.js";
7
+ import { generateCollections } from "../generate-collections.js";
8
+ import { generateComponents } from "../generate-components.js";
9
+ import { generateRoutes } from "../generate-routes.js";
10
+ import { Builder } from "./builder.js";
11
+ import { rewrite_asset_imports_plugin } from "../esbuild-plugins/rewrite-asset-imports.js";
12
+ import { load_assets_plugin } from "../esbuild-plugins/load-assets.js";
13
+
14
+ export class BackendTSBuilder extends Builder {
15
+ getName(): string {
16
+ return "backend-ts";
17
+ }
18
+
19
+ ownsFile(file_path: string) {
20
+ return (
21
+ (file_path.endsWith(".ts") || file_path.endsWith(".tsx")) &&
22
+ !file_path.endsWith("src/back/collections/collections.ts") &&
23
+ !file_path.endsWith("src/back/routes/routes.ts") &&
24
+ !file_path.endsWith("src/back/routes/urls.ts") &&
25
+ !file_path.endsWith("stimulus.ts") &&
26
+ !file_path.startsWith("src/front")
27
+ );
28
+ }
29
+
30
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
31
+ async dispose(): Promise<void> {}
32
+
33
+ async _build(): Promise<void> {
34
+ await Promise.all([
35
+ generateCollections(),
36
+ generateRoutes(),
37
+ generateComponents(),
38
+ ]);
39
+ const entryPoints = (
40
+ await glob(
41
+ `./src/back/**/*.{ts,tsx,${embeddable_file_extensions.join(
42
+ ","
43
+ )}}`
44
+ )
45
+ ).filter((path) => !path.includes(".#"));
46
+
47
+ const { metafile } = await build({
48
+ entryPoints,
49
+ sourcemap: true,
50
+ bundle: false,
51
+ jsxFactory: "TempstreamJSX.createElement",
52
+ jsxFragment: "TempstreamJSX.Fragment",
53
+ outdir: "./dist/back",
54
+ logLevel: "info",
55
+ platform: "node",
56
+ target: "es2022",
57
+ format: "esm",
58
+ loader: Object.fromEntries(
59
+ embeddable_file_extensions.map((ext) => ["." + ext, "js"])
60
+ ),
61
+ plugins: [
62
+ rewrite_asset_imports_plugin(this.project_dir),
63
+ load_assets_plugin,
64
+ ],
65
+ metafile: true,
66
+ });
67
+
68
+ await fs.writeFile(
69
+ relative(this.project_dir, "dist") + "/" + "back" + ".meta.json",
70
+ JSON.stringify(metafile)
71
+ );
72
+ }
73
+ }
@@ -0,0 +1,25 @@
1
+ export abstract class Builder {
2
+ abstract ownsFile(file_path: string): boolean;
3
+ abstract getName(): string;
4
+ abstract _build(): Promise<void>;
5
+ abstract dispose(): Promise<void>;
6
+
7
+ constructor(public project_dir: string, public style_dirs: string[]) {}
8
+
9
+ public ongoing_build: Promise<void> | null = null;
10
+
11
+ public build(notifier?: (message: string) => void) {
12
+ if (!this.ongoing_build) {
13
+ const build = this._build()
14
+ .catch((err) => {
15
+ console.error(err);
16
+ })
17
+ .then(() => {
18
+ notifier?.(this.getName());
19
+ this.ongoing_build = null;
20
+ });
21
+ this.ongoing_build = build;
22
+ }
23
+ return this.ongoing_build;
24
+ }
25
+ }
@@ -0,0 +1,214 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+ import { build } from "esbuild";
3
+ import tempfile from "tempfile";
4
+ import { promises as fs } from "fs";
5
+ import { resolve } from "path";
6
+ import { colord } from "colord";
7
+
8
+ import { formatWithPrettier } from "../utils/prettier.js";
9
+
10
+ import { Builder } from "./builder.js";
11
+ import { exec } from "../utils/exec.js";
12
+
13
+ export const COLORS_TS_PATH = "src/back/colors.ts"; // keeping it in src/back instead of just src to make typescript hints work there
14
+ export const COLORS_CSS_PATH = "src/colors.css";
15
+ export const COLORS_HTML_PATH = "src/colors.html";
16
+
17
+ export class ColorsBuilder extends Builder {
18
+ getName(): string {
19
+ return "colors";
20
+ }
21
+
22
+ ownsFile(file_path: string) {
23
+ return file_path == COLORS_TS_PATH;
24
+ }
25
+
26
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
27
+ async dispose(): Promise<void> {}
28
+
29
+ async _build() {
30
+ const outfile = tempfile({ extension: "mjs" });
31
+ await build({
32
+ entryPoints: [COLORS_TS_PATH],
33
+ bundle: true,
34
+ format: "esm",
35
+ outfile,
36
+ });
37
+ await fs.appendFile(outfile, "console.log(JSON.stringify(colors))");
38
+ const output = await exec("node", [outfile]);
39
+ const colors = JSON.parse(output.stdout) as Record<
40
+ string,
41
+ Record<string, string>
42
+ >;
43
+
44
+ let css_colors = "";
45
+ let html_colors = "";
46
+ for (const group_name in colors) {
47
+ const sorted_colors = Object.entries(colors[group_name]).sort(
48
+ ([key1], [key2]) => (key1 > key2 ? 1 : -1)
49
+ );
50
+ html_colors += `<h2>${group_name}</h2><div class="group"><div class="base">`;
51
+ let last_color_name_base = "";
52
+ for (const [color_name, color] of sorted_colors) {
53
+ const color_name_elements = color_name.split("-");
54
+ const color_name_base = color_name_elements
55
+ .filter((s) => !/^(light|dark)[0-9]$/.test(s))
56
+ .join("-");
57
+ let index = 0;
58
+ const modifier = color_name_elements.at(-1);
59
+ let multiplier = 1;
60
+ let steps = 0;
61
+ if (modifier) {
62
+ if (modifier.startsWith("dark")) {
63
+ multiplier = -1;
64
+ }
65
+ }
66
+ if (
67
+ modifier &&
68
+ modifier.match(/[0-9]$/) &&
69
+ modifier.match(/^(dark|light)[0-9]/)
70
+ ) {
71
+ steps = parseInt(modifier.at(-1)!);
72
+ }
73
+ index = multiplier * steps;
74
+ const is_new_base = color_name_base != last_color_name_base;
75
+ css_colors += `--color-${group_name}-${color_name}: ${color};`;
76
+ if (is_new_base) {
77
+ html_colors += `</div><h3>${color_name_base}</h3><div class="base" base="${color_name_base}">`;
78
+ }
79
+ if (color != "#000000" && color != "#ffffff") {
80
+ html_colors += /* HTML */ `<div
81
+ class="color-box ${colord(color).isDark()
82
+ ? "is-dark"
83
+ : ""} ${index == 0 ? "is-main" : ""}"
84
+ style="background-color: ${color}; order: ${index}"
85
+ data-color-name="${color_name}"
86
+ data-color-group="${group_name}"
87
+ ></div>`;
88
+ }
89
+ last_color_name_base = color_name_base;
90
+ }
91
+ html_colors += "</div></div>"; // close group and base
92
+ }
93
+
94
+ const css = await formatWithPrettier(
95
+ `/* DO NOT EDIT! This file is automatically generated by sealgen */
96
+ :root {
97
+ ${css_colors}
98
+ }`,
99
+ "css"
100
+ );
101
+
102
+ const html = await formatWithPrettier(
103
+ /* HTML */ `<!DOCTYPE html>
104
+ <html>
105
+ <head>
106
+ <style>
107
+ * {
108
+ font-family: sans-serif;
109
+ }
110
+
111
+ .base {
112
+ display: flex;
113
+ }
114
+
115
+ .color-box {
116
+ width: 100px;
117
+ height: 100px;
118
+ transition: all 50ms;
119
+ transition-timing-function: ease-in-out;
120
+ cursor: pointer;
121
+
122
+ &:hover {
123
+ transform: scale(1.1);
124
+ }
125
+
126
+ &.is-main {
127
+ height: 116px;
128
+ width: 116px;
129
+ margin-top: -8px;
130
+ box-shadow: 0px 0px 6px 2px white;
131
+ z-index: 1;
132
+ }
133
+ }
134
+
135
+ .color-box:after {
136
+ content: attr(data-color-name);
137
+ display: block;
138
+ color: black;
139
+ font-size: 14px;
140
+ width: 100%;
141
+ height: 100%;
142
+ padding: 10px;
143
+ opacity: 0.3;
144
+ }
145
+
146
+ .color-box.is-dark:after {
147
+ color: white;
148
+ }
149
+
150
+ @keyframes float-up {
151
+ from {
152
+ transform: translateY(0);
153
+ opacity: 1;
154
+ }
155
+
156
+ to {
157
+ transform: translateY(-20px);
158
+ opacity: 0;
159
+ }
160
+ }
161
+
162
+ .toast {
163
+ background-color: black;
164
+ position: absolute;
165
+ color: white;
166
+ border-radius: 8px;
167
+ padding: 8px;
168
+ animation: float-up 800ms;
169
+ animation-timing-function: ease-out;
170
+ z-index: 2;
171
+ }
172
+ </style>
173
+ </head>
174
+ <body>
175
+ ${html_colors}
176
+ </body>
177
+ <script>
178
+ document.addEventListener("click", (event) => {
179
+ navigator.clipboard.writeText(
180
+ \`var(--color-\${event.target.getAttribute(
181
+ "data-color-group"
182
+ )}-\${event.target.getAttribute(
183
+ "data-color-name"
184
+ )})\`
185
+ );
186
+ const toast = document.createElement("div");
187
+ document.body.appendChild(toast);
188
+ toast.classList.add("toast");
189
+ toast.textContent = "Copied!";
190
+ toast.style.setProperty(
191
+ "top",
192
+ event.clientY +
193
+ document.scrollingElement.scrollTop -
194
+ 40 +
195
+ "px"
196
+ );
197
+ toast.style.setProperty(
198
+ "left",
199
+ event.clientX + "px"
200
+ );
201
+ toast.addEventListener("animationend", () => {
202
+ toast.remove();
203
+ });
204
+ });
205
+ </script>
206
+ </html>`,
207
+ "html"
208
+ );
209
+ await Promise.all([
210
+ fs.writeFile(resolve(this.project_dir, COLORS_CSS_PATH), css),
211
+ fs.writeFile(resolve(this.project_dir, COLORS_HTML_PATH), html),
212
+ ]);
213
+ }
214
+ }
@@ -2,7 +2,7 @@ import { build } from "esbuild";
2
2
  import { resolve, relative, basename, dirname } from "node:path";
3
3
  import glob from "tiny-glob";
4
4
  import { promises as fs } from "fs";
5
- import { walkDir } from "./utils/walk.js";
5
+ import { walkDir } from "../utils/walk.js";
6
6
  import { Builder } from "./builder.js";
7
7
 
8
8
  export class CSSClump {
@@ -0,0 +1,19 @@
1
+ import { Builder } from "./builder.js";
2
+ import { FONTS_CONFIG_PATH, getFonts } from "../get-fonts.js";
3
+
4
+ export class FontsBuilder extends Builder {
5
+ getName(): string {
6
+ return "fonts";
7
+ }
8
+
9
+ ownsFile(file_path: string) {
10
+ return file_path == FONTS_CONFIG_PATH;
11
+ }
12
+
13
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
14
+ async dispose(): Promise<void> {}
15
+
16
+ async _build() {
17
+ return getFonts();
18
+ }
19
+ }
@@ -0,0 +1,47 @@
1
+ import { generateStimulusControllers } from "../generate-stimulus.js";
2
+ import { promises as fs } from "fs";
3
+ import { build } from "esbuild";
4
+ import { Builder } from "./builder.js";
5
+ import { relative } from "node:path";
6
+
7
+ export class FrontendTSBuilder extends Builder {
8
+ constructor(
9
+ public project_dir: string,
10
+ public style_dirs: string[],
11
+ public controller_dirs: string[]
12
+ ) {
13
+ super(project_dir, style_dirs);
14
+ }
15
+
16
+ getName(): string {
17
+ return "frontend-ts";
18
+ }
19
+
20
+ ownsFile(file_path: string) {
21
+ return (
22
+ file_path.startsWith("src/front") ||
23
+ file_path.endsWith(".stimulus.ts")
24
+ );
25
+ }
26
+
27
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
28
+ async dispose(): Promise<void> {}
29
+
30
+ async _build(): Promise<void> {
31
+ await generateStimulusControllers(this.controller_dirs);
32
+ const { metafile } = await build({
33
+ entryPoints: ["./src/front/index.ts"],
34
+ sourcemap: true,
35
+ outfile: "./public/dist/bundle.js",
36
+ logLevel: "info",
37
+ bundle: true,
38
+ minify: true,
39
+ metafile: true,
40
+ treeShaking: true,
41
+ });
42
+ await fs.writeFile(
43
+ relative(this.project_dir, "dist") + "/" + "front" + ".meta.json",
44
+ JSON.stringify(metafile)
45
+ );
46
+ }
47
+ }
package/src/get-fonts.ts CHANGED
@@ -2,6 +2,7 @@ import { readFile, writeFile } from "fs/promises";
2
2
  import { constructURL, download } from "google-fonts-helper";
3
3
  import { resolve } from "node:path";
4
4
  import { target_locreq } from "./target-locreq.js";
5
+ import JSON5 from "json5";
5
6
 
6
7
  export const FONTS_CONFIG_PATH = "src/fonts.json";
7
8
 
@@ -9,7 +10,7 @@ export async function getFonts(_: Record<string, string | boolean> = {}) {
9
10
  const fonts_config_path = target_locreq.resolve(FONTS_CONFIG_PATH);
10
11
 
11
12
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
12
- const fonts_config = JSON.parse(await readFile(fonts_config_path, "utf8"));
13
+ const fonts_config = JSON5.parse(await readFile(fonts_config_path, "utf8"));
13
14
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
14
15
  const fonts_url = constructURL(fonts_config.googleFonts);
15
16
  if (!fonts_url) {
package/lib/builder.js DELETED
@@ -1,133 +0,0 @@
1
- import { build } from "esbuild";
2
- import glob from "tiny-glob";
3
- import { promises as fs } from "fs";
4
- import { embeddable_file_extensions } from "./embeddable-file-extensions.js";
5
- import { load_assets_plugin } from "./esbuild-plugins/load-assets.js";
6
- import { rewrite_asset_imports_plugin } from "./esbuild-plugins/rewrite-asset-imports.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
- import { relative } from "node:path";
13
- class Builder {
14
- constructor(project_dir, style_dirs) {
15
- this.project_dir = project_dir;
16
- this.style_dirs = style_dirs;
17
- this.ongoing_build = null;
18
- }
19
- build(notifier) {
20
- if (!this.ongoing_build) {
21
- const build2 = this._build().catch((err) => {
22
- console.error(err);
23
- }).then(() => {
24
- notifier?.(this.getName());
25
- this.ongoing_build = null;
26
- });
27
- this.ongoing_build = build2;
28
- }
29
- return this.ongoing_build;
30
- }
31
- }
32
- class FontsBuilder extends Builder {
33
- getName() {
34
- return "fonts";
35
- }
36
- ownsFile(file_path) {
37
- return file_path == FONTS_CONFIG_PATH;
38
- }
39
- // eslint-disable-next-line @typescript-eslint/no-empty-function
40
- async dispose() {
41
- }
42
- async _build() {
43
- return getFonts();
44
- }
45
- }
46
- class BackendTSBuilder extends Builder {
47
- getName() {
48
- return "backend-ts";
49
- }
50
- ownsFile(file_path) {
51
- return (file_path.endsWith(".ts") || file_path.endsWith(".tsx")) && !file_path.endsWith("src/back/collections/collections.ts") && !file_path.endsWith("src/back/routes/routes.ts") && !file_path.endsWith("src/back/routes/urls.ts") && !file_path.endsWith("stimulus.ts") && !file_path.startsWith("src/front");
52
- }
53
- // eslint-disable-next-line @typescript-eslint/no-empty-function
54
- async dispose() {
55
- }
56
- async _build() {
57
- await Promise.all([
58
- generateCollections(),
59
- generateRoutes(),
60
- generateComponents()
61
- ]);
62
- const entryPoints = (await glob(
63
- `./src/back/**/*.{ts,tsx,${embeddable_file_extensions.join(
64
- ","
65
- )}}`
66
- )).filter((path) => !path.includes(".#"));
67
- const { metafile } = await build({
68
- entryPoints,
69
- sourcemap: true,
70
- bundle: false,
71
- jsxFactory: "TempstreamJSX.createElement",
72
- jsxFragment: "TempstreamJSX.Fragment",
73
- outdir: "./dist/back",
74
- logLevel: "info",
75
- platform: "node",
76
- target: "es2022",
77
- format: "esm",
78
- loader: Object.fromEntries(
79
- embeddable_file_extensions.map((ext) => ["." + ext, "js"])
80
- ),
81
- plugins: [
82
- rewrite_asset_imports_plugin(this.project_dir),
83
- load_assets_plugin
84
- ],
85
- metafile: true
86
- });
87
- await fs.writeFile(
88
- relative(this.project_dir, "dist") + "/back.meta.json",
89
- JSON.stringify(metafile)
90
- );
91
- }
92
- }
93
- class FrontendTSBuilder extends Builder {
94
- constructor(project_dir, style_dirs, controller_dirs) {
95
- super(project_dir, style_dirs);
96
- this.project_dir = project_dir;
97
- this.style_dirs = style_dirs;
98
- this.controller_dirs = controller_dirs;
99
- }
100
- getName() {
101
- return "frontend-ts";
102
- }
103
- ownsFile(file_path) {
104
- return file_path.startsWith("src/front") || file_path.endsWith(".stimulus.ts");
105
- }
106
- // eslint-disable-next-line @typescript-eslint/no-empty-function
107
- async dispose() {
108
- }
109
- async _build() {
110
- await generateStimulusControllers(this.controller_dirs);
111
- const { metafile } = await build({
112
- entryPoints: ["./src/front/index.ts"],
113
- sourcemap: true,
114
- outfile: "./public/dist/bundle.js",
115
- logLevel: "info",
116
- bundle: true,
117
- minify: true,
118
- metafile: true,
119
- treeShaking: true
120
- });
121
- await fs.writeFile(
122
- relative(this.project_dir, "dist") + "/front.meta.json",
123
- JSON.stringify(metafile)
124
- );
125
- }
126
- }
127
- export {
128
- BackendTSBuilder,
129
- Builder,
130
- FontsBuilder,
131
- FrontendTSBuilder
132
- };
133
- //# sourceMappingURL=builder.js.map
package/src/builder.ts DELETED
@@ -1,158 +0,0 @@
1
- import { build } from "esbuild";
2
- import glob from "tiny-glob";
3
- import { promises as fs } from "fs";
4
- import { embeddable_file_extensions } from "./embeddable-file-extensions.js";
5
- import { load_assets_plugin } from "./esbuild-plugins/load-assets.js";
6
- import { rewrite_asset_imports_plugin } from "./esbuild-plugins/rewrite-asset-imports.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
- import { relative, resolve } from "node:path";
13
-
14
- export abstract class Builder {
15
- abstract ownsFile(file_path: string): boolean;
16
- abstract getName(): string;
17
- abstract _build(): Promise<void>;
18
- abstract dispose(): Promise<void>;
19
-
20
- constructor(public project_dir: string, public style_dirs: string[]) {}
21
-
22
- public ongoing_build: Promise<void> | null = null;
23
-
24
- public build(notifier?: (message: string) => void) {
25
- if (!this.ongoing_build) {
26
- const build = this._build()
27
- .catch((err) => {
28
- console.error(err);
29
- })
30
- .then(() => {
31
- notifier?.(this.getName());
32
- this.ongoing_build = null;
33
- });
34
- this.ongoing_build = build;
35
- }
36
- return this.ongoing_build;
37
- }
38
- }
39
-
40
- export class FontsBuilder extends Builder {
41
- getName(): string {
42
- return "fonts";
43
- }
44
-
45
- ownsFile(file_path: string) {
46
- return file_path == FONTS_CONFIG_PATH;
47
- }
48
-
49
- // eslint-disable-next-line @typescript-eslint/no-empty-function
50
- async dispose(): Promise<void> {}
51
-
52
- async _build() {
53
- return getFonts();
54
- }
55
- }
56
-
57
- export class BackendTSBuilder extends Builder {
58
- getName(): string {
59
- return "backend-ts";
60
- }
61
-
62
- ownsFile(file_path: string) {
63
- return (
64
- (file_path.endsWith(".ts") || file_path.endsWith(".tsx")) &&
65
- !file_path.endsWith("src/back/collections/collections.ts") &&
66
- !file_path.endsWith("src/back/routes/routes.ts") &&
67
- !file_path.endsWith("src/back/routes/urls.ts") &&
68
- !file_path.endsWith("stimulus.ts") &&
69
- !file_path.startsWith("src/front")
70
- );
71
- }
72
-
73
- // eslint-disable-next-line @typescript-eslint/no-empty-function
74
- async dispose(): Promise<void> {}
75
-
76
- async _build(): Promise<void> {
77
- await Promise.all([
78
- generateCollections(),
79
- generateRoutes(),
80
- generateComponents(),
81
- ]);
82
- const entryPoints = (
83
- await glob(
84
- `./src/back/**/*.{ts,tsx,${embeddable_file_extensions.join(
85
- ","
86
- )}}`
87
- )
88
- ).filter((path) => !path.includes(".#"));
89
-
90
- const { metafile } = await build({
91
- entryPoints,
92
- sourcemap: true,
93
- bundle: false,
94
- jsxFactory: "TempstreamJSX.createElement",
95
- jsxFragment: "TempstreamJSX.Fragment",
96
- outdir: "./dist/back",
97
- logLevel: "info",
98
- platform: "node",
99
- target: "es2022",
100
- format: "esm",
101
- loader: Object.fromEntries(
102
- embeddable_file_extensions.map((ext) => ["." + ext, "js"])
103
- ),
104
- plugins: [
105
- rewrite_asset_imports_plugin(this.project_dir),
106
- load_assets_plugin,
107
- ],
108
- metafile: true,
109
- });
110
-
111
- await fs.writeFile(
112
- relative(this.project_dir, "dist") + "/" + "back" + ".meta.json",
113
- JSON.stringify(metafile)
114
- );
115
- }
116
- }
117
-
118
- export class FrontendTSBuilder extends Builder {
119
- constructor(
120
- public project_dir: string,
121
- public style_dirs: string[],
122
- public controller_dirs: string[]
123
- ) {
124
- super(project_dir, style_dirs);
125
- }
126
-
127
- getName(): string {
128
- return "frontend-ts";
129
- }
130
-
131
- ownsFile(file_path: string) {
132
- return (
133
- file_path.startsWith("src/front") ||
134
- file_path.endsWith(".stimulus.ts")
135
- );
136
- }
137
-
138
- // eslint-disable-next-line @typescript-eslint/no-empty-function
139
- async dispose(): Promise<void> {}
140
-
141
- async _build(): Promise<void> {
142
- await generateStimulusControllers(this.controller_dirs);
143
- const { metafile } = await build({
144
- entryPoints: ["./src/front/index.ts"],
145
- sourcemap: true,
146
- outfile: "./public/dist/bundle.js",
147
- logLevel: "info",
148
- bundle: true,
149
- minify: true,
150
- metafile: true,
151
- treeShaking: true,
152
- });
153
- await fs.writeFile(
154
- relative(this.project_dir, "dist") + "/" + "front" + ".meta.json",
155
- JSON.stringify(metafile)
156
- );
157
- }
158
- }