@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.
- package/dist/adapter.d.ts +55 -0
- package/dist/adapter.js +204 -0
- package/dist/agent-discover.d.ts +5 -0
- package/dist/agent-discover.js +75 -0
- package/dist/agent-durable.d.ts +60 -0
- package/dist/agent-durable.js +106 -0
- package/dist/agent-native.d.ts +40 -0
- package/dist/agent-native.js +179 -0
- package/dist/app.d.ts +15 -0
- package/dist/app.js +218 -0
- package/dist/blob.d.ts +23 -0
- package/dist/blob.js +78 -0
- package/dist/build.d.ts +40 -0
- package/dist/build.js +551 -0
- package/dist/client-bundle.d.ts +15 -0
- package/dist/client-bundle.js +91 -0
- package/dist/config-loader.d.ts +9 -0
- package/dist/config-loader.js +25 -0
- package/dist/content.d.ts +39 -0
- package/dist/content.js +201 -0
- package/dist/css-modules-ambient.d.ts +10 -0
- package/dist/css-modules.d.ts +24 -0
- package/dist/css-modules.js +127 -0
- package/dist/css.d.ts +14 -0
- package/dist/css.js +114 -0
- package/dist/d1-BkdUg9kg.d.ts +24 -0
- package/dist/d1-migrate.d.ts +31 -0
- package/dist/d1-migrate.js +156 -0
- package/dist/d1.d.ts +2 -0
- package/dist/d1.js +32 -0
- package/dist/db.d.ts +11 -0
- package/dist/db.js +20 -0
- package/dist/deploy.d.ts +25 -0
- package/dist/deploy.js +158 -0
- package/dist/dev-reload.d.ts +5 -0
- package/dist/dev-reload.js +123 -0
- package/dist/dev.d.ts +13 -0
- package/dist/dev.js +55 -0
- package/dist/host.d.ts +25 -0
- package/dist/host.js +107 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +26 -0
- package/dist/instrumentation.d.ts +4 -0
- package/dist/instrumentation.js +15 -0
- package/dist/island-registry.d.ts +8 -0
- package/dist/island-registry.js +117 -0
- package/dist/kv.d.ts +8 -0
- package/dist/kv.js +35 -0
- package/dist/migrate-BbWt-PuB.d.ts +32 -0
- package/dist/migrate.d.ts +2 -0
- package/dist/migrate.js +115 -0
- package/dist/mysql-driver-BsrWVqbp.d.ts +12 -0
- package/dist/mysql-driver.d.ts +2 -0
- package/dist/mysql-driver.js +52 -0
- package/dist/negotiate.d.ts +11 -0
- package/dist/negotiate.js +39 -0
- package/dist/pipeline-B2V6tKfO.d.ts +49 -0
- package/dist/pipeline.d.ts +2 -0
- package/dist/pipeline.js +235 -0
- package/dist/postgres-driver-DkkBFikA.d.ts +15 -0
- package/dist/postgres-driver.d.ts +2 -0
- package/dist/postgres-driver.js +50 -0
- package/dist/resources.d.ts +15 -0
- package/dist/resources.js +31 -0
- package/dist/router.d.ts +32 -0
- package/dist/router.js +183 -0
- package/dist/rsc-build.d.ts +17 -0
- package/dist/rsc-build.js +68 -0
- package/dist/rsc-bundle.d.ts +8 -0
- package/dist/rsc-bundle.js +39 -0
- package/dist/rsc-manifest.d.ts +8 -0
- package/dist/rsc-manifest.js +67 -0
- package/dist/rsc-runtime/flight-render-routes.d.ts +5 -0
- package/dist/rsc-runtime/flight-render-routes.js +26 -0
- package/dist/rsc-runtime/flight-render.d.ts +4 -0
- package/dist/rsc-runtime/flight-render.js +21 -0
- package/dist/rsc-runtime/flight-to-html.d.ts +4 -0
- package/dist/rsc-runtime/flight-to-html.js +32 -0
- package/dist/rsc-runtime/rsc-worker.d.ts +6 -0
- package/dist/rsc-runtime/rsc-worker.js +46 -0
- package/dist/rsc-runtime/rsdw-shims.d.ts +42 -0
- package/dist/segment.d.ts +14 -0
- package/dist/segment.js +33 -0
- package/dist/sqlite-driver.d.ts +25 -0
- package/dist/sqlite-driver.js +102 -0
- package/dist/tsconfig-jsx.d.ts +15 -0
- package/dist/tsconfig-jsx.js +35 -0
- package/dist/turso-driver-C1wEigMZ.d.ts +22 -0
- package/dist/turso-driver.d.ts +2 -0
- package/dist/turso-driver.js +68 -0
- package/dist/worker.d.ts +55 -0
- package/dist/worker.js +209 -0
- package/package.json +115 -23
- package/src/adapter.ts +4 -4
- package/src/client-bundle.ts +3 -1
- 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
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 };
|
package/dist/deploy.d.ts
ADDED
|
@@ -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,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 };
|