@npio/cli
Version:
A free visual website editor, powered with your own SolidJS components.
202 lines (171 loc) • 5.78 kB
text/typescript
;
import { createId, init } from "@paralleldrive/cuid2";
import { spawnSync } from "child_process";
import { access, mkdir, readFile, writeFile } from "fs/promises";
import {
getSchemaRootDir,
initialize,
runtimePath,
titleCase,
} from "nitropage/internals";
import { name, version } from "nitropage/package.json";
import path, { dirname } from "path";
import c from "picocolors";
import sade from "sade";
import { fileURLToPath, pathToFileURL } from "url";
import { configCommands } from "./config";
import { demoCommands } from "./demo";
import { userCommands } from "./user";
import { getDatabaseProvider } from "./util";
const { default: config } = await import(
pathToFileURL(
path.join(process.cwd(), runtimePath, "server.config.ts"),
).toString()
);
initialize(config, { cli: true });
const prog = sade(name).version(version);
prog
.command("update")
.describe(
"Import the Nitropage specific prisma models into your schema folder",
)
.option(
"--migrate, -m",
`Run ${c.magenta("prisma migrate dev")} after import`,
)
.action(async (opts: { migrate: boolean }) => {
const depPath = path.dirname(fileURLToPath(import.meta.url));
// TODO: find the prisma schema location based on prisma conventions
// https://www.prisma.io/docs/concepts/components/prisma-schema#prisma-schema-file-location
let provider = await getDatabaseProvider();
const supportedProviders = ["sqlite"];
if (!supportedProviders.includes(provider)) {
const fallbackProvider = "sqlite";
console.warn(
c.magenta(
`Detected datasource provider ${c.bold(
provider,
)} is not officially supported. Trying ${c.bold(
fallbackProvider,
)} instead.`,
),
);
provider = fallbackProvider;
}
console.log(`Importing schema for ${c.bold(provider)} datasource provider`);
const schemaSourcePath = path.resolve(
depPath,
"..",
"prisma",
`${provider}.prisma`,
);
const sourceSchema = await readFile(schemaSourcePath, { encoding: "utf8" });
const sourceRegex = /(model[\S\s]*)/;
const sourceModels = sourceSchema.match(sourceRegex)![1];
const npSchema =
"// Generated by Nitropage, do not manually change this file.\n\n" +
sourceModels;
const npSchemaPath = path.join(getSchemaRootDir(), "nitropage.prisma");
await writeFile(npSchemaPath, npSchema);
console.log(`Updated prisma schema: ${c.green(npSchemaPath)}`);
if (!opts.migrate) {
return;
}
spawnSync("pnpm", "prisma migrate dev".split(" "), {
stdio: "inherit",
});
});
prog
.command("env")
.describe("Generate a basic .env file")
.action(async () => {
const provider = await getDatabaseProvider();
const providerExamples = {
sqlite: "file:../.data/dev.db",
postgres: "postgresql://USER:PASSWORD@HOST:PORT/DATABASE",
mysql: "mysql://USER:PASSWORD@HOST:PORT/DATABASE",
mongodb: "mongodb://USERNAME:PASSWORD@HOST/DATABASE",
};
const result = `# URL Options: https://pris.ly/d/connection-strings
DATABASE_URL=${(providerExamples as any)[provider] || providerExamples.sqlite}
NP_AUTH_SALT=${init({ length: 20 })()}
NP_AUTH_PASSWORD=${init({ length: 32 })()}`;
console.log(result);
});
prog
.command("blueprint <name>")
.describe("Generate a new blueprint")
.example("blueprint button")
.example("blueprint menu")
.option("--out-dir, -d", "Provide path to elements", "src/blueprints")
.action(async (blueprintName: string, opts: { d: string }) => {
const componentsPath = path.join(process.cwd(), opts.d);
await access(componentsPath);
const findAvailableName = async function (
number?: number,
): Promise<{ filename: string; filepath: string; number?: number }> {
if (number != null && number > 9) {
throw new Error(
`Blueprint name ${c.bold(
blueprintName,
)} is already used nine times, what are you doing?`,
);
}
const suffix = number != null ? number : "";
const filename = `${blueprintName}${suffix}.np.tsx`;
const filepath = path.join(componentsPath, filename);
const exists = await access(filepath)
.then(() => true)
.catch(() => false);
if (!exists) {
return { filename, filepath, number };
}
number = number || 1;
number++;
return findAvailableName(number);
};
const { filepath, number } = await findAvailableName();
await mkdir(dirname(filepath), { recursive: true });
const titleSuffix = number != null ? " " + number : "";
const title = `${titleCase(blueprintName)}${titleSuffix}`;
const titleJson =
number != null ? "\n" + ` title: () => "${title}",` : "";
const template = `import { BaseBlueprint, BlueprintComponent, createBlueprint } from "nitropage";
import { useCss } from "nitropage/css";
const MyComponent: BlueprintComponent<typeof blueprint> = function (props) {
const css = useCss();
return (
<div
class={css({
padding: "3rem",
})}
{...props.elementProps}
>
{props.handles}
<props.Slot key="default" />
</div>
);
};
export const blueprint = createBlueprint(
() => {
return {${titleJson}
slots: { default: {} },
data: {},
} satisfies BaseBlueprint;
},
import.meta.hot,
);
export default MyComponent;
export const id = "${createId()}";
`;
await writeFile(filepath, template);
console.log(
c.green(
`Scaffolded new blueprint "${c.bold(title)}" in ${c.bold(filepath)}`,
),
);
});
configCommands(prog);
userCommands(prog);
demoCommands(prog);
prog.parse(process.argv);