elm-ssr 0.5.2 → 0.6.1
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 +78 -4
- package/lib/scaffold.mjs +39 -5
- package/package.json +1 -1
package/bin/elm-ssr.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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";
|
|
@@ -16,7 +17,26 @@ const findFlagValue = (flagName) => {
|
|
|
16
17
|
return index >= 0 ? args[index + 1] : undefined;
|
|
17
18
|
};
|
|
18
19
|
|
|
19
|
-
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));
|
|
20
40
|
const packageJsonPath = resolve(rootPath, "package.json");
|
|
21
41
|
|
|
22
42
|
let packageJson = { name: "unknown" };
|
|
@@ -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
|
|
@@ -88,6 +109,42 @@ switch (command) {
|
|
|
88
109
|
await build({ rootPath, config });
|
|
89
110
|
}
|
|
90
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
|
+
|
|
91
148
|
let wranglerCmd = "./node_modules/.bin/wrangler";
|
|
92
149
|
let wranglerArgs = ["dev"];
|
|
93
150
|
try {
|
|
@@ -116,7 +173,24 @@ switch (command) {
|
|
|
116
173
|
}
|
|
117
174
|
}
|
|
118
175
|
|
|
119
|
-
|
|
176
|
+
try {
|
|
177
|
+
await run(wranglerCmd, wranglerArgs, rootPath);
|
|
178
|
+
} finally {
|
|
179
|
+
cleanup();
|
|
180
|
+
}
|
|
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`);
|
|
120
194
|
break;
|
|
121
195
|
}
|
|
122
196
|
|
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
|
|
|
@@ -15,13 +15,13 @@ const toPascalCase = (value) =>
|
|
|
15
15
|
|
|
16
16
|
const ensureValidName = (name) => {
|
|
17
17
|
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
18
|
-
throw new Error("
|
|
18
|
+
throw new Error("App name must use lowercase letters, numbers, and dashes only.");
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
const ensureAppMissing = (config, name) => {
|
|
23
23
|
if (config.apps.some((app) => app.name === name)) {
|
|
24
|
-
throw new Error(`
|
|
24
|
+
throw new Error(`App "${name}" already exists in elm-ssr.config.json. If you want to recreate it, remove its entry from elm-ssr.config.json first.`);
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
|
|
@@ -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.1",
|
|
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>",
|