Merged main to rebase

This commit is contained in:
Kevin FEDYNA
2025-01-21 13:45:22 +01:00
7 changed files with 278 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
FROM denoland/deno:alpine
WORKDIR /app
COPY . .
RUN deno cache main.ts
RUN deno task build
USER deno
EXPOSE 80
EXPOSE 443
CMD ["run", "-A", "main.ts"]
+10
View File
@@ -0,0 +1,10 @@
services:
app:
container_name: deno_fresh_app
build: .
ports:
- "80:80"
- "443:443"
volumes:
- .:/app
command: deno run -A main.ts
+2
View File
@@ -1,6 +1,8 @@
import { Database } from "@db/sqlite";
export default async function ensureDatabases() {
await Deno.mkdir("databases/data", { recursive: true });
for await (const file of Deno.readDir("databases/init")) {
if (!file.isFile) {
console.warn(`[WARN] Path ${file.name} is not a file.`);
+2
View File
@@ -3,6 +3,7 @@
"tasks": {
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
"cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -",
"pmpr": "deno run -A toolbox/cli.ts",
"manifest": "deno task cli manifest $(pwd)",
"start": "deno run -A --unstable-ffi --watch=static/,routes/ dev.ts",
"build": "deno run -A --unstable-ffi dev.ts build",
@@ -25,6 +26,7 @@
"@db/sqlite": "jsr:@db/sqlite@^0.12.0",
"@melvdouc/xml-parser": "jsr:@melvdouc/xml-parser@^0.1.1",
"@popov/jwt": "jsr:@popov/jwt@^1.0.1",
"@std/cli": "jsr:@std/cli@^1.0.10",
"preact": "https://esm.sh/preact@10.22.0",
"preact/": "https://esm.sh/preact@10.22.0/",
"@preact/signals": "https://esm.sh/*@preact/signals@1.2.2",
+104
View File
@@ -0,0 +1,104 @@
import { parseArgs, type ParseOptions } from "@std/cli/parse-args";
import { createModule } from "$root/toolbox/module/create.ts";
import { listModules } from "$root/toolbox/module/list.ts";
type CLICommand = (...args: Array<string>) => void;
type CLIAsyncCommand = (...args: Array<string>) => Promise<void>;
interface CLI {
[command: string]: CLI | CLICommand | CLIAsyncCommand;
}
const argSpec: ParseOptions = {};
const cli: CLI = {
help: () => displayHelp(cli),
module: {
list: () => listModules(),
create: (name: string) => createModule(name),
},
};
function displayHelp(
cli: CLI | CLICommand | CLIAsyncCommand,
errorMessage?: string,
): never {
const loggingFunction = errorMessage ? console.error : console.log;
if (errorMessage) {
console.error(errorMessage);
}
if (typeof cli == "function") {
loggingFunction("Usage:");
displayFunctionHelp(cli, loggingFunction);
} else {
loggingFunction("Commands:");
displayObjectHelp(cli, loggingFunction);
}
Deno.exit(errorMessage ? 1 : 0);
}
function displayObjectHelp(
cli: CLI,
loggingFunction: typeof console.log,
level: number = 1,
) {
for (const [key, value] of Object.entries(cli)) {
if (typeof value == "function") {
displayFunctionHelp(value, loggingFunction, level);
} else {
loggingFunction(`${" ".repeat(level * 2)}${key}`);
displayObjectHelp(value, loggingFunction, level + 1);
}
}
}
function displayFunctionHelp(
cli: CLICommand | CLIAsyncCommand,
loggingFunction: typeof console.log,
level: number = 1,
) {
const command = cli.name;
const matched = cli.toString().match(/(?<=^\()[^\)]+(?=\))/);
const args = matched?.[0].split(",").map((arg) => `<${arg.trim()}>`);
loggingFunction(
`${" ".repeat(level * 2)}${command} ${args?.join(" ") ?? ""}`,
);
}
function runCommand(
commands: Array<string | number>,
cli: CLI,
): never | {
command: CLICommand | CLIAsyncCommand;
args: Array<string | number>;
} {
if (commands.length == 0) {
displayHelp(cli, `No command provided.`);
}
const command = commands.shift()!.toString();
if (cli[command] == undefined) {
displayHelp(cli, `Command "${command}" doesn't exist.`);
}
if (typeof cli[command] == "object") {
return runCommand(commands, cli[command]);
}
if (cli[command].length != commands.length) {
displayHelp(cli[command], `Wrong usage of command "${command}".`);
}
return { command: cli[command], args: commands };
}
function main() {
const argv = parseArgs(Deno.args, argSpec);
const { command, args } = runCommand(argv._, cli);
command(...args.map((element) => element.toString()));
}
main();
+140
View File
@@ -0,0 +1,140 @@
export async function createModule(name: string): Promise<void> {
if (!name.match(/^[a-zA-Z0-9](?:(?:\-(?!\-))?[a-zA-Z0-9]*)*[a-zA-Z0-9]$/)) {
console.error("Module names must be in kebab case.");
Deno.exit(1);
}
console.log(`Checking for module ${name}...`);
try {
await Deno.mkdir(`routes/(apps)/${name}`);
} catch (error) {
if (!(error instanceof Deno.errors.AlreadyExists)) {
throw error;
}
console.error(`Some module is already named ${name}, aborting.`);
Deno.exit(1);
}
const capitalizedName = name.match(/(^\w)|(\-\w)/g)!.reduce(
(word, pattern) => word.replace(pattern, pattern.at(-1)!.toUpperCase()),
name,
);
Promise.allSettled([
createDir(`routes/(apps)/${name}/(_props)`),
createDir(`routes/(apps)/${name}/partials`),
createDir(`routes/(apps)/${name}/(_islands)`),
createDir(`routes/(apps)/${name}/(_components)`),
createDir(`routes/(apps)/${name}/api`),
]);
Promise.allSettled([
createFile(
`routes/(apps)/${name}/index.tsx`,
getIndexContent(capitalizedName),
),
createFile(
`routes/(apps)/${name}/(_props)/props.ts`,
getPropsContent(capitalizedName),
),
createFile(
`routes/(apps)/${name}/partials/index.tsx`,
getPartialIndexContent(capitalizedName),
),
createFile(
`routes/(apps)/${name}/api/example.ts`,
getApiExampleContent(capitalizedName),
),
]);
const formatter = new Deno.Command(Deno.execPath(), {
args: [
"fmt",
`routes/(apps)/${name}`,
],
});
formatter.output();
}
function createFile(path: string, content: string): Promise<void> {
console.log(`Creating file ${path}...`);
return Deno.writeTextFile(path, content);
}
function createDir(path: string): Promise<void> {
console.log(`Creating directory ${path}...`);
return Deno.mkdir(path);
}
function getIndexContent(_name: string) {
return `
import makeIndex from "$root/defaults/makeIndex.ts";
export default makeIndex(import.meta.dirname!);
`;
}
function getPartialIndexContent(name: string) {
return `
import { EmptyObject } from "$root/defaults/interfaces.ts";
import {
getPartialsConfig,
makePartials,
} from "$root/defaults/makePartials.tsx";
type ${name}IndexProps = EmptyObject;
export function Index(_props: ${name}IndexProps) {
return <h2>Welcome to ${name}.</h2>;
}
export const config = getPartialsConfig();
export default makePartials(Index);
`;
}
function getPropsContent(name: string) {
return `
import { AppProperties } from "$root/defaults/interfaces.ts";
const properties: AppProperties = {
name: "${name}",
icon: "school",
pages: {
index: "Homepage",
},
adminOnly: [],
hint: "PolyMPR module",
};
export default properties;
`;
}
function getApiExampleContent(_name: string) {
return `
import { Handlers } from "$fresh/server.ts";
export const handler: Handlers = {
async POST(request, context) {
if (request.headers.get("content-type") != "application/json") {
return new Response(null, {
status: 400
});
}
const responseBody = {
requestBody: await request.json(),
context,
};
return new Response(JSON.stringify(responseBody), {
headers: {
"content-type": "application/json",
},
});
},
};
`;
}
+7
View File
@@ -0,0 +1,7 @@
export async function listModules(): Promise<void> {
for await (const path of Deno.readDir("routes/(apps)")) {
if (path.isDirectory) {
console.log(path.name);
}
}
}