Merge pull request #18 from fedyna-k/PMPR-17
Added first CLI functions and patched DB creation when data folder is missing
This commit is contained in:
@@ -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.`);
|
||||
|
||||
@@ -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
@@ -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();
|
||||
@@ -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",
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
`;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user