elm-ssr 0.5.1 → 0.6.0
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/bin/elm-ssr.mjs +122 -14
- package/lib/scaffold.mjs +37 -3
- package/package.json +1 -1
package/bin/elm-ssr.mjs
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
-
import {
|
|
3
|
+
import { readFile, stat } from "node:fs/promises";
|
|
4
|
+
import { watch } from "node:fs";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
5
6
|
import { createAppScaffold } from "../lib/scaffold.mjs";
|
|
6
7
|
import { readWorkspaceConfig } from "../lib/workspace.mjs";
|
|
7
8
|
import { build } from "../lib/build.mjs";
|
|
8
9
|
import { migrate } from "../lib/migrate.mjs";
|
|
9
10
|
|
|
10
11
|
const defaultRootPath = process.cwd();
|
|
11
|
-
const packageJsonPath = resolve(defaultRootPath, "package.json");
|
|
12
|
-
|
|
13
|
-
let packageJson = { name: "unknown" };
|
|
14
|
-
try {
|
|
15
|
-
packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
16
|
-
} catch {
|
|
17
|
-
// Not in a package root, that's okay for some commands
|
|
18
|
-
}
|
|
19
12
|
const args = process.argv.slice(2);
|
|
20
13
|
const command = args[0] ?? "help";
|
|
21
14
|
|
|
@@ -24,7 +17,34 @@ const findFlagValue = (flagName) => {
|
|
|
24
17
|
return index >= 0 ? args[index + 1] : undefined;
|
|
25
18
|
};
|
|
26
19
|
|
|
27
|
-
const
|
|
20
|
+
const findWorkspaceRoot = async (startPath) => {
|
|
21
|
+
let current = startPath;
|
|
22
|
+
while (true) {
|
|
23
|
+
try {
|
|
24
|
+
const configPath = resolve(current, "elm-ssr.config.json");
|
|
25
|
+
await stat(configPath);
|
|
26
|
+
return current;
|
|
27
|
+
} catch {
|
|
28
|
+
const parent = dirname(current);
|
|
29
|
+
if (parent === current) {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
current = parent;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return startPath;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const userRoot = findFlagValue("--root");
|
|
39
|
+
const rootPath = resolve(userRoot ?? await findWorkspaceRoot(defaultRootPath));
|
|
40
|
+
const packageJsonPath = resolve(rootPath, "package.json");
|
|
41
|
+
|
|
42
|
+
let packageJson = { name: "unknown" };
|
|
43
|
+
try {
|
|
44
|
+
packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
45
|
+
} catch {
|
|
46
|
+
// Not in a package root, that's okay for some commands
|
|
47
|
+
}
|
|
28
48
|
|
|
29
49
|
const run = async (cmd, cmdArgs, cwd = rootPath) => {
|
|
30
50
|
const child = Bun.spawn([cmd, ...cmdArgs], {
|
|
@@ -48,6 +68,7 @@ const printHelp = () => {
|
|
|
48
68
|
build Generate wrapper modules and compile configured Elm SSR apps
|
|
49
69
|
compress Pre-compress island and app bundles using Gzip for faster edge delivery
|
|
50
70
|
dev Build and start wrangler dev using the current workspace config
|
|
71
|
+
init <name> Initialize a self-contained single-app project in the current directory
|
|
51
72
|
new <name> Create a new app at <workspace>/<name>/ (or <workspace>/<subdir>/<name>/
|
|
52
73
|
with --in <subdir>) and register it in elm-ssr.config.json
|
|
53
74
|
routes Print configured apps and their public modules
|
|
@@ -80,11 +101,98 @@ switch (command) {
|
|
|
80
101
|
await build({ rootPath, config });
|
|
81
102
|
break;
|
|
82
103
|
|
|
83
|
-
case "dev":
|
|
104
|
+
case "dev": {
|
|
84
105
|
requireConfig();
|
|
85
|
-
|
|
86
|
-
|
|
106
|
+
if (packageJson && packageJson.scripts && packageJson.scripts.build) {
|
|
107
|
+
await run("bun", ["run", "build"], rootPath);
|
|
108
|
+
} else {
|
|
109
|
+
await build({ rootPath, config });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let buildTimeout = null;
|
|
113
|
+
const triggerBuild = () => {
|
|
114
|
+
if (buildTimeout) clearTimeout(buildTimeout);
|
|
115
|
+
buildTimeout = setTimeout(async () => {
|
|
116
|
+
console.log("\n[elm-ssr] File change detected. Rebuilding...");
|
|
117
|
+
try {
|
|
118
|
+
await build({ rootPath, config });
|
|
119
|
+
console.log("[elm-ssr] Rebuild successful.");
|
|
120
|
+
} catch (err) {
|
|
121
|
+
console.error("[elm-ssr] Rebuild failed:", err);
|
|
122
|
+
}
|
|
123
|
+
}, 100);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const watchers = [];
|
|
127
|
+
for (const app of config.apps) {
|
|
128
|
+
const srcDir = resolve(rootPath, app.root, "src");
|
|
129
|
+
try {
|
|
130
|
+
const watcher = watch(srcDir, { recursive: true }, (eventType, filename) => {
|
|
131
|
+
if (filename && filename.endsWith(".elm")) {
|
|
132
|
+
triggerBuild();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
watchers.push(watcher);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.warn(`[elm-ssr] Could not watch directory ${srcDir}:`, err);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const cleanup = () => {
|
|
142
|
+
for (const w of watchers) w.close();
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
process.on("SIGINT", cleanup);
|
|
146
|
+
process.on("exit", cleanup);
|
|
147
|
+
|
|
148
|
+
let wranglerCmd = "./node_modules/.bin/wrangler";
|
|
149
|
+
let wranglerArgs = ["dev"];
|
|
150
|
+
try {
|
|
151
|
+
await readFile(resolve(rootPath, wranglerCmd));
|
|
152
|
+
} catch {
|
|
153
|
+
wranglerCmd = "bunx";
|
|
154
|
+
wranglerArgs = ["wrangler", "dev"];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let hasWranglerConfig = false;
|
|
158
|
+
try {
|
|
159
|
+
const tomlContent = await readFile(resolve(rootPath, "wrangler.toml"), "utf8");
|
|
160
|
+
if (tomlContent.trim().length > 0) hasWranglerConfig = true;
|
|
161
|
+
} catch {}
|
|
162
|
+
try {
|
|
163
|
+
const jsonContent = await readFile(resolve(rootPath, "wrangler.jsonc"), "utf8");
|
|
164
|
+
if (jsonContent.trim().length > 0) hasWranglerConfig = true;
|
|
165
|
+
} catch {}
|
|
166
|
+
|
|
167
|
+
if (!hasWranglerConfig) {
|
|
168
|
+
const app = config.apps[0];
|
|
169
|
+
if (app) {
|
|
170
|
+
wranglerArgs.push(`${app.root}/worker.ts`);
|
|
171
|
+
wranglerArgs.push("--compatibility-date", "2026-05-28");
|
|
172
|
+
wranglerArgs.push("--compatibility-flags", "nodejs_compat");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
await run(wranglerCmd, wranglerArgs, rootPath);
|
|
178
|
+
} finally {
|
|
179
|
+
cleanup();
|
|
180
|
+
}
|
|
87
181
|
break;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
case "init": {
|
|
185
|
+
const name = args[1];
|
|
186
|
+
|
|
187
|
+
if (!name) {
|
|
188
|
+
console.error("Usage: elm-ssr init <name>");
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const created = await createAppScaffold(rootPath, name, { root: "." });
|
|
193
|
+
console.log(`Initialized ${created.name} in current directory`);
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
88
196
|
|
|
89
197
|
case "new": {
|
|
90
198
|
const name = args[1];
|
package/lib/scaffold.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { readWorkspaceConfig, writeWorkspaceConfig } from "./workspace.mjs";
|
|
4
4
|
|
|
@@ -276,8 +276,8 @@ const runtimeTemplate = (appRoot) => {
|
|
|
276
276
|
// appRoot is a slash-separated path like "my-app" or "apps/my-app".
|
|
277
277
|
// The generated bundles live at <workspaceRoot>/generated/<appRoot>/. From
|
|
278
278
|
// <workspaceRoot>/<appRoot>/runtime.ts, we climb out by one ".." per segment.
|
|
279
|
-
const upToRoot = appRoot.split("/").map(() => "..").join("/");
|
|
280
|
-
const generatedPrefix = `${upToRoot}/generated/${appRoot}
|
|
279
|
+
const upToRoot = appRoot === "." ? "." : appRoot.split("/").map(() => "..").join("/");
|
|
280
|
+
const generatedPrefix = `${upToRoot}/generated/${appRoot === "." ? "" : appRoot}`.replace(/\/+$/, "");
|
|
281
281
|
return `import { createWorkerApp } from "elm-ssr";
|
|
282
282
|
import { renderApp, type CompiledElmModule } from "elm-ssr/render";
|
|
283
283
|
import type { RouteCatalog } from "elm-ssr/http";
|
|
@@ -467,6 +467,38 @@ const normalizeAppRoot = (rawRoot, name) => {
|
|
|
467
467
|
return candidate;
|
|
468
468
|
};
|
|
469
469
|
|
|
470
|
+
const ensurePackageJson = async (rootPath, appName) => {
|
|
471
|
+
const packageJsonPath = resolve(rootPath, "package.json");
|
|
472
|
+
let packageJson = {};
|
|
473
|
+
try {
|
|
474
|
+
packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
475
|
+
} catch {
|
|
476
|
+
packageJson = {
|
|
477
|
+
name: appName,
|
|
478
|
+
version: "0.1.0",
|
|
479
|
+
type: "module",
|
|
480
|
+
scripts: {},
|
|
481
|
+
devDependencies: {}
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
packageJson.scripts = {
|
|
486
|
+
build: "elm-ssr build",
|
|
487
|
+
dev: "elm-ssr dev",
|
|
488
|
+
routes: "elm-ssr routes",
|
|
489
|
+
migrate: "elm-ssr migrate",
|
|
490
|
+
...packageJson.scripts
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
packageJson.devDependencies = {
|
|
494
|
+
"elm-ssr": "latest",
|
|
495
|
+
wrangler: "^4.0.0",
|
|
496
|
+
...packageJson.devDependencies
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf8");
|
|
500
|
+
};
|
|
501
|
+
|
|
470
502
|
/**
|
|
471
503
|
* Scaffold a new elm-ssr app under `<rootPath>/<appRoot>/` and register it in
|
|
472
504
|
* elm-ssr.config.json. `appRoot` defaults to the app's `name`; pass an
|
|
@@ -502,6 +534,8 @@ export const createAppScaffold = async (rootPath, name, options = {}) => {
|
|
|
502
534
|
apps: [...config.apps, configEntry]
|
|
503
535
|
});
|
|
504
536
|
|
|
537
|
+
await ensurePackageJson(rootPath, name);
|
|
538
|
+
|
|
505
539
|
return configEntry;
|
|
506
540
|
};
|
|
507
541
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elm-ssr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Elm-first SSR library and framework for Cloudflare Workers (and Bun): file-based routes/islands, backend-neutral effect adapters (KV/D1/Redis/Postgres), background tasks (waitUntil/Queues), SQL-file migrations, CLI scaffold + build.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Michał Majchrzak <michmajchrzak@gmail.com>",
|