@junejs/server 0.1.0-dev.4 → 0.1.0-dev.5

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.
Files changed (96) hide show
  1. package/dist/adapter.d.ts +55 -0
  2. package/dist/adapter.js +204 -0
  3. package/dist/agent-discover.d.ts +5 -0
  4. package/dist/agent-discover.js +75 -0
  5. package/dist/agent-durable.d.ts +60 -0
  6. package/dist/agent-durable.js +106 -0
  7. package/dist/agent-native.d.ts +40 -0
  8. package/dist/agent-native.js +179 -0
  9. package/dist/app.d.ts +15 -0
  10. package/dist/app.js +218 -0
  11. package/dist/blob.d.ts +23 -0
  12. package/dist/blob.js +78 -0
  13. package/dist/build.d.ts +40 -0
  14. package/dist/build.js +551 -0
  15. package/dist/client-bundle.d.ts +15 -0
  16. package/dist/client-bundle.js +91 -0
  17. package/dist/config-loader.d.ts +9 -0
  18. package/dist/config-loader.js +25 -0
  19. package/dist/content.d.ts +39 -0
  20. package/dist/content.js +201 -0
  21. package/dist/css-modules-ambient.d.ts +10 -0
  22. package/dist/css-modules.d.ts +24 -0
  23. package/dist/css-modules.js +127 -0
  24. package/dist/css.d.ts +14 -0
  25. package/dist/css.js +114 -0
  26. package/dist/d1-BkdUg9kg.d.ts +24 -0
  27. package/dist/d1-migrate.d.ts +31 -0
  28. package/dist/d1-migrate.js +156 -0
  29. package/dist/d1.d.ts +2 -0
  30. package/dist/d1.js +32 -0
  31. package/dist/db.d.ts +11 -0
  32. package/dist/db.js +20 -0
  33. package/dist/deploy.d.ts +25 -0
  34. package/dist/deploy.js +158 -0
  35. package/dist/dev-reload.d.ts +5 -0
  36. package/dist/dev-reload.js +123 -0
  37. package/dist/dev.d.ts +13 -0
  38. package/dist/dev.js +55 -0
  39. package/dist/host.d.ts +25 -0
  40. package/dist/host.js +107 -0
  41. package/dist/index.d.ts +26 -0
  42. package/dist/index.js +26 -0
  43. package/dist/instrumentation.d.ts +4 -0
  44. package/dist/instrumentation.js +15 -0
  45. package/dist/island-registry.d.ts +8 -0
  46. package/dist/island-registry.js +117 -0
  47. package/dist/kv.d.ts +8 -0
  48. package/dist/kv.js +35 -0
  49. package/dist/migrate-BbWt-PuB.d.ts +32 -0
  50. package/dist/migrate.d.ts +2 -0
  51. package/dist/migrate.js +115 -0
  52. package/dist/mysql-driver-BsrWVqbp.d.ts +12 -0
  53. package/dist/mysql-driver.d.ts +2 -0
  54. package/dist/mysql-driver.js +52 -0
  55. package/dist/negotiate.d.ts +11 -0
  56. package/dist/negotiate.js +39 -0
  57. package/dist/pipeline-B2V6tKfO.d.ts +49 -0
  58. package/dist/pipeline.d.ts +2 -0
  59. package/dist/pipeline.js +235 -0
  60. package/dist/postgres-driver-DkkBFikA.d.ts +15 -0
  61. package/dist/postgres-driver.d.ts +2 -0
  62. package/dist/postgres-driver.js +50 -0
  63. package/dist/resources.d.ts +15 -0
  64. package/dist/resources.js +31 -0
  65. package/dist/router.d.ts +32 -0
  66. package/dist/router.js +183 -0
  67. package/dist/rsc-build.d.ts +17 -0
  68. package/dist/rsc-build.js +68 -0
  69. package/dist/rsc-bundle.d.ts +8 -0
  70. package/dist/rsc-bundle.js +39 -0
  71. package/dist/rsc-manifest.d.ts +8 -0
  72. package/dist/rsc-manifest.js +67 -0
  73. package/dist/rsc-runtime/flight-render-routes.d.ts +5 -0
  74. package/dist/rsc-runtime/flight-render-routes.js +26 -0
  75. package/dist/rsc-runtime/flight-render.d.ts +4 -0
  76. package/dist/rsc-runtime/flight-render.js +21 -0
  77. package/dist/rsc-runtime/flight-to-html.d.ts +4 -0
  78. package/dist/rsc-runtime/flight-to-html.js +32 -0
  79. package/dist/rsc-runtime/rsc-worker.d.ts +6 -0
  80. package/dist/rsc-runtime/rsc-worker.js +46 -0
  81. package/dist/rsc-runtime/rsdw-shims.d.ts +42 -0
  82. package/dist/segment.d.ts +14 -0
  83. package/dist/segment.js +33 -0
  84. package/dist/sqlite-driver.d.ts +25 -0
  85. package/dist/sqlite-driver.js +102 -0
  86. package/dist/tsconfig-jsx.d.ts +15 -0
  87. package/dist/tsconfig-jsx.js +35 -0
  88. package/dist/turso-driver-C1wEigMZ.d.ts +22 -0
  89. package/dist/turso-driver.d.ts +2 -0
  90. package/dist/turso-driver.js +68 -0
  91. package/dist/worker.d.ts +55 -0
  92. package/dist/worker.js +209 -0
  93. package/package.json +115 -23
  94. package/src/adapter.ts +4 -4
  95. package/src/client-bundle.ts +3 -1
  96. package/src/rsc-bundle.ts +3 -2
@@ -0,0 +1,156 @@
1
+ import { migrate } from "./migrate.js";
2
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { basename, join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ //#region src/d1-migrate.ts
6
+ function inlineParams(sql, params) {
7
+ let i = 0;
8
+ return sql.replace(/\?/g, () => {
9
+ const p = params[i++];
10
+ if (p === null || p === void 0) return "null";
11
+ if (typeof p === "number" || typeof p === "bigint") return String(p);
12
+ if (typeof p === "boolean") return p ? "1" : "0";
13
+ return `'${String(p).replace(/'/g, "''")}'`;
14
+ });
15
+ }
16
+ function parseD1Json(stdout) {
17
+ const pick = (arr) => {
18
+ const f = arr[0] ?? {};
19
+ return {
20
+ results: f.results ?? [],
21
+ meta: f.meta ?? {}
22
+ };
23
+ };
24
+ const trimmed = stdout.trim();
25
+ try {
26
+ return pick(JSON.parse(trimmed));
27
+ } catch {
28
+ const lines = trimmed.split("\n");
29
+ for (let i = lines.length - 1; i >= 0; i--) if (lines[i].trimStart().startsWith("[")) try {
30
+ return pick(JSON.parse(lines.slice(i).join("\n")));
31
+ } catch {}
32
+ throw new Error(`wrangler d1 execute: no JSON array in output:\n${stdout}`);
33
+ }
34
+ }
35
+ function defaultExec(opts) {
36
+ return async ({ sql, mode, json }) => {
37
+ const base = [
38
+ "bunx",
39
+ `wrangler@${opts.wranglerVersion}`,
40
+ "d1",
41
+ "execute",
42
+ opts.database,
43
+ "--remote",
44
+ "--config",
45
+ opts.configPath
46
+ ];
47
+ let tmpDir;
48
+ if (mode === "file") {
49
+ tmpDir = await mkdtemp(join(tmpdir(), "june-d1-"));
50
+ const tmp = join(tmpDir, "migration.sql");
51
+ await writeFile(tmp, sql);
52
+ base.push("--file", tmp);
53
+ } else base.push("--command", sql);
54
+ if (json) base.push("--json");
55
+ try {
56
+ const proc = Bun.spawn(base, {
57
+ cwd: opts.cwd,
58
+ stdout: "pipe",
59
+ stderr: "pipe"
60
+ });
61
+ const [stdout, stderr, exitCode] = await Promise.all([
62
+ new Response(proc.stdout).text(),
63
+ new Response(proc.stderr).text(),
64
+ proc.exited
65
+ ]);
66
+ return {
67
+ stdout,
68
+ stderr,
69
+ exitCode
70
+ };
71
+ } finally {
72
+ if (tmpDir) await rm(tmpDir, {
73
+ recursive: true,
74
+ force: true
75
+ });
76
+ }
77
+ };
78
+ }
79
+ function wranglerD1(opts) {
80
+ const exec = opts.exec ?? defaultExec({
81
+ ...opts,
82
+ wranglerVersion: opts.wranglerVersion ?? "4.99.0"
83
+ });
84
+ const fail = (r) => {
85
+ throw new Error(`wrangler d1 execute failed (exit ${r.exitCode})\n${r.stderr || r.stdout}`.trim());
86
+ };
87
+ const db = {
88
+ async query(sql, params = []) {
89
+ const r = await exec({
90
+ sql: inlineParams(sql, params),
91
+ mode: "command",
92
+ json: true
93
+ });
94
+ if (r.exitCode !== 0) fail(r);
95
+ return parseD1Json(r.stdout).results;
96
+ },
97
+ async get(sql, params = []) {
98
+ return (await db.query(sql, params))[0] ?? void 0;
99
+ },
100
+ async run(sql, params = []) {
101
+ const r = await exec({
102
+ sql: inlineParams(sql, params),
103
+ mode: "command",
104
+ json: true
105
+ });
106
+ if (r.exitCode !== 0) fail(r);
107
+ const { meta } = parseD1Json(r.stdout);
108
+ return {
109
+ changes: meta.changes ?? 0,
110
+ lastInsertRowid: meta.last_row_id ?? 0
111
+ };
112
+ },
113
+ async exec(sql) {
114
+ const r = await exec({
115
+ sql,
116
+ mode: "file",
117
+ json: false
118
+ });
119
+ if (r.exitCode !== 0) fail(r);
120
+ },
121
+ async transaction(fn) {
122
+ return fn(db);
123
+ },
124
+ async close() {}
125
+ };
126
+ return db;
127
+ }
128
+ async function resolveD1Database(appRoot, configPath) {
129
+ try {
130
+ const raw = await Bun.file(configPath).text();
131
+ if (/\.jsonc?$/.test(configPath)) {
132
+ const json = raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
133
+ const name = JSON.parse(json).d1_databases?.[0]?.database_name;
134
+ if (name) return name;
135
+ }
136
+ } catch {}
137
+ const pkgPath = join(appRoot, "package.json");
138
+ let pkgName;
139
+ try {
140
+ pkgName = JSON.parse(await Bun.file(pkgPath).text()).name;
141
+ } catch {}
142
+ return `${(pkgName ?? basename(appRoot)).replace(/[^a-z0-9-]/gi, "-").toLowerCase()}-db`;
143
+ }
144
+ async function migrateD1(opts) {
145
+ return migrate(wranglerD1({
146
+ database: opts.database,
147
+ configPath: opts.configPath,
148
+ cwd: opts.appRoot,
149
+ exec: opts.exec
150
+ }), join(opts.appRoot, "db", "migrations"), {
151
+ allowDestructive: opts.allowDestructive,
152
+ now: opts.now
153
+ });
154
+ }
155
+ //#endregion
156
+ export { inlineParams, migrateD1, resolveD1Database, wranglerD1 };
package/dist/d1.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { n as d1, t as D1Database } from "./d1-BkdUg9kg.js";
2
+ export { D1Database, d1 };
package/dist/d1.js ADDED
@@ -0,0 +1,32 @@
1
+ //#region src/d1.ts
2
+ function d1(binding) {
3
+ const db = {
4
+ dialect: "sqlite",
5
+ async query(sql, params = []) {
6
+ return (await binding.prepare(sql).bind(...params).all()).results;
7
+ },
8
+ async get(sql, params = []) {
9
+ return await binding.prepare(sql).bind(...params).first() ?? void 0;
10
+ },
11
+ async run(sql, params = []) {
12
+ const { meta } = await binding.prepare(sql).bind(...params).run();
13
+ return {
14
+ changes: meta.changes ?? 0,
15
+ lastInsertRowid: meta.last_row_id ?? 0
16
+ };
17
+ },
18
+ async exec(sql) {
19
+ await binding.exec(sql);
20
+ },
21
+ async transaction(fn) {
22
+ return fn(db);
23
+ },
24
+ async close() {}
25
+ };
26
+ return {
27
+ kind: "d1",
28
+ open: async () => db
29
+ };
30
+ }
31
+ //#endregion
32
+ export { d1 };
package/dist/db.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { n as d1, t as D1Database } from "./d1-BkdUg9kg.js";
2
+ import { n as pgJuneDb, r as postgres, t as PgClientLike } from "./postgres-driver-DkkBFikA.js";
3
+ import { n as mysql, r as mysqlJuneDb, t as MysqlConnLike } from "./mysql-driver-BsrWVqbp.js";
4
+ import { i as turso, r as libsqlJuneDb, t as LibsqlClientLike } from "./turso-driver-C1wEigMZ.js";
5
+ import { DbFactory } from "@junejs/core/resources";
6
+ //#region src/db.d.ts
7
+ declare function sqlite(opts?: {
8
+ path?: string;
9
+ }): DbFactory;
10
+ //#endregion
11
+ export { type D1Database, type LibsqlClientLike, type MysqlConnLike, type PgClientLike, d1, libsqlJuneDb, mysql, mysqlJuneDb, pgJuneDb, postgres, sqlite, turso };
package/dist/db.js ADDED
@@ -0,0 +1,20 @@
1
+ import { d1 } from "./d1.js";
2
+ import { pgJuneDb, postgres } from "./postgres-driver.js";
3
+ import { mysql, mysqlJuneDb } from "./mysql-driver.js";
4
+ import { libsqlJuneDb, turso } from "./turso-driver.js";
5
+ //#region src/db.ts
6
+ function sqlite(opts = {}) {
7
+ const path = opts.path ?? ".june/dev.sqlite";
8
+ return {
9
+ kind: "sqlite",
10
+ open: async () => {
11
+ const { mkdirSync } = await import("node:fs");
12
+ const { dirname } = await import("node:path");
13
+ const { host } = await import("./host.js");
14
+ if (path !== ":memory:") mkdirSync(dirname(path), { recursive: true });
15
+ return host.openDb(path);
16
+ }
17
+ };
18
+ }
19
+ //#endregion
20
+ export { d1, libsqlJuneDb, mysql, mysqlJuneDb, pgJuneDb, postgres, sqlite, turso };
@@ -0,0 +1,25 @@
1
+ import { D1Exec } from "./d1-migrate.js";
2
+ //#region src/deploy.d.ts
3
+ type CliRun = (args: string[], cwd: string) => Promise<{
4
+ stdout: string;
5
+ stderr: string;
6
+ exitCode: number;
7
+ }>;
8
+ type DeployResult = {
9
+ url: string | null;
10
+ dryRun: boolean;
11
+ configPath: string;
12
+ migrated: string[];
13
+ };
14
+ type DeployOptions = {
15
+ dryRun?: boolean;
16
+ skipBuild?: boolean;
17
+ skipMigrate?: boolean;
18
+ allowDestructive?: boolean;
19
+ prod?: boolean;
20
+ d1Exec?: D1Exec;
21
+ runCli?: CliRun;
22
+ };
23
+ declare function juneDeploy(appRoot: string, options?: DeployOptions): Promise<DeployResult>;
24
+ //#endregion
25
+ export { DeployResult, juneDeploy };
package/dist/deploy.js ADDED
@@ -0,0 +1,158 @@
1
+ import { loadJuneConfig } from "./config-loader.js";
2
+ import { juneBuild } from "./build.js";
3
+ import { blockedMessage } from "./migrate.js";
4
+ import { migrateD1, resolveD1Database } from "./d1-migrate.js";
5
+ import { existsSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ //#region src/deploy.ts
8
+ const spawnCli = async (args, cwd) => {
9
+ const proc = Bun.spawn(args, {
10
+ cwd,
11
+ stdout: "pipe",
12
+ stderr: "pipe"
13
+ });
14
+ const [stdout, stderr, exitCode] = await Promise.all([
15
+ new Response(proc.stdout).text(),
16
+ new Response(proc.stderr).text(),
17
+ proc.exited
18
+ ]);
19
+ return {
20
+ stdout,
21
+ stderr,
22
+ exitCode
23
+ };
24
+ };
25
+ async function juneDeploy(appRoot, options = {}) {
26
+ const cfg = await loadJuneConfig(appRoot);
27
+ const target = (cfg.deploy?.adapter)?.name ?? cfg.deploy?.target ?? "workers";
28
+ if (target !== "workers" && target !== "vercel" && target !== "deno" && target !== "static") throw new Error(`unknown deploy target: ${target}`);
29
+ if (!options.skipBuild) {
30
+ const built = await juneBuild(appRoot);
31
+ console.log(`built ${built.outFile}`);
32
+ }
33
+ const run = options.runCli ?? spawnCli;
34
+ if (target === "static") return deployStatic(appRoot);
35
+ if (target === "vercel") return deployVercel(appRoot, options, run);
36
+ if (target === "deno") return deployDeno(appRoot, cfg, options, run);
37
+ return deployWorkers(appRoot, cfg, options, run);
38
+ }
39
+ async function deployStatic(appRoot) {
40
+ const site = join(appRoot, "dist", "static");
41
+ const index = join(site, "index.html");
42
+ if (!existsSync(index)) throw new Error(`no static site (expected ${index}) — run \`june build\` with the static() adapter first`);
43
+ console.log(`static site built → ${site}\n publish it with your host's CI (GitHub Pages: actions/upload-pages-artifact + deploy-pages,
44
+ or peaceiris/actions-gh-pages with publish_dir: dist/static). \`june deploy\` does not push.`);
45
+ return {
46
+ url: null,
47
+ dryRun: false,
48
+ configPath: site,
49
+ migrated: []
50
+ };
51
+ }
52
+ async function deployDeno(appRoot, cfg, options, run) {
53
+ const dist = join(appRoot, "dist");
54
+ if (!existsSync(join(dist, "worker.js"))) throw new Error(`no built bundle (expected ${join(dist, "worker.js")}) — run june build first`);
55
+ if (options.dryRun) {
56
+ console.log(`dry-run: built ${dist} (skipping deno deploy)`);
57
+ return {
58
+ url: null,
59
+ dryRun: true,
60
+ configPath: dist,
61
+ migrated: []
62
+ };
63
+ }
64
+ if (!process.env.DENO_DEPLOY_TOKEN) console.log("note: DENO_DEPLOY_TOKEN not set — `deno deploy` will use its own login\n (run `deno deploy login` once, or export the token).");
65
+ const args = ["deno", "deploy"];
66
+ if (options.prod) args.push("--prod");
67
+ const { stdout, stderr, exitCode } = await run(args, dist);
68
+ if (stdout.trim()) console.log(stdout.trimEnd());
69
+ if (exitCode !== 0) {
70
+ const out = stderr + stdout;
71
+ if (/token|login|unauthorized|forbidden|organization was not found|app.*not found/i.test(out)) throw new Error("Deno Deploy auth/org failed — set DENO_DEPLOY_TOKEN (or `deno deploy login`) and the\n org/app in deno({ org, app }).\n first deploy? provision the app in dynamic mode (auto-detect won't find an entrypoint):\n deno deploy create --org=<org> --app=<app> --source=local \\\n --do-not-use-detected-build-config --runtime-mode=dynamic --entrypoint=worker.js\n" + stderr.trim());
72
+ if (/no runtime entrypoint|build config/i.test(out)) throw new Error("Deno Deploy build failed — first deploy? provision the app in dynamic mode (auto-detect won't find an entrypoint):\n deno deploy create --org=<org> --app=<app> --source=local \\\n --do-not-use-detected-build-config --runtime-mode=dynamic --entrypoint=worker.js\n" + stderr.trim());
73
+ throw new Error(`deno deploy failed (exit ${exitCode})\n${stderr.trim()}`);
74
+ }
75
+ return {
76
+ url: stdout.match(/https:\/\/\S+\.deno\.(net|dev)\S*/)?.[0] ?? null,
77
+ dryRun: false,
78
+ configPath: dist,
79
+ migrated: []
80
+ };
81
+ }
82
+ async function deployVercel(appRoot, options, run) {
83
+ const out = join(appRoot, ".vercel", "output");
84
+ if (!existsSync(join(out, "config.json"))) throw new Error(`no Vercel Build Output found (expected ${out}/config.json) — run june build first`);
85
+ if (options.dryRun) {
86
+ console.log(`dry-run: built ${out} (skipping vercel deploy)`);
87
+ return {
88
+ url: null,
89
+ dryRun: true,
90
+ configPath: out,
91
+ migrated: []
92
+ };
93
+ }
94
+ if (!process.env.VERCEL_TOKEN) console.log("note: VERCEL_TOKEN not set — the vercel CLI will use its own login\n (run `bunx vercel login` once, or export the token).");
95
+ const args = [
96
+ "bunx",
97
+ "vercel@latest",
98
+ "deploy",
99
+ "--prebuilt",
100
+ "--yes"
101
+ ];
102
+ if (options.prod) args.push("--prod");
103
+ const { stdout, stderr, exitCode } = await run(args, appRoot);
104
+ if (stdout.trim()) console.log(stdout.trimEnd());
105
+ if (exitCode !== 0) {
106
+ if (/credentials|token|forbidden|unauthorized/i.test(stderr + stdout)) throw new Error("vercel authentication failed — set VERCEL_TOKEN or run `bunx vercel login`.\n" + stderr.trim());
107
+ throw new Error(`vercel deploy failed (exit ${exitCode})\n${stderr.trim()}`);
108
+ }
109
+ return {
110
+ url: stdout.match(/https:\/\/\S+\.vercel\.app\S*/)?.[0] ?? null,
111
+ dryRun: false,
112
+ configPath: out,
113
+ migrated: []
114
+ };
115
+ }
116
+ async function deployWorkers(appRoot, cfg, options, run) {
117
+ const configPath = [join(appRoot, "wrangler.toml"), join(appRoot, "wrangler.jsonc")].find(existsSync) ?? join(appRoot, "dist/wrangler.jsonc");
118
+ if (!existsSync(configPath)) throw new Error(`no wrangler config found (expected ${configPath}) — run june build first`);
119
+ const migrated = [];
120
+ if (cfg.resources?.db && !options.skipMigrate && !options.dryRun) {
121
+ const database = await resolveD1Database(appRoot, configPath);
122
+ if (database) {
123
+ const r = await migrateD1({
124
+ appRoot,
125
+ database,
126
+ configPath,
127
+ allowDestructive: options.allowDestructive,
128
+ exec: options.d1Exec
129
+ });
130
+ if (r.blocked) throw new Error(`D1 ${database}: ${blockedMessage(r.blocked)}`);
131
+ migrated.push(...r.applied);
132
+ console.log(r.applied.length ? `migrated D1 ${database}: ${r.applied.join(", ")}` : `D1 ${database}: migrations up to date`);
133
+ }
134
+ }
135
+ if (!options.dryRun && !process.env.CLOUDFLARE_API_TOKEN) console.log("note: CLOUDFLARE_API_TOKEN not set — wrangler will fall back to its own login\n (run `bunx wrangler login` once, or export the token).");
136
+ const args = [
137
+ "bunx",
138
+ "wrangler@4.99.0",
139
+ "deploy",
140
+ "--config",
141
+ configPath
142
+ ];
143
+ if (options.dryRun) args.push("--dry-run");
144
+ const { stdout: out, stderr: err, exitCode: code } = await run(args, appRoot);
145
+ if (out.trim()) console.log(out.trimEnd());
146
+ if (code !== 0) {
147
+ if (/authentication|login|10000/i.test(err + out)) throw new Error("wrangler authentication failed — set CLOUDFLARE_API_TOKEN or run `bunx wrangler login`.\n" + err.trim());
148
+ throw new Error(`wrangler deploy failed (exit ${code})\n${err.trim()}`);
149
+ }
150
+ return {
151
+ url: out.match(/https:\/\/\S+\.workers\.dev\S*/)?.[0] ?? null,
152
+ dryRun: options.dryRun ?? false,
153
+ configPath,
154
+ migrated
155
+ };
156
+ }
157
+ //#endregion
158
+ export { juneDeploy };
@@ -0,0 +1,5 @@
1
+ //#region src/dev-reload.d.ts
2
+ declare function notifyCssChange(): void;
3
+ declare function withLiveReload(fetchApp: (req: Request) => Promise<Response>): (req: Request) => Promise<Response>;
4
+ //#endregion
5
+ export { notifyCssChange, withLiveReload };
@@ -0,0 +1,123 @@
1
+ //#region src/dev-reload.ts
2
+ const EVENTS_PATH = "/__june/events";
3
+ const SCRIPT_PATH = "/__june/reload.js";
4
+ const RELOAD_JS = `// june dev live-reload: reconnect-after-drop → reload; "css" event → hot-swap
5
+ (() => {
6
+ let dropped = false;
7
+ let es;
8
+ const swapCss = () => {
9
+ document.querySelectorAll('link[rel="stylesheet"]').forEach((old) => {
10
+ const u = new URL(old.href);
11
+ if (u.pathname !== "/_june/global.css") return;
12
+ // Clone with a cache-busted href; drop the old one once the new loads so
13
+ // there's no flash of unstyled content. No page reload → island state and
14
+ // scroll survive.
15
+ const next = old.cloneNode();
16
+ u.searchParams.set("t", Date.now());
17
+ next.href = u.pathname + u.search;
18
+ next.addEventListener("load", () => old.remove(), { once: true });
19
+ old.parentNode.insertBefore(next, old.nextSibling);
20
+ });
21
+ };
22
+ const connect = () => {
23
+ es = new EventSource(${JSON.stringify(EVENTS_PATH)});
24
+ es.addEventListener("open", () => {
25
+ if (dropped) {
26
+ // push-HMR: morph the freshly-restarted page in place (state survives);
27
+ // hard-reload only if there's no morph hook or it can't apply. On a
28
+ // successful morph also swap the stylesheet: a .tsx edit can change the
29
+ // class set, so the Tailwind-compiled /_june/global.css must refresh too
30
+ // (cache-busted) — otherwise new utility classes render unstyled.
31
+ const hot = window.__juneLiveReload;
32
+ (hot ? hot() : Promise.resolve(false)).then(
33
+ (ok) => { if (ok) swapCss(); else location.reload(); },
34
+ () => location.reload(),
35
+ );
36
+ }
37
+ dropped = false;
38
+ });
39
+ es.addEventListener("css", swapCss); // stylesheet edit → swap, no reload
40
+ es.addEventListener("error", () => {
41
+ dropped = true;
42
+ // EventSource auto-retries while the connection is flaky, but goes to
43
+ // CLOSED (and stays there) on hard failures like a refused connection
44
+ // mid-restart — recreate it ourselves in that case.
45
+ if (es.readyState === EventSource.CLOSED) {
46
+ es.close();
47
+ setTimeout(connect, 300);
48
+ }
49
+ });
50
+ };
51
+ // Release the SSE on navigation. In MPA mode every click is a full reload that
52
+ // opens a fresh EventSource; without this the old connection lingers and, since
53
+ // each held SSE consumes one of the browser's ~6 HTTP/1.1 slots per host, rapid
54
+ // navigation saturates them — further page loads stall (pending) / 503.
55
+ addEventListener("pagehide", () => { try { es && es.close(); } catch (e) {} });
56
+ connect();
57
+ })();
58
+ `;
59
+ const clients = /* @__PURE__ */ new Set();
60
+ const enc = new TextEncoder();
61
+ function notifyCssChange() {
62
+ const msg = enc.encode("event: css\ndata: 1\n\n");
63
+ for (const c of clients) try {
64
+ c.enqueue(msg);
65
+ } catch {
66
+ clients.delete(c);
67
+ }
68
+ }
69
+ function devEvents() {
70
+ let heartbeat;
71
+ let self;
72
+ const stream = new ReadableStream({
73
+ start(controller) {
74
+ self = controller;
75
+ clients.add(controller);
76
+ controller.enqueue(enc.encode("retry: 300\n\ndata: connected\n\n"));
77
+ heartbeat = setInterval(() => {
78
+ try {
79
+ controller.enqueue(enc.encode(":hb\n\n"));
80
+ } catch {
81
+ clearInterval(heartbeat);
82
+ }
83
+ }, 2e4);
84
+ },
85
+ cancel() {
86
+ clearInterval(heartbeat);
87
+ if (self) clients.delete(self);
88
+ }
89
+ });
90
+ return new Response(stream, { headers: {
91
+ "content-type": "text/event-stream",
92
+ "cache-control": "no-store"
93
+ } });
94
+ }
95
+ function withLiveReload(fetchApp) {
96
+ return async (req) => {
97
+ const { pathname } = new URL(req.url);
98
+ if (pathname === EVENTS_PATH) return devEvents();
99
+ if (pathname === SCRIPT_PATH) return new Response(RELOAD_JS, { headers: {
100
+ "content-type": "text/javascript; charset=utf-8",
101
+ "cache-control": "no-store"
102
+ } });
103
+ const res = await fetchApp(req);
104
+ if (!res.headers.get("content-type")?.includes("text/html") || !res.body) return res;
105
+ const tag = new TextEncoder().encode(`<script src="${SCRIPT_PATH}" defer><\/script>`);
106
+ const tagged = res.body.pipeThrough(new TransformStream({
107
+ transform(chunk, controller) {
108
+ controller.enqueue(chunk);
109
+ },
110
+ flush(controller) {
111
+ controller.enqueue(tag);
112
+ }
113
+ }));
114
+ const headers = new Headers(res.headers);
115
+ headers.delete("content-length");
116
+ return new Response(tagged, {
117
+ status: res.status,
118
+ headers
119
+ });
120
+ };
121
+ }
122
+ //#endregion
123
+ export { notifyCssChange, withLiveReload };
package/dist/dev.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { JuneHost, ServeHandle } from "./host.js";
2
+ //#region src/dev.d.ts
3
+ type DevServerOptions = {
4
+ appDir: string;
5
+ port?: number;
6
+ host?: JuneHost;
7
+ };
8
+ type DevServer = ServeHandle & {
9
+ url: string;
10
+ };
11
+ declare function startDevServer({ appDir, port, host }: DevServerOptions): Promise<DevServer>;
12
+ //#endregion
13
+ export { DevServer, DevServerOptions, startDevServer };
package/dist/dev.js ADDED
@@ -0,0 +1,55 @@
1
+ import { findGlobalCss, invalidateCss, processCssCached } from "./css.js";
2
+ import { createApp } from "./app.js";
3
+ import { loadJuneConfig } from "./config-loader.js";
4
+ import { blockedMessage, migrateApp } from "./migrate.js";
5
+ import { notifyCssChange, withLiveReload } from "./dev-reload.js";
6
+ import { installAsyncContext } from "./instrumentation.js";
7
+ import { host } from "./host.js";
8
+ import { watch } from "node:fs";
9
+ import { dirname } from "node:path";
10
+ import { createServer } from "node:net";
11
+ //#region src/dev.ts
12
+ async function findFreePort(start, tries = 20) {
13
+ for (let p = start; p < start + tries; p++) if (await new Promise((resolve) => {
14
+ const probe = createServer();
15
+ probe.once("error", () => resolve(false));
16
+ probe.listen(p, () => probe.close(() => resolve(true)));
17
+ })) return p;
18
+ throw new Error(`june dev: no free port between ${start} and ${start + tries - 1}`);
19
+ }
20
+ async function startDevServer({ appDir, port = 3e3, host: host$1 = host }) {
21
+ await installAsyncContext();
22
+ const config = await loadJuneConfig(appDir);
23
+ const m = await migrateApp(dirname(appDir), config);
24
+ if (m?.applied.length) console.log(`[june] migrated: ${m.applied.join(", ")}`);
25
+ if (m?.blocked) console.warn(`[june] ${blockedMessage(m.blocked)}`);
26
+ const app = createApp({
27
+ appDir,
28
+ config
29
+ });
30
+ await app.warmup();
31
+ const freePort = await findFreePort(port);
32
+ if (freePort !== port) console.log(`[june] port ${port} is taken → using ${freePort}`);
33
+ port = freePort;
34
+ const handle = host$1.serve(withLiveReload((req) => app.fetch(req)), {
35
+ port,
36
+ earlyHints: () => app.earlyHints()
37
+ });
38
+ if (findGlobalCss(appDir)) {
39
+ watch(appDir, { recursive: true }, (_event, file) => {
40
+ if (file && file.endsWith(".css")) {
41
+ invalidateCss();
42
+ notifyCssChange();
43
+ }
44
+ });
45
+ processCssCached(appDir).catch(() => {});
46
+ }
47
+ const url = `http://localhost:${handle.port}`;
48
+ console.log(`june dev → ${url} (host: ${host$1.name})`);
49
+ return {
50
+ ...handle,
51
+ url
52
+ };
53
+ }
54
+ //#endregion
55
+ export { startDevServer };
package/dist/host.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { JuneDb, RunResult } from "@junejs/core/resources";
2
+ //#region src/host.d.ts
3
+ type ServeHandle = {
4
+ port: number;
5
+ stop(force?: boolean): void;
6
+ };
7
+ type SpawnedModule = {
8
+ stdout: ReadableStream<Uint8Array>;
9
+ stderrText(): Promise<string>;
10
+ exited: Promise<number>;
11
+ };
12
+ interface JuneHost {
13
+ readonly name: "bun" | "node";
14
+ serve(handler: (req: Request) => Promise<Response>, opts: {
15
+ port: number;
16
+ earlyHints?: () => string[];
17
+ }): ServeHandle;
18
+ spawnModule(entry: string, args: string[], opts: {
19
+ conditions?: string[];
20
+ }): SpawnedModule;
21
+ openDb(path: string): Promise<JuneDb>;
22
+ }
23
+ declare const host: JuneHost;
24
+ //#endregion
25
+ export { type JuneDb, JuneHost, type RunResult, ServeHandle, SpawnedModule, host };