prasi-srv/internal/api/_deploy.ts

242 lines
6.8 KiB
TypeScript

import { $ } from "bun";
import { readdirSync } from "fs";
import { readAsync, removeAsync, writeAsync } from "fs-jetpack";
import { apiContext } from "utils/api-context";
import { config as _config, type SiteConfig } from "utils/config";
import { fs } from "utils/fs";
import { genEnv, parseEnv } from "utils/parse-env";
export const _ = {
url: "/_deploy",
async api(
action: (
| { type: "check" }
| { type: "db-update"; url: string; orm: SiteConfig["db"]["orm"] }
| { type: "db-pull" }
| { type: "db-gen" }
| { type: "db-ver" }
| { type: "db-sync"; url: string }
| { type: "restart" }
| { type: "domain-add"; domain: string }
| { type: "domain-del"; domain: string }
| { type: "deploy-del"; ts: string }
| { type: "deploy"; load_from?: string }
| { type: "deploy-status" }
| { type: "redeploy"; ts: string }
) & {
id_site: string;
}
) {
const { res, req } = apiContext(this);
const deploy = _config.current?.deploy!;
const config = _config.current!;
if (typeof req.query_parameters["export"] !== "undefined") {
return new Response(
Bun.file(fs.path(`site:deploy/current/${deploy.current}.gz`))
);
}
switch (action.type) {
case "check":
const deploys = readdirSync(fs.path("site:deploy/history"));
return {
now: Date.now(),
current: deploy.current,
deploys: deploys
.filter((e) => e.endsWith(".gz"))
.map((e) => parseInt(e.replace(".gz", ""))),
db: {
url: config.db.url,
orm: config.db.orm,
},
};
case "db-ver": {
return (await fs.read(`site:app/db/version`, "string")) || "";
}
case "db-sync": {
const res = await fetch(action.url);
const text = await res.text();
if (text) {
await Bun.write(fs.path("site:app/db/prisma/schema.prisma"), text);
await Bun.write(
fs.path(`site:app/db/version`),
Date.now().toString()
);
}
return "ok";
}
case "db-update":
if (action.url) {
config.db.url = action.url;
config.db.orm = action.orm;
const env = genEnv({
...parseEnv(await Bun.file(fs.path("site:app/db/.env")).text()),
DATABASE_URL: action.url,
});
await Bun.write(fs.path("site:app/db/.env"), env);
}
return "ok";
case "db-gen":
{
await $`bun prisma generate`.cwd(fs.path("site:app/db"));
res.send("ok");
setTimeout(() => {
// restartServer();
}, 300);
}
break;
case "db-pull":
{
let env = await readAsync(fs.path("site:app/db/.env"));
if (env) {
const ENV = parseEnv(env);
if (typeof ENV.DATABASE_URL === "string") {
const type = ENV.DATABASE_URL.split("://").shift();
if (type) {
await writeAsync(
fs.path("site:app/db/prisma/schema.prisma"),
`\
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "${type}"
url = env("DATABASE_URL")
}`
);
try {
await Bun.write(
fs.path("site:app/db/.env"),
`DATABASE_URL=${ENV.DATABASE_URL}`
);
await $`bun install`.cwd(fs.path("site:app/db"));
await $`bun prisma db pull --force`.cwd(
fs.path("site:app/db")
);
await $`bun prisma generate`.cwd(fs.path("site:app/db"));
await Bun.write(
fs.path(`site:app/db/version`),
Date.now().toString()
);
} catch (e) {
console.error(e);
}
res.send("ok");
setTimeout(() => {
// restartServer();
}, 300);
}
}
}
}
break;
case "restart":
{
res.send("ok");
setTimeout(() => {
// restartServer();
}, 300);
}
break;
case "deploy-del":
{
await removeAsync(fs.path(`site:deploy/history/${action.ts}.gz`));
const deploys = readdirSync(fs.path(`site:deploy/history`));
return {
now: Date.now(),
current: deploy.current,
deploys: deploys
.filter((e) => e.endsWith(".gz"))
.map((e) => parseInt(e.replace(".gz", ""))),
};
}
break;
case "deploy-status":
break;
case "deploy":
{
await _config.init("site:site.json");
await _config.set("site_id", action.id_site);
fs.init(config);
return {
now: Date.now(),
current: deploy.current,
deploys: config.deploy.history,
};
}
break;
case "redeploy":
{
// deploy.config.deploy.ts = action.ts;
// await deploy.saveConfig();
// await deploy.load(action.ts);
// const deploys = fs.readdirSync(dir(`/app/web/deploy`));
// return {
// now: Date.now(),
// current: parseInt(deploy.config.deploy.ts),
// deploys: deploys
// .filter((e) => e.endsWith(".gz"))
// .map((e) => parseInt(e.replace(".gz", ""))),
// };
}
break;
}
},
};
export const downloadFile = async (
url: string,
filePath: string,
progress?: (rec: number, total: number) => void
) => {
try {
const _url = new URL(url);
if (_url.hostname === "localhost") {
_url.hostname = "127.0.0.1";
}
// g.log.info(`Downloading ${url} to ${filePath}`);
const res = await fetch(_url as any);
if (res.body) {
const file = Bun.file(filePath);
const writer = file.writer();
const reader = res.body.getReader();
// Step 3: read the data
let receivedLength = 0; // received that many bytes at the moment
let chunks = []; // array of received binary chunks (comprises the body)
while (true) {
const { done, value } = await reader.read();
if (done) {
writer.end();
break;
}
chunks.push(value);
writer.write(value);
receivedLength += value.length;
if (progress) {
progress(
receivedLength,
parseInt(res.headers.get("content-length") || "0")
);
}
}
}
return true;
} catch (e) {
console.log(e);
return false;
}
};