@sealcode/sealgen 0.16.3 → 0.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +1 -0
- package/@types/src/CSSBuilder.d.ts +1 -1
- package/@types/src/builder.d.ts +6 -1
- package/@types/src/esbuild-plugins/rewrite-asset-imports.d.ts +1 -1
- package/@types/src/generate-stimulus.d.ts +1 -1
- package/@types/src/index.d.ts +27 -0
- package/lib/CSSBuilder.js +6 -6
- package/lib/build.js +52 -4
- package/lib/builder.js +13 -3
- package/lib/cli.js +1 -3
- package/lib/esbuild-plugins/rewrite-asset-imports.js +19 -4
- package/lib/generate-routes.js +5 -2
- package/lib/generate-stimulus.js +6 -3
- package/package.json +1 -1
- package/src/CSSBuilder.ts +9 -6
- package/src/build.ts +55 -4
- package/src/builder.ts +20 -4
- package/src/cli.ts +0 -2
- package/src/esbuild-plugins/rewrite-asset-imports.ts +19 -5
- package/src/generate-routes.ts +7 -2
- package/src/generate-stimulus.ts +11 -3
- package/src/index.ts +32 -0
package/.eslintrc.cjs
CHANGED
|
@@ -20,6 +20,7 @@ module.exports = {
|
|
|
20
20
|
"@typescript-eslint/require-await": 0,
|
|
21
21
|
"no-await-in-loop": 2,
|
|
22
22
|
"@typescript-eslint/no-unused-vars": ["warn", { args: "none", "varsIgnorePattern": "^_", }],
|
|
23
|
+
"@typescript-eslint/no-this-alias": 0
|
|
23
24
|
},
|
|
24
25
|
settings: { jsdoc: { mode: "typescript" } },
|
|
25
26
|
overrides: [
|
|
@@ -9,7 +9,7 @@ export declare class CSSClump {
|
|
|
9
9
|
getEntrypointPath(): string;
|
|
10
10
|
makeEntrypoint(): Promise<void>;
|
|
11
11
|
static assignFileToClumps(file_path: string): string[];
|
|
12
|
-
static findCSSClumps(project_dir: string): Promise<CSSClump[]>;
|
|
12
|
+
static findCSSClumps(project_dir: string, style_dirs: string[]): Promise<CSSClump[]>;
|
|
13
13
|
}
|
|
14
14
|
export declare class CSSBuilder extends Builder {
|
|
15
15
|
getName(): string;
|
package/@types/src/builder.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export declare abstract class Builder {
|
|
2
2
|
project_dir: string;
|
|
3
|
+
style_dirs: string[];
|
|
3
4
|
abstract ownsFile(file_path: string): boolean;
|
|
4
5
|
abstract getName(): string;
|
|
5
6
|
abstract _build(): Promise<void>;
|
|
6
7
|
abstract dispose(): Promise<void>;
|
|
7
|
-
constructor(project_dir: string);
|
|
8
|
+
constructor(project_dir: string, style_dirs: string[]);
|
|
8
9
|
ongoing_build: Promise<void> | null;
|
|
9
10
|
build(notifier?: (message: string) => void): Promise<void>;
|
|
10
11
|
}
|
|
@@ -21,6 +22,10 @@ export declare class BackendTSBuilder extends Builder {
|
|
|
21
22
|
_build(): Promise<void>;
|
|
22
23
|
}
|
|
23
24
|
export declare class FrontendTSBuilder extends Builder {
|
|
25
|
+
project_dir: string;
|
|
26
|
+
style_dirs: string[];
|
|
27
|
+
controller_dirs: string[];
|
|
28
|
+
constructor(project_dir: string, style_dirs: string[], controller_dirs: string[]);
|
|
24
29
|
getName(): string;
|
|
25
30
|
ownsFile(file_path: string): boolean;
|
|
26
31
|
dispose(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function generateStimulusControllers(): Promise<void>;
|
|
1
|
+
export declare function generateStimulusControllers(controller_dirs: string[]): Promise<void>;
|
package/@types/src/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
1
2
|
export * as Controls from "./forms/controls/controls.js";
|
|
2
3
|
export * from "./forms/fields/field.js";
|
|
3
4
|
export * as Fields from "./forms/fields/fields.js";
|
|
@@ -16,3 +17,29 @@ export * from "./forms/multiform.js";
|
|
|
16
17
|
export * as Components from "./components/components.js";
|
|
17
18
|
export * from "./mount.js";
|
|
18
19
|
export * from "./page/default-list-filters.js";
|
|
20
|
+
import { Context } from "koa";
|
|
21
|
+
import { Readable } from "node:stream";
|
|
22
|
+
export interface HTMLOptions {
|
|
23
|
+
morphing?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface HTMLHeadOptions {
|
|
26
|
+
ctx: Context;
|
|
27
|
+
title: string | Promise<string | Readable>;
|
|
28
|
+
options: Partial<HTMLOptions>;
|
|
29
|
+
metaImage?: string;
|
|
30
|
+
canonicalPath?: string;
|
|
31
|
+
css_clumps: string[];
|
|
32
|
+
description: string;
|
|
33
|
+
}
|
|
34
|
+
export interface htmlArgs {
|
|
35
|
+
ctx: Context;
|
|
36
|
+
title: string | Promise<string>;
|
|
37
|
+
body: string | Promise<string>;
|
|
38
|
+
htmlOptions?: Partial<HTMLOptions>;
|
|
39
|
+
makeHead?: (args: HTMLHeadOptions) => string | Promise<string> | Readable | Promise<Readable>;
|
|
40
|
+
metaImage?: string;
|
|
41
|
+
canonicalPath?: string;
|
|
42
|
+
css_clumps?: string[];
|
|
43
|
+
description: string;
|
|
44
|
+
hideNavigation?: boolean;
|
|
45
|
+
}
|
package/lib/CSSBuilder.js
CHANGED
|
@@ -89,9 +89,10 @@ class CSSClump {
|
|
|
89
89
|
}
|
|
90
90
|
return segments;
|
|
91
91
|
}
|
|
92
|
-
static async findCSSClumps(project_dir) {
|
|
92
|
+
static async findCSSClumps(project_dir, style_dirs) {
|
|
93
93
|
const clumps = {};
|
|
94
|
-
const
|
|
94
|
+
const dirs_to_scan = [resolve(project_dir, "src"), ...style_dirs];
|
|
95
|
+
const all_files = (await Promise.all(dirs_to_scan.map(walkDir))).flat();
|
|
95
96
|
for (const file of all_files) {
|
|
96
97
|
if (!file.endsWith(".css"))
|
|
97
98
|
continue;
|
|
@@ -125,10 +126,9 @@ class CSSBuilder extends Builder {
|
|
|
125
126
|
(rel_path) => fs.unlink(resolve(this.project_dir, rel_path))
|
|
126
127
|
)
|
|
127
128
|
);
|
|
128
|
-
const clumps = await CSSClump.findCSSClumps(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
clumps.map((c) => c.name)
|
|
129
|
+
const clumps = await CSSClump.findCSSClumps(
|
|
130
|
+
this.project_dir,
|
|
131
|
+
this.style_dirs
|
|
132
132
|
);
|
|
133
133
|
await Promise.all(clumps.map((clump) => clump.build()));
|
|
134
134
|
} catch (e) {
|
package/lib/build.js
CHANGED
|
@@ -2,6 +2,7 @@ import { promises as fs } from "fs";
|
|
|
2
2
|
import * as chokidar from "chokidar";
|
|
3
3
|
import { make_notifier } from "./notifier.js";
|
|
4
4
|
import getPort from "get-port";
|
|
5
|
+
import { resolve } from "node:path";
|
|
5
6
|
import _locreq from "locreq";
|
|
6
7
|
import {
|
|
7
8
|
BackendTSBuilder,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
FrontendTSBuilder
|
|
10
11
|
} from "./builder.js";
|
|
11
12
|
import { CSSBuilder } from "./CSSBuilder.js";
|
|
13
|
+
import { hasShape, predicates } from "@sealcode/ts-predicates";
|
|
12
14
|
const target_locreq = _locreq(process.cwd());
|
|
13
15
|
async function get_notifier_port() {
|
|
14
16
|
return getPort({ port: 4e3 });
|
|
@@ -23,12 +25,58 @@ async function write_notifier_config(watch, port) {
|
|
|
23
25
|
JSON.stringify(config)
|
|
24
26
|
);
|
|
25
27
|
}
|
|
28
|
+
const package_json_shape = {
|
|
29
|
+
sealgen: predicates.maybe(
|
|
30
|
+
predicates.shape({
|
|
31
|
+
styleDirs: predicates.maybe(predicates.array(predicates.string)),
|
|
32
|
+
controllerDirs: predicates.maybe(
|
|
33
|
+
predicates.array(predicates.string)
|
|
34
|
+
),
|
|
35
|
+
copyToPublic: predicates.maybe(
|
|
36
|
+
predicates.array(
|
|
37
|
+
predicates.shape({
|
|
38
|
+
from: predicates.string,
|
|
39
|
+
to: predicates.string
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
)
|
|
45
|
+
};
|
|
26
46
|
async function build(watch) {
|
|
27
47
|
const project_dir = target_locreq.resolve("");
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
48
|
+
const package_json = JSON.parse(
|
|
49
|
+
await fs.readFile(resolve(project_dir, "package.json"), "utf-8")
|
|
50
|
+
);
|
|
51
|
+
if (!hasShape(package_json_shape, package_json)) {
|
|
52
|
+
throw new Error("Misshaped package.json props");
|
|
53
|
+
}
|
|
54
|
+
const style_dirs = package_json.sealgen?.styleDirs || [];
|
|
55
|
+
const controller_dirs = package_json.sealgen?.controllerDirs || [];
|
|
56
|
+
const copy_to_public = package_json.sealgen?.copyToPublic || [];
|
|
57
|
+
await Promise.all(
|
|
58
|
+
copy_to_public.map(async ({ from, to }) => {
|
|
59
|
+
try {
|
|
60
|
+
await fs.stat(resolve(project_dir, "public", to));
|
|
61
|
+
} catch (e) {
|
|
62
|
+
await fs.mkdir(resolve(project_dir, "public", to, "../"), {
|
|
63
|
+
recursive: true
|
|
64
|
+
});
|
|
65
|
+
await fs.symlink(
|
|
66
|
+
resolve(project_dir, from),
|
|
67
|
+
resolve(project_dir, "public", to)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
const fonts_builder = new FontsBuilder(project_dir, style_dirs);
|
|
73
|
+
const backend_ts_builder = new BackendTSBuilder(project_dir, style_dirs);
|
|
74
|
+
const frontend_ts_builder = new FrontendTSBuilder(
|
|
75
|
+
project_dir,
|
|
76
|
+
style_dirs,
|
|
77
|
+
controller_dirs
|
|
78
|
+
);
|
|
79
|
+
const css_builder = new CSSBuilder(project_dir, style_dirs);
|
|
32
80
|
const builders = [
|
|
33
81
|
fonts_builder,
|
|
34
82
|
backend_ts_builder,
|
package/lib/builder.js
CHANGED
|
@@ -11,8 +11,9 @@ import { generateStimulusControllers } from "./generate-stimulus.js";
|
|
|
11
11
|
import { FONTS_CONFIG_PATH, getFonts } from "./get-fonts.js";
|
|
12
12
|
import { relative } from "node:path";
|
|
13
13
|
class Builder {
|
|
14
|
-
constructor(project_dir) {
|
|
14
|
+
constructor(project_dir, style_dirs) {
|
|
15
15
|
this.project_dir = project_dir;
|
|
16
|
+
this.style_dirs = style_dirs;
|
|
16
17
|
this.ongoing_build = null;
|
|
17
18
|
}
|
|
18
19
|
build(notifier) {
|
|
@@ -77,7 +78,10 @@ class BackendTSBuilder extends Builder {
|
|
|
77
78
|
loader: Object.fromEntries(
|
|
78
79
|
embeddable_file_extensions.map((ext) => ["." + ext, "js"])
|
|
79
80
|
),
|
|
80
|
-
plugins: [
|
|
81
|
+
plugins: [
|
|
82
|
+
rewrite_asset_imports_plugin(this.project_dir),
|
|
83
|
+
load_assets_plugin
|
|
84
|
+
],
|
|
81
85
|
metafile: true
|
|
82
86
|
});
|
|
83
87
|
await fs.writeFile(
|
|
@@ -87,6 +91,12 @@ class BackendTSBuilder extends Builder {
|
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
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
|
+
}
|
|
90
100
|
getName() {
|
|
91
101
|
return "frontend-ts";
|
|
92
102
|
}
|
|
@@ -97,7 +107,7 @@ class FrontendTSBuilder extends Builder {
|
|
|
97
107
|
async dispose() {
|
|
98
108
|
}
|
|
99
109
|
async _build() {
|
|
100
|
-
await generateStimulusControllers();
|
|
110
|
+
await generateStimulusControllers(this.controller_dirs);
|
|
101
111
|
const { metafile } = await build({
|
|
102
112
|
entryPoints: ["./src/front/index.ts"],
|
|
103
113
|
sourcemap: true,
|
package/lib/cli.js
CHANGED
|
@@ -9,7 +9,6 @@ import { generateRoutes } from "./generate-routes.js";
|
|
|
9
9
|
import { makeEnv } from "./make-env.js";
|
|
10
10
|
import yargs from "yargs/yargs";
|
|
11
11
|
import { getFonts } from "./get-fonts.js";
|
|
12
|
-
import { generateStimulusControllers } from "./generate-stimulus.js";
|
|
13
12
|
import { addCRUD } from "./add-crud.js";
|
|
14
13
|
const actions = {
|
|
15
14
|
"add-collection": addCollection,
|
|
@@ -27,8 +26,7 @@ const actions = {
|
|
|
27
26
|
},
|
|
28
27
|
"make-env": makeEnv,
|
|
29
28
|
"add-component": addJDDComponent,
|
|
30
|
-
"get-fonts": getFonts
|
|
31
|
-
"generate-stimulus": generateStimulusControllers
|
|
29
|
+
"get-fonts": getFonts
|
|
32
30
|
};
|
|
33
31
|
void async function() {
|
|
34
32
|
const action = process.argv.at(2);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { promises as fs } from "fs";
|
|
2
|
-
import { extname } from "node:path";
|
|
2
|
+
import { extname, resolve } from "node:path";
|
|
3
3
|
import { embeddable_file_extensions } from "../embeddable-file-extensions.js";
|
|
4
4
|
import { removeNamedImports } from "../../remove-named-imports.js";
|
|
5
|
-
const rewrite_asset_imports_plugin = {
|
|
5
|
+
const rewrite_asset_imports_plugin = (project_dir) => ({
|
|
6
6
|
name: "sealgen-rewrite-asset-imports",
|
|
7
7
|
setup(build) {
|
|
8
8
|
build.onLoad({ filter: /\.tsx?$/ }, async (args) => {
|
|
@@ -13,9 +13,24 @@ const rewrite_asset_imports_plugin = {
|
|
|
13
13
|
"gm"
|
|
14
14
|
);
|
|
15
15
|
contents = contents.replaceAll(regex_assets, (line) => {
|
|
16
|
-
|
|
16
|
+
line = line.replace(`.${ext}"`, '.js"');
|
|
17
|
+
return line;
|
|
17
18
|
});
|
|
18
19
|
}
|
|
20
|
+
const regex_import = new RegExp(
|
|
21
|
+
`^import ([^ ]+, )?(\\w+|{[^}]+}) from "([^"]+)";`,
|
|
22
|
+
"gm"
|
|
23
|
+
);
|
|
24
|
+
contents = contents.replaceAll(
|
|
25
|
+
regex_import,
|
|
26
|
+
(line, _, original_path) => {
|
|
27
|
+
const replaced = line.replace(
|
|
28
|
+
`"src/`,
|
|
29
|
+
`"` + resolve(project_dir, "./dist") + "/"
|
|
30
|
+
);
|
|
31
|
+
return replaced;
|
|
32
|
+
}
|
|
33
|
+
);
|
|
19
34
|
contents = removeNamedImports(contents);
|
|
20
35
|
return {
|
|
21
36
|
contents,
|
|
@@ -23,7 +38,7 @@ const rewrite_asset_imports_plugin = {
|
|
|
23
38
|
};
|
|
24
39
|
});
|
|
25
40
|
}
|
|
26
|
-
};
|
|
41
|
+
});
|
|
27
42
|
export {
|
|
28
43
|
rewrite_asset_imports_plugin
|
|
29
44
|
};
|
package/lib/generate-routes.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from "fs";
|
|
2
2
|
import * as prettier from "prettier";
|
|
3
|
-
import { relative } from "path";
|
|
3
|
+
import { relative, resolve, basename } from "path";
|
|
4
4
|
import _locreq from "locreq";
|
|
5
5
|
import { walkDir } from "./utils/walk.js";
|
|
6
6
|
import { importPath } from "./utils/import-path.js";
|
|
@@ -45,7 +45,10 @@ async function generateRoutes() {
|
|
|
45
45
|
url: unescape_url_params(
|
|
46
46
|
"/" + relative(
|
|
47
47
|
target_locreq.resolve("src/back/routes"),
|
|
48
|
-
|
|
48
|
+
resolve(
|
|
49
|
+
fullpath,
|
|
50
|
+
basename(fullpath).startsWith("index.") ? "../" : ""
|
|
51
|
+
)
|
|
49
52
|
).replace(/\..+/, "") + "/"
|
|
50
53
|
// trailing slash is important here, as it enables to use the entire path while building relative URLs. For example, while visiting /users/123, the path ./add-photo leads to /users/add-photo. While visiting /users/123/ (note the trailing slash), the path ./add-photo leads to /users/123/add-photo
|
|
51
54
|
)
|
package/lib/generate-stimulus.js
CHANGED
|
@@ -6,10 +6,13 @@ import recursive from "recursive-readdir";
|
|
|
6
6
|
import path, { dirname } from "path";
|
|
7
7
|
import { toKebabCase, toPascalCase } from "js-convert-case";
|
|
8
8
|
const target_locreq = _locreq(process.cwd());
|
|
9
|
-
async function generateStimulusControllers() {
|
|
9
|
+
async function generateStimulusControllers(controller_dirs) {
|
|
10
10
|
const output_file = "src/front/controllers.ts";
|
|
11
|
-
const
|
|
12
|
-
|
|
11
|
+
const stimulus_search_dirs = [
|
|
12
|
+
target_locreq.resolve("src/"),
|
|
13
|
+
...controller_dirs
|
|
14
|
+
];
|
|
15
|
+
const controller_names = (await Promise.all(stimulus_search_dirs.map((dir) => recursive(dir)))).flat().filter((c) => c.endsWith(".stimulus.ts")).sort();
|
|
13
16
|
const controller_data = controller_names.map(
|
|
14
17
|
(p) => "./" + path.relative(target_locreq.resolve(dirname(output_file)), p).replace(/\.[tj]sx?$/, ".js")
|
|
15
18
|
).map((relative_path) => {
|
package/package.json
CHANGED
package/src/CSSBuilder.ts
CHANGED
|
@@ -100,9 +100,13 @@ export class CSSClump {
|
|
|
100
100
|
return segments;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
static async findCSSClumps(
|
|
103
|
+
static async findCSSClumps(
|
|
104
|
+
project_dir: string,
|
|
105
|
+
style_dirs: string[]
|
|
106
|
+
): Promise<CSSClump[]> {
|
|
104
107
|
const clumps: Record<string, CSSClump> = {};
|
|
105
|
-
const
|
|
108
|
+
const dirs_to_scan = [resolve(project_dir, "src"), ...style_dirs];
|
|
109
|
+
const all_files = (await Promise.all(dirs_to_scan.map(walkDir))).flat();
|
|
106
110
|
for (const file of all_files) {
|
|
107
111
|
if (!file.endsWith(".css")) continue;
|
|
108
112
|
const clump_names = CSSClump.assignFileToClumps(file);
|
|
@@ -141,10 +145,9 @@ export class CSSBuilder extends Builder {
|
|
|
141
145
|
fs.unlink(resolve(this.project_dir, rel_path))
|
|
142
146
|
)
|
|
143
147
|
);
|
|
144
|
-
const clumps = await CSSClump.findCSSClumps(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
clumps.map((c) => c.name)
|
|
148
|
+
const clumps = await CSSClump.findCSSClumps(
|
|
149
|
+
this.project_dir,
|
|
150
|
+
this.style_dirs
|
|
148
151
|
);
|
|
149
152
|
await Promise.all(clumps.map((clump) => clump.build()));
|
|
150
153
|
} catch (e) {
|
package/src/build.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { promises as fs } from "fs";
|
|
|
2
2
|
import * as chokidar from "chokidar";
|
|
3
3
|
import { make_notifier } from "./notifier.js";
|
|
4
4
|
import getPort from "get-port";
|
|
5
|
+
import { resolve } from "node:path";
|
|
5
6
|
|
|
6
7
|
import _locreq from "locreq";
|
|
7
8
|
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
FrontendTSBuilder,
|
|
12
13
|
} from "./builder.js";
|
|
13
14
|
import { CSSBuilder } from "./CSSBuilder.js";
|
|
15
|
+
import { hasShape, predicates } from "@sealcode/ts-predicates";
|
|
14
16
|
const target_locreq = _locreq(process.cwd());
|
|
15
17
|
|
|
16
18
|
async function get_notifier_port() {
|
|
@@ -28,12 +30,61 @@ async function write_notifier_config(watch: boolean, port?: number) {
|
|
|
28
30
|
);
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
const package_json_shape = {
|
|
34
|
+
sealgen: predicates.maybe(
|
|
35
|
+
predicates.shape({
|
|
36
|
+
styleDirs: predicates.maybe(predicates.array(predicates.string)),
|
|
37
|
+
controllerDirs: predicates.maybe(
|
|
38
|
+
predicates.array(predicates.string)
|
|
39
|
+
),
|
|
40
|
+
copyToPublic: predicates.maybe(
|
|
41
|
+
predicates.array(
|
|
42
|
+
predicates.shape({
|
|
43
|
+
from: predicates.string,
|
|
44
|
+
to: predicates.string,
|
|
45
|
+
})
|
|
46
|
+
)
|
|
47
|
+
),
|
|
48
|
+
})
|
|
49
|
+
),
|
|
50
|
+
};
|
|
51
|
+
|
|
31
52
|
async function build(watch: boolean): Promise<void> {
|
|
32
53
|
const project_dir = target_locreq.resolve("");
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
54
|
+
const package_json = JSON.parse(
|
|
55
|
+
await fs.readFile(resolve(project_dir, "package.json"), "utf-8")
|
|
56
|
+
) as Record<string, unknown>;
|
|
57
|
+
if (!hasShape(package_json_shape, package_json)) {
|
|
58
|
+
throw new Error("Misshaped package.json props");
|
|
59
|
+
}
|
|
60
|
+
const style_dirs = package_json.sealgen?.styleDirs || [];
|
|
61
|
+
const controller_dirs = package_json.sealgen?.controllerDirs || [];
|
|
62
|
+
const copy_to_public = package_json.sealgen?.copyToPublic || [];
|
|
63
|
+
|
|
64
|
+
await Promise.all(
|
|
65
|
+
copy_to_public.map(async ({ from, to }) => {
|
|
66
|
+
try {
|
|
67
|
+
await fs.stat(resolve(project_dir, "public", to));
|
|
68
|
+
} catch (e) {
|
|
69
|
+
await fs.mkdir(resolve(project_dir, "public", to, "../"), {
|
|
70
|
+
recursive: true,
|
|
71
|
+
});
|
|
72
|
+
await fs.symlink(
|
|
73
|
+
resolve(project_dir, from),
|
|
74
|
+
resolve(project_dir, "public", to)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const fonts_builder = new FontsBuilder(project_dir, style_dirs);
|
|
81
|
+
const backend_ts_builder = new BackendTSBuilder(project_dir, style_dirs);
|
|
82
|
+
const frontend_ts_builder = new FrontendTSBuilder(
|
|
83
|
+
project_dir,
|
|
84
|
+
style_dirs,
|
|
85
|
+
controller_dirs
|
|
86
|
+
);
|
|
87
|
+
const css_builder = new CSSBuilder(project_dir, style_dirs);
|
|
37
88
|
const builders = [
|
|
38
89
|
fonts_builder,
|
|
39
90
|
backend_ts_builder,
|
package/src/builder.ts
CHANGED
|
@@ -9,7 +9,11 @@ import { generateComponents } from "./generate-components.js";
|
|
|
9
9
|
import { generateRoutes } from "./generate-routes.js";
|
|
10
10
|
import { generateStimulusControllers } from "./generate-stimulus.js";
|
|
11
11
|
import { FONTS_CONFIG_PATH, getFonts } from "./get-fonts.js";
|
|
12
|
-
import { relative } from "node:path";
|
|
12
|
+
import { relative, resolve } from "node:path";
|
|
13
|
+
import {
|
|
14
|
+
prepareSingleFileReplaceTscAliasPaths,
|
|
15
|
+
SingleFileReplacer,
|
|
16
|
+
} from "tsc-alias";
|
|
13
17
|
|
|
14
18
|
export abstract class Builder {
|
|
15
19
|
abstract ownsFile(file_path: string): boolean;
|
|
@@ -17,7 +21,7 @@ export abstract class Builder {
|
|
|
17
21
|
abstract _build(): Promise<void>;
|
|
18
22
|
abstract dispose(): Promise<void>;
|
|
19
23
|
|
|
20
|
-
constructor(public project_dir: string) {}
|
|
24
|
+
constructor(public project_dir: string, public style_dirs: string[]) {}
|
|
21
25
|
|
|
22
26
|
public ongoing_build: Promise<void> | null = null;
|
|
23
27
|
|
|
@@ -86,6 +90,7 @@ export class BackendTSBuilder extends Builder {
|
|
|
86
90
|
)}}`
|
|
87
91
|
)
|
|
88
92
|
).filter((path) => !path.includes(".#"));
|
|
93
|
+
|
|
89
94
|
const { metafile } = await build({
|
|
90
95
|
entryPoints,
|
|
91
96
|
sourcemap: true,
|
|
@@ -100,7 +105,10 @@ export class BackendTSBuilder extends Builder {
|
|
|
100
105
|
loader: Object.fromEntries(
|
|
101
106
|
embeddable_file_extensions.map((ext) => ["." + ext, "js"])
|
|
102
107
|
),
|
|
103
|
-
plugins: [
|
|
108
|
+
plugins: [
|
|
109
|
+
rewrite_asset_imports_plugin(this.project_dir),
|
|
110
|
+
load_assets_plugin,
|
|
111
|
+
],
|
|
104
112
|
metafile: true,
|
|
105
113
|
});
|
|
106
114
|
|
|
@@ -112,6 +120,14 @@ export class BackendTSBuilder extends Builder {
|
|
|
112
120
|
}
|
|
113
121
|
|
|
114
122
|
export class FrontendTSBuilder extends Builder {
|
|
123
|
+
constructor(
|
|
124
|
+
public project_dir: string,
|
|
125
|
+
public style_dirs: string[],
|
|
126
|
+
public controller_dirs: string[]
|
|
127
|
+
) {
|
|
128
|
+
super(project_dir, style_dirs);
|
|
129
|
+
}
|
|
130
|
+
|
|
115
131
|
getName(): string {
|
|
116
132
|
return "frontend-ts";
|
|
117
133
|
}
|
|
@@ -127,7 +143,7 @@ export class FrontendTSBuilder extends Builder {
|
|
|
127
143
|
async dispose(): Promise<void> {}
|
|
128
144
|
|
|
129
145
|
async _build(): Promise<void> {
|
|
130
|
-
await generateStimulusControllers();
|
|
146
|
+
await generateStimulusControllers(this.controller_dirs);
|
|
131
147
|
const { metafile } = await build({
|
|
132
148
|
entryPoints: ["./src/front/index.ts"],
|
|
133
149
|
sourcemap: true,
|
package/src/cli.ts
CHANGED
|
@@ -9,7 +9,6 @@ import { generateRoutes } from "./generate-routes.js";
|
|
|
9
9
|
import { makeEnv } from "./make-env.js";
|
|
10
10
|
import yargs from "yargs/yargs";
|
|
11
11
|
import { getFonts } from "./get-fonts.js";
|
|
12
|
-
import { generateStimulusControllers } from "./generate-stimulus.js";
|
|
13
12
|
import { addCRUD } from "./add-crud.js";
|
|
14
13
|
|
|
15
14
|
const actions: Record<
|
|
@@ -34,7 +33,6 @@ const actions: Record<
|
|
|
34
33
|
"make-env": makeEnv,
|
|
35
34
|
"add-component": addJDDComponent,
|
|
36
35
|
"get-fonts": getFonts,
|
|
37
|
-
"generate-stimulus": generateStimulusControllers,
|
|
38
36
|
};
|
|
39
37
|
|
|
40
38
|
void (async function () {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { promises as fs } from "fs";
|
|
2
|
-
import { extname } from "node:path";
|
|
2
|
+
import { extname, resolve } from "node:path";
|
|
3
3
|
import { embeddable_file_extensions } from "../embeddable-file-extensions.js";
|
|
4
4
|
import { removeNamedImports } from "../../remove-named-imports.js";
|
|
5
5
|
import { PluginBuild } from "esbuild";
|
|
6
6
|
|
|
7
|
-
export const rewrite_asset_imports_plugin = {
|
|
7
|
+
export const rewrite_asset_imports_plugin = (project_dir: string) => ({
|
|
8
8
|
name: "sealgen-rewrite-asset-imports",
|
|
9
9
|
setup(build: PluginBuild) {
|
|
10
10
|
build.onLoad({ filter: /\.tsx?$/ }, async (args) => {
|
|
@@ -15,16 +15,30 @@ export const rewrite_asset_imports_plugin = {
|
|
|
15
15
|
"gm"
|
|
16
16
|
);
|
|
17
17
|
contents = contents.replaceAll(regex_assets, (line) => {
|
|
18
|
-
|
|
18
|
+
line = line.replace(`.${ext}"`, '.js"');
|
|
19
|
+
return line;
|
|
19
20
|
});
|
|
20
21
|
}
|
|
22
|
+
const regex_import = new RegExp(
|
|
23
|
+
`^import ([^ ]+, )?(\\w+|{[^}]+}) from "([^"]+)";`,
|
|
24
|
+
"gm"
|
|
25
|
+
);
|
|
26
|
+
contents = contents.replaceAll(
|
|
27
|
+
regex_import,
|
|
28
|
+
(line, _: string, original_path: string) => {
|
|
29
|
+
const replaced = line.replace(
|
|
30
|
+
`"src/`,
|
|
31
|
+
`"` + resolve(project_dir, "./dist") + "/"
|
|
32
|
+
);
|
|
33
|
+
return replaced;
|
|
34
|
+
}
|
|
35
|
+
);
|
|
21
36
|
|
|
22
37
|
contents = removeNamedImports(contents) as string;
|
|
23
|
-
|
|
24
38
|
return {
|
|
25
39
|
contents,
|
|
26
40
|
loader: extname(args.path) === ".tsx" ? "tsx" : "ts",
|
|
27
41
|
};
|
|
28
42
|
});
|
|
29
43
|
},
|
|
30
|
-
};
|
|
44
|
+
});
|
package/src/generate-routes.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from "fs";
|
|
2
2
|
import * as prettier from "prettier";
|
|
3
|
-
import { relative } from "path";
|
|
3
|
+
import { relative, resolve, basename } from "path";
|
|
4
4
|
import _locreq from "locreq";
|
|
5
5
|
import { walkDir } from "./utils/walk.js";
|
|
6
6
|
import { importPath } from "./utils/import-path.js";
|
|
@@ -65,7 +65,12 @@ export async function generateRoutes(): Promise<void> {
|
|
|
65
65
|
"/" +
|
|
66
66
|
relative(
|
|
67
67
|
target_locreq.resolve("src/back/routes"),
|
|
68
|
-
|
|
68
|
+
resolve(
|
|
69
|
+
fullpath,
|
|
70
|
+
basename(fullpath).startsWith("index.")
|
|
71
|
+
? "../"
|
|
72
|
+
: ""
|
|
73
|
+
)
|
|
69
74
|
).replace(/\..+/, "") +
|
|
70
75
|
"/" // trailing slash is important here, as it enables to use the entire path while building relative URLs. For example, while visiting /users/123, the path ./add-photo leads to /users/add-photo. While visiting /users/123/ (note the trailing slash), the path ./add-photo leads to /users/123/add-photo
|
|
71
76
|
),
|
package/src/generate-stimulus.ts
CHANGED
|
@@ -8,10 +8,18 @@ import { toKebabCase, toPascalCase } from "js-convert-case";
|
|
|
8
8
|
|
|
9
9
|
const target_locreq = _locreq(process.cwd());
|
|
10
10
|
|
|
11
|
-
export async function generateStimulusControllers(
|
|
11
|
+
export async function generateStimulusControllers(
|
|
12
|
+
controller_dirs: string[]
|
|
13
|
+
): Promise<void> {
|
|
12
14
|
const output_file = "src/front/controllers.ts";
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
+
const stimulus_search_dirs = [
|
|
16
|
+
target_locreq.resolve("src/"),
|
|
17
|
+
...controller_dirs,
|
|
18
|
+
];
|
|
19
|
+
const controller_names = (
|
|
20
|
+
await Promise.all(stimulus_search_dirs.map((dir) => recursive(dir)))
|
|
21
|
+
)
|
|
22
|
+
.flat()
|
|
15
23
|
.filter((c) => c.endsWith(".stimulus.ts"))
|
|
16
24
|
.sort();
|
|
17
25
|
const controller_data = controller_names
|
package/src/index.ts
CHANGED
|
@@ -16,3 +16,35 @@ export * from "./forms/multiform.js";
|
|
|
16
16
|
export * as Components from "./components/components.js";
|
|
17
17
|
export * from "./mount.js";
|
|
18
18
|
export * from "./page/default-list-filters.js";
|
|
19
|
+
|
|
20
|
+
import { Context } from "koa";
|
|
21
|
+
import { Readable } from "node:stream";
|
|
22
|
+
|
|
23
|
+
export interface HTMLOptions {
|
|
24
|
+
morphing?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface HTMLHeadOptions {
|
|
28
|
+
ctx: Context;
|
|
29
|
+
title: string | Promise<string | Readable>;
|
|
30
|
+
options: Partial<HTMLOptions>;
|
|
31
|
+
metaImage?: string;
|
|
32
|
+
canonicalPath?: string;
|
|
33
|
+
css_clumps: string[];
|
|
34
|
+
description: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface htmlArgs {
|
|
38
|
+
ctx: Context;
|
|
39
|
+
title: string | Promise<string>;
|
|
40
|
+
body: string | Promise<string>;
|
|
41
|
+
htmlOptions?: Partial<HTMLOptions>;
|
|
42
|
+
makeHead?: (
|
|
43
|
+
args: HTMLHeadOptions
|
|
44
|
+
) => string | Promise<string> | Readable | Promise<Readable>;
|
|
45
|
+
metaImage?: string;
|
|
46
|
+
canonicalPath?: string;
|
|
47
|
+
css_clumps?: string[];
|
|
48
|
+
description: string;
|
|
49
|
+
hideNavigation?: boolean;
|
|
50
|
+
}
|