elm-ssr 0.5.0 → 0.5.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/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # elm-ssr
2
2
 
3
- Elm-first SSR library and framework for Cloudflare Workers (and Bun locally).
3
+ Elm-first SSR library and framework for Fetch-compatible runtimes.
4
4
  One package, three pieces:
5
5
 
6
6
  - A **CLI** (`elm-ssr build|new|migrate|dev`) that scans your routes/islands,
7
7
  generates the router + manifest, and runs `elm make`.
8
- - A **Worker runtime** (`createWorkerApp`, `renderApp`, effect adapters,
8
+ - A **Fetch-compatible runtime** (`createWorkerApp`, `renderApp`, effect adapters,
9
9
  background tasks, SQL migrations, middleware) exported via subpaths.
10
10
  - A set of **Elm authoring modules** under `elm-src/ElmSsr/` (Route, Loader,
11
11
  Action, Html, Svg, Island, Page, Document, Runtime) which the build syncs
@@ -17,8 +17,12 @@ One package, three pieces:
17
17
  bun add elm-ssr
18
18
  ```
19
19
 
20
- Then use the CLI as `elm-ssr <command>` (or `bun elm-ssr <command>` in a
21
- workspace) and import the runtime via subpaths.
20
+ Then import the runtime via subpaths and run the CLI with `bunx`:
21
+
22
+ ```sh
23
+ bunx elm-ssr new my-app
24
+ bunx elm-ssr build
25
+ ```
22
26
 
23
27
  ## CLI commands
24
28
 
@@ -29,7 +33,9 @@ workspace) and import the runtime via subpaths.
29
33
  (one combined island bundle exposing `Elm.<App>.Islands.<Name>`).
30
34
  - **`elm-ssr new <name>`** — scaffold a new app and register it in
31
35
  `elm-ssr.config.json`.
32
- - **`elm-ssr dev`** — `build` then `wrangler dev`.
36
+ - **`elm-ssr dev`** — `build` then `wrangler dev` for Cloudflare-like local
37
+ development. For other hosts, build with `elm-ssr build` and adapt
38
+ `worker.fetch` in your own server/entrypoint.
33
39
  - **`elm-ssr compress`** — pre-compress generated bundles with gzip.
34
40
  - **`elm-ssr migrate <up|down|status>`** — apply / revert / inspect SQL-file
35
41
  migrations. `--db postgres://…`, `sqlite://path`, or a plain SQLite file path;
package/bin/elm-ssr.mjs CHANGED
@@ -8,14 +8,6 @@ import { build } from "../lib/build.mjs";
8
8
  import { migrate } from "../lib/migrate.mjs";
9
9
 
10
10
  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
11
  const args = process.argv.slice(2);
20
12
  const command = args[0] ?? "help";
21
13
 
@@ -25,6 +17,14 @@ const findFlagValue = (flagName) => {
25
17
  };
26
18
 
27
19
  const rootPath = resolve(findFlagValue("--root") ?? defaultRootPath);
20
+ const packageJsonPath = resolve(rootPath, "package.json");
21
+
22
+ let packageJson = { name: "unknown" };
23
+ try {
24
+ packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
25
+ } catch {
26
+ // Not in a package root, that's okay for some commands
27
+ }
28
28
 
29
29
  const run = async (cmd, cmdArgs, cwd = rootPath) => {
30
30
  const child = Bun.spawn([cmd, ...cmdArgs], {
@@ -56,18 +56,69 @@ const printHelp = () => {
56
56
  `);
57
57
  };
58
58
 
59
- const config = await readWorkspaceConfig(rootPath);
59
+ let config = null;
60
+ try {
61
+ config = await readWorkspaceConfig(rootPath);
62
+ } catch (err) {
63
+ if (!err || typeof err !== "object" || !("code" in err) || err.code !== "ENOENT") {
64
+ throw err;
65
+ }
66
+ }
67
+
68
+ const requireConfig = () => {
69
+ if (!config) {
70
+ console.error(`Error: elm-ssr.config.json not found at ${rootPath}`);
71
+ console.error("Please run 'elm-ssr new <name>' to create a new workspace and app.");
72
+ process.exit(1);
73
+ }
74
+ };
60
75
 
61
76
  switch (command) {
62
77
  case "build":
63
78
  case "compress":
79
+ requireConfig();
64
80
  await build({ rootPath, config });
65
81
  break;
66
82
 
67
- case "dev":
68
- await run("bun", ["run", "build"], rootPath);
69
- await run("./node_modules/.bin/wrangler", ["dev"], rootPath);
83
+ case "dev": {
84
+ requireConfig();
85
+ if (packageJson && packageJson.scripts && packageJson.scripts.build) {
86
+ await run("bun", ["run", "build"], rootPath);
87
+ } else {
88
+ await build({ rootPath, config });
89
+ }
90
+
91
+ let wranglerCmd = "./node_modules/.bin/wrangler";
92
+ let wranglerArgs = ["dev"];
93
+ try {
94
+ await readFile(resolve(rootPath, wranglerCmd));
95
+ } catch {
96
+ wranglerCmd = "bunx";
97
+ wranglerArgs = ["wrangler", "dev"];
98
+ }
99
+
100
+ let hasWranglerConfig = false;
101
+ try {
102
+ const tomlContent = await readFile(resolve(rootPath, "wrangler.toml"), "utf8");
103
+ if (tomlContent.trim().length > 0) hasWranglerConfig = true;
104
+ } catch {}
105
+ try {
106
+ const jsonContent = await readFile(resolve(rootPath, "wrangler.jsonc"), "utf8");
107
+ if (jsonContent.trim().length > 0) hasWranglerConfig = true;
108
+ } catch {}
109
+
110
+ if (!hasWranglerConfig) {
111
+ const app = config.apps[0];
112
+ if (app) {
113
+ wranglerArgs.push(`${app.root}/worker.ts`);
114
+ wranglerArgs.push("--compatibility-date", "2026-05-28");
115
+ wranglerArgs.push("--compatibility-flags", "nodejs_compat");
116
+ }
117
+ }
118
+
119
+ await run(wranglerCmd, wranglerArgs, rootPath);
70
120
  break;
121
+ }
71
122
 
72
123
  case "new": {
73
124
  const name = args[1];
@@ -87,12 +138,14 @@ switch (command) {
87
138
  }
88
139
 
89
140
  case "routes":
141
+ requireConfig();
90
142
  for (const app of config.apps) {
91
143
  console.log(`${app.name}: root=${app.root} module=${app.module} routes=src/${app.module.split(".").join("/")}/Routes`);
92
144
  }
93
145
  break;
94
146
 
95
147
  case "info":
148
+ requireConfig();
96
149
  console.log(`workspace: ${packageJson.name}`);
97
150
  console.log(`apps: ${config.apps.map((app) => app.name).join(", ")}`);
98
151
  break;
package/lib/scaffold.mjs CHANGED
@@ -475,7 +475,16 @@ const normalizeAppRoot = (rawRoot, name) => {
475
475
  export const createAppScaffold = async (rootPath, name, options = {}) => {
476
476
  ensureValidName(name);
477
477
 
478
- const config = await readWorkspaceConfig(rootPath);
478
+ let config;
479
+ try {
480
+ config = await readWorkspaceConfig(rootPath);
481
+ } catch (err) {
482
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
483
+ config = { apps: [] };
484
+ } else {
485
+ throw err;
486
+ }
487
+ }
479
488
  ensureAppMissing(config, name);
480
489
 
481
490
  const appRoot = normalizeAppRoot(options.root, name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elm-ssr",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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>",