@adaas/are-html 0.0.22 → 0.0.24
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/dist/browser/index.d.mts +194 -10
- package/dist/browser/index.mjs +696 -245
- package/dist/browser/index.mjs.map +1 -1
- package/dist/node/{AreBinding.attribute-doUvtOjc.d.mts → AreBinding.attribute-BWzEIw6H.d.mts} +45 -0
- package/dist/node/{AreBinding.attribute-Bm5LlOyE.d.ts → AreBinding.attribute-GpT-5Qmf.d.ts} +45 -0
- package/dist/node/attributes/AreBinding.attribute.d.mts +1 -1
- package/dist/node/attributes/AreBinding.attribute.d.ts +1 -1
- package/dist/node/attributes/AreDirective.attribute.d.mts +1 -1
- package/dist/node/attributes/AreDirective.attribute.d.ts +1 -1
- package/dist/node/attributes/AreEvent.attribute.d.mts +1 -1
- package/dist/node/attributes/AreEvent.attribute.d.ts +1 -1
- package/dist/node/attributes/AreStatic.attribute.d.mts +1 -1
- package/dist/node/attributes/AreStatic.attribute.d.ts +1 -1
- package/dist/node/directives/AreDirectiveFor.directive.d.mts +18 -1
- package/dist/node/directives/AreDirectiveFor.directive.d.ts +18 -1
- package/dist/node/directives/AreDirectiveFor.directive.js +57 -9
- package/dist/node/directives/AreDirectiveFor.directive.js.map +1 -1
- package/dist/node/directives/AreDirectiveFor.directive.mjs +57 -9
- package/dist/node/directives/AreDirectiveFor.directive.mjs.map +1 -1
- package/dist/node/directives/AreDirectiveIf.directive.d.mts +18 -2
- package/dist/node/directives/AreDirectiveIf.directive.d.ts +18 -2
- package/dist/node/directives/AreDirectiveIf.directive.js +29 -6
- package/dist/node/directives/AreDirectiveIf.directive.js.map +1 -1
- package/dist/node/directives/AreDirectiveIf.directive.mjs +29 -6
- package/dist/node/directives/AreDirectiveIf.directive.mjs.map +1 -1
- package/dist/node/directives/AreDirectiveShow.directive.d.mts +1 -1
- package/dist/node/directives/AreDirectiveShow.directive.d.ts +1 -1
- package/dist/node/engine/AreHTML.compiler.d.mts +4 -2
- package/dist/node/engine/AreHTML.compiler.d.ts +4 -2
- package/dist/node/engine/AreHTML.compiler.js +11 -4
- package/dist/node/engine/AreHTML.compiler.js.map +1 -1
- package/dist/node/engine/AreHTML.compiler.mjs +11 -4
- package/dist/node/engine/AreHTML.compiler.mjs.map +1 -1
- package/dist/node/engine/AreHTML.constants.d.mts +33 -1
- package/dist/node/engine/AreHTML.constants.d.ts +33 -1
- package/dist/node/engine/AreHTML.constants.js +166 -0
- package/dist/node/engine/AreHTML.constants.js.map +1 -1
- package/dist/node/engine/AreHTML.constants.mjs +165 -1
- package/dist/node/engine/AreHTML.constants.mjs.map +1 -1
- package/dist/node/engine/AreHTML.context.d.mts +66 -0
- package/dist/node/engine/AreHTML.context.d.ts +66 -0
- package/dist/node/engine/AreHTML.context.js +98 -0
- package/dist/node/engine/AreHTML.context.js.map +1 -1
- package/dist/node/engine/AreHTML.context.mjs +98 -0
- package/dist/node/engine/AreHTML.context.mjs.map +1 -1
- package/dist/node/engine/AreHTML.interpreter.d.mts +3 -0
- package/dist/node/engine/AreHTML.interpreter.d.ts +3 -0
- package/dist/node/engine/AreHTML.interpreter.js +66 -10
- package/dist/node/engine/AreHTML.interpreter.js.map +1 -1
- package/dist/node/engine/AreHTML.interpreter.mjs +66 -10
- package/dist/node/engine/AreHTML.interpreter.mjs.map +1 -1
- package/dist/node/engine/AreHTML.lifecycle.d.mts +1 -8
- package/dist/node/engine/AreHTML.lifecycle.d.ts +1 -8
- package/dist/node/engine/AreHTML.lifecycle.js +29 -44
- package/dist/node/engine/AreHTML.lifecycle.js.map +1 -1
- package/dist/node/engine/AreHTML.lifecycle.mjs +29 -44
- package/dist/node/engine/AreHTML.lifecycle.mjs.map +1 -1
- package/dist/node/engine/AreHTML.tokenizer.d.mts +1 -1
- package/dist/node/engine/AreHTML.tokenizer.d.ts +1 -1
- package/dist/node/engine/AreHTML.tokenizer.js +7 -1
- package/dist/node/engine/AreHTML.tokenizer.js.map +1 -1
- package/dist/node/engine/AreHTML.tokenizer.mjs +7 -1
- package/dist/node/engine/AreHTML.tokenizer.mjs.map +1 -1
- package/dist/node/engine/AreHTML.transformer.d.mts +1 -1
- package/dist/node/engine/AreHTML.transformer.d.ts +1 -1
- package/dist/node/index.d.mts +4 -3
- package/dist/node/index.d.ts +4 -3
- package/dist/node/index.js +7 -0
- package/dist/node/index.mjs +1 -0
- package/dist/node/instructions/AddStaticHTML.instruction.d.mts +8 -0
- package/dist/node/instructions/AddStaticHTML.instruction.d.ts +8 -0
- package/dist/node/instructions/AddStaticHTML.instruction.js +31 -0
- package/dist/node/instructions/AddStaticHTML.instruction.js.map +1 -0
- package/dist/node/instructions/AddStaticHTML.instruction.mjs +24 -0
- package/dist/node/instructions/AddStaticHTML.instruction.mjs.map +1 -0
- package/dist/node/instructions/AreHTML.instructions.constants.d.mts +1 -0
- package/dist/node/instructions/AreHTML.instructions.constants.d.ts +1 -0
- package/dist/node/instructions/AreHTML.instructions.constants.js +1 -0
- package/dist/node/instructions/AreHTML.instructions.constants.js.map +1 -1
- package/dist/node/instructions/AreHTML.instructions.constants.mjs +1 -0
- package/dist/node/instructions/AreHTML.instructions.constants.mjs.map +1 -1
- package/dist/node/instructions/AreHTML.instructions.types.d.mts +9 -1
- package/dist/node/instructions/AreHTML.instructions.types.d.ts +9 -1
- package/dist/node/lib/AreDirective/AreDirective.component.d.mts +1 -1
- package/dist/node/lib/AreDirective/AreDirective.component.d.ts +1 -1
- package/dist/node/lib/AreDirective/AreDirective.types.d.mts +1 -1
- package/dist/node/lib/AreDirective/AreDirective.types.d.ts +1 -1
- package/dist/node/lib/AreHTML/AreHTML.tokenizer.d.mts +1 -1
- package/dist/node/lib/AreHTML/AreHTML.tokenizer.d.ts +1 -1
- package/dist/node/lib/AreHTMLAttribute/AreHTML.attribute.d.mts +1 -1
- package/dist/node/lib/AreHTMLAttribute/AreHTML.attribute.d.ts +1 -1
- package/dist/node/lib/AreHTMLNode/AreHTMLNode.d.mts +1 -1
- package/dist/node/lib/AreHTMLNode/AreHTMLNode.d.ts +1 -1
- package/dist/node/lib/AreHTMLNode/AreHTMLNode.js +51 -0
- package/dist/node/lib/AreHTMLNode/AreHTMLNode.js.map +1 -1
- package/dist/node/lib/AreHTMLNode/AreHTMLNode.mjs +51 -0
- package/dist/node/lib/AreHTMLNode/AreHTMLNode.mjs.map +1 -1
- package/dist/node/lib/AreRoot/AreRoot.component.js.map +1 -1
- package/dist/node/lib/AreRoot/AreRoot.component.mjs.map +1 -1
- package/dist/node/nodes/AreComment.d.mts +1 -1
- package/dist/node/nodes/AreComment.d.ts +1 -1
- package/dist/node/nodes/AreComponent.d.mts +1 -1
- package/dist/node/nodes/AreComponent.d.ts +1 -1
- package/dist/node/nodes/AreInterpolation.d.mts +1 -1
- package/dist/node/nodes/AreInterpolation.d.ts +1 -1
- package/dist/node/nodes/AreRoot.d.mts +1 -1
- package/dist/node/nodes/AreRoot.d.ts +1 -1
- package/dist/node/nodes/AreText.d.mts +1 -1
- package/dist/node/nodes/AreText.d.ts +1 -1
- package/examples/dashboard/concept.ts +1 -1
- package/examples/dashboard/dist/index.html +1 -1
- package/examples/dashboard/dist/{mqh9ryml-xat335.js → mqiw5sqa-ypckmj.js} +403 -57
- package/examples/for-perf/dist/index.html +1 -1
- package/examples/for-perf/dist/{mqh9ryfo-6a8d0o.js → mqp8i2py-vltsx0.js} +3030 -2474
- package/examples/lazy-loading/README.md +76 -0
- package/examples/lazy-loading/concept.ts +55 -0
- package/examples/lazy-loading/containers/UI.container.ts +215 -0
- package/examples/lazy-loading/dist/app.js +3803 -0
- package/examples/{for-perf/dist/mqh9ryfq-4pf5cv.js → lazy-loading/dist/chunks/chunk-6K72IBO4.js} +2708 -5476
- package/examples/lazy-loading/dist/index.html +36 -0
- package/examples/lazy-loading/dist/lazy/about-page.js +59 -0
- package/examples/lazy-loading/dist/lazy/reports-page.js +65 -0
- package/examples/lazy-loading/dist/lazy/settings-page.js +54 -0
- package/examples/lazy-loading/public/index.html +36 -0
- package/examples/lazy-loading/src/components/AppShell.component.ts +44 -0
- package/examples/lazy-loading/src/components/HomePage.component.ts +59 -0
- package/examples/lazy-loading/src/components/LazyOutlet.component.ts +108 -0
- package/examples/lazy-loading/src/components/NavBar.component.ts +98 -0
- package/examples/lazy-loading/src/concept.ts +116 -0
- package/examples/lazy-loading/src/lazy/AboutPage.component.ts +54 -0
- package/examples/lazy-loading/src/lazy/ReportsPage.component.ts +56 -0
- package/examples/lazy-loading/src/lazy/SettingsPage.component.ts +45 -0
- package/examples/lazy-loading/src/runtime/ComponentManifest.fragment.ts +61 -0
- package/examples/lazy-loading/src/runtime/LazyComponentResolver.fragment.ts +77 -0
- package/examples/os-desktop/README.md +91 -0
- package/examples/os-desktop/concept.ts +54 -0
- package/examples/os-desktop/containers/OS.container.ts +198 -0
- package/examples/os-desktop/containers/apps/AppBackend.ts +29 -0
- package/examples/os-desktop/containers/apps/GanttApp.backend.ts +56 -0
- package/examples/os-desktop/containers/apps/MarketingApp.backend.ts +68 -0
- package/examples/os-desktop/dist/app.js +4410 -0
- package/examples/os-desktop/dist/apps/gantt/app.js +271 -0
- package/examples/os-desktop/dist/apps/marketing/app.js +346 -0
- package/examples/{for-perf/dist/mqh9ryde-m243t8.js → os-desktop/dist/chunks/chunk-6K72IBO4.js} +2708 -5476
- package/examples/os-desktop/dist/chunks/chunk-EIIGUL6N.js +30 -0
- package/examples/os-desktop/dist/chunks/chunk-WOH7L5UR.js +30 -0
- package/examples/os-desktop/dist/index.html +33 -0
- package/examples/os-desktop/public/index.html +33 -0
- package/examples/os-desktop/src/apps/gantt/GanttApp.component.ts +41 -0
- package/examples/os-desktop/src/apps/gantt/GanttChart.component.ts +126 -0
- package/examples/os-desktop/src/apps/gantt/GanttStore.ts +47 -0
- package/examples/os-desktop/src/apps/gantt/GanttToolbar.component.ts +73 -0
- package/examples/os-desktop/src/apps/gantt/index.ts +13 -0
- package/examples/os-desktop/src/apps/marketing/MarketingApp.component.ts +53 -0
- package/examples/os-desktop/src/apps/marketing/MarketingStore.ts +34 -0
- package/examples/os-desktop/src/apps/marketing/PostEditor.component.ts +153 -0
- package/examples/os-desktop/src/apps/marketing/PostPreview.component.ts +110 -0
- package/examples/os-desktop/src/apps/marketing/index.ts +16 -0
- package/examples/os-desktop/src/concept.ts +126 -0
- package/examples/os-desktop/src/os/AppStage.component.ts +112 -0
- package/examples/os-desktop/src/os/AppWindow.component.ts +102 -0
- package/examples/os-desktop/src/os/Desktop.component.ts +106 -0
- package/examples/os-desktop/src/os/Dock.component.ts +174 -0
- package/examples/os-desktop/src/os/Hud.component.ts +83 -0
- package/examples/os-desktop/src/os/Launchpad.component.ts +191 -0
- package/examples/os-desktop/src/os/MenuBar.component.ts +156 -0
- package/examples/os-desktop/src/runtime/AppComponentResolver.fragment.ts +121 -0
- package/examples/os-desktop/src/runtime/AppRegistry.fragment.ts +104 -0
- package/examples/os-desktop/src/signals/MouseState.signal.ts +34 -0
- package/examples/os-desktop/src/signals/OSRoute.signal.ts +37 -0
- package/examples/os-desktop/src/signals/SelectionState.signal.ts +34 -0
- package/examples/signal-routing/dist/index.html +1 -1
- package/examples/signal-routing/dist/{mqh9ryc9-dkcbkx.js → mqp8hgce-4d6rh0.js} +3196 -2640
- package/package.json +13 -9
- package/src/directives/AreDirectiveFor.directive.ts +99 -16
- package/src/directives/AreDirectiveIf.directive.ts +33 -4
- package/src/engine/AreHTML.compiler.ts +25 -2
- package/src/engine/AreHTML.constants.ts +142 -0
- package/src/engine/AreHTML.context.ts +112 -0
- package/src/engine/AreHTML.interpreter.ts +114 -13
- package/src/engine/AreHTML.lifecycle.ts +81 -74
- package/src/engine/AreHTML.tokenizer.ts +30 -1
- package/src/index.ts +1 -0
- package/src/instructions/AddStaticHTML.instruction.ts +23 -0
- package/src/instructions/AreHTML.instructions.constants.ts +1 -0
- package/src/instructions/AreHTML.instructions.types.ts +9 -0
- package/src/lib/AreHTMLNode/AreHTMLNode.ts +74 -0
- package/src/lib/AreRoot/AreRoot.component.ts +3 -3
- package/tests/PropPropagation.test.ts +181 -0
- package/tests/StaticIsland.test.ts +115 -0
- package/tests/jest.setup.ts +11 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { A_Concept, A_Inject } from "@adaas/a-concept";
|
|
2
|
+
import { A_Config } from "@adaas/a-utils/a-config";
|
|
3
|
+
import { A_Logger } from "@adaas/a-utils/a-logger";
|
|
4
|
+
import { A_Service } from "@adaas/a-utils/a-service";
|
|
5
|
+
import { build } from "esbuild";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import http from "http";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { AppBackend } from "./apps/AppBackend";
|
|
10
|
+
import { MarketingAppBackend } from "./apps/MarketingApp.backend";
|
|
11
|
+
import { GanttAppBackend } from "./apps/GanttApp.backend";
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* OS.container — the "kernel" of the OS-desktop example.
|
|
16
|
+
*
|
|
17
|
+
* It is a backend service that:
|
|
18
|
+
* 1. builds the OS shell bundle AND every installed app's bundle in ONE
|
|
19
|
+
* code-split esbuild pass (so the shell and all apps share the SAME
|
|
20
|
+
* framework runtime singletons — required for `import()`ed app classes to
|
|
21
|
+
* `extend` the same `Are`/DI context the shell runs),
|
|
22
|
+
* 2. serves the static SPA (with deep-link fallback to index.html),
|
|
23
|
+
* 3. advertises the installable apps at `GET /api/apps`, and
|
|
24
|
+
* 4. delegates `/apps/<id>/api/*` to that app's OWN backend.
|
|
25
|
+
*
|
|
26
|
+
* Apps are registered ONLY by adding their backend to {@link backends} — its
|
|
27
|
+
* descriptor drives the build entry, the `/api/apps` catalogue, and the API
|
|
28
|
+
* routing all at once.
|
|
29
|
+
*/
|
|
30
|
+
export class OSContainer extends A_Service {
|
|
31
|
+
|
|
32
|
+
protected server!: http.Server;
|
|
33
|
+
|
|
34
|
+
/** Every installable app = one backend (descriptor + bundle entry + API). */
|
|
35
|
+
protected backends: AppBackend[] = [
|
|
36
|
+
new MarketingAppBackend(),
|
|
37
|
+
new GanttAppBackend(),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
@A_Concept.Build()
|
|
41
|
+
async build(
|
|
42
|
+
@A_Inject(A_Logger) logger: A_Logger,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
logger.log('Building OS Desktop example...');
|
|
45
|
+
|
|
46
|
+
const distDir = path.resolve(__dirname, "../dist");
|
|
47
|
+
const shellEntry = path.resolve(__dirname, "../src/concept.ts");
|
|
48
|
+
|
|
49
|
+
if (fs.existsSync(distDir)) {
|
|
50
|
+
fs.rmSync(distDir, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// OS shell + every app bundle, code-split in a single pass. esbuild
|
|
54
|
+
// hoists shared framework code (@adaas/are, @adaas/a-concept, …) into
|
|
55
|
+
// shared chunks that the shell and each app import by URL, so a
|
|
56
|
+
// dynamically-imported app reuses the already-evaluated runtime.
|
|
57
|
+
const entryPoints: Record<string, string> = {
|
|
58
|
+
app: shellEntry,
|
|
59
|
+
};
|
|
60
|
+
for (const backend of this.backends) {
|
|
61
|
+
// bundle URL '/apps/marketing/app.js' → entry key 'apps/marketing/app'
|
|
62
|
+
const key = backend.descriptor.bundle.replace(/^\//, '').replace(/\.js$/, '');
|
|
63
|
+
entryPoints[key] = path.resolve(__dirname, "..", backend.entry);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
await build({
|
|
67
|
+
entryPoints,
|
|
68
|
+
outdir: distDir,
|
|
69
|
+
bundle: true,
|
|
70
|
+
splitting: true,
|
|
71
|
+
format: "esm",
|
|
72
|
+
target: "es2020",
|
|
73
|
+
keepNames: true,
|
|
74
|
+
minify: false,
|
|
75
|
+
sourcemap: false,
|
|
76
|
+
entryNames: '[dir]/[name]',
|
|
77
|
+
chunkNames: 'chunks/[name]-[hash]',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
logger.log('green', `OS shell + ${this.backends.length} app bundles built (code-split).`);
|
|
81
|
+
|
|
82
|
+
const indexHtml = await fs.promises.readFile(
|
|
83
|
+
path.resolve(__dirname, "../public/index.html"), 'utf-8'
|
|
84
|
+
);
|
|
85
|
+
await fs.promises.writeFile(path.join(distDir, "index.html"), indexHtml);
|
|
86
|
+
|
|
87
|
+
const publicDir = path.resolve(__dirname, "../public");
|
|
88
|
+
const entries = await fs.promises.readdir(publicDir, { withFileTypes: true });
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
if (entry.isFile() && entry.name !== 'index.html') {
|
|
91
|
+
await fs.promises.copyFile(
|
|
92
|
+
path.join(publicDir, entry.name),
|
|
93
|
+
path.join(distDir, entry.name),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
logger.log('green', 'Static assets copied.');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@A_Concept.Load()
|
|
102
|
+
async preLoadBuild(
|
|
103
|
+
@A_Inject(A_Logger) logger: A_Logger,
|
|
104
|
+
) {
|
|
105
|
+
await this.build(logger);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@A_Concept.Start()
|
|
109
|
+
async startServer(
|
|
110
|
+
@A_Inject(A_Logger) logger: A_Logger,
|
|
111
|
+
@A_Inject(A_Config) config: A_Config,
|
|
112
|
+
) {
|
|
113
|
+
this.server = http.createServer(this.handleRequest.bind(this));
|
|
114
|
+
const PORT = config.get('PORT') || 8084;
|
|
115
|
+
this.server.listen(PORT, () => {
|
|
116
|
+
logger.log('green', `OS Desktop example running at http://localhost:${PORT}`);
|
|
117
|
+
logger.log('blue', `App catalogue at http://localhost:${PORT}/api/apps`);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Routes:
|
|
123
|
+
* - GET /api/apps → the installable-app catalogue (descriptors)
|
|
124
|
+
* - /apps/<id>/api/* → delegated to that app's own backend
|
|
125
|
+
* - everything else → static files (SPA fallback to index.html)
|
|
126
|
+
*/
|
|
127
|
+
protected handleRequest(
|
|
128
|
+
req: http.IncomingMessage,
|
|
129
|
+
res: http.ServerResponse,
|
|
130
|
+
) {
|
|
131
|
+
const parsed = new URL(req.url || '/', 'http://localhost');
|
|
132
|
+
const pathname = parsed.pathname;
|
|
133
|
+
|
|
134
|
+
if (pathname === '/api/apps') {
|
|
135
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
136
|
+
res.end(JSON.stringify(this.backends.map(b => b.descriptor)));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Delegate app API calls to the owning app's backend.
|
|
141
|
+
const backend = this.backends.find(b => pathname.startsWith(`${b.descriptor.api}/`));
|
|
142
|
+
if (backend) {
|
|
143
|
+
const handled = backend.handle(pathname, parsed.searchParams, req, res);
|
|
144
|
+
if (!handled) {
|
|
145
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
146
|
+
res.end(JSON.stringify({ error: `No handler for ${pathname}` }));
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.serveStaticFile(pathname, res);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
protected serveStaticFile(
|
|
155
|
+
url: string,
|
|
156
|
+
res: http.ServerResponse,
|
|
157
|
+
) {
|
|
158
|
+
const distDir = path.resolve(__dirname, "../dist");
|
|
159
|
+
let filePath = path.join(distDir, url === '/' ? 'index.html' : url);
|
|
160
|
+
|
|
161
|
+
const logger = this.scope.resolve<A_Logger>(A_Logger)!;
|
|
162
|
+
|
|
163
|
+
const mimeTypes: Record<string, string> = {
|
|
164
|
+
'.html': 'text/html',
|
|
165
|
+
'.js': 'text/javascript',
|
|
166
|
+
'.css': 'text/css',
|
|
167
|
+
'.json': 'application/json',
|
|
168
|
+
'.png': 'image/png',
|
|
169
|
+
'.svg': 'image/svg+xml',
|
|
170
|
+
};
|
|
171
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
172
|
+
|
|
173
|
+
if (!fs.existsSync(filePath)) {
|
|
174
|
+
// SPA fallback for deep links (e.g. /app/marketing). Asset misses
|
|
175
|
+
// (.js) 404 honestly so loading bugs stay visible.
|
|
176
|
+
if (ext && ext !== '.html') {
|
|
177
|
+
logger.log('red', `404: ${filePath}`);
|
|
178
|
+
res.writeHead(404);
|
|
179
|
+
res.end(`Not Found: ${url}`);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
filePath = path.join(distDir, 'index.html');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const servedExt = path.extname(filePath).toLowerCase();
|
|
186
|
+
const contentType = mimeTypes[servedExt] || 'application/octet-stream';
|
|
187
|
+
|
|
188
|
+
fs.readFile(filePath, (err, content) => {
|
|
189
|
+
if (err) {
|
|
190
|
+
res.writeHead(500);
|
|
191
|
+
res.end(`Server Error: ${err.code}`);
|
|
192
|
+
} else {
|
|
193
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
194
|
+
res.end(content);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import http from "http";
|
|
2
|
+
import type { AppDescriptor } from "../../src/runtime/AppRegistry.fragment";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* AppBackend — the server-side half of an installable application.
|
|
7
|
+
*
|
|
8
|
+
* Each app owns BOTH a frontend bundle and a backend. The OS kernel
|
|
9
|
+
* (OS.container.ts) never looks inside an app: it only asks each backend for
|
|
10
|
+
* its {@link descriptor} (advertised at `GET /api/apps`), builds the bundle
|
|
11
|
+
* named by {@link entry}, and forwards any request under the app's API base to
|
|
12
|
+
* {@link handle}. This keeps every app fully self-contained.
|
|
13
|
+
*/
|
|
14
|
+
export interface AppBackend {
|
|
15
|
+
/** Public metadata + bundle URL + component manifest for this app. */
|
|
16
|
+
readonly descriptor: AppDescriptor;
|
|
17
|
+
/** esbuild source entry for this app's single code-split bundle. */
|
|
18
|
+
readonly entry: string;
|
|
19
|
+
/**
|
|
20
|
+
* Handle a request addressed to this app's API base (`descriptor.api/*`).
|
|
21
|
+
* Return `true` if the request was served, `false` to let the kernel 404.
|
|
22
|
+
*/
|
|
23
|
+
handle(
|
|
24
|
+
pathname: string,
|
|
25
|
+
query: URLSearchParams,
|
|
26
|
+
req: http.IncomingMessage,
|
|
27
|
+
res: http.ServerResponse,
|
|
28
|
+
): boolean;
|
|
29
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import http from "http";
|
|
2
|
+
import type { AppDescriptor } from "../../src/runtime/AppRegistry.fragment";
|
|
3
|
+
import { AppBackend } from "./AppBackend";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/** Seed project plan served by the Gantt app's backend. */
|
|
7
|
+
const SEED_TASKS = [
|
|
8
|
+
{ id: 's1', name: 'Discovery', start: 0, end: 5, color: '#5b8def', track: 0 },
|
|
9
|
+
{ id: 's2', name: 'Design', start: 4, end: 11, color: '#34c759', track: 1 },
|
|
10
|
+
{ id: 's3', name: 'Build', start: 9, end: 22, color: '#ff9f0a', track: 2 },
|
|
11
|
+
{ id: 's4', name: 'QA', start: 18, end: 26, color: '#bf5af2', track: 3 },
|
|
12
|
+
{ id: 's5', name: 'Launch', start: 25, end: 30, color: '#ff375f', track: 4 },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* GanttAppBackend — the Gantt app's OWN server.
|
|
18
|
+
*
|
|
19
|
+
* Frontend: gantt-app / gantt-toolbar / gantt-chart (the gantt bundle).
|
|
20
|
+
* Backend: a single `/apps/gantt/api/tasks` endpoint that serves the project
|
|
21
|
+
* plan the chart renders.
|
|
22
|
+
*/
|
|
23
|
+
export class GanttAppBackend implements AppBackend {
|
|
24
|
+
|
|
25
|
+
readonly entry = 'src/apps/gantt/index.ts';
|
|
26
|
+
|
|
27
|
+
readonly descriptor: AppDescriptor = {
|
|
28
|
+
id: 'gantt',
|
|
29
|
+
name: 'Timeline',
|
|
30
|
+
icon: '📊',
|
|
31
|
+
accent: '#34c759',
|
|
32
|
+
tagline: 'A project Gantt chart whose tasks are served by its own backend.',
|
|
33
|
+
rootTag: 'gantt-app',
|
|
34
|
+
bundle: '/apps/gantt/app.js',
|
|
35
|
+
api: '/apps/gantt/api',
|
|
36
|
+
components: [
|
|
37
|
+
{ tag: 'gantt-app', export: 'GanttApp' },
|
|
38
|
+
{ tag: 'gantt-toolbar', export: 'GanttToolbar' },
|
|
39
|
+
{ tag: 'gantt-chart', export: 'GanttChart' },
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
handle(
|
|
44
|
+
pathname: string,
|
|
45
|
+
_query: URLSearchParams,
|
|
46
|
+
_req: http.IncomingMessage,
|
|
47
|
+
res: http.ServerResponse,
|
|
48
|
+
): boolean {
|
|
49
|
+
if (pathname === '/apps/gantt/api/tasks') {
|
|
50
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
51
|
+
res.end(JSON.stringify({ tasks: SEED_TASKS }));
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import http from "http";
|
|
2
|
+
import type { AppDescriptor } from "../../src/runtime/AppRegistry.fragment";
|
|
3
|
+
import { AppBackend } from "./AppBackend";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/** Tiny themed hashtag generator — stands in for a real marketing service. */
|
|
7
|
+
const HASHTAG_BANK: Record<string, string[]> = {
|
|
8
|
+
launch: ['ProductLaunch', 'ShipIt', 'NowLive', 'BuildInPublic'],
|
|
9
|
+
runtime: ['RuntimeLoading', 'WebPerf', 'LazyLoad', 'Microfrontends'],
|
|
10
|
+
team: ['TeamWork', 'Hiring', 'Culture', 'RemoteWork'],
|
|
11
|
+
growth: ['GrowthMindset', 'Startup', 'SaaS', 'GoToMarket'],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const DEFAULT_TAGS = ['Engineering', 'Innovation', 'TechLeadership', 'OpenSource'];
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* MarketingAppBackend — the Marketing app's OWN server.
|
|
19
|
+
*
|
|
20
|
+
* Frontend: marketing-app / post-editor / post-preview (the marketing bundle).
|
|
21
|
+
* Backend: a single `/apps/marketing/api/hashtags` endpoint that returns topic
|
|
22
|
+
* hashtag suggestions, proving the app ships with its own server logic.
|
|
23
|
+
*/
|
|
24
|
+
export class MarketingAppBackend implements AppBackend {
|
|
25
|
+
|
|
26
|
+
readonly entry = 'src/apps/marketing/index.ts';
|
|
27
|
+
|
|
28
|
+
readonly descriptor: AppDescriptor = {
|
|
29
|
+
id: 'marketing',
|
|
30
|
+
name: 'Marketing',
|
|
31
|
+
icon: '📣',
|
|
32
|
+
accent: '#2f6df6',
|
|
33
|
+
tagline: 'Draft a LinkedIn post and pull hashtag ideas from the app backend.',
|
|
34
|
+
rootTag: 'marketing-app',
|
|
35
|
+
bundle: '/apps/marketing/app.js',
|
|
36
|
+
api: '/apps/marketing/api',
|
|
37
|
+
components: [
|
|
38
|
+
{ tag: 'marketing-app', export: 'MarketingApp' },
|
|
39
|
+
{ tag: 'post-editor', export: 'PostEditor' },
|
|
40
|
+
{ tag: 'post-preview', export: 'PostPreview' },
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
handle(
|
|
45
|
+
pathname: string,
|
|
46
|
+
query: URLSearchParams,
|
|
47
|
+
_req: http.IncomingMessage,
|
|
48
|
+
res: http.ServerResponse,
|
|
49
|
+
): boolean {
|
|
50
|
+
if (pathname === '/apps/marketing/api/hashtags') {
|
|
51
|
+
const topic = (query.get('topic') || '').toLowerCase();
|
|
52
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
53
|
+
res.end(JSON.stringify({ hashtags: this.suggest(topic) }));
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected suggest(topic: string): string[] {
|
|
60
|
+
const matched = Object.keys(HASHTAG_BANK)
|
|
61
|
+
.filter(key => topic.includes(key))
|
|
62
|
+
.flatMap(key => HASHTAG_BANK[key]);
|
|
63
|
+
|
|
64
|
+
const tags = matched.length ? matched : DEFAULT_TAGS;
|
|
65
|
+
// De-dupe + cap.
|
|
66
|
+
return Array.from(new Set(tags)).slice(0, 6);
|
|
67
|
+
}
|
|
68
|
+
}
|