init
This commit is contained in:
commit
5d731ee191
|
|
@ -0,0 +1,178 @@
|
||||||
|
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
npm-debug.log_
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Caches
|
||||||
|
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
|
||||||
|
pids
|
||||||
|
_.pid
|
||||||
|
_.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
site
|
||||||
|
site/*
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.git": true,
|
||||||
|
"**/.svn": true,
|
||||||
|
"**/.hg": true,
|
||||||
|
"**/CVS": true,
|
||||||
|
"**/.DS_Store": true,
|
||||||
|
"**/Thumbs.db": true,
|
||||||
|
".codesandbox": true,
|
||||||
|
".devcontainer": true,
|
||||||
|
".vscode": true,
|
||||||
|
"node_modules": true,
|
||||||
|
".gitignore": true,
|
||||||
|
"bun.lockb": true,
|
||||||
|
"port.json": true
|
||||||
|
},
|
||||||
|
"hide-files.files": []
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { $ } from "bun";
|
||||||
|
import { dirAsync, removeAsync } from "fs-jetpack";
|
||||||
|
import { c } from "utils/color";
|
||||||
|
import { config } from "utils/config";
|
||||||
|
import { fs } from "utils/fs";
|
||||||
|
import { g } from "utils/global";
|
||||||
|
import { dbLog } from "utils/log";
|
||||||
|
|
||||||
|
export const ensureDBReady = async () => {
|
||||||
|
if (!config.current?.db?.url) {
|
||||||
|
dbLog("Warning: db.url is empty. Please set it in site.json");
|
||||||
|
} else {
|
||||||
|
const db = config.current?.db;
|
||||||
|
if (db.orm === "prisma") {
|
||||||
|
const cwd = fs.path(`site:app/db`);
|
||||||
|
|
||||||
|
const url = new URL(db.url);
|
||||||
|
const host = `[${c.blue}${url.hostname}${c.esc}]`;
|
||||||
|
const db_type = `[${c.red}${url.protocol.slice(0, -1).toUpperCase()}${
|
||||||
|
c.esc
|
||||||
|
}]`;
|
||||||
|
if (!fs.exists("site:app/db")) {
|
||||||
|
dbLog(`Preparing PrismaDB ${db_type} on ${host} ${url.pathname}`);
|
||||||
|
await removeAsync(cwd);
|
||||||
|
await dirAsync(cwd);
|
||||||
|
|
||||||
|
await $`bun init .`.cwd(cwd).quiet();
|
||||||
|
await $`bun add prisma`.cwd(cwd).quiet();
|
||||||
|
await $`bun prisma init`.cwd(cwd).quiet();
|
||||||
|
|
||||||
|
dbLog(`PrismaDB created at ${cwd}`);
|
||||||
|
fs.write(`site:app/db/.env`, `DATABASE_URL=${db.url}`);
|
||||||
|
|
||||||
|
await $`bun prisma db pull`.cwd(cwd).quiet();
|
||||||
|
dbLog(`PrismaDB instrospected (db pull)`);
|
||||||
|
|
||||||
|
await $`bun prisma generate`.cwd(cwd).quiet();
|
||||||
|
dbLog(`PrismaDB ready`);
|
||||||
|
|
||||||
|
await fs.write(
|
||||||
|
`site:app/db/index.ts`,
|
||||||
|
`\
|
||||||
|
import { PrismaClient } from "@prisma/client/extension";
|
||||||
|
export const db = new PrismaClient();
|
||||||
|
`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await $`bun prisma generate`.cwd(cwd).quiet();
|
||||||
|
dbLog(`PrismaDB Ready: ${db_type} on ${host} ${url.pathname}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { config } from "utils/config";
|
||||||
|
import { downloadFile } from "utils/download";
|
||||||
|
import { fs } from "utils/fs";
|
||||||
|
import { siteLog } from "utils/log";
|
||||||
|
|
||||||
|
export const downloadDeployedSite = async (site_id: string) => {
|
||||||
|
let base_url = "https://prasi.avolut.com";
|
||||||
|
const ts = Date.now();
|
||||||
|
siteLog("Downloading site deploy: ");
|
||||||
|
await downloadFile(
|
||||||
|
`${base_url}/prod-zip/${site_id}?ts=${ts}&msgpack=1`,
|
||||||
|
fs.path(`site:deploy/history/${ts}.gz`),
|
||||||
|
(rec, total) => {
|
||||||
|
if (rec % 10 === 0) process.stdout.write(".");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
config.set("deploy.current", ts);
|
||||||
|
config.set("deploy.history", [...(config.current?.deploy.history || []), ts]);
|
||||||
|
await fs.copy(`site:deploy/history/${ts}.gz`, `site:deploy/current/${ts}.gz`);
|
||||||
|
return ts;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { config } from "utils/config";
|
||||||
|
import { fs } from "utils/fs";
|
||||||
|
import { downloadDeployedSite } from "./download";
|
||||||
|
|
||||||
|
export const ensureDeployExists = async (site_id: string) => {
|
||||||
|
let download_deploy = false;
|
||||||
|
const ts = config.current?.deploy.current;
|
||||||
|
if (!ts) {
|
||||||
|
download_deploy = true;
|
||||||
|
} else if (!fs.exists(`site:deploy/current/${ts}.gz`)) {
|
||||||
|
if (fs.exists(`site:deploy/history/${ts}.gz`)) {
|
||||||
|
await fs.copy(
|
||||||
|
`site:deploy/history/${ts}.gz`,
|
||||||
|
`site:deploy/current/${ts}.gz`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
download_deploy = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (download_deploy) {
|
||||||
|
const ts = await downloadDeployedSite(site_id);
|
||||||
|
process.stdout.write("\n");
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
return ts as number;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { gunzipSync } from "bun";
|
||||||
|
import { removeAsync } from "fs-jetpack";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { decode } from "msgpackr";
|
||||||
|
import { config } from "utils/config";
|
||||||
|
import { fs } from "utils/fs";
|
||||||
|
import { g } from "utils/global";
|
||||||
|
|
||||||
|
export const loadCurrentDeploy = async (ts: number) => {
|
||||||
|
if (fs.exists(`site:deploy/current/${ts}.gz`)) {
|
||||||
|
await removeAsync(fs.path(`site:deploy/current/files`));
|
||||||
|
|
||||||
|
const content = decode(
|
||||||
|
gunzipSync(
|
||||||
|
new Uint8Array(
|
||||||
|
await Bun.file(fs.path(`site:deploy/current/${ts}.gz`)).arrayBuffer()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
g.site = {
|
||||||
|
layouts: content.layouts,
|
||||||
|
pages: content.pages,
|
||||||
|
comps: content.comps,
|
||||||
|
db: config.current?.db,
|
||||||
|
info: content.site,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const key of ["public", "code.server", "code.site", "code.core"]) {
|
||||||
|
const files = get(content, key);
|
||||||
|
if (files) {
|
||||||
|
for (const [path, raw_content] of Object.entries(files)) {
|
||||||
|
const prefix = key.split(".").pop() || "";
|
||||||
|
await fs.write(
|
||||||
|
`site:deploy/current/files/${prefix ? `${prefix}/` : ""}${path}`,
|
||||||
|
raw_content,
|
||||||
|
{
|
||||||
|
mode: "raw",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { $ } from "bun";
|
||||||
|
import { fs } from "utils/fs";
|
||||||
|
|
||||||
|
export const ensureServerReady = async (is_dev: boolean) => {
|
||||||
|
if (!fs.exists("site:app/package.json")) {
|
||||||
|
await fs.write(`site:app/package.json`, {
|
||||||
|
name: "prasi-app",
|
||||||
|
workspaces: ["db"],
|
||||||
|
dependencies: { db: "workspace:*" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await $`bun i`.cwd(fs.path("site:app")).quiet().nothrow();
|
||||||
|
|
||||||
|
if (is_dev) {
|
||||||
|
const rebuild = async () => {
|
||||||
|
try {
|
||||||
|
await $`bun build --watch --target bun --entry ${fs.path(
|
||||||
|
"internal:server/server.ts"
|
||||||
|
)} --outdir ${fs.path("site:app")} --sourcemap=linked`.quiet();
|
||||||
|
} catch (e) {
|
||||||
|
rebuild();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rebuild();
|
||||||
|
} else {
|
||||||
|
await Bun.build({
|
||||||
|
target: "bun",
|
||||||
|
entrypoints: [fs.path(`internal:server/server.ts`)],
|
||||||
|
outdir: fs.path("site:app"),
|
||||||
|
sourcemap: "linked",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { c } from "utils/color";
|
||||||
|
import { config } from "utils/config";
|
||||||
|
import { g, startup } from "utils/global";
|
||||||
|
import { siteLog } from "utils/log";
|
||||||
|
import { loadCurrentDeploy } from "../deploy/load";
|
||||||
|
|
||||||
|
startup("site", async () => {
|
||||||
|
await config.init("site:site.json");
|
||||||
|
const ts = config.current?.deploy.current;
|
||||||
|
if (ts) {
|
||||||
|
await loadCurrentDeploy(ts);
|
||||||
|
siteLog(
|
||||||
|
`Site Loaded [${c.green}${g.site.pages.length} pages${c.esc}] [${c.blue}${g.site.comps.length} components${c.esc}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { fs } from "utils/fs";
|
||||||
|
import { g } from "utils/global";
|
||||||
|
import { spawn } from "utils/spawn";
|
||||||
|
|
||||||
|
export const startServer = (is_dev: boolean) => {
|
||||||
|
g.server = spawn({
|
||||||
|
cmd: is_dev ? "bun run --watch server.js" : "bun run server.js",
|
||||||
|
cwd: fs.path("site:app"),
|
||||||
|
mode: "passthrough",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { c } from "utils/color";
|
||||||
|
import { config } from "utils/config";
|
||||||
|
import { fs } from "utils/fs";
|
||||||
|
import { startup } from "utils/global";
|
||||||
|
import { siteLog } from "utils/log";
|
||||||
|
import { ensureDBReady } from "./db/ensure";
|
||||||
|
import { ensureDeployExists } from "./deploy/ensure";
|
||||||
|
import { ensureServerReady } from "./server/ensure";
|
||||||
|
import { startServer } from "./server/start";
|
||||||
|
|
||||||
|
const is_dev = process.argv.includes("--dev");
|
||||||
|
startup("supervisor", async () => {
|
||||||
|
console.log(`${c.green}Prasi Server:${c.esc} ${fs.path("site:")}`);
|
||||||
|
await config.init("site:site.json");
|
||||||
|
|
||||||
|
const site_id = config.get("site_id") as string;
|
||||||
|
if (!site_id) {
|
||||||
|
siteLog("No Site Loaded");
|
||||||
|
} else {
|
||||||
|
siteLog(`Site ID: ${site_id}`);
|
||||||
|
await ensureDeployExists(site_id);
|
||||||
|
await ensureServerReady(is_dev);
|
||||||
|
await ensureDBReady();
|
||||||
|
|
||||||
|
startServer(is_dev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
export const c = {
|
||||||
|
esc: "\x1b[0m",
|
||||||
|
black: "\x1b[30m",
|
||||||
|
red: "\x1b[31m",
|
||||||
|
green: "\x1b[32m",
|
||||||
|
yellow: "\x1b[33m",
|
||||||
|
blue: "\x1b[34m",
|
||||||
|
magenta: "\x1b[35m",
|
||||||
|
cyan: "\x1b[36m",
|
||||||
|
white: "\x1b[37m",
|
||||||
|
blackbg: "\x1b[40m",
|
||||||
|
redbg: "\x1b[41m",
|
||||||
|
greenbg: "\x1b[42m",
|
||||||
|
yellowbg: "\x1b[43m",
|
||||||
|
bluebg: "\x1b[44m",
|
||||||
|
magentabg: "\x1b[45m",
|
||||||
|
cyanbg: "\x1b[46m",
|
||||||
|
whitebg: "\x1b[47m",
|
||||||
|
bold: "\x1b[1m",
|
||||||
|
dim: "\x1b[2m",
|
||||||
|
italic: "\x1b[3m",
|
||||||
|
underline: "\x1b[4m",
|
||||||
|
rgb: (r: number, g: number, b: number) => `\x1b[38;2;${r};${g};${b}m`,
|
||||||
|
rgbbg: (r: number, g: number, b: number) => `\x1b[48;2;${r};${g};${b}m`,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { dirAsync } from "fs-jetpack";
|
||||||
|
import { fs } from "./fs";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import set from "lodash.set";
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
async init(path: string) {
|
||||||
|
if (!fs.exists(path)) {
|
||||||
|
await fs.write(path, default_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await fs.read(path, "json");
|
||||||
|
this.current = result as typeof default_config;
|
||||||
|
this.file_path = path;
|
||||||
|
return result as typeof default_config;
|
||||||
|
},
|
||||||
|
get(path: string) {
|
||||||
|
return get(this.current, path);
|
||||||
|
},
|
||||||
|
async set(path: string, value: any) {
|
||||||
|
set(this.current as any, path, value);
|
||||||
|
await fs.write(this.file_path, this.current);
|
||||||
|
},
|
||||||
|
file_path: "",
|
||||||
|
current: null as null | typeof default_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const default_config = {
|
||||||
|
site_id: "",
|
||||||
|
port: 0,
|
||||||
|
upload_path: "upload",
|
||||||
|
db: { orm: "prisma" as "prisma" | "prasi", url: "" },
|
||||||
|
deploy: {
|
||||||
|
current: 0,
|
||||||
|
history: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SiteConfig = typeof default_config;
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { dirAsync } from "fs-jetpack";
|
||||||
|
import { dirname } from "path";
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
const res = await fetch(_url as any);
|
||||||
|
if (res.body) {
|
||||||
|
await dirAsync(dirname(filePath));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { mkdirSync, statSync } from "fs";
|
||||||
|
import { copyAsync } from "fs-jetpack";
|
||||||
|
import { g } from "./global";
|
||||||
|
import { dirname, join } from "path";
|
||||||
|
const internal = Symbol("internal");
|
||||||
|
|
||||||
|
export const fs = {
|
||||||
|
exists(path: string) {
|
||||||
|
try {
|
||||||
|
const s = statSync(this.path(path));
|
||||||
|
return s.isDirectory() || s.isFile();
|
||||||
|
} catch (e) {}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
path(path: string) {
|
||||||
|
const all_prefix = this[internal].prefix as Record<string, string>;
|
||||||
|
const prefix_key = Object.keys(all_prefix).find((e) => path.startsWith(e));
|
||||||
|
const prefix_path = all_prefix[prefix_key!];
|
||||||
|
|
||||||
|
if (prefix_key && prefix_path) {
|
||||||
|
return `${prefix_path}/${path.substring(prefix_key.length + 1)}`;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
async copy(from: string, to: string) {
|
||||||
|
const from_dir = this.path(from);
|
||||||
|
const to_path = this.path(to);
|
||||||
|
const is_dir = statSync(from_dir).isDirectory();
|
||||||
|
if (is_dir && !this.exists(to)) {
|
||||||
|
mkdirSync(to_path, { recursive: true });
|
||||||
|
} else {
|
||||||
|
const to_dir = dirname(to_path);
|
||||||
|
if (!fs.exists(to_dir)) {
|
||||||
|
mkdirSync(to_dir, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await copyAsync(from_dir, to_path, { overwrite: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
async modify(arg: {
|
||||||
|
path: string;
|
||||||
|
save: (content: any) => string | object | Promise<string | object>;
|
||||||
|
as?: "json" | "string";
|
||||||
|
}) {
|
||||||
|
const as = arg.as || arg.path.endsWith(".json") ? "json" : "string";
|
||||||
|
const content = await this.read(arg.path, as);
|
||||||
|
const result = await arg.save(content);
|
||||||
|
return await this.write(arg.path, result);
|
||||||
|
},
|
||||||
|
async read(path: string, as?: "json" | "string") {
|
||||||
|
const file = Bun.file(this.path(path));
|
||||||
|
if (as === "json") {
|
||||||
|
return await file.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await file.text();
|
||||||
|
},
|
||||||
|
|
||||||
|
async write(
|
||||||
|
path: string,
|
||||||
|
data: any,
|
||||||
|
opt?: {
|
||||||
|
mode?: "json" | "raw";
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const file = Bun.file(this.path(path));
|
||||||
|
if (typeof data === "object" && opt?.mode !== "raw") {
|
||||||
|
return await Bun.write(file, JSON.stringify(data, null, 2), {
|
||||||
|
createPath: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Bun.write(file, data, {
|
||||||
|
createPath: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
this[internal].prefix.site = join(g.dir.root, "site");
|
||||||
|
this[internal].prefix.internal = join(process.cwd(), "internal");
|
||||||
|
},
|
||||||
|
[internal]: {
|
||||||
|
prefix: {
|
||||||
|
site: "",
|
||||||
|
internal: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { join, resolve } from "path";
|
||||||
|
import { fs } from "./fs";
|
||||||
|
import type { SiteConfig } from "./config";
|
||||||
|
import type { spawn } from "./spawn";
|
||||||
|
|
||||||
|
if (!(globalThis as any).prasi) {
|
||||||
|
(globalThis as any).prasi = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const g = (globalThis as any).prasi as unknown as {
|
||||||
|
dir: { root: string };
|
||||||
|
server: ReturnType<typeof spawn>;
|
||||||
|
site: {
|
||||||
|
db?: SiteConfig["db"];
|
||||||
|
layouts: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
content_tree: any;
|
||||||
|
is_default_layout: boolean;
|
||||||
|
}[];
|
||||||
|
pages: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
content_tree: any;
|
||||||
|
}[];
|
||||||
|
comps: {
|
||||||
|
id: string;
|
||||||
|
content_tree: any;
|
||||||
|
}[];
|
||||||
|
info: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
config?: {
|
||||||
|
api_url: string;
|
||||||
|
};
|
||||||
|
responsive: string;
|
||||||
|
domain: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const startup = (mode: "supervisor" | "site", fn: () => void) => {
|
||||||
|
g.dir = { root: "" };
|
||||||
|
if (mode === "supervisor") {
|
||||||
|
const argv = process.argv.filter((e) => e !== "--dev");
|
||||||
|
if (argv.length > 2) {
|
||||||
|
g.dir.root = resolve(argv[2]);
|
||||||
|
} else {
|
||||||
|
g.dir.root = process.cwd();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g.dir.root = join(process.cwd(), "..", "..");
|
||||||
|
}
|
||||||
|
fs.init();
|
||||||
|
|
||||||
|
fn();
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { c } from "./color";
|
||||||
|
|
||||||
|
export const siteLog = (msg: string) => {
|
||||||
|
console.log(`${c.magenta}[SITE]${c.esc} ${msg}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dbLog = (msg: string) => {
|
||||||
|
console.log(`${c.cyan}[ DB ]${c.esc} ${msg}`);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { spawn as bunSpawn, type Subprocess } from "bun";
|
||||||
|
import { Readable } from "node:stream";
|
||||||
|
|
||||||
|
export const spawn = (
|
||||||
|
arg: {
|
||||||
|
cmd: string;
|
||||||
|
cwd?: string;
|
||||||
|
log?: false | { max_lines: number };
|
||||||
|
ipc?(message: any, subprocess: Subprocess): void;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
onMessage: (arg: {
|
||||||
|
from: "stdout" | "stderr";
|
||||||
|
text: string;
|
||||||
|
raw: string;
|
||||||
|
}) => void;
|
||||||
|
mode?: "pipe";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
mode?: "passthrough";
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) => {
|
||||||
|
const log = {
|
||||||
|
lines: 0,
|
||||||
|
text: [] as string[],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function processStream(
|
||||||
|
stream: AsyncIterable<Buffer>,
|
||||||
|
from: "stderr" | "stdout"
|
||||||
|
) {
|
||||||
|
for await (const x of stream) {
|
||||||
|
const buf = x as Buffer;
|
||||||
|
const raw = buf.toString("utf-8");
|
||||||
|
const text = raw
|
||||||
|
.trim()
|
||||||
|
.replace(
|
||||||
|
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
if (arg.log) {
|
||||||
|
log.lines += 1;
|
||||||
|
log.text.push(text);
|
||||||
|
if (log.lines > arg.log.max_lines) {
|
||||||
|
log.text.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const on_msg = (arg as any).onMessage;
|
||||||
|
if (arg.mode !== "passthrough" && on_msg) {
|
||||||
|
on_msg({ from: from, text, raw });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const is_piped = arg.mode === "pipe" || !arg.mode;
|
||||||
|
|
||||||
|
const proc = bunSpawn({
|
||||||
|
cmd: arg.cmd.split(" "),
|
||||||
|
cwd: arg.cwd,
|
||||||
|
env: { ...process.env, FORCE_COLOR: "1" },
|
||||||
|
...(is_piped
|
||||||
|
? { stderr: "pipe", stdout: "pipe" }
|
||||||
|
: { stderr: "inherit", stdout: "inherit" }),
|
||||||
|
...(arg.ipc ? { ipc: arg.ipc } : undefined),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (is_piped) {
|
||||||
|
const stdout = Readable.fromWeb(proc.stdout as any);
|
||||||
|
const stderr = Readable.fromWeb(proc.stderr as any);
|
||||||
|
processStream(stdout, "stdout");
|
||||||
|
processStream(stderr, "stderr");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
process: proc,
|
||||||
|
exited: proc.exited,
|
||||||
|
log,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "prasi-srv",
|
||||||
|
"module": "index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "bun run --watch internal/supervisor.ts --dev"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/lodash.get": "^4.4.9",
|
||||||
|
"@types/lodash.set": "^4.3.9",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"fs-jetpack": "^5.1.0",
|
||||||
|
"lodash.get": "^4.4.2",
|
||||||
|
"lodash.set": "^4.3.2",
|
||||||
|
"msgpackr": "^1.11.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"exclude": ["site/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"utils/*": ["./internal/utils/*"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Enable latest features
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue