revine 0.8.1 → 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 (47) hide show
  1. package/README.md +81 -81
  2. package/dist/config/package.js +4 -0
  3. package/dist/index.js +51 -7
  4. package/dist/prompts/project.js +11 -8
  5. package/dist/runtime/bundler/defaults/vite.js +17 -0
  6. package/dist/runtime/bundler/generateConfig.js +27 -0
  7. package/dist/runtime/bundler/revinePlugin.js +68 -0
  8. package/dist/runtime/bundler/utils/loadUserConfig.js +20 -0
  9. package/dist/runtime/bundler/vite.config.js +7 -0
  10. package/dist/runtime/bundler/viteLoggerPlugin.js +27 -0
  11. package/dist/runtime/routing/fileBased.js +29 -0
  12. package/dist/setup/dependencies.js +8 -0
  13. package/dist/setup/tailwind.js +128 -133
  14. package/package.json +44 -29
  15. package/src/commands/createProject.ts +88 -88
  16. package/src/config/package.ts +28 -23
  17. package/src/config/readme.ts +7 -7
  18. package/src/config/vite.ts +19 -19
  19. package/src/index.ts +91 -34
  20. package/src/prompts/git.ts +61 -61
  21. package/src/prompts/index.ts +3 -3
  22. package/src/prompts/project.ts +34 -31
  23. package/src/prompts/tailwind.ts +13 -13
  24. package/{template/.revine → src/runtime}/bundler/defaults/vite.ts +18 -18
  25. package/src/runtime/bundler/generateConfig.ts +36 -0
  26. package/src/runtime/bundler/revinePlugin.ts +71 -0
  27. package/src/runtime/bundler/utils/loadUserConfig.ts +19 -0
  28. package/{template/.revine → src/runtime}/bundler/vite.config.ts +8 -8
  29. package/{template/.revine → src/runtime}/bundler/viteLoggerPlugin.ts +33 -33
  30. package/{template/.revine → src/runtime}/routing/fileBased.tsx +46 -44
  31. package/src/setup/dependencies.ts +26 -16
  32. package/src/setup/tailwind.ts +151 -163
  33. package/src/utils/file.ts +9 -9
  34. package/src/utils/logger.ts +9 -9
  35. package/template/README.md +62 -62
  36. package/template/index.html +19 -19
  37. package/template/package.json +21 -24
  38. package/template/revine.config.ts +13 -13
  39. package/template/src/NotFound.tsx +13 -13
  40. package/template/src/pages/index.tsx +62 -62
  41. package/template/src/root.tsx +14 -14
  42. package/template/src/styles/global.css +191 -191
  43. package/template/tsconfig.json +20 -24
  44. package/tsconfig.json +15 -14
  45. package/template/.revine/bundler/generateConfig.ts +0 -18
  46. package/template/.revine/bundler/utils/loadUserConfig.ts +0 -13
  47. /package/{template/.revine → src/runtime}/bundler/types/vite-env.d.ts +0 -0
package/README.md CHANGED
@@ -1,81 +1,81 @@
1
- # Revine
2
-
3
- [![npm version](https://img.shields.io/npm/v/revine)](https://www.npmjs.com/package/revine)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
-
6
- A minimal React framework with file-based routing and TypeScript-first approach.
7
-
8
- ## Features
9
-
10
- - ⚡️ Vite-powered development
11
- - 🗂 File-based routing (Next.js style)
12
- - 🛠 TypeScript support out-of-the-box
13
- - 🚀 Zero-config setup
14
- - 🔥 Hot Module Replacement (HMR)
15
-
16
- ## Installation
17
-
18
- Create a new project with:
19
-
20
- ```bash
21
- npx revine my-project
22
- ```
23
-
24
- ## Documentation
25
-
26
- ### CLI Options
27
-
28
- ```bash
29
- npx revine <project-name>
30
- ```
31
-
32
- ### Project Structure
33
-
34
- Generated projects follow this structure:
35
-
36
- ```
37
- my-project/
38
- ├── src/
39
- │ ├── pages/ # Route components
40
- │ │ └── index.tsx
41
- │ ├── App.tsx # Router configuration
42
- │ └── main.tsx # Entry point
43
- ├── public/ # Static assets
44
- ├── vite.config.ts # Vite configuration
45
- └── package.json
46
- ```
47
-
48
- ### Routing Convention
49
-
50
- src/pages/index.tsx → /
51
-
52
- src/pages/about.tsx → /about
53
-
54
- src/pages/blog/[slug].tsx → /blog/:slug
55
-
56
- ## Contributing
57
-
58
- ### Clone repository
59
-
60
- ```bash
61
- git clone https://github.com/your-username/revine.git
62
- ```
63
-
64
- ### Install dependencies
65
-
66
- ```bash
67
- npm install
68
- ```
69
-
70
- ### Build and link locally
71
-
72
- ```bash
73
- npm run build
74
- npm link
75
- ```
76
-
77
- ### Test locally
78
-
79
- revine test-project
80
-
81
- ### Thank you for contributing!
1
+ # Revine
2
+
3
+ [![npm version](https://img.shields.io/npm/v/revine)](https://www.npmjs.com/package/revine)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ A minimal React framework with file-based routing and TypeScript-first approach.
7
+
8
+ ## Features
9
+
10
+ - ⚡️ Vite-powered development
11
+ - 🗂 File-based routing (Next.js style)
12
+ - 🛠 TypeScript support out-of-the-box
13
+ - 🚀 Zero-config setup
14
+ - 🔥 Hot Module Replacement (HMR)
15
+
16
+ ## Installation
17
+
18
+ Create a new project with:
19
+
20
+ ```bash
21
+ npx revine my-project
22
+ ```
23
+
24
+ ## Documentation
25
+
26
+ ### CLI Options
27
+
28
+ ```bash
29
+ npx revine <project-name>
30
+ ```
31
+
32
+ ### Project Structure
33
+
34
+ Generated projects follow this structure:
35
+
36
+ ```
37
+ my-project/
38
+ ├── src/
39
+ │ ├── pages/ # Route components
40
+ │ │ └── index.tsx
41
+ │ ├── App.tsx # Router configuration
42
+ │ └── main.tsx # Entry point
43
+ ├── public/ # Static assets
44
+ ├── vite.config.ts # Vite configuration
45
+ └── package.json
46
+ ```
47
+
48
+ ### Routing Convention
49
+
50
+ src/pages/index.tsx → /
51
+
52
+ src/pages/about.tsx → /about
53
+
54
+ src/pages/blog/[slug].tsx → /blog/:slug
55
+
56
+ ## Contributing
57
+
58
+ ### Clone repository
59
+
60
+ ```bash
61
+ git clone https://github.com/your-username/revine.git
62
+ ```
63
+
64
+ ### Install dependencies
65
+
66
+ ```bash
67
+ npm install
68
+ ```
69
+
70
+ ### Build and link locally
71
+
72
+ ```bash
73
+ npm run build
74
+ npm link
75
+ ```
76
+
77
+ ### Test locally
78
+
79
+ revine test-project
80
+
81
+ ### Thank you for contributing!
@@ -3,6 +3,10 @@ export async function updatePackageJson(filePath, projectName, options = {}) {
3
3
  const packageJson = await fs.readJson(filePath);
4
4
  packageJson.name = projectName;
5
5
  packageJson.type = "module";
6
+ packageJson.dependencies = {
7
+ ...packageJson.dependencies,
8
+ revine: "latest",
9
+ };
6
10
  if (options.useTailwind) {
7
11
  packageJson.devDependencies = {
8
12
  ...packageJson.devDependencies,
package/dist/index.js CHANGED
@@ -1,21 +1,53 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
  import { createProject } from "./commands/createProject.js";
4
+ import { spawn } from "child_process";
5
+ import path from "path";
6
+ import { fileURLToPath } from "url";
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
4
9
  const program = new Command();
5
10
  // Main command handler for direct project creation
6
11
  const handleProjectCreation = async (projectName, options) => {
7
12
  await createProject(projectName, options);
8
13
  };
9
- // Root command (npx revine my-app)
14
+ // Helper to run vite with internal config
15
+ const runViteCommand = (command) => {
16
+ // Path to the compiled vite.config.js in dist
17
+ const configPath = path.resolve(__dirname, "runtime/bundler/vite.config.js");
18
+ const args = [command, "--config", configPath];
19
+ if (command === "dev") {
20
+ // Vite dev doesn't need 'dev' argument, just calling vite is enough
21
+ args.shift();
22
+ }
23
+ spawn("npx", ["vite", ...args], {
24
+ stdio: "inherit",
25
+ shell: true,
26
+ });
27
+ };
28
+ // Main command handler for direct project creation with command check
29
+ const handleRootAction = async (projectName, options) => {
30
+ const knownCommands = ["create", "dev", "build", "preview"];
31
+ if (projectName && !knownCommands.includes(projectName)) {
32
+ await handleProjectCreation(projectName, options);
33
+ }
34
+ else if (!projectName) {
35
+ program.help();
36
+ }
37
+ };
38
+ // Root command
10
39
  program
11
- .version("0.1.0")
12
- .argument("[project-name]")
40
+ .version("0.8.0")
41
+ .argument("[project-name/command]")
13
42
  .option("-f, --force", "Force creation in non-empty directory")
14
- .action(async (projectName, options) => {
15
- if (projectName) {
16
- await handleProjectCreation(projectName, options);
43
+ .action(async (arg, options) => {
44
+ // If it's a known command, Commander will handle it in the subcommand action.
45
+ // We only handle it here if it's NOT a known command.
46
+ const knownCommands = ["create", "dev", "build", "preview"];
47
+ if (arg && !knownCommands.includes(arg)) {
48
+ await handleProjectCreation(arg, options);
17
49
  }
18
- else {
50
+ else if (!arg) {
19
51
  program.help();
20
52
  }
21
53
  });
@@ -27,4 +59,16 @@ program
27
59
  .action(async (projectName, options) => {
28
60
  await handleProjectCreation(projectName, options);
29
61
  });
62
+ program
63
+ .command("dev")
64
+ .description("Start the development server")
65
+ .action(() => runViteCommand("dev"));
66
+ program
67
+ .command("build")
68
+ .description("Build the project for production")
69
+ .action(() => runViteCommand("build"));
70
+ program
71
+ .command("preview")
72
+ .description("Preview the production build")
73
+ .action(() => runViteCommand("preview"));
30
74
  program.parse(process.argv);
@@ -1,9 +1,10 @@
1
1
  import inquirer from "inquirer";
2
- import { execSync } from "child_process";
2
+ import { spawn } from "child_process";
3
3
  import { logInfo } from "../utils/logger.js";
4
4
  /**
5
5
  * Ask the user if they want to run the project after setup is complete.
6
- * If the user confirms, it will run `npm run dev` or the equivalent command.
6
+ * If the user confirms, it will start the dev server using the same revine
7
+ * binary that was invoked (i.e. the local build), rather than the npm package.
7
8
  * @param projectDir - The directory where the project was set up.
8
9
  */
9
10
  export default async function runProject(projectDir) {
@@ -17,11 +18,13 @@ export default async function runProject(projectDir) {
17
18
  ]);
18
19
  if (runProject) {
19
20
  logInfo("Running your Revine project on dev server...");
20
- try {
21
- execSync("npm run dev", { cwd: projectDir, stdio: "inherit" });
22
- }
23
- catch (error) {
24
- logInfo("Failed to start the project. You can manually run `npm run dev`.");
25
- }
21
+ // Use the same revine binary that the user invoked (process.argv[1])
22
+ // so that local development always uses the local build.
23
+ const revineBin = process.argv[1];
24
+ spawn("node", [revineBin, "dev"], {
25
+ cwd: projectDir,
26
+ stdio: "inherit",
27
+ shell: false,
28
+ });
26
29
  }
27
30
  }
@@ -0,0 +1,17 @@
1
+ import react from "@vitejs/plugin-react";
2
+ import { revineLoggerPlugin } from "../viteLoggerPlugin.js";
3
+ import { revinePlugin } from "../revinePlugin.js";
4
+ export const defaultViteConfig = {
5
+ plugins: [react(), revinePlugin(), revineLoggerPlugin()],
6
+ logLevel: "silent",
7
+ server: {
8
+ clearScreen: false,
9
+ open: true,
10
+ port: 3000,
11
+ host: true,
12
+ },
13
+ build: {
14
+ outDir: "build",
15
+ emptyOutDir: true,
16
+ },
17
+ };
@@ -0,0 +1,27 @@
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
+ export async function generateRevineViteConfig() {
7
+ // Load the user's revine.config.ts
8
+ const userConfig = (await loadUserConfig());
9
+ // Merge user "vite" overrides with your default config
10
+ const finalConfig = merge({}, defaultViteConfig, userConfig.vite || {});
11
+ // Dynamically add Tailwind if present in the project
12
+ try {
13
+ const projectPkgPath = path.resolve(process.cwd(), "package.json");
14
+ const pkg = await fs.readJson(projectPkgPath);
15
+ const hasTailwind = pkg.devDependencies?.["@tailwindcss/vite"] ||
16
+ pkg.dependencies?.["@tailwindcss/vite"];
17
+ if (hasTailwind) {
18
+ const tailwindModule = "@tailwindcss/vite";
19
+ const { default: tailwindcss } = (await import(tailwindModule));
20
+ finalConfig.plugins = [...(finalConfig.plugins || []), tailwindcss()];
21
+ }
22
+ }
23
+ catch (e) {
24
+ // Ignore error if package.json not found or tailwind not importable
25
+ }
26
+ return finalConfig;
27
+ }
@@ -0,0 +1,68 @@
1
+ const VIRTUAL_ROUTING_ID = "\0revine:routing";
2
+ /**
3
+ * The Revine Vite plugin.
4
+ *
5
+ * Provides a virtual module for `revine/routing` so that:
6
+ * - `import.meta.glob` is resolved by Vite in the *project* context (not node_modules)
7
+ * - React runtime is resolved from the project's own node_modules
8
+ */
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ export function revinePlugin() {
11
+ return {
12
+ name: "revine",
13
+ resolveId(id) {
14
+ if (id === "revine/routing") {
15
+ return VIRTUAL_ROUTING_ID;
16
+ }
17
+ },
18
+ // Return the routing source as a virtual module.
19
+ // Because it's virtual (not inside node_modules), Vite processes
20
+ // import.meta.glob and all imports normally in the project context.
21
+ load(id) {
22
+ if (id === VIRTUAL_ROUTING_ID) {
23
+ return `
24
+ import { createBrowserRouter } from "react-router-dom";
25
+ import { lazy, Suspense, createElement } from "react";
26
+
27
+ // Eagerly load NotFound from the project's src directory.
28
+ const notFoundModules = import.meta.glob("/src/NotFound.tsx", { eager: true });
29
+ const NotFoundComponent = Object.values(notFoundModules)[0]?.default;
30
+
31
+ // Lazily load all page components under /src/pages.
32
+ const pages = import.meta.glob("/src/pages/**/*.tsx");
33
+
34
+ const routes = Object.entries(pages).map(([filePath, component]) => {
35
+ let cleaned = filePath.replace(/\\\\/g, "/");
36
+ cleaned = cleaned.replace(/.*\\/pages\\//, "");
37
+ cleaned = cleaned.replace(/\\.tsx$/i, "");
38
+ cleaned = cleaned.replace(/\\/index$/, "");
39
+ cleaned = cleaned.replace(/\\[(\\w+)\\]/g, ":$1");
40
+ if (cleaned === "index") cleaned = "";
41
+
42
+ const routePath = cleaned === "" ? "/" : \`/\${cleaned}\`;
43
+ const Component = lazy(component);
44
+
45
+ return {
46
+ path: routePath,
47
+ element: createElement(
48
+ Suspense,
49
+ { fallback: createElement("div", null, "Loading\u2026") },
50
+ createElement(Component)
51
+ ),
52
+ };
53
+ });
54
+
55
+ // 404 fallback
56
+ routes.push({
57
+ path: "*",
58
+ element: NotFoundComponent
59
+ ? createElement(NotFoundComponent)
60
+ : createElement("div", null, "404 - Page Not Found"),
61
+ });
62
+
63
+ export const router = createBrowserRouter(routes);
64
+ `;
65
+ }
66
+ },
67
+ };
68
+ }
@@ -0,0 +1,20 @@
1
+ import path from "path";
2
+ import { pathToFileURL } from "url";
3
+ export async function loadUserConfig() {
4
+ const configPath = path.resolve(process.cwd(), "revine.config.ts");
5
+ try {
6
+ const configModule = await import(pathToFileURL(configPath).href);
7
+ return configModule.default || {};
8
+ }
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
+ }
16
+ catch (e) {
17
+ return {};
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vite";
2
+ import { generateRevineViteConfig } from "./generateConfig.js";
3
+ // Vite supports async config. We can do:
4
+ export default defineConfig(async () => {
5
+ // Merge defaults + user overrides
6
+ return (await generateRevineViteConfig());
7
+ });
@@ -0,0 +1,27 @@
1
+ import chalk from "chalk";
2
+ export function revineLoggerPlugin() {
3
+ // custom chalk instance pointing to the indigo color
4
+ const indigo = chalk.hex("#6d28d9");
5
+ return {
6
+ name: "revine-logger",
7
+ configureServer(server) {
8
+ server.httpServer?.once("listening", () => {
9
+ const protocol = server.config.server.https ? "https" : "http";
10
+ const host = server.resolvedUrls?.local[0] || "localhost:3000";
11
+ const { network = [] } = server.resolvedUrls ?? {};
12
+ // Use the 'indigo' instance in place of 'chalk.cyan'
13
+ console.log(indigo("─────────────────────────────────────────────"));
14
+ console.log(indigo.bold("🚀 Revine Dev Server is now running!"));
15
+ console.log(indigo("─────────────────────────────────────────────"));
16
+ console.log(indigo(`Local: ${chalk.green(`${protocol}://${host}`)}`));
17
+ if (network.length) {
18
+ network.forEach((url) => {
19
+ console.log(indigo(`Network: ${chalk.green(url)}`));
20
+ });
21
+ }
22
+ console.log(indigo("─────────────────────────────────────────────"));
23
+ console.log("");
24
+ });
25
+ },
26
+ };
27
+ }
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createBrowserRouter } from "react-router-dom";
3
+ import { lazy, Suspense } from "react";
4
+ // @ts-ignore
5
+ import NotFound from "/src/NotFound";
6
+ // @ts-ignore
7
+ const pages = import.meta.glob("/src/pages/**/*.tsx");
8
+ const routes = Object.entries(pages).map(([filePath, component]) => {
9
+ let cleaned = filePath.replace(/\\/g, "/");
10
+ cleaned = cleaned.replace(/.*\/pages\//, "");
11
+ cleaned = cleaned.replace(/\.tsx$/i, "");
12
+ cleaned = cleaned.replace(/\/index$/, "");
13
+ cleaned = cleaned.replace(/\[(\w+)\]/g, ":$1");
14
+ if (cleaned === "index") {
15
+ cleaned = "";
16
+ }
17
+ const routePath = cleaned === "" ? "/" : `/${cleaned}`;
18
+ const Component = lazy(component);
19
+ return {
20
+ path: routePath,
21
+ element: (_jsx(Suspense, { fallback: _jsx("div", { children: "Loading..." }), children: _jsx(Component, {}) })),
22
+ };
23
+ });
24
+ // fallback route for 404s
25
+ routes.push({
26
+ path: "*",
27
+ element: _jsx(NotFound, {}),
28
+ });
29
+ export const router = createBrowserRouter(routes);
@@ -2,6 +2,7 @@ import { spawnSync } from "child_process";
2
2
  import { logError, logInfo } from "../utils/logger.js";
3
3
  export async function installDependencies(projectDir) {
4
4
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
5
+ // Step 1: npm install
5
6
  const installResult = spawnSync(npmCmd, ["install"], {
6
7
  stdio: "inherit",
7
8
  cwd: projectDir,
@@ -12,4 +13,11 @@ export async function installDependencies(projectDir) {
12
13
  logInfo("Try running manually: npm install");
13
14
  process.exit(1);
14
15
  }
16
+ // Step 2: Link local revine if available (replaces npm version with local build)
17
+ // This is a no-op if revine hasn't been globally linked via `npm link` in the revine repo.
18
+ spawnSync(npmCmd, ["link", "revine"], {
19
+ stdio: "pipe", // suppress output — silently skip if not linked
20
+ cwd: projectDir,
21
+ shell: true,
22
+ });
15
23
  }