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/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!
@@ -1,5 +1,6 @@
1
1
  import path from "path";
2
2
  import { fileURLToPath } from "url";
3
+ import fs from "fs-extra";
3
4
  import { updatePackageJson } from "../config/package.js";
4
5
  import { updateReadme } from "../config/readme.js";
5
6
  import { askForTailwindSetup, initGit, runProject } from "../prompts/index.js";
@@ -16,18 +17,35 @@ export async function createProject(projectName, options) {
16
17
  const isCurrentDir = [".", "./"].includes(projectName);
17
18
  try {
18
19
  logInfo(`Creating project in ${projectDir}...`);
20
+ // Ensure the project directory exists
21
+ await fs.ensureDir(projectDir);
19
22
  // This copies everything, including hidden directories like .revine
20
23
  await copyTemplate(templateDir, projectDir, options.force);
21
24
  // Derive final project name
22
25
  const finalProjectName = isCurrentDir
23
26
  ? path.basename(projectDir)
24
27
  : projectName;
25
- // Update package.json (e.g., set name, set "type": "module", add Tailwind deps if chosen)
28
+ // Check if package.json exists after template copy
26
29
  const packageJsonPath = path.join(projectDir, "package.json");
30
+ // Create basic package.json if it doesn't exist
31
+ if (!await fs.pathExists(packageJsonPath)) {
32
+ const basicPackageJson = {
33
+ name: finalProjectName,
34
+ version: "0.1.0",
35
+ private: true,
36
+ type: "module"
37
+ };
38
+ await fs.writeJSON(packageJsonPath, basicPackageJson, { spaces: 2 });
39
+ }
40
+ // Update package.json with the correct details
27
41
  const useTailwind = await askForTailwindSetup();
28
42
  await updatePackageJson(packageJsonPath, finalProjectName, { useTailwind });
29
- // Update README with the project name
43
+ // Check if README exists, create it if it doesn't
30
44
  const readmePath = path.join(projectDir, "README.md");
45
+ if (!await fs.pathExists(readmePath)) {
46
+ await fs.writeFile(readmePath, `# ${finalProjectName}\n\nCreated with Revine`);
47
+ }
48
+ // Update README with the project name
31
49
  await updateReadme(readmePath, finalProjectName);
32
50
  // Install dependencies
33
51
  logInfo("\nInstalling dependencies...");
@@ -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,12 +1,74 @@
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();
10
+ // Main command handler for direct project creation
11
+ const handleProjectCreation = async (projectName, options) => {
12
+ await createProject(projectName, options);
13
+ };
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
39
+ program
40
+ .version("0.8.0")
41
+ .argument("[project-name/command]")
42
+ .option("-f, --force", "Force creation in non-empty directory")
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);
49
+ }
50
+ else if (!arg) {
51
+ program.help();
52
+ }
53
+ });
54
+ // Create subcommand (npx revine create my-app)
5
55
  program
6
- .version("0.1.0")
56
+ .command("create")
7
57
  .argument("<project-name>")
8
58
  .option("-f, --force", "Force creation in non-empty directory")
9
59
  .action(async (projectName, options) => {
10
- await createProject(projectName, options);
60
+ await handleProjectCreation(projectName, options);
11
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"));
12
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
  }