revine 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +81 -81
  2. package/dist/commands/createProject.js +20 -2
  3. package/dist/config/package.js +4 -0
  4. package/dist/index.js +64 -2
  5. package/dist/prompts/project.js +11 -8
  6. package/dist/runtime/bundler/defaults/vite.js +17 -0
  7. package/dist/runtime/bundler/generateConfig.js +27 -0
  8. package/dist/runtime/bundler/revinePlugin.js +68 -0
  9. package/dist/runtime/bundler/utils/loadUserConfig.js +20 -0
  10. package/dist/runtime/bundler/vite.config.js +7 -0
  11. package/dist/runtime/bundler/viteLoggerPlugin.js +27 -0
  12. package/dist/runtime/routing/fileBased.js +29 -0
  13. package/dist/setup/dependencies.js +8 -0
  14. package/dist/setup/tailwind.js +128 -133
  15. package/package.json +44 -29
  16. package/src/commands/createProject.ts +88 -65
  17. package/src/config/package.ts +28 -23
  18. package/src/config/readme.ts +7 -7
  19. package/src/config/vite.ts +19 -19
  20. package/src/index.ts +91 -15
  21. package/src/prompts/git.ts +61 -61
  22. package/src/prompts/index.ts +3 -3
  23. package/src/prompts/project.ts +34 -31
  24. package/src/prompts/tailwind.ts +13 -13
  25. package/{template/.revine → src/runtime}/bundler/defaults/vite.ts +18 -18
  26. package/src/runtime/bundler/generateConfig.ts +36 -0
  27. package/src/runtime/bundler/revinePlugin.ts +71 -0
  28. package/src/runtime/bundler/utils/loadUserConfig.ts +19 -0
  29. package/{template/.revine → src/runtime}/bundler/vite.config.ts +8 -8
  30. package/{template/.revine → src/runtime}/bundler/viteLoggerPlugin.ts +33 -33
  31. package/{template/.revine → src/runtime}/routing/fileBased.tsx +46 -44
  32. package/src/setup/dependencies.ts +26 -16
  33. package/src/setup/tailwind.ts +151 -163
  34. package/src/utils/file.ts +9 -9
  35. package/src/utils/logger.ts +9 -9
  36. package/template/README.md +62 -62
  37. package/template/index.html +19 -19
  38. package/template/package.json +21 -24
  39. package/template/revine.config.ts +13 -13
  40. package/template/src/NotFound.tsx +13 -13
  41. package/template/src/pages/index.tsx +62 -62
  42. package/template/src/root.tsx +14 -14
  43. package/template/src/styles/global.css +191 -191
  44. package/template/tsconfig.json +20 -24
  45. package/tsconfig.json +15 -14
  46. package/dist/commands/create.js +0 -65
  47. package/dist/installers/dependencies.js +0 -15
  48. package/dist/lib/setup/directory.js +0 -21
  49. package/dist/lib/setup/package.js +0 -17
  50. package/dist/lib/setup/tailwind.js +0 -22
  51. package/dist/lib/utils/exec.js +0 -15
  52. package/dist/lib/utils/paths.js +0 -6
  53. package/template/.revine/bundler/generateConfig.ts +0 -18
  54. package/template/.revine/bundler/utils/loadUserConfig.ts +0 -13
  55. /package/{template/.revine → src/runtime}/bundler/types/vite-env.d.ts +0 -0
package/src/index.ts CHANGED
@@ -1,15 +1,91 @@
1
- #!/usr/bin/env node
2
- import { Command } from "commander";
3
- import { createProject } from "./commands/createProject.js";
4
-
5
- const program = new Command();
6
-
7
- program
8
- .version("0.1.0")
9
- .argument("<project-name>")
10
- .option("-f, --force", "Force creation in non-empty directory")
11
- .action(async (projectName: string, options: { force?: boolean }) => {
12
- await createProject(projectName, options);
13
- });
14
-
15
- program.parse(process.argv);
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { createProject } from "./commands/createProject.js";
4
+ import { spawn } from "child_process";
5
+ import path from "path";
6
+ import { fileURLToPath } from "url";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ const program = new Command();
12
+
13
+ // Main command handler for direct project creation
14
+ const handleProjectCreation = async (
15
+ projectName: string,
16
+ options: { force?: boolean },
17
+ ) => {
18
+ await createProject(projectName, options);
19
+ };
20
+
21
+ // Helper to run vite with internal config
22
+ const runViteCommand = (command: string) => {
23
+ // Path to the compiled vite.config.js in dist
24
+ const configPath = path.resolve(__dirname, "runtime/bundler/vite.config.js");
25
+
26
+ const args = [command, "--config", configPath];
27
+ if (command === "dev") {
28
+ // Vite dev doesn't need 'dev' argument, just calling vite is enough
29
+ args.shift();
30
+ }
31
+
32
+ spawn("npx", ["vite", ...args], {
33
+ stdio: "inherit",
34
+ shell: true,
35
+ });
36
+ };
37
+
38
+ // Main command handler for direct project creation with command check
39
+ const handleRootAction = async (
40
+ projectName: string | undefined,
41
+ options: { force?: boolean },
42
+ ) => {
43
+ const knownCommands = ["create", "dev", "build", "preview"];
44
+ if (projectName && !knownCommands.includes(projectName)) {
45
+ await handleProjectCreation(projectName, options);
46
+ } else if (!projectName) {
47
+ program.help();
48
+ }
49
+ };
50
+
51
+ // Root command
52
+ program
53
+ .version("0.8.0")
54
+ .argument("[project-name/command]")
55
+ .option("-f, --force", "Force creation in non-empty directory")
56
+ .action(async (arg: string | undefined, options: { force?: boolean }) => {
57
+ // If it's a known command, Commander will handle it in the subcommand action.
58
+ // We only handle it here if it's NOT a known command.
59
+ const knownCommands = ["create", "dev", "build", "preview"];
60
+ if (arg && !knownCommands.includes(arg)) {
61
+ await handleProjectCreation(arg, options);
62
+ } else if (!arg) {
63
+ program.help();
64
+ }
65
+ });
66
+
67
+ // Create subcommand (npx revine create my-app)
68
+ program
69
+ .command("create")
70
+ .argument("<project-name>")
71
+ .option("-f, --force", "Force creation in non-empty directory")
72
+ .action(async (projectName: string, options: { force?: boolean }) => {
73
+ await handleProjectCreation(projectName, options);
74
+ });
75
+
76
+ program
77
+ .command("dev")
78
+ .description("Start the development server")
79
+ .action(() => runViteCommand("dev"));
80
+
81
+ program
82
+ .command("build")
83
+ .description("Build the project for production")
84
+ .action(() => runViteCommand("build"));
85
+
86
+ program
87
+ .command("preview")
88
+ .description("Preview the production build")
89
+ .action(() => runViteCommand("preview"));
90
+
91
+ program.parse(process.argv);
@@ -1,61 +1,61 @@
1
- import inquirer from "inquirer";
2
- import { execSync } from "child_process";
3
- import { logInfo, logError } from "../utils/logger.js";
4
-
5
- /**
6
- * Check if Git is installed on the system.
7
- * @returns {boolean} true if Git exists, false otherwise.
8
- */
9
- function checkGitInstalled() {
10
- try {
11
- execSync("git --version", { stdio: "ignore" });
12
- return true;
13
- } catch {
14
- return false;
15
- }
16
- }
17
-
18
- /**
19
- * Initialize a Git repository in the project directory.
20
- * @param projectDir - The directory where the project was set up.
21
- */
22
- export default async function initGit(projectDir: string) {
23
- if (!checkGitInstalled()) {
24
- logError(
25
- "Git is not installed. Please install Git to use version control."
26
- );
27
- return;
28
- }
29
-
30
- const { initializeGit } = await inquirer.prompt([
31
- {
32
- type: "confirm",
33
- name: "initializeGit",
34
- message: "Do you want to initialize a Git repository?",
35
- default: true,
36
- },
37
- ]);
38
-
39
- if (initializeGit) {
40
- const { commitMessage } = await inquirer.prompt([
41
- {
42
- type: "input",
43
- name: "commitMessage",
44
- message: "Enter the initial commit message:",
45
- default: "chore: initial setup from revine",
46
- },
47
- ]);
48
-
49
- try {
50
- execSync("git init", { cwd: projectDir, stdio: "inherit" });
51
- execSync("git add .", { cwd: projectDir, stdio: "inherit" });
52
- execSync(`git commit -m "${commitMessage}"`, {
53
- cwd: projectDir,
54
- stdio: "inherit",
55
- });
56
- logInfo("Git repository initialized and initial commit created.");
57
- } catch (error) {
58
- logError("Failed to initialize Git repository or create commit.");
59
- }
60
- }
61
- }
1
+ import inquirer from "inquirer";
2
+ import { execSync } from "child_process";
3
+ import { logInfo, logError } from "../utils/logger.js";
4
+
5
+ /**
6
+ * Check if Git is installed on the system.
7
+ * @returns {boolean} true if Git exists, false otherwise.
8
+ */
9
+ function checkGitInstalled() {
10
+ try {
11
+ execSync("git --version", { stdio: "ignore" });
12
+ return true;
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Initialize a Git repository in the project directory.
20
+ * @param projectDir - The directory where the project was set up.
21
+ */
22
+ export default async function initGit(projectDir: string) {
23
+ if (!checkGitInstalled()) {
24
+ logError(
25
+ "Git is not installed. Please install Git to use version control."
26
+ );
27
+ return;
28
+ }
29
+
30
+ const { initializeGit } = await inquirer.prompt([
31
+ {
32
+ type: "confirm",
33
+ name: "initializeGit",
34
+ message: "Do you want to initialize a Git repository?",
35
+ default: true,
36
+ },
37
+ ]);
38
+
39
+ if (initializeGit) {
40
+ const { commitMessage } = await inquirer.prompt([
41
+ {
42
+ type: "input",
43
+ name: "commitMessage",
44
+ message: "Enter the initial commit message:",
45
+ default: "chore: initial setup from revine",
46
+ },
47
+ ]);
48
+
49
+ try {
50
+ execSync("git init", { cwd: projectDir, stdio: "inherit" });
51
+ execSync("git add .", { cwd: projectDir, stdio: "inherit" });
52
+ execSync(`git commit -m "${commitMessage}"`, {
53
+ cwd: projectDir,
54
+ stdio: "inherit",
55
+ });
56
+ logInfo("Git repository initialized and initial commit created.");
57
+ } catch (error) {
58
+ logError("Failed to initialize Git repository or create commit.");
59
+ }
60
+ }
61
+ }
@@ -1,3 +1,3 @@
1
- export { default as askForTailwindSetup } from "./tailwind.js";
2
- export { default as runProject } from "./project.js";
3
- export { default as initGit } from "./git.js";
1
+ export { default as askForTailwindSetup } from "./tailwind.js";
2
+ export { default as runProject } from "./project.js";
3
+ export { default as initGit } from "./git.js";
@@ -1,31 +1,34 @@
1
- import inquirer from "inquirer";
2
- import { execSync } from "child_process";
3
- import { logInfo } from "../utils/logger.js";
4
-
5
- /**
6
- * Ask the user if they want to run the project after setup is complete.
7
- * If the user confirms, it will run `npm run dev` or the equivalent command.
8
- * @param projectDir - The directory where the project was set up.
9
- */
10
- export default async function runProject(projectDir: string) {
11
- const { runProject } = await inquirer.prompt([
12
- {
13
- type: "confirm",
14
- name: "runProject",
15
- message: "Do you want to run the project now?",
16
- default: true,
17
- },
18
- ]);
19
-
20
- if (runProject) {
21
- logInfo("Running your Revine project on dev server...");
22
-
23
- try {
24
- execSync("npm run dev", { cwd: projectDir, stdio: "inherit" });
25
- } catch (error) {
26
- logInfo(
27
- "Failed to start the project. You can manually run `npm run dev`."
28
- );
29
- }
30
- }
31
- }
1
+ import inquirer from "inquirer";
2
+ import { spawn } from "child_process";
3
+ import { logInfo } from "../utils/logger.js";
4
+
5
+ /**
6
+ * Ask the user if they want to run the project after setup is complete.
7
+ * If the user confirms, it will start the dev server using the same revine
8
+ * binary that was invoked (i.e. the local build), rather than the npm package.
9
+ * @param projectDir - The directory where the project was set up.
10
+ */
11
+ export default async function runProject(projectDir: string) {
12
+ const { runProject } = await inquirer.prompt([
13
+ {
14
+ type: "confirm",
15
+ name: "runProject",
16
+ message: "Do you want to run the project now?",
17
+ default: true,
18
+ },
19
+ ]);
20
+
21
+ if (runProject) {
22
+ logInfo("Running your Revine project on dev server...");
23
+
24
+ // Use the same revine binary that the user invoked (process.argv[1])
25
+ // so that local development always uses the local build.
26
+ const revineBin = process.argv[1];
27
+
28
+ spawn("node", [revineBin, "dev"], {
29
+ cwd: projectDir,
30
+ stdio: "inherit",
31
+ shell: false,
32
+ });
33
+ }
34
+ }
@@ -1,13 +1,13 @@
1
- import inquirer from "inquirer";
2
-
3
- export default async function askForTailwindSetup(): Promise<boolean> {
4
- const { useTailwind } = await inquirer.prompt([
5
- {
6
- type: "confirm",
7
- name: "useTailwind",
8
- message: "Would you like to set up Tailwind CSS?",
9
- default: true,
10
- },
11
- ]);
12
- return useTailwind;
13
- }
1
+ import inquirer from "inquirer";
2
+
3
+ export default async function askForTailwindSetup(): Promise<boolean> {
4
+ const { useTailwind } = await inquirer.prompt([
5
+ {
6
+ type: "confirm",
7
+ name: "useTailwind",
8
+ message: "Would you like to set up Tailwind CSS?",
9
+ default: true,
10
+ },
11
+ ]);
12
+ return useTailwind;
13
+ }
@@ -1,18 +1,18 @@
1
- import react from "@vitejs/plugin-react";
2
- import { revineLoggerPlugin } from "../viteLoggerPlugin";
3
-
4
- export const defaultViteConfig = {
5
- plugins: [react(), revineLoggerPlugin()],
6
- logLevel: "silent",
7
-
8
- server: {
9
- clearScreen: false,
10
- open: true,
11
- port: 3000,
12
- host: true,
13
- },
14
- build: {
15
- outDir: "build",
16
- emptyOutDir: true,
17
- },
18
- };
1
+ import react from "@vitejs/plugin-react";
2
+ import { revineLoggerPlugin } from "../viteLoggerPlugin.js";
3
+ import { revinePlugin } from "../revinePlugin.js";
4
+
5
+ export const defaultViteConfig = {
6
+ plugins: [react(), revinePlugin(), revineLoggerPlugin()],
7
+ logLevel: "silent",
8
+ server: {
9
+ clearScreen: false,
10
+ open: true,
11
+ port: 3000,
12
+ host: true,
13
+ },
14
+ build: {
15
+ outDir: "build",
16
+ emptyOutDir: true,
17
+ },
18
+ };
@@ -0,0 +1,36 @@
1
+ import { merge } from "lodash-es";
2
+ import path from "path";
3
+ import fs from "fs-extra";
4
+ import { defaultViteConfig } from "./defaults/vite.js";
5
+ import { loadUserConfig } from "./utils/loadUserConfig.js";
6
+
7
+ interface UserConfig {
8
+ vite?: Record<string, unknown>;
9
+ }
10
+
11
+ export async function generateRevineViteConfig() {
12
+ // Load the user's revine.config.ts
13
+ const userConfig = (await loadUserConfig()) as UserConfig;
14
+
15
+ // Merge user "vite" overrides with your default config
16
+ const finalConfig = merge({}, defaultViteConfig, userConfig.vite || {});
17
+
18
+ // Dynamically add Tailwind if present in the project
19
+ try {
20
+ const projectPkgPath = path.resolve(process.cwd(), "package.json");
21
+ const pkg = await fs.readJson(projectPkgPath);
22
+ const hasTailwind =
23
+ pkg.devDependencies?.["@tailwindcss/vite"] ||
24
+ pkg.dependencies?.["@tailwindcss/vite"];
25
+
26
+ if (hasTailwind) {
27
+ const tailwindModule = "@tailwindcss/vite";
28
+ const { default: tailwindcss } = (await import(tailwindModule)) as any;
29
+ finalConfig.plugins = [...(finalConfig.plugins || []), tailwindcss()];
30
+ }
31
+ } catch (e) {
32
+ // Ignore error if package.json not found or tailwind not importable
33
+ }
34
+
35
+ return finalConfig as any;
36
+ }
@@ -0,0 +1,71 @@
1
+ const VIRTUAL_ROUTING_ID = "\0revine:routing";
2
+
3
+ /**
4
+ * The Revine Vite plugin.
5
+ *
6
+ * Provides a virtual module for `revine/routing` so that:
7
+ * - `import.meta.glob` is resolved by Vite in the *project* context (not node_modules)
8
+ * - React runtime is resolved from the project's own node_modules
9
+ */
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ export function revinePlugin(): any {
12
+ return {
13
+ name: "revine",
14
+
15
+ resolveId(id: string) {
16
+ if (id === "revine/routing") {
17
+ return VIRTUAL_ROUTING_ID;
18
+ }
19
+ },
20
+
21
+ // Return the routing source as a virtual module.
22
+ // Because it's virtual (not inside node_modules), Vite processes
23
+ // import.meta.glob and all imports normally in the project context.
24
+ load(id: string) {
25
+ if (id === VIRTUAL_ROUTING_ID) {
26
+ return `
27
+ import { createBrowserRouter } from "react-router-dom";
28
+ import { lazy, Suspense, createElement } from "react";
29
+
30
+ // Eagerly load NotFound from the project's src directory.
31
+ const notFoundModules = import.meta.glob("/src/NotFound.tsx", { eager: true });
32
+ const NotFoundComponent = Object.values(notFoundModules)[0]?.default;
33
+
34
+ // Lazily load all page components under /src/pages.
35
+ const pages = import.meta.glob("/src/pages/**/*.tsx");
36
+
37
+ const routes = Object.entries(pages).map(([filePath, component]) => {
38
+ let cleaned = filePath.replace(/\\\\/g, "/");
39
+ cleaned = cleaned.replace(/.*\\/pages\\//, "");
40
+ cleaned = cleaned.replace(/\\.tsx$/i, "");
41
+ cleaned = cleaned.replace(/\\/index$/, "");
42
+ cleaned = cleaned.replace(/\\[(\\w+)\\]/g, ":$1");
43
+ if (cleaned === "index") cleaned = "";
44
+
45
+ const routePath = cleaned === "" ? "/" : \`/\${cleaned}\`;
46
+ const Component = lazy(component);
47
+
48
+ return {
49
+ path: routePath,
50
+ element: createElement(
51
+ Suspense,
52
+ { fallback: createElement("div", null, "Loading\u2026") },
53
+ createElement(Component)
54
+ ),
55
+ };
56
+ });
57
+
58
+ // 404 fallback
59
+ routes.push({
60
+ path: "*",
61
+ element: NotFoundComponent
62
+ ? createElement(NotFoundComponent)
63
+ : createElement("div", null, "404 - Page Not Found"),
64
+ });
65
+
66
+ export const router = createBrowserRouter(routes);
67
+ `;
68
+ }
69
+ },
70
+ };
71
+ }
@@ -0,0 +1,19 @@
1
+ import path from "path";
2
+ import { pathToFileURL } from "url";
3
+
4
+ export async function loadUserConfig() {
5
+ const configPath = path.resolve(process.cwd(), "revine.config.ts");
6
+ try {
7
+ const configModule = await import(pathToFileURL(configPath).href);
8
+ return configModule.default || {};
9
+ } catch (error) {
10
+ // If .ts fails, try .js or just return empty
11
+ try {
12
+ const configPathJs = path.resolve(process.cwd(), "revine.config.js");
13
+ const configModule = await import(pathToFileURL(configPathJs).href);
14
+ return configModule.default || {};
15
+ } catch (e) {
16
+ return {};
17
+ }
18
+ }
19
+ }
@@ -1,8 +1,8 @@
1
- import { defineConfig } from "vite";
2
- import { generateRevineViteConfig } from "./generateConfig";
3
-
4
- // Vite supports async config. We can do:
5
- export default defineConfig(async () => {
6
- // Merge defaults + user overrides
7
- return await generateRevineViteConfig();
8
- });
1
+ import { defineConfig } from "vite";
2
+ import { generateRevineViteConfig } from "./generateConfig.js";
3
+
4
+ // Vite supports async config. We can do:
5
+ export default defineConfig(async () => {
6
+ // Merge defaults + user overrides
7
+ return (await generateRevineViteConfig()) as any;
8
+ });
@@ -1,33 +1,33 @@
1
- import type { Plugin, ViteDevServer } from "vite";
2
- import chalk from "chalk";
3
-
4
- export function revineLoggerPlugin(): Plugin {
5
- // custom chalk instance pointing to the indigo color
6
- const indigo = chalk.hex("#6d28d9");
7
-
8
- return {
9
- name: "revine-logger",
10
- configureServer(server: ViteDevServer) {
11
- server.httpServer?.once("listening", () => {
12
- const protocol = server.config.server.https ? "https" : "http";
13
- const host = server.resolvedUrls?.local[0] || "localhost:3000";
14
- const { network = [] } = server.resolvedUrls ?? {};
15
-
16
- // Use the 'indigo' instance in place of 'chalk.cyan'
17
- console.log(indigo("─────────────────────────────────────────────"));
18
- console.log(indigo.bold("🚀 Revine Dev Server is now running!"));
19
- console.log(indigo("─────────────────────────────────────────────"));
20
- console.log(indigo(`Local: ${chalk.green(`${protocol}://${host}`)}`));
21
-
22
- if (network.length) {
23
- network.forEach((url: string) => {
24
- console.log(indigo(`Network: ${chalk.green(url)}`));
25
- });
26
- }
27
-
28
- console.log(indigo("─────────────────────────────────────────────"));
29
- console.log("");
30
- });
31
- },
32
- };
33
- }
1
+ import type { Plugin, ViteDevServer } from "vite";
2
+ import chalk from "chalk";
3
+
4
+ export function revineLoggerPlugin(): Plugin {
5
+ // custom chalk instance pointing to the indigo color
6
+ const indigo = chalk.hex("#6d28d9");
7
+
8
+ return {
9
+ name: "revine-logger",
10
+ configureServer(server: ViteDevServer) {
11
+ server.httpServer?.once("listening", () => {
12
+ const protocol = server.config.server.https ? "https" : "http";
13
+ const host = server.resolvedUrls?.local[0] || "localhost:3000";
14
+ const { network = [] } = server.resolvedUrls ?? {};
15
+
16
+ // Use the 'indigo' instance in place of 'chalk.cyan'
17
+ console.log(indigo("─────────────────────────────────────────────"));
18
+ console.log(indigo.bold("🚀 Revine Dev Server is now running!"));
19
+ console.log(indigo("─────────────────────────────────────────────"));
20
+ console.log(indigo(`Local: ${chalk.green(`${protocol}://${host}`)}`));
21
+
22
+ if (network.length) {
23
+ network.forEach((url: string) => {
24
+ console.log(indigo(`Network: ${chalk.green(url)}`));
25
+ });
26
+ }
27
+
28
+ console.log(indigo("─────────────────────────────────────────────"));
29
+ console.log("");
30
+ });
31
+ },
32
+ };
33
+ }