@marko/run 0.1.5 → 0.1.7

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
@@ -481,9 +481,9 @@ express()
481
481
 
482
482
  **`MarkoRun.Handler`** - Type that represents a handler function to be exported by a +handler or +middleware file
483
483
 
484
- **`MarkoRun.CurrentRoute`** - Type of the route's params and metadata
484
+ **`MarkoRun.Route`** - Type of the route's params and metadata
485
485
 
486
- **`MarkoRun.CurrentContext`** - Type of the request context object in a handler and `out.global` in your Marko files
486
+ **`MarkoRun.Context`** - Type of the request context object in a handler and `out.global` in your Marko files
487
487
 
488
488
 
489
489
  ### Generated Types
@@ -493,13 +493,13 @@ If a [TSConfig](https://www.typescriptlang.org/tsconfig) file is discovered in t
493
493
  These types are replaced with more specific versions per routable file:
494
494
 
495
495
  **`MarkoRun.Handler`**
496
- - Overrides context with specific MarkoRun.CurrentContext
496
+ - Overrides context with specific MarkoRun.Context
497
497
 
498
- **`MarkoRun.CurrentRoute`**
498
+ **`MarkoRun.Route`**
499
499
  - Adds specific parameters and meta types
500
500
  - In middleware and layouts which are used in many routes, this type will be a union of all possible routes that the file will see
501
501
 
502
- **`MarkoRun.CurrentContext`**
502
+ **`MarkoRun.Context`**
503
503
  - In middleware and layouts which are used in many routes, this type will be a union of all possible routes that the file will see.
504
504
  - When an adapter is used, it can provide types for the platform
505
505
 
@@ -0,0 +1,36 @@
1
+ import { createServer } from "vite";
2
+
3
+ let activeDevServers;
4
+
5
+ process
6
+ .on("message", (message) => {
7
+ switch (message.type) {
8
+ case "start":
9
+ return start(message.entry, message.config);
10
+ case "shutdown":
11
+ return shutdown();
12
+ }
13
+ })
14
+ .send("ready");
15
+
16
+ async function start(entry, config) {
17
+ let changed = false;
18
+ const loader = await createServer(config);
19
+ ({ activeDevServers } = await loader.ssrLoadModule("@marko/run/adapter"));
20
+ await loader.ssrLoadModule(entry);
21
+
22
+ loader.watcher.on("change", (path) => {
23
+ if (!changed && loader.moduleGraph.getModulesByFile(path)) {
24
+ changed = true;
25
+ process.send("restart");
26
+ }
27
+ });
28
+ }
29
+
30
+ function shutdown() {
31
+ if (activeDevServers) {
32
+ for (const devServer of activeDevServers) {
33
+ devServer.ws.send({ type: "full-reload" });
34
+ }
35
+ }
36
+ }
@@ -1,4 +1,6 @@
1
- import { InlineConfig, ViteDevServer } from "vite";
1
+ import { type InlineConfig, type ViteDevServer } from "vite";
2
2
  import type { NodeMiddleware } from "./middleware";
3
+ export declare const activeDevServers: Set<ViteDevServer>;
3
4
  export declare function createViteDevMiddleware<T>(devServer: ViteDevServer, load: (prev: T | undefined) => Promise<T>, factory: (value: T) => NodeMiddleware): NodeMiddleware;
5
+ export declare function createViteDevServer(config?: InlineConfig): Promise<ViteDevServer>;
4
6
  export declare function createDevServer(config?: InlineConfig): Promise<ViteDevServer>;
@@ -26,8 +26,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
26
26
  // src/adapter/index.ts
27
27
  var adapter_exports = {};
28
28
  __export(adapter_exports, {
29
+ activeDevServers: () => activeDevServers,
29
30
  createDevServer: () => createDevServer,
30
31
  createViteDevMiddleware: () => createViteDevMiddleware,
32
+ createViteDevServer: () => createViteDevServer,
31
33
  default: () => adapter
32
34
  });
33
35
  module.exports = __toCommonJS(adapter_exports);
@@ -37,6 +39,7 @@ var import_url = require("url");
37
39
  // src/adapter/dev-server.ts
38
40
  var import_vite = require("vite");
39
41
  var import_strip_ansi = __toESM(require("strip-ansi"), 1);
42
+ var activeDevServers = /* @__PURE__ */ new Set();
40
43
  function createViteDevMiddleware(devServer, load, factory) {
41
44
  let value;
42
45
  let middleware;
@@ -59,12 +62,22 @@ function createViteDevMiddleware(devServer, load, factory) {
59
62
  }
60
63
  };
61
64
  }
62
- async function createDevServer(config2) {
65
+ async function createViteDevServer(config2) {
63
66
  const devServer = await (0, import_vite.createServer)({
64
67
  ...config2,
65
68
  appType: "custom",
66
69
  server: { middlewareMode: true }
67
70
  });
71
+ const originalClose = devServer.close;
72
+ devServer.close = () => {
73
+ activeDevServers.delete(devServer);
74
+ return originalClose.call(devServer);
75
+ };
76
+ activeDevServers.add(devServer);
77
+ return devServer;
78
+ }
79
+ async function createDevServer(config2) {
80
+ const devServer = await createViteDevServer(config2);
68
81
  const { createMiddleware } = await devServer.ssrLoadModule(
69
82
  "@marko/run/adapter/middleware"
70
83
  );
@@ -82,6 +95,7 @@ var import_net = __toESM(require("net"), 1);
82
95
  var import_child_process = __toESM(require("child_process"), 1);
83
96
  var import_dotenv = require("dotenv");
84
97
  var import_fs = __toESM(require("fs"), 1);
98
+ var import_cluster = __toESM(require("cluster"), 1);
85
99
  async function parseEnv(envFile) {
86
100
  if (import_fs.default.existsSync(envFile)) {
87
101
  const content = await import_fs.default.promises.readFile(envFile, "utf8");
@@ -120,9 +134,37 @@ async function spawnServer(cmd, args = [], port = 0, env, cwd = process.cwd(), w
120
134
  close
121
135
  };
122
136
  }
137
+ async function spawnServerWorker(module2, args = [], port = 0, env) {
138
+ if (port <= 0) {
139
+ port = await getAvailablePort();
140
+ }
141
+ if (typeof env === "string") {
142
+ env = await parseEnv(env);
143
+ }
144
+ const originalExec = import_cluster.default.settings.exec;
145
+ const originalArgs = import_cluster.default.settings.execArgv;
146
+ try {
147
+ import_cluster.default.settings.exec = module2;
148
+ import_cluster.default.settings.execArgv = args;
149
+ const worker = import_cluster.default.fork({ ...env, NODE_ENV: "development", ...process.env, PORT: `${port}` });
150
+ return new Promise((resolve) => {
151
+ function ready(message) {
152
+ if (message === "ready") {
153
+ worker.off("message", ready);
154
+ resolve(worker);
155
+ }
156
+ }
157
+ worker.on("message", ready);
158
+ });
159
+ } finally {
160
+ import_cluster.default.settings.exec = originalExec;
161
+ import_cluster.default.settings.execArgv = originalArgs;
162
+ }
163
+ }
123
164
  async function waitForServer(port, wait = 0) {
124
165
  let remaining = wait > 0 ? wait : Infinity;
125
- while (!await isPortInUse(port)) {
166
+ let connection;
167
+ while (!(connection = await getConnection(port))) {
126
168
  if (remaining >= 100) {
127
169
  remaining -= 100;
128
170
  await sleep(100);
@@ -132,14 +174,16 @@ async function waitForServer(port, wait = 0) {
132
174
  );
133
175
  }
134
176
  }
177
+ return connection;
135
178
  }
136
- async function isPortInUse(port) {
179
+ async function getConnection(port) {
137
180
  return new Promise((resolve) => {
138
- const connection = import_net.default.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => done(false)).on("connect", () => done(true));
139
- function done(connected) {
181
+ const connection = import_net.default.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => {
140
182
  connection.end();
141
- resolve(connected);
142
- }
183
+ resolve(null);
184
+ }).on("connect", () => {
185
+ resolve(connection);
186
+ });
143
187
  });
144
188
  }
145
189
  async function getAvailablePort() {
@@ -158,20 +202,56 @@ function sleep(ms) {
158
202
  var import_parse_node_args = __toESM(require("parse-node-args"), 1);
159
203
  var import_meta = {};
160
204
  var __dirname = (0, import_url.fileURLToPath)(new URL(".", import_meta.url));
205
+ var defaultEntry = import_path.default.join(__dirname, "default-entry");
206
+ var loadDevWorker = import_path.default.join(__dirname, "load-dev-worker.mjs");
161
207
  function adapter() {
162
208
  return {
163
209
  name: "base-adapter",
164
210
  async getEntryFile() {
165
- const entry = import_path.default.join(__dirname, "default-entry");
166
- return entry;
211
+ return defaultEntry;
167
212
  },
168
- async startDev(config2, { port = 3e3, envFile }) {
169
- const { root, configFile } = config2;
213
+ async startDev(entry, config2, options) {
214
+ const { port = 3e3, envFile } = options;
215
+ if (entry) {
216
+ const { nodeArgs } = (0, import_parse_node_args.default)(options.args);
217
+ let worker;
218
+ async function start() {
219
+ const nextWorker = await spawnServerWorker(
220
+ loadDevWorker,
221
+ nodeArgs,
222
+ port,
223
+ envFile
224
+ );
225
+ nextWorker.on("message", (messsage) => {
226
+ if (messsage === "restart") {
227
+ start();
228
+ }
229
+ }).send({ type: "start", entry, config: config2 });
230
+ await waitForWorker(nextWorker, port);
231
+ if (worker) {
232
+ const prevWorker = worker;
233
+ let timeout;
234
+ worker.once("disconnect", () => {
235
+ clearTimeout(timeout);
236
+ });
237
+ worker.send({ type: "shutdown" });
238
+ worker.disconnect();
239
+ timeout = setTimeout(() => {
240
+ prevWorker.kill();
241
+ }, 2e3);
242
+ }
243
+ worker = nextWorker;
244
+ }
245
+ await start();
246
+ return {
247
+ port,
248
+ close() {
249
+ worker.kill();
250
+ }
251
+ };
252
+ }
253
+ const devServer = await createDevServer(config2);
170
254
  envFile && await loadEnv(envFile);
171
- const devServer = await createDevServer({
172
- root,
173
- configFile
174
- });
175
255
  return new Promise((resolve) => {
176
256
  const listener = devServer.middlewares.listen(port, () => {
177
257
  const address = listener.address();
@@ -195,8 +275,21 @@ function adapter() {
195
275
  }
196
276
  };
197
277
  }
278
+ async function waitForWorker(worker, port) {
279
+ return new Promise((resolve) => {
280
+ function listening(address) {
281
+ if (address.port === port) {
282
+ worker.off("listening", listening);
283
+ resolve();
284
+ }
285
+ }
286
+ worker.on("listening", listening);
287
+ });
288
+ }
198
289
  // Annotate the CommonJS export names for ESM import in node:
199
290
  0 && (module.exports = {
291
+ activeDevServers,
200
292
  createDevServer,
201
- createViteDevMiddleware
293
+ createViteDevMiddleware,
294
+ createViteDevServer
202
295
  });
@@ -1,6 +1,6 @@
1
1
  import type { Adapter } from "../vite";
2
2
  import { type SpawnedServer } from "../vite/utils/server";
3
- export { createDevServer, createViteDevMiddleware } from "./dev-server";
3
+ export { activeDevServers, createDevServer, createViteDevServer, createViteDevMiddleware, } from "./dev-server";
4
4
  export type { Adapter, SpawnedServer };
5
5
  export type { NodePlatformInfo } from "./middleware";
6
6
  export default function adapter(): Adapter;
@@ -5,6 +5,7 @@ import { fileURLToPath } from "url";
5
5
  // src/adapter/dev-server.ts
6
6
  import { createServer } from "vite";
7
7
  import stripAnsi from "strip-ansi";
8
+ var activeDevServers = /* @__PURE__ */ new Set();
8
9
  function createViteDevMiddleware(devServer, load, factory) {
9
10
  let value;
10
11
  let middleware;
@@ -27,12 +28,22 @@ function createViteDevMiddleware(devServer, load, factory) {
27
28
  }
28
29
  };
29
30
  }
30
- async function createDevServer(config2) {
31
+ async function createViteDevServer(config2) {
31
32
  const devServer = await createServer({
32
33
  ...config2,
33
34
  appType: "custom",
34
35
  server: { middlewareMode: true }
35
36
  });
37
+ const originalClose = devServer.close;
38
+ devServer.close = () => {
39
+ activeDevServers.delete(devServer);
40
+ return originalClose.call(devServer);
41
+ };
42
+ activeDevServers.add(devServer);
43
+ return devServer;
44
+ }
45
+ async function createDevServer(config2) {
46
+ const devServer = await createViteDevServer(config2);
36
47
  const { createMiddleware } = await devServer.ssrLoadModule(
37
48
  "@marko/run/adapter/middleware"
38
49
  );
@@ -50,6 +61,7 @@ import net from "net";
50
61
  import cp from "child_process";
51
62
  import { parse, config } from "dotenv";
52
63
  import fs from "fs";
64
+ import cluster from "cluster";
53
65
  async function parseEnv(envFile) {
54
66
  if (fs.existsSync(envFile)) {
55
67
  const content = await fs.promises.readFile(envFile, "utf8");
@@ -88,9 +100,37 @@ async function spawnServer(cmd, args = [], port = 0, env, cwd = process.cwd(), w
88
100
  close
89
101
  };
90
102
  }
103
+ async function spawnServerWorker(module, args = [], port = 0, env) {
104
+ if (port <= 0) {
105
+ port = await getAvailablePort();
106
+ }
107
+ if (typeof env === "string") {
108
+ env = await parseEnv(env);
109
+ }
110
+ const originalExec = cluster.settings.exec;
111
+ const originalArgs = cluster.settings.execArgv;
112
+ try {
113
+ cluster.settings.exec = module;
114
+ cluster.settings.execArgv = args;
115
+ const worker = cluster.fork({ ...env, NODE_ENV: "development", ...process.env, PORT: `${port}` });
116
+ return new Promise((resolve) => {
117
+ function ready(message) {
118
+ if (message === "ready") {
119
+ worker.off("message", ready);
120
+ resolve(worker);
121
+ }
122
+ }
123
+ worker.on("message", ready);
124
+ });
125
+ } finally {
126
+ cluster.settings.exec = originalExec;
127
+ cluster.settings.execArgv = originalArgs;
128
+ }
129
+ }
91
130
  async function waitForServer(port, wait = 0) {
92
131
  let remaining = wait > 0 ? wait : Infinity;
93
- while (!await isPortInUse(port)) {
132
+ let connection;
133
+ while (!(connection = await getConnection(port))) {
94
134
  if (remaining >= 100) {
95
135
  remaining -= 100;
96
136
  await sleep(100);
@@ -100,14 +140,16 @@ async function waitForServer(port, wait = 0) {
100
140
  );
101
141
  }
102
142
  }
143
+ return connection;
103
144
  }
104
- async function isPortInUse(port) {
145
+ async function getConnection(port) {
105
146
  return new Promise((resolve) => {
106
- const connection = net.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => done(false)).on("connect", () => done(true));
107
- function done(connected) {
147
+ const connection = net.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => {
108
148
  connection.end();
109
- resolve(connected);
110
- }
149
+ resolve(null);
150
+ }).on("connect", () => {
151
+ resolve(connection);
152
+ });
111
153
  });
112
154
  }
113
155
  async function getAvailablePort() {
@@ -125,20 +167,56 @@ function sleep(ms) {
125
167
  // src/adapter/index.ts
126
168
  import parseNodeArgs from "parse-node-args";
127
169
  var __dirname = fileURLToPath(new URL(".", import.meta.url));
170
+ var defaultEntry = path.join(__dirname, "default-entry");
171
+ var loadDevWorker = path.join(__dirname, "load-dev-worker.mjs");
128
172
  function adapter() {
129
173
  return {
130
174
  name: "base-adapter",
131
175
  async getEntryFile() {
132
- const entry = path.join(__dirname, "default-entry");
133
- return entry;
176
+ return defaultEntry;
134
177
  },
135
- async startDev(config2, { port = 3e3, envFile }) {
136
- const { root, configFile } = config2;
178
+ async startDev(entry, config2, options) {
179
+ const { port = 3e3, envFile } = options;
180
+ if (entry) {
181
+ const { nodeArgs } = parseNodeArgs(options.args);
182
+ let worker;
183
+ async function start() {
184
+ const nextWorker = await spawnServerWorker(
185
+ loadDevWorker,
186
+ nodeArgs,
187
+ port,
188
+ envFile
189
+ );
190
+ nextWorker.on("message", (messsage) => {
191
+ if (messsage === "restart") {
192
+ start();
193
+ }
194
+ }).send({ type: "start", entry, config: config2 });
195
+ await waitForWorker(nextWorker, port);
196
+ if (worker) {
197
+ const prevWorker = worker;
198
+ let timeout;
199
+ worker.once("disconnect", () => {
200
+ clearTimeout(timeout);
201
+ });
202
+ worker.send({ type: "shutdown" });
203
+ worker.disconnect();
204
+ timeout = setTimeout(() => {
205
+ prevWorker.kill();
206
+ }, 2e3);
207
+ }
208
+ worker = nextWorker;
209
+ }
210
+ await start();
211
+ return {
212
+ port,
213
+ close() {
214
+ worker.kill();
215
+ }
216
+ };
217
+ }
218
+ const devServer = await createDevServer(config2);
137
219
  envFile && await loadEnv(envFile);
138
- const devServer = await createDevServer({
139
- root,
140
- configFile
141
- });
142
220
  return new Promise((resolve) => {
143
221
  const listener = devServer.middlewares.listen(port, () => {
144
222
  const address = listener.address();
@@ -162,8 +240,21 @@ function adapter() {
162
240
  }
163
241
  };
164
242
  }
243
+ async function waitForWorker(worker, port) {
244
+ return new Promise((resolve) => {
245
+ function listening(address) {
246
+ if (address.port === port) {
247
+ worker.off("listening", listening);
248
+ resolve();
249
+ }
250
+ }
251
+ worker.on("listening", listening);
252
+ });
253
+ }
165
254
  export {
255
+ activeDevServers,
166
256
  createDevServer,
167
257
  createViteDevMiddleware,
258
+ createViteDevServer,
168
259
  adapter as default
169
260
  };
@@ -0,0 +1,37 @@
1
+ import { createServer } from "vite";
2
+
3
+ let activeDevServers;
4
+
5
+ process
6
+ .on("message", (message) => {
7
+ switch (message.type) {
8
+ case "start":
9
+ return start(message.entry, message.config);
10
+ case "shutdown":
11
+ return shutdown();
12
+ }
13
+ })
14
+ .send("ready");
15
+
16
+ async function start(entry, config) {
17
+ let changed = false;
18
+ const loader = await createServer(config);
19
+ ({ activeDevServers } = await loader.ssrLoadModule("@marko/run/adapter"));
20
+ await loader.ssrLoadModule(entry);
21
+
22
+ loader.watcher.on("change", (path) => {
23
+ if (!changed && loader.moduleGraph.getModulesByFile(path)) {
24
+ changed = true;
25
+ process.send("restart");
26
+ }
27
+ });
28
+ }
29
+
30
+ function shutdown() {
31
+ if (activeDevServers) {
32
+ for (const devServer of activeDevServers) {
33
+ devServer.ws.send({ type: "full-reload" });
34
+ }
35
+ activeDevServers.clear();
36
+ }
37
+ }
@@ -5,7 +5,7 @@ import sade from "sade";
5
5
 
6
6
  // src/cli/commands.ts
7
7
  import path3 from "path";
8
- import fs4 from "fs";
8
+ import fs3 from "fs";
9
9
  import { fileURLToPath as fileURLToPath2 } from "url";
10
10
  import { build as viteBuild, resolveConfig } from "vite";
11
11
 
@@ -26,84 +26,10 @@ var setExternalAdapterOptions = (viteConfig, value) => setConfig(viteConfig, Ada
26
26
  // src/cli/commands.ts
27
27
  import { MemoryStore } from "@marko/vite";
28
28
 
29
- // src/vite/utils/server.ts
30
- import net from "net";
31
- import cp from "child_process";
32
- import { parse, config } from "dotenv";
33
- import fs from "fs";
34
- async function parseEnv(envFile) {
35
- if (fs.existsSync(envFile)) {
36
- const content = await fs.promises.readFile(envFile, "utf8");
37
- return parse(content);
38
- }
39
- }
40
- async function spawnServer(cmd, args = [], port = 0, env, cwd = process.cwd(), wait = 3e4) {
41
- if (port <= 0) {
42
- port = await getAvailablePort();
43
- }
44
- if (typeof env === "string") {
45
- env = await parseEnv(env);
46
- }
47
- const proc = cp.spawn(cmd, args, {
48
- cwd,
49
- shell: true,
50
- stdio: "inherit",
51
- windowsHide: true,
52
- env: { ...env, NODE_ENV: "development", ...process.env, PORT: `${port}` }
53
- });
54
- const close = () => {
55
- proc.unref();
56
- proc.kill();
57
- };
58
- try {
59
- await waitForServer(port, wait);
60
- } catch (err) {
61
- close();
62
- throw err;
63
- }
64
- return {
65
- port,
66
- close
67
- };
68
- }
69
- async function waitForServer(port, wait = 0) {
70
- let remaining = wait > 0 ? wait : Infinity;
71
- while (!await isPortInUse(port)) {
72
- if (remaining >= 100) {
73
- remaining -= 100;
74
- await sleep(100);
75
- } else {
76
- throw new Error(
77
- `site-write: timeout while wating for server to start on port "${port}".`
78
- );
79
- }
80
- }
81
- }
82
- async function isPortInUse(port) {
83
- return new Promise((resolve) => {
84
- const connection = net.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => done(false)).on("connect", () => done(true));
85
- function done(connected) {
86
- connection.end();
87
- resolve(connected);
88
- }
89
- });
90
- }
91
- async function getAvailablePort() {
92
- return new Promise((resolve) => {
93
- const server = net.createServer().listen(0, () => {
94
- const { port } = server.address();
95
- server.close(() => resolve(port));
96
- });
97
- });
98
- }
99
- function sleep(ms) {
100
- return new Promise((resolve) => setTimeout(resolve, ms));
101
- }
102
-
103
29
  // src/vite/plugin.ts
104
30
  import path2 from "path";
105
31
  import crypto from "crypto";
106
- import fs3 from "fs";
32
+ import fs2 from "fs";
107
33
  import glob from "glob";
108
34
  import { mergeConfig } from "vite";
109
35
  import markoVitePlugin, { FileStore } from "@marko/vite";
@@ -131,7 +57,7 @@ var routeableFileRegex = new RegExp(
131
57
  );
132
58
 
133
59
  // src/vite/routes/walk.ts
134
- import fs2 from "fs";
60
+ import fs from "fs";
135
61
  import path from "path";
136
62
 
137
63
  // src/vite/utils/ast.ts
@@ -216,7 +142,7 @@ async function preview(entry, cwd, configFile, port, outDir, envFile, args = [])
216
142
  };
217
143
  return await adapter.startPreview(entryFile, options);
218
144
  }
219
- async function dev(cmd, cwd, configFile, port, envFile, args = []) {
145
+ async function dev(entry, cwd, configFile, port, envFile, args = []) {
220
146
  const resolvedConfig = await resolveConfig(
221
147
  { root: cwd, configFile, logLevel: "silent" },
222
148
  "build"
@@ -227,27 +153,23 @@ async function dev(cmd, cwd, configFile, port, envFile, args = []) {
227
153
  if (envFile) {
228
154
  envFile = path3.resolve(cwd, envFile);
229
155
  }
230
- if (cmd) {
231
- return await spawnServer(cmd, args, port, envFile, cwd);
232
- } else {
233
- const adapter = await resolveAdapter2(resolvedConfig);
234
- if (!adapter) {
235
- throw new Error(
236
- "No adapter specified for 'dev' command without custom target"
237
- );
238
- } else if (!adapter.startDev) {
239
- throw new Error(
240
- `Adapter '${adapter.name}' does not support 'serve' command`
241
- );
242
- }
243
- const options = {
244
- cwd,
245
- args,
246
- port,
247
- envFile
248
- };
249
- return await adapter.startDev({ root: cwd, configFile }, options);
156
+ const adapter = await resolveAdapter2(resolvedConfig);
157
+ if (!adapter) {
158
+ throw new Error(
159
+ "No adapter specified for 'dev' command without custom target"
160
+ );
161
+ } else if (!adapter.startDev) {
162
+ throw new Error(
163
+ `Adapter '${adapter.name}' does not support 'serve' command`
164
+ );
250
165
  }
166
+ const options = {
167
+ cwd,
168
+ args,
169
+ port,
170
+ envFile
171
+ };
172
+ return await adapter.startDev(entry, { root: cwd, configFile }, options);
251
173
  }
252
174
  async function build(entry, cwd, configFile, outDir, envFile) {
253
175
  var _a;
@@ -308,7 +230,7 @@ async function build(entry, cwd, configFile, outDir, envFile) {
308
230
  function findFileWithExt(dir, base, extensions = defaultConfigFileExts) {
309
231
  for (const ext of extensions) {
310
232
  const filePath = path3.join(dir, base + ext);
311
- if (fs4.existsSync(filePath)) {
233
+ if (fs3.existsSync(filePath)) {
312
234
  return filePath;
313
235
  }
314
236
  }
@@ -317,7 +239,7 @@ function findFileWithExt(dir, base, extensions = defaultConfigFileExts) {
317
239
  async function getViteConfig(dir, configFile, bases = defaultConfigFileBases) {
318
240
  if (configFile) {
319
241
  const configFilePath = path3.join(dir, configFile);
320
- if (!fs4.existsSync(configFilePath)) {
242
+ if (!fs3.existsSync(configFilePath)) {
321
243
  throw new Error(`No config file found at '${configFilePath}'`);
322
244
  }
323
245
  return configFile;
@@ -330,12 +252,12 @@ async function getViteConfig(dir, configFile, bases = defaultConfigFileBases) {
330
252
  }
331
253
  return path3.join(__dirname2, "default.config.mjs");
332
254
  }
333
- async function resolveAdapter2(config2) {
334
- const options = getExternalPluginOptions(config2);
255
+ async function resolveAdapter2(config) {
256
+ const options = getExternalPluginOptions(config);
335
257
  if (!options) {
336
258
  throw new Error("Unable to resolve @marko/run options");
337
259
  }
338
- return resolveAdapter(config2.root, options);
260
+ return resolveAdapter(config.root, options);
339
261
  }
340
262
 
341
263
  // src/cli/index.ts
@@ -355,12 +277,12 @@ prog.command("preview [entry]").describe("Start a production-like server for alr
355
277
  process.env.NODE_ENV = "production";
356
278
  const cwd = process.cwd();
357
279
  const args = process.argv.slice(entry ? 4 : 3);
358
- const config2 = await getViteConfig(cwd, opts.config);
359
- await build(entry, cwd, config2, opts.output, opts.env);
280
+ const config = await getViteConfig(cwd, opts.config);
281
+ await build(entry, cwd, config, opts.output, opts.env);
360
282
  await preview(
361
283
  opts.file,
362
284
  cwd,
363
- config2,
285
+ config,
364
286
  opts.port,
365
287
  opts.output,
366
288
  opts.env,
@@ -373,15 +295,15 @@ prog.command("dev [entry]", "", { default: true }).describe("Start development s
373
295
  ).example("dev --config vite.config.js").action(async (entry, opts) => {
374
296
  const cwd = process.cwd();
375
297
  const args = process.argv.slice(entry ? 4 : 3);
376
- const config2 = await getViteConfig(cwd, opts.config);
377
- await dev(entry, cwd, config2, opts.port, opts.env, args);
298
+ const config = await getViteConfig(cwd, opts.config);
299
+ await dev(entry, cwd, config, opts.port, opts.env, args);
378
300
  });
379
301
  prog.command("build [entry]").describe("Build the application (without serving it)").option(
380
302
  "-o, --output",
381
303
  "Directory to write built files (default: 'build.outDir' in Vite config)"
382
304
  ).example("build --config vite.config.js").action(async (entry, opts) => {
383
305
  const cwd = process.cwd();
384
- const config2 = await getViteConfig(cwd, opts.config);
385
- await build(entry, cwd, config2, opts.ouput, opts.env);
306
+ const config = await getViteConfig(cwd, opts.config);
307
+ await build(entry, cwd, config, opts.ouput, opts.env);
386
308
  });
387
309
  prog.parse(process.argv);
@@ -1,5 +1,6 @@
1
- import type { HandlerLike, ParamsObject, Route as AnyRoute, Context as AnyContext } from "./types";
1
+ import type { HandlerLike, ParamsObject, Route as AnyRoute, Context as AnyContext, RuntimeModule } from "./types";
2
2
  declare global {
3
+ var __marko_run__: RuntimeModule;
3
4
  namespace MarkoRun {
4
5
  const NotHandled: unique symbol;
5
6
  const NotMatched: unique symbol;
@@ -25,14 +25,20 @@ __export(router_exports, {
25
25
  match: () => match
26
26
  });
27
27
  module.exports = __toCommonJS(router_exports);
28
- function notImplemented() {
29
- throw new Error(
30
- "This should have been replaced by the @marko/run plugin at build/dev time"
31
- );
28
+ function fromRuntime(name) {
29
+ return (...args) => {
30
+ const runtime = globalThis.__marko_run__;
31
+ if (!runtime) {
32
+ throw new Error(
33
+ "This should have been replaced by the @marko/run plugin at build/dev time"
34
+ );
35
+ }
36
+ return runtime[name](...args);
37
+ };
32
38
  }
33
- var fetch = notImplemented;
34
- var match = notImplemented;
35
- var invoke = notImplemented;
39
+ var fetch = fromRuntime("fetch");
40
+ var match = fromRuntime("match");
41
+ var invoke = fromRuntime("invoke");
36
42
  // Annotate the CommonJS export names for ESM import in node:
37
43
  0 && (module.exports = {
38
44
  fetch,
@@ -1,12 +1,18 @@
1
1
  // src/runtime/router.ts
2
- function notImplemented() {
3
- throw new Error(
4
- "This should have been replaced by the @marko/run plugin at build/dev time"
5
- );
2
+ function fromRuntime(name) {
3
+ return (...args) => {
4
+ const runtime = globalThis.__marko_run__;
5
+ if (!runtime) {
6
+ throw new Error(
7
+ "This should have been replaced by the @marko/run plugin at build/dev time"
8
+ );
9
+ }
10
+ return runtime[name](...args);
11
+ };
6
12
  }
7
- var fetch = notImplemented;
8
- var match = notImplemented;
9
- var invoke = notImplemented;
13
+ var fetch = fromRuntime("fetch");
14
+ var match = fromRuntime("match");
15
+ var invoke = fromRuntime("invoke");
10
16
  export {
11
17
  fetch,
12
18
  invoke,
@@ -232,7 +232,7 @@ async function buildRoutes(walk, basePath) {
232
232
  });
233
233
  }
234
234
  }
235
- pathStack.push(name.toLowerCase());
235
+ pathStack.push(name);
236
236
  }
237
237
  dirStack.push(name);
238
238
  isRoot = false;
@@ -680,7 +680,9 @@ function renderRouter(routes, options = {
680
680
  `import page${key} from '${virtualFilePrefix}/${markoRunFilePrefix}special__${key}.marko${serverEntryQuery}';`
681
681
  );
682
682
  }
683
- writer.writeLines("").writeBlockStart(`export function match(method, pathname) {`).writeLines(
683
+ writer.writeLines(`
684
+ globalThis.__marko_run__ = { match, fetch, invoke };
685
+ `).writeBlockStart(`export function match(method, pathname) {`).writeLines(
684
686
  `if (!pathname) {
685
687
  pathname = '/';
686
688
  } else if (pathname.charAt(0) !== '/') {
@@ -955,7 +957,8 @@ function renderMatch(verb, route, pathIndex) {
955
957
  const handler = `${verb}${route.index}`;
956
958
  const params = ((_a = route.params) == null ? void 0 : _a.length) ? renderParamsInfo(route.params, pathIndex) : "{}";
957
959
  const meta = route.meta ? `meta${route.index}` : "{}";
958
- return `{ handler: ${handler}, params: ${params}, meta: ${meta} }`;
960
+ const path3 = pathToURLPatternString(route.path);
961
+ return `{ handler: ${handler}, params: ${params}, meta: ${meta}, path: '${path3}' }`;
959
962
  }
960
963
  function renderMiddleware(middleware) {
961
964
  const writer = createStringWriter();
@@ -1014,13 +1017,7 @@ interface NoMeta {}
1014
1017
  for (const route of routes.list) {
1015
1018
  const { meta, handler, params, middleware, page, layouts } = route;
1016
1019
  const routeType = `Route${route.index}`;
1017
- const pathType = `\`${route.path.replace(
1018
- /\/\$(\$?)([^\/]*)/,
1019
- (_, catchAll, name) => {
1020
- name = decodeURIComponent(name);
1021
- return catchAll ? `/:${name || "rest"}*` : `/:${name}`;
1022
- }
1023
- )}\``;
1020
+ const pathType = `\`${pathToURLPatternString(route.path)}\``;
1024
1021
  const paramsType = (params == null ? void 0 : params.length) ? renderParamsInfoType(params) : "NoParams";
1025
1022
  let metaType = "NoMeta";
1026
1023
  if (page || handler) {
@@ -1199,6 +1196,15 @@ function writeModuleDeclaration(writer, path3 = "global", routeType = "AnyRoute"
1199
1196
  }
1200
1197
  }`);
1201
1198
  }
1199
+ function pathToURLPatternString(path3) {
1200
+ return path3.replace(
1201
+ /\/\$(\$?)([^\/]*)/g,
1202
+ (_, catchAll, name) => {
1203
+ name = decodeURIComponent(name);
1204
+ return catchAll ? `/:${name || "rest"}*` : `/:${name}`;
1205
+ }
1206
+ );
1207
+ }
1202
1208
 
1203
1209
  // src/vite/utils/ast.ts
1204
1210
  var t = __toESM(require("@babel/types"), 1);
@@ -1812,6 +1818,7 @@ var import_net = __toESM(require("net"), 1);
1812
1818
  var import_child_process = __toESM(require("child_process"), 1);
1813
1819
  var import_dotenv = require("dotenv");
1814
1820
  var import_fs3 = __toESM(require("fs"), 1);
1821
+ var import_cluster = __toESM(require("cluster"), 1);
1815
1822
  async function parseEnv(envFile) {
1816
1823
  if (import_fs3.default.existsSync(envFile)) {
1817
1824
  const content = await import_fs3.default.promises.readFile(envFile, "utf8");
@@ -1852,7 +1859,8 @@ async function spawnServer(cmd, args = [], port = 0, env, cwd = process.cwd(), w
1852
1859
  }
1853
1860
  async function waitForServer(port, wait = 0) {
1854
1861
  let remaining = wait > 0 ? wait : Infinity;
1855
- while (!await isPortInUse(port)) {
1862
+ let connection;
1863
+ while (!(connection = await getConnection(port))) {
1856
1864
  if (remaining >= 100) {
1857
1865
  remaining -= 100;
1858
1866
  await sleep(100);
@@ -1862,16 +1870,21 @@ async function waitForServer(port, wait = 0) {
1862
1870
  );
1863
1871
  }
1864
1872
  }
1873
+ return connection;
1865
1874
  }
1866
- async function isPortInUse(port) {
1875
+ async function getConnection(port) {
1867
1876
  return new Promise((resolve) => {
1868
- const connection = import_net.default.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => done(false)).on("connect", () => done(true));
1869
- function done(connected) {
1877
+ const connection = import_net.default.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => {
1870
1878
  connection.end();
1871
- resolve(connected);
1872
- }
1879
+ resolve(null);
1880
+ }).on("connect", () => {
1881
+ resolve(connection);
1882
+ });
1873
1883
  });
1874
1884
  }
1885
+ async function isPortInUse(port) {
1886
+ return Boolean(await getConnection(port));
1887
+ }
1875
1888
  async function getAvailablePort() {
1876
1889
  return new Promise((resolve) => {
1877
1890
  const server = import_net.default.createServer().listen(0, () => {
@@ -195,7 +195,7 @@ async function buildRoutes(walk, basePath) {
195
195
  });
196
196
  }
197
197
  }
198
- pathStack.push(name.toLowerCase());
198
+ pathStack.push(name);
199
199
  }
200
200
  dirStack.push(name);
201
201
  isRoot = false;
@@ -643,7 +643,9 @@ function renderRouter(routes, options = {
643
643
  `import page${key} from '${virtualFilePrefix}/${markoRunFilePrefix}special__${key}.marko${serverEntryQuery}';`
644
644
  );
645
645
  }
646
- writer.writeLines("").writeBlockStart(`export function match(method, pathname) {`).writeLines(
646
+ writer.writeLines(`
647
+ globalThis.__marko_run__ = { match, fetch, invoke };
648
+ `).writeBlockStart(`export function match(method, pathname) {`).writeLines(
647
649
  `if (!pathname) {
648
650
  pathname = '/';
649
651
  } else if (pathname.charAt(0) !== '/') {
@@ -918,7 +920,8 @@ function renderMatch(verb, route, pathIndex) {
918
920
  const handler = `${verb}${route.index}`;
919
921
  const params = ((_a = route.params) == null ? void 0 : _a.length) ? renderParamsInfo(route.params, pathIndex) : "{}";
920
922
  const meta = route.meta ? `meta${route.index}` : "{}";
921
- return `{ handler: ${handler}, params: ${params}, meta: ${meta} }`;
923
+ const path3 = pathToURLPatternString(route.path);
924
+ return `{ handler: ${handler}, params: ${params}, meta: ${meta}, path: '${path3}' }`;
922
925
  }
923
926
  function renderMiddleware(middleware) {
924
927
  const writer = createStringWriter();
@@ -977,13 +980,7 @@ interface NoMeta {}
977
980
  for (const route of routes.list) {
978
981
  const { meta, handler, params, middleware, page, layouts } = route;
979
982
  const routeType = `Route${route.index}`;
980
- const pathType = `\`${route.path.replace(
981
- /\/\$(\$?)([^\/]*)/,
982
- (_, catchAll, name) => {
983
- name = decodeURIComponent(name);
984
- return catchAll ? `/:${name || "rest"}*` : `/:${name}`;
985
- }
986
- )}\``;
983
+ const pathType = `\`${pathToURLPatternString(route.path)}\``;
987
984
  const paramsType = (params == null ? void 0 : params.length) ? renderParamsInfoType(params) : "NoParams";
988
985
  let metaType = "NoMeta";
989
986
  if (page || handler) {
@@ -1162,6 +1159,15 @@ function writeModuleDeclaration(writer, path3 = "global", routeType = "AnyRoute"
1162
1159
  }
1163
1160
  }`);
1164
1161
  }
1162
+ function pathToURLPatternString(path3) {
1163
+ return path3.replace(
1164
+ /\/\$(\$?)([^\/]*)/g,
1165
+ (_, catchAll, name) => {
1166
+ name = decodeURIComponent(name);
1167
+ return catchAll ? `/:${name || "rest"}*` : `/:${name}`;
1168
+ }
1169
+ );
1170
+ }
1165
1171
 
1166
1172
  // src/vite/utils/ast.ts
1167
1173
  import * as t from "@babel/types";
@@ -1774,6 +1780,7 @@ import net from "net";
1774
1780
  import cp from "child_process";
1775
1781
  import { parse, config } from "dotenv";
1776
1782
  import fs3 from "fs";
1783
+ import cluster from "cluster";
1777
1784
  async function parseEnv(envFile) {
1778
1785
  if (fs3.existsSync(envFile)) {
1779
1786
  const content = await fs3.promises.readFile(envFile, "utf8");
@@ -1814,7 +1821,8 @@ async function spawnServer(cmd, args = [], port = 0, env, cwd = process.cwd(), w
1814
1821
  }
1815
1822
  async function waitForServer(port, wait = 0) {
1816
1823
  let remaining = wait > 0 ? wait : Infinity;
1817
- while (!await isPortInUse(port)) {
1824
+ let connection;
1825
+ while (!(connection = await getConnection(port))) {
1818
1826
  if (remaining >= 100) {
1819
1827
  remaining -= 100;
1820
1828
  await sleep(100);
@@ -1824,16 +1832,21 @@ async function waitForServer(port, wait = 0) {
1824
1832
  );
1825
1833
  }
1826
1834
  }
1835
+ return connection;
1827
1836
  }
1828
- async function isPortInUse(port) {
1837
+ async function getConnection(port) {
1829
1838
  return new Promise((resolve) => {
1830
- const connection = net.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => done(false)).on("connect", () => done(true));
1831
- function done(connected) {
1839
+ const connection = net.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => {
1832
1840
  connection.end();
1833
- resolve(connected);
1834
- }
1841
+ resolve(null);
1842
+ }).on("connect", () => {
1843
+ resolve(connection);
1844
+ });
1835
1845
  });
1836
1846
  }
1847
+ async function isPortInUse(port) {
1848
+ return Boolean(await getConnection(port));
1849
+ }
1837
1850
  async function getAvailablePort() {
1838
1851
  return new Promise((resolve) => {
1839
1852
  const server = net.createServer().listen(0, () => {
@@ -24,7 +24,7 @@ export interface Adapter {
24
24
  pluginOptions?(options: Options): Promise<Options> | Options | undefined;
25
25
  viteConfig?(config: UserConfig): Promise<UserConfig> | UserConfig | undefined;
26
26
  getEntryFile?(): Promise<string> | string;
27
- startDev?(config: InlineConfig, options: StartDevOptions): Promise<SpawnedServer> | SpawnedServer;
27
+ startDev?(entry: string | undefined, config: InlineConfig, options: StartDevOptions): Promise<SpawnedServer> | SpawnedServer;
28
28
  startPreview?(entry: string | undefined, options: StartPreviewOptions): Promise<SpawnedServer> | SpawnedServer;
29
29
  buildEnd?(config: ResolvedConfig, routes: Route[], builtEntries: string[], sourceEntries: string[]): Promise<void> | void;
30
30
  typeInfo?(writer: (data: string) => void): Promise<string> | string;
@@ -1,3 +1,6 @@
1
+ /// <reference types="node" />
2
+ import { type Socket } from "net";
3
+ import { type Worker } from "cluster";
1
4
  export interface SpawnedServer {
2
5
  port: number;
3
6
  close(): Promise<void> | void;
@@ -5,6 +8,8 @@ export interface SpawnedServer {
5
8
  export declare function parseEnv(envFile: string): Promise<import("dotenv").DotenvParseOutput | undefined>;
6
9
  export declare function loadEnv(envFile: string): void;
7
10
  export declare function spawnServer(cmd: string, args?: string[], port?: number, env?: string | Record<string, string>, cwd?: string, wait?: number): Promise<SpawnedServer>;
8
- export declare function waitForServer(port: number, wait?: number): Promise<void>;
11
+ export declare function spawnServerWorker(module: string, args?: string[], port?: number, env?: string | Record<string, string>): Promise<Worker>;
12
+ export declare function waitForServer(port: number, wait?: number): Promise<Socket>;
13
+ export declare function getConnection(port: number): Promise<Socket | null>;
9
14
  export declare function isPortInUse(port: number): Promise<boolean>;
10
15
  export declare function getAvailablePort(): Promise<number>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marko/run",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "The Marko application framework.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/marko-js/run/tree/main/packages/run",