@effindomv2/create-fui-as-app 0.1.32 → 0.1.33

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.
@@ -1,2 +1,2 @@
1
- export declare const FUI_AS_VERSION = "0.1.26";
1
+ export declare const FUI_AS_VERSION = "0.1.27";
2
2
  export declare const RUNTIME_VERSION = "0.1.10";
@@ -1,2 +1,2 @@
1
- export const FUI_AS_VERSION = "0.1.26";
1
+ export const FUI_AS_VERSION = "0.1.27";
2
2
  export const RUNTIME_VERSION = "0.1.10";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effindomv2/create-fui-as-app",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "Scaffold a minimal EffinDom v2 FUI-AS app",
@@ -8,6 +8,32 @@ This template is the smallest FUI-AS app shape:
8
8
  - `src/host/host-services.ts` and `src/host/host-events.ts` define app-owned JS bridge contracts.
9
9
  - `src/host/generated/*` is generated from those host definition files.
10
10
 
11
+ ## Staging project assets
12
+
13
+ `stage-assets.json` is a convention-over-configuration manifest for copying project-owned files into `public/runtime/` during `npm run build:assets`. You should never need to edit `scripts/prepare-runtime.ts` — just drop files into your project and declare them here.
14
+
15
+ ```json
16
+ {
17
+ "stage": {
18
+ "fonts/*.ttf": "fonts",
19
+ "images/**": "images"
20
+ }
21
+ }
22
+ ```
23
+
24
+ - **Keys** are **glob patterns** relative to the project root. Supported patterns:
25
+ - `"*.ext"` — all files with a given extension in the project root.
26
+ - `"dir/*.ext"` — all files matching the extension inside `dir/` (non-recursive).
27
+ - `"dir/**"` — copy the entire `dir/` tree recursively.
28
+ - **Values** are the **destination subdirectory** under `public/runtime/`. The value can be a nested path like `"fonts/custom"` and directories are created automatically.
29
+ - The config is optional — if `stage-assets.json` is missing, the build script skips staging with no error.
30
+
31
+ For example, to ship custom fonts alongside the bundled Noto set:
32
+
33
+ 1. Place your `.ttf` files in a `fonts/` directory at the project root.
34
+ 2. Add `"fonts/*.ttf": "fonts"` to `stage-assets.json`.
35
+ 3. Reference them in app code as `/runtime/fonts/YourFont.ttf`.
36
+
11
37
  ## Typical workflow
12
38
 
13
39
  1. Build UI from `src/App.ts` with controls/nodes from `./fui/Fui`.
@@ -1,12 +1,79 @@
1
- import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
1
+ import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { basename, join } from "node:path";
2
3
 
3
4
  const outputDir = "public";
4
5
 
6
+ interface StageConfig {
7
+ stage: Record<string, string>;
8
+ }
9
+
10
+ function resolveGlob(pattern: string): string[] {
11
+ const slash = pattern.lastIndexOf("/");
12
+ if (slash === -1) {
13
+ const suffix = pattern.startsWith("*.") ? pattern.slice(1) : "";
14
+ if (suffix.length === 0) return [];
15
+ return readdirSync(".").filter((f) => f.endsWith(suffix));
16
+ }
17
+
18
+ const dir = pattern.slice(0, slash);
19
+ const filePart = pattern.slice(slash + 1);
20
+
21
+ if (filePart === "**") {
22
+ return [dir];
23
+ }
24
+
25
+ if (filePart.startsWith("*.")) {
26
+ const suffix = filePart.slice(1);
27
+ try {
28
+ return readdirSync(dir)
29
+ .filter((f) => f.endsWith(suffix))
30
+ .map((f) => join(dir, f));
31
+ } catch {
32
+ return [];
33
+ }
34
+ }
35
+
36
+ if (existsSync(pattern)) {
37
+ return [pattern];
38
+ }
39
+
40
+ return [];
41
+ }
42
+
43
+ function stageProjectAssets(outputDir: string): void {
44
+ const configPath = "stage-assets.json";
45
+ if (!existsSync(configPath)) {
46
+ return;
47
+ }
48
+
49
+ const raw = readFileSync(configPath, "utf8");
50
+ const config: StageConfig = JSON.parse(raw);
51
+
52
+ for (const [pattern, destDir] of Object.entries(config.stage)) {
53
+ const matches = resolveGlob(pattern);
54
+ if (matches.length === 0) {
55
+ continue;
56
+ }
57
+
58
+ const dest = join(outputDir, "runtime", destDir);
59
+ mkdirSync(dest, { recursive: true });
60
+
61
+ for (const match of matches) {
62
+ if (statSync(match).isDirectory()) {
63
+ cpSync(match, dest, { recursive: true });
64
+ } else {
65
+ copyFileSync(match, join(dest, basename(match)));
66
+ }
67
+ }
68
+ }
69
+ }
70
+
5
71
  rmSync(outputDir, { recursive: true, force: true });
6
72
  mkdirSync(`${outputDir}/runtime`, { recursive: true });
7
73
 
8
74
  cpSync("node_modules/@effindomv2/runtime/dist", `${outputDir}/runtime/dist`, { recursive: true });
9
75
  cpSync("node_modules/@effindomv2/runtime/dist/fonts", `${outputDir}/runtime/fonts`, { recursive: true });
76
+ stageProjectAssets(outputDir);
10
77
  copyFileSync("node_modules/@effindomv2/runtime/dist/bridge.js", `${outputDir}/bridge.js`);
11
78
  if (existsSync("node_modules/@effindomv2/runtime/dist/bridge.js.map")) {
12
79
  copyFileSync("node_modules/@effindomv2/runtime/dist/bridge.js.map", `${outputDir}/bridge.js.map`);
@@ -0,0 +1,3 @@
1
+ {
2
+ "stage": {}
3
+ }
@@ -12,6 +12,32 @@ This template scaffolds a multi-route FUI-AS app with explicit page-level MVC.
12
12
  - `route-shell.html` is the per-route host shell copied for each route path.
13
13
  - `src/host/host-services.ts` and `src/host/host-events.ts` are app-owned bridge contracts; `src/host/generated/*` is generated output.
14
14
 
15
+ ## Staging project assets
16
+
17
+ `stage-assets.json` is a convention-over-configuration manifest for copying project-owned files into `public/runtime/` during `npm run build:assets`. You should never need to edit `scripts/prepare-runtime.ts` — just drop files into your project and declare them here.
18
+
19
+ ```json
20
+ {
21
+ "stage": {
22
+ "fonts/*.ttf": "fonts",
23
+ "images/**": "images"
24
+ }
25
+ }
26
+ ```
27
+
28
+ - **Keys** are **glob patterns** relative to the project root. Supported patterns:
29
+ - `"*.ext"` — all files with a given extension in the project root.
30
+ - `"dir/*.ext"` — all files matching the extension inside `dir/` (non-recursive).
31
+ - `"dir/**"` — copy the entire `dir/` tree recursively.
32
+ - **Values** are the **destination subdirectory** under `public/runtime/`. The value can be a nested path like `"fonts/custom"` and directories are created automatically.
33
+ - The config is optional — if `stage-assets.json` is missing, the build script skips staging with no error.
34
+
35
+ For example, to ship custom fonts alongside the bundled Noto set:
36
+
37
+ 1. Place your `.ttf` files in a `fonts/` directory at the project root.
38
+ 2. Add `"fonts/*.ttf": "fonts"` to `stage-assets.json`.
39
+ 3. Reference them in app code as `/runtime/fonts/YourFont.ttf`.
40
+
15
41
  ## Routing + deployment model
16
42
 
17
43
  Each route builds to its own wasm (`home.wasm`, `settings.wasm`) and is mounted by routed harness config. This is designed for true MFE slices: each route app can evolve and deploy independently while still sharing the same browser host/runtime contract.
@@ -1,4 +1,5 @@
1
- import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
1
+ import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { basename, join } from "node:path";
2
3
  import { renderRoutedPageHead, resolveRouteManifest, routeHead } from "@effindomv2/fui-as/browser/routed-app-conventions";
3
4
  import { routeManifest } from "../src/route-config";
4
5
 
@@ -9,6 +10,71 @@ const routeShellTemplate = readFileSync("route-shell.html", "utf8");
9
10
  const loadingOverlayStyles = readFileSync("node_modules/@effindomv2/fui-as/browser/loading-overlay-styles.html", "utf8");
10
11
  const loadingOverlayBody = readFileSync("node_modules/@effindomv2/fui-as/browser/loading-overlay-body.html", "utf8");
11
12
 
13
+ interface StageConfig {
14
+ stage: Record<string, string>;
15
+ }
16
+
17
+ function resolveGlob(pattern: string): string[] {
18
+ const slash = pattern.lastIndexOf("/");
19
+ if (slash === -1) {
20
+ const suffix = pattern.startsWith("*.") ? pattern.slice(1) : "";
21
+ if (suffix.length === 0) return [];
22
+ return readdirSync(".").filter((f) => f.endsWith(suffix));
23
+ }
24
+
25
+ const dir = pattern.slice(0, slash);
26
+ const filePart = pattern.slice(slash + 1);
27
+
28
+ if (filePart === "**") {
29
+ return [dir];
30
+ }
31
+
32
+ if (filePart.startsWith("*.")) {
33
+ const suffix = filePart.slice(1);
34
+ try {
35
+ return readdirSync(dir)
36
+ .filter((f) => f.endsWith(suffix))
37
+ .map((f) => join(dir, f));
38
+ } catch {
39
+ return [];
40
+ }
41
+ }
42
+
43
+ if (existsSync(pattern)) {
44
+ return [pattern];
45
+ }
46
+
47
+ return [];
48
+ }
49
+
50
+ function stageProjectAssets(outputDir: string): void {
51
+ const configPath = "stage-assets.json";
52
+ if (!existsSync(configPath)) {
53
+ return;
54
+ }
55
+
56
+ const raw = readFileSync(configPath, "utf8");
57
+ const config: StageConfig = JSON.parse(raw);
58
+
59
+ for (const [pattern, destDir] of Object.entries(config.stage)) {
60
+ const matches = resolveGlob(pattern);
61
+ if (matches.length === 0) {
62
+ continue;
63
+ }
64
+
65
+ const dest = join(outputDir, "runtime", destDir);
66
+ mkdirSync(dest, { recursive: true });
67
+
68
+ for (const match of matches) {
69
+ if (statSync(match).isDirectory()) {
70
+ cpSync(match, dest, { recursive: true });
71
+ } else {
72
+ copyFileSync(match, join(dest, basename(match)));
73
+ }
74
+ }
75
+ }
76
+ }
77
+
12
78
  function renderLoadingOverlay(template: string): string {
13
79
  return template
14
80
  .replace("{{LOADING_OVERLAY_STYLES}}", loadingOverlayStyles)
@@ -23,6 +89,7 @@ for (const route of resolvedManifest.routes) {
23
89
 
24
90
  cpSync("node_modules/@effindomv2/runtime/dist", `${outputDir}/runtime/dist`, { recursive: true });
25
91
  cpSync("node_modules/@effindomv2/runtime/dist/fonts", `${outputDir}/runtime/fonts`, { recursive: true });
92
+ stageProjectAssets(outputDir);
26
93
  copyFileSync("node_modules/@effindomv2/runtime/dist/bridge.js", `${outputDir}/bridge.js`);
27
94
  if (existsSync("node_modules/@effindomv2/runtime/dist/bridge.js.map")) {
28
95
  copyFileSync("node_modules/@effindomv2/runtime/dist/bridge.js.map", `${outputDir}/bridge.js.map`);
@@ -0,0 +1,3 @@
1
+ {
2
+ "stage": {}
3
+ }