revine 1.4.1 → 1.5.1
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.
- package/README.md +42 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -0
- package/dist/commands/createProject.d.ts.map +1 -1
- package/dist/commands/createProject.js +24 -8
- package/dist/hooks/useFetch.d.ts +14 -0
- package/dist/hooks/useFetch.d.ts.map +1 -0
- package/dist/hooks/useFetch.js +38 -0
- package/dist/index.js +13 -2
- package/dist/runtime/bundler/defaults/vite.d.ts.map +1 -1
- package/dist/runtime/bundler/defaults/vite.js +1 -2
- package/dist/runtime/cache.d.ts +10 -0
- package/dist/runtime/cache.d.ts.map +1 -0
- package/dist/runtime/cache.js +61 -0
- package/dist/runtime/fetch.d.ts +12 -0
- package/dist/runtime/fetch.d.ts.map +1 -0
- package/dist/runtime/fetch.js +32 -0
- package/dist/utils/logger.d.ts +5 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +58 -0
- package/package.json +3 -1
- package/roadmap.md +4 -1
- package/src/client.ts +3 -0
- package/src/commands/createProject.ts +26 -7
- package/src/hooks/useFetch.ts +50 -0
- package/src/index.ts +14 -2
- package/src/runtime/bundler/defaults/vite.ts +1 -2
- package/src/runtime/cache.ts +71 -0
- package/src/runtime/fetch.ts +50 -0
- package/src/utils/logger.ts +70 -0
- package/src/runtime/bundler/viteLoggerPlugin.ts +0 -63
package/README.md
CHANGED
|
@@ -53,6 +53,48 @@ src/pages/about.tsx → /about
|
|
|
53
53
|
|
|
54
54
|
src/pages/blog/[slug].tsx → /blog/:slug
|
|
55
55
|
|
|
56
|
+
## Data Fetching & Caching
|
|
57
|
+
|
|
58
|
+
Revine includes built-in support for cached API calls, allowing you to easily store and reuse server responses.
|
|
59
|
+
|
|
60
|
+
### `revineFetch`
|
|
61
|
+
|
|
62
|
+
A wrapper around the native `fetch` API with caching capabilities.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { revineFetch } from "revine";
|
|
66
|
+
|
|
67
|
+
const data = await revineFetch("https://api.example.com/data", {
|
|
68
|
+
cacheTTL: 60000, // Cache for 1 minute (in ms)
|
|
69
|
+
persist: true // Optional: Persist to localStorage
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `useFetch` Hook
|
|
74
|
+
|
|
75
|
+
A React hook for making cached API calls within components.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { useFetch } from "revine";
|
|
79
|
+
|
|
80
|
+
function MyComponent() {
|
|
81
|
+
const { data, loading, error, revalidate } = useFetch("https://api.example.com/data", {
|
|
82
|
+
cacheTTL: 300000, // 5 minutes
|
|
83
|
+
persist: true
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (loading) return <div>Loading...</div>;
|
|
87
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div>
|
|
91
|
+
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
92
|
+
<button onClick={() => revalidate()}>Refresh Data</button>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
56
98
|
## Contributing
|
|
57
99
|
|
|
58
100
|
### Clone repository
|
package/dist/client.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ export type { LinkProps } from "./components/Link.js";
|
|
|
6
6
|
export { NavLink } from "./components/NavLink.js";
|
|
7
7
|
export type { NavLinkProps } from "./components/NavLink.js";
|
|
8
8
|
export { useRouter } from "./hooks/useRouter.js";
|
|
9
|
+
export { useFetch } from "./hooks/useFetch.js";
|
|
10
|
+
export { revineFetch } from "./runtime/fetch.js";
|
|
11
|
+
export type { RevineFetchOptions } from "./runtime/fetch.js";
|
|
9
12
|
export { defineConfig } from "./runtime/defineConfig.js";
|
|
10
13
|
export { env, envAll } from "./runtime/env.js";
|
|
11
14
|
export { middlewareResponse } from "./runtime/middleware.js";
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,cAAc,EACd,WAAW,EACX,WAAW,EACX,SAAS,EACT,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACrH,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,cAAc,EACd,WAAW,EACX,WAAW,EACX,SAAS,EACT,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACrH,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/client.js
CHANGED
|
@@ -3,6 +3,8 @@ export { Image } from "./components/Image.js";
|
|
|
3
3
|
export { Link } from "./components/Link.js";
|
|
4
4
|
export { NavLink } from "./components/NavLink.js";
|
|
5
5
|
export { useRouter } from "./hooks/useRouter.js";
|
|
6
|
+
export { useFetch } from "./hooks/useFetch.js";
|
|
7
|
+
export { revineFetch } from "./runtime/fetch.js";
|
|
6
8
|
export { defineConfig } from "./runtime/defineConfig.js";
|
|
7
9
|
export { env, envAll } from "./runtime/env.js";
|
|
8
10
|
export { middlewareResponse } from "./runtime/middleware.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createProject.d.ts","sourceRoot":"","sources":["../../src/commands/createProject.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"createProject.d.ts","sourceRoot":"","sources":["../../src/commands/createProject.ts"],"names":[],"mappings":"AAwDA,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,iBA8F7B"}
|
|
@@ -7,7 +7,9 @@ import { askForTailwindSetup, initGit, runProject } from "../prompts/index.js";
|
|
|
7
7
|
import { installDependencies } from "../setup/dependencies.js";
|
|
8
8
|
import { setupTailwind } from "../setup/tailwind.js";
|
|
9
9
|
import { copyTemplate } from "../utils/file.js";
|
|
10
|
-
import { logError,
|
|
10
|
+
import { logError, logStep, logSuccess, getLogo } from "../utils/logger.js";
|
|
11
|
+
import boxen from "boxen";
|
|
12
|
+
import chalk from "chalk";
|
|
11
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
14
|
const __dirname = path.dirname(__filename);
|
|
13
15
|
const GITIGNORE_CONTENT = `# Dependencies
|
|
@@ -55,7 +57,7 @@ export async function createProject(projectName, options) {
|
|
|
55
57
|
const projectDir = path.resolve(projectName);
|
|
56
58
|
const isCurrentDir = [".", "./"].includes(projectName);
|
|
57
59
|
try {
|
|
58
|
-
|
|
60
|
+
logStep(`Creating project in ${chalk.cyan(projectDir)}...`);
|
|
59
61
|
// Ensure the project directory exists
|
|
60
62
|
await fs.ensureDir(projectDir);
|
|
61
63
|
// This copies everything, including hidden directories like .revine
|
|
@@ -89,17 +91,31 @@ export async function createProject(projectName, options) {
|
|
|
89
91
|
// Update README with the project name
|
|
90
92
|
await updateReadme(readmePath, finalProjectName);
|
|
91
93
|
// Install dependencies
|
|
92
|
-
|
|
94
|
+
logStep("Installing dependencies...");
|
|
93
95
|
await installDependencies(projectDir);
|
|
94
96
|
// If Tailwind is selected, set it up
|
|
95
97
|
if (useTailwind) {
|
|
98
|
+
logStep("Setting up Tailwind CSS...");
|
|
96
99
|
await setupTailwind(projectDir);
|
|
97
100
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
logSuccess(`Project created at ${chalk.cyan(projectDir)}`);
|
|
102
|
+
const successContent = [
|
|
103
|
+
getLogo(),
|
|
104
|
+
"",
|
|
105
|
+
`${chalk.bold.green("Success!")} Your Revine project is ready.`,
|
|
106
|
+
"",
|
|
107
|
+
`${chalk.white("Next steps:")}`,
|
|
108
|
+
!isCurrentDir ? `${chalk.dim("1.")} ${chalk.cyan(`cd ${projectName}`)}` : "",
|
|
109
|
+
`${isCurrentDir ? chalk.dim("1.") : chalk.dim("2.")} ${chalk.cyan("npm run dev")}`,
|
|
110
|
+
"",
|
|
111
|
+
`${chalk.dim("Happy coding!")}`
|
|
112
|
+
].filter(Boolean).join("\n");
|
|
113
|
+
console.log(boxen(successContent, {
|
|
114
|
+
padding: 1,
|
|
115
|
+
margin: { top: 1, bottom: 1, left: 0, right: 0 },
|
|
116
|
+
borderStyle: "round",
|
|
117
|
+
borderColor: "green",
|
|
118
|
+
}));
|
|
103
119
|
// Check if Git exists and initialize repository if user agrees
|
|
104
120
|
await initGit(projectDir);
|
|
105
121
|
// Prompt to run project
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RevineFetchOptions } from "../runtime/fetch.js";
|
|
2
|
+
export interface UseFetchResult<T> {
|
|
3
|
+
data: T | null;
|
|
4
|
+
loading: boolean;
|
|
5
|
+
error: Error | null;
|
|
6
|
+
revalidate: () => Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A React hook for making cached API calls.
|
|
10
|
+
* @param url The URL to fetch
|
|
11
|
+
* @param options Fetch and cache options
|
|
12
|
+
*/
|
|
13
|
+
export declare function useFetch<T = any>(url: string, options?: RevineFetchOptions): UseFetchResult<T>;
|
|
14
|
+
//# sourceMappingURL=useFetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFetch.d.ts","sourceRoot":"","sources":["../../src/hooks/useFetch.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEtE,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,GAAG,GAAG,EAC9B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,kBAAuB,GAC/B,cAAc,CAAC,CAAC,CAAC,CA+BnB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { revineFetch } from "../runtime/fetch.js";
|
|
3
|
+
/**
|
|
4
|
+
* A React hook for making cached API calls.
|
|
5
|
+
* @param url The URL to fetch
|
|
6
|
+
* @param options Fetch and cache options
|
|
7
|
+
*/
|
|
8
|
+
export function useFetch(url, options = {}) {
|
|
9
|
+
const [data, setData] = useState(null);
|
|
10
|
+
const [loading, setLoading] = useState(true);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
const fetchData = async (isRevalidating = false) => {
|
|
13
|
+
try {
|
|
14
|
+
setLoading(true);
|
|
15
|
+
const result = await revineFetch(url, {
|
|
16
|
+
...options,
|
|
17
|
+
revalidate: isRevalidating || options.revalidate,
|
|
18
|
+
});
|
|
19
|
+
setData(result);
|
|
20
|
+
setError(null);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
setLoading(false);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
fetchData();
|
|
31
|
+
}, [url, JSON.stringify(options)]);
|
|
32
|
+
return {
|
|
33
|
+
data,
|
|
34
|
+
loading,
|
|
35
|
+
error,
|
|
36
|
+
revalidate: () => fetchData(true),
|
|
37
|
+
};
|
|
38
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,8 @@ import { readFileSync } from "fs";
|
|
|
4
4
|
import path from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { createProject } from "./commands/createProject.js";
|
|
7
|
+
import { printDevServerInfo, logStep, logSuccess, logBrand } from "./utils/logger.js";
|
|
8
|
+
import chalk from "chalk";
|
|
7
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
10
|
const __dirname = path.dirname(__filename);
|
|
9
11
|
const pkgPath = path.resolve(__dirname, "../package.json");
|
|
@@ -23,25 +25,33 @@ const runViteCommand = async (command) => {
|
|
|
23
25
|
const { generateRevineViteConfig } = await import(path.resolve(__dirname, "runtime/bundler/generateConfig.js"));
|
|
24
26
|
const config = await generateRevineViteConfig();
|
|
25
27
|
if (command === "dev") {
|
|
28
|
+
const startTime = Date.now();
|
|
26
29
|
const server = await vite.createServer({
|
|
27
30
|
...config,
|
|
28
31
|
configFile: false, // we pass config directly, no file needed
|
|
29
32
|
});
|
|
30
33
|
await server.listen();
|
|
31
|
-
server.
|
|
34
|
+
const port = server.config.server.port || 3000;
|
|
35
|
+
printDevServerInfo(pkg.version, port, startTime);
|
|
32
36
|
}
|
|
33
37
|
else if (command === "build") {
|
|
38
|
+
const startTime = Date.now();
|
|
39
|
+
logStep("Building project for production...");
|
|
34
40
|
await vite.build({
|
|
35
41
|
...config,
|
|
36
42
|
configFile: false,
|
|
37
43
|
});
|
|
44
|
+
const duration = Date.now() - startTime;
|
|
45
|
+
logSuccess(`Build completed in ${chalk.bold(duration)}ms`);
|
|
38
46
|
}
|
|
39
47
|
else if (command === "preview") {
|
|
48
|
+
const startTime = Date.now();
|
|
40
49
|
const server = await vite.preview({
|
|
41
50
|
...config,
|
|
42
51
|
configFile: false,
|
|
43
52
|
});
|
|
44
|
-
server.
|
|
53
|
+
const port = server.config.preview.port || 3000;
|
|
54
|
+
printDevServerInfo(pkg.version, port, startTime);
|
|
45
55
|
}
|
|
46
56
|
};
|
|
47
57
|
// Root command — handles: npx revine <project-name>
|
|
@@ -78,4 +88,5 @@ program
|
|
|
78
88
|
.command("preview")
|
|
79
89
|
.description("Preview the production build")
|
|
80
90
|
.action(() => runViteCommand("preview"));
|
|
91
|
+
logBrand();
|
|
81
92
|
program.parse(process.argv);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../../../../src/runtime/bundler/defaults/vite.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../../../../src/runtime/bundler/defaults/vite.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;CAsB7B,CAAC"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import react from "@vitejs/plugin-react";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { revinePlugin } from "../revinePlugin.js";
|
|
4
|
-
import { revineLoggerPlugin } from "../viteLoggerPlugin.js";
|
|
5
4
|
export const defaultViteConfig = {
|
|
6
|
-
plugins: [react(), revinePlugin()
|
|
5
|
+
plugins: [react(), revinePlugin()],
|
|
7
6
|
logLevel: "silent",
|
|
8
7
|
// Only expose env variables prefixed with REVINE_PUBLIC_ to the browser bundle.
|
|
9
8
|
// Variables without this prefix are never included in client-side code.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare class RevineCache {
|
|
2
|
+
private memoryCache;
|
|
3
|
+
set<T>(key: string, data: T, ttl: number, persist?: boolean): void;
|
|
4
|
+
get<T>(key: string): T | null;
|
|
5
|
+
delete(key: string): void;
|
|
6
|
+
clear(): void;
|
|
7
|
+
}
|
|
8
|
+
export declare const revineCache: RevineCache;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/runtime/cache.ts"],"names":[],"mappings":"AAKA,cAAM,WAAW;IACf,OAAO,CAAC,WAAW,CAAsC;IAEzD,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,OAAe;IAiBlE,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI;IA4B7B,MAAM,CAAC,GAAG,EAAE,MAAM;IAOlB,KAAK;CAQN;AAED,eAAO,MAAM,WAAW,aAAoB,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
class RevineCache {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.memoryCache = new Map();
|
|
4
|
+
}
|
|
5
|
+
set(key, data, ttl, persist = false) {
|
|
6
|
+
const entry = {
|
|
7
|
+
data,
|
|
8
|
+
expiry: Date.now() + ttl,
|
|
9
|
+
};
|
|
10
|
+
this.memoryCache.set(key, entry);
|
|
11
|
+
if (persist && typeof window !== "undefined") {
|
|
12
|
+
try {
|
|
13
|
+
localStorage.setItem(`revine_cache_${key}`, JSON.stringify(entry));
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
console.warn("Revine Cache: Failed to persist to localStorage", e);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
get(key) {
|
|
21
|
+
// Check memory cache first
|
|
22
|
+
let entry = this.memoryCache.get(key);
|
|
23
|
+
// If not in memory, check localStorage
|
|
24
|
+
if (!entry && typeof window !== "undefined") {
|
|
25
|
+
try {
|
|
26
|
+
const persisted = localStorage.getItem(`revine_cache_${key}`);
|
|
27
|
+
if (persisted) {
|
|
28
|
+
entry = JSON.parse(persisted);
|
|
29
|
+
// Sync back to memory cache
|
|
30
|
+
if (entry)
|
|
31
|
+
this.memoryCache.set(key, entry);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
console.warn("Revine Cache: Failed to read from localStorage", e);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!entry)
|
|
39
|
+
return null;
|
|
40
|
+
if (Date.now() > entry.expiry) {
|
|
41
|
+
this.delete(key);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return entry.data;
|
|
45
|
+
}
|
|
46
|
+
delete(key) {
|
|
47
|
+
this.memoryCache.delete(key);
|
|
48
|
+
if (typeof window !== "undefined") {
|
|
49
|
+
localStorage.removeItem(`revine_cache_${key}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
clear() {
|
|
53
|
+
this.memoryCache.clear();
|
|
54
|
+
if (typeof window !== "undefined") {
|
|
55
|
+
Object.keys(localStorage)
|
|
56
|
+
.filter((key) => key.startsWith("revine_cache_"))
|
|
57
|
+
.forEach((key) => localStorage.removeItem(key));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export const revineCache = new RevineCache();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface RevineFetchOptions extends RequestInit {
|
|
2
|
+
cacheTTL?: number;
|
|
3
|
+
persist?: boolean;
|
|
4
|
+
revalidate?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Enhanced fetch with caching capabilities.
|
|
8
|
+
* @param url The URL to fetch
|
|
9
|
+
* @param options Fetch options plus cache configuration
|
|
10
|
+
*/
|
|
11
|
+
export declare function revineFetch<T = any>(url: string, options?: RevineFetchOptions): Promise<T>;
|
|
12
|
+
//# sourceMappingURL=fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/runtime/fetch.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,CAAC,GAAG,GAAG,EACvC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,CAAC,CAAC,CAiCZ"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { revineCache } from "./cache.js";
|
|
2
|
+
/**
|
|
3
|
+
* Enhanced fetch with caching capabilities.
|
|
4
|
+
* @param url The URL to fetch
|
|
5
|
+
* @param options Fetch options plus cache configuration
|
|
6
|
+
*/
|
|
7
|
+
export async function revineFetch(url, options = {}) {
|
|
8
|
+
const { cacheTTL = 0, persist = false, revalidate = false, ...fetchOptions } = options;
|
|
9
|
+
// Cache works if cacheTTL > 0. Usually only for GET, but GraphQL uses POST.
|
|
10
|
+
const isCacheable = cacheTTL > 0;
|
|
11
|
+
// Create a more robust cache key that includes the method and body for POST requests
|
|
12
|
+
const method = (fetchOptions.method || "GET").toUpperCase();
|
|
13
|
+
const bodyKey = fetchOptions.body ? `_body:${fetchOptions.body}` : "";
|
|
14
|
+
const cacheKey = `fetch_${method}_${url}_${JSON.stringify(fetchOptions.headers || {})}${bodyKey}`;
|
|
15
|
+
if (isCacheable && !revalidate) {
|
|
16
|
+
const cachedData = revineCache.get(cacheKey);
|
|
17
|
+
if (cachedData !== null) {
|
|
18
|
+
return cachedData;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const response = await fetch(url, fetchOptions);
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
const error = new Error(`Revine Fetch Error: ${response.status} ${response.statusText}`);
|
|
24
|
+
error.status = response.status;
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
if (isCacheable) {
|
|
29
|
+
revineCache.set(cacheKey, data, cacheTTL, persist);
|
|
30
|
+
}
|
|
31
|
+
return data;
|
|
32
|
+
}
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export declare function logInfo(message: string): void;
|
|
2
2
|
export declare function logError(message: string, error?: any): void;
|
|
3
|
+
export declare function logBrand(): void;
|
|
4
|
+
export declare function getLogo(): string;
|
|
5
|
+
export declare function logStep(message: string): void;
|
|
6
|
+
export declare function logSuccess(message: string): void;
|
|
7
|
+
export declare function printDevServerInfo(version: string, port: number, startTime: number): void;
|
|
3
8
|
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAKA,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,QAEtC;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,QAEpD;AAID,wBAAgB,QAAQ,SAEvB;AAED,wBAAgB,OAAO,WAQtB;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,QAEtC;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,QAEzC;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAyClF"}
|
package/dist/utils/logger.js
CHANGED
|
@@ -1,7 +1,65 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import boxen from "boxen";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import gradient from "gradient-string";
|
|
2
5
|
export function logInfo(message) {
|
|
3
6
|
console.log(chalk.cyan(message));
|
|
4
7
|
}
|
|
5
8
|
export function logError(message, error) {
|
|
6
9
|
console.error(chalk.red(message), error || "");
|
|
7
10
|
}
|
|
11
|
+
const revineGradient = gradient(["#7c3aed", "#a78bfa", "#f472b6"]);
|
|
12
|
+
export function logBrand() {
|
|
13
|
+
console.log(chalk.bold(revineGradient("\n ◆ REVINE\n")));
|
|
14
|
+
}
|
|
15
|
+
export function getLogo() {
|
|
16
|
+
return chalk.bold(revineGradient.multiline([
|
|
17
|
+
" ____ _______ _____ _ _ _____ ",
|
|
18
|
+
" | _ \\| ____\\ \\ / /_ _| \\ | || ____|",
|
|
19
|
+
" | |_) | _| \\ V / | || \\| || _| ",
|
|
20
|
+
" | _ <| |___ \\ / | || |\\ || |___ ",
|
|
21
|
+
" |_| \\_\\_____| \\_/ |___|_| \\_||_____|"
|
|
22
|
+
].join("\n")));
|
|
23
|
+
}
|
|
24
|
+
export function logStep(message) {
|
|
25
|
+
console.log(`${revineGradient("⚡")} ${message}`);
|
|
26
|
+
}
|
|
27
|
+
export function logSuccess(message) {
|
|
28
|
+
console.log(`${chalk.green("✔")} ${message}`);
|
|
29
|
+
}
|
|
30
|
+
export function printDevServerInfo(version, port, startTime) {
|
|
31
|
+
const duration = Date.now() - startTime;
|
|
32
|
+
const localUrl = `http://localhost:${port}/`;
|
|
33
|
+
const networkInterfaces = os.networkInterfaces();
|
|
34
|
+
const networkUrls = [];
|
|
35
|
+
for (const interfaceName in networkInterfaces) {
|
|
36
|
+
const interfaces = networkInterfaces[interfaceName];
|
|
37
|
+
if (interfaces) {
|
|
38
|
+
for (const iface of interfaces) {
|
|
39
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
40
|
+
networkUrls.push(`http://${iface.address}:${port}/`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const logo = getLogo();
|
|
46
|
+
const content = [
|
|
47
|
+
logo,
|
|
48
|
+
"",
|
|
49
|
+
`${chalk.dim("Framework Version:")} ${chalk.bold.white(`v${version}`)}`,
|
|
50
|
+
"",
|
|
51
|
+
`${chalk.cyan("➜")} ${chalk.bold("Local:")} ${chalk.blue(localUrl)}`,
|
|
52
|
+
...networkUrls.map(url => `${chalk.cyan("➜")} ${chalk.bold("Network:")} ${chalk.blue(url)}`),
|
|
53
|
+
"",
|
|
54
|
+
`${chalk.dim("Ready in")} ${chalk.bold.white(duration)}${chalk.dim("ms")}`,
|
|
55
|
+
].join("\n");
|
|
56
|
+
const boxed = boxen(content, {
|
|
57
|
+
padding: 1,
|
|
58
|
+
margin: { top: 1, bottom: 1, left: 0, right: 0 },
|
|
59
|
+
borderStyle: "round",
|
|
60
|
+
borderColor: "dim",
|
|
61
|
+
title: chalk.bold.white(" DEV SERVER "),
|
|
62
|
+
titleAlignment: "left",
|
|
63
|
+
});
|
|
64
|
+
console.log("\n" + boxed + "\n");
|
|
65
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "revine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "A react framework, but better.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Rachit Bharadwaj",
|
|
@@ -27,10 +27,12 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@vitejs/plugin-react": "^4.2.1",
|
|
30
|
+
"boxen": "^8.0.1",
|
|
30
31
|
"chalk": "^5.4.1",
|
|
31
32
|
"commander": "^13.1.0",
|
|
32
33
|
"dotenv": "^16.4.5",
|
|
33
34
|
"fs-extra": "^11.3.0",
|
|
35
|
+
"gradient-string": "^3.0.0",
|
|
34
36
|
"inquirer": "^12.4.1",
|
|
35
37
|
"lodash-es": "^4.17.21",
|
|
36
38
|
"react": "^18.2.0",
|
package/roadmap.md
CHANGED
package/src/client.ts
CHANGED
|
@@ -13,6 +13,9 @@ export type { LinkProps } from "./components/Link.js";
|
|
|
13
13
|
export { NavLink } from "./components/NavLink.js";
|
|
14
14
|
export type { NavLinkProps } from "./components/NavLink.js";
|
|
15
15
|
export { useRouter } from "./hooks/useRouter.js";
|
|
16
|
+
export { useFetch } from "./hooks/useFetch.js";
|
|
17
|
+
export { revineFetch } from "./runtime/fetch.js";
|
|
18
|
+
export type { RevineFetchOptions } from "./runtime/fetch.js";
|
|
16
19
|
export { defineConfig } from "./runtime/defineConfig.js";
|
|
17
20
|
export { env, envAll } from "./runtime/env.js";
|
|
18
21
|
export { middlewareResponse } from "./runtime/middleware.js";
|
|
@@ -7,7 +7,9 @@ import { askForTailwindSetup, initGit, runProject } from "../prompts/index.js";
|
|
|
7
7
|
import { installDependencies } from "../setup/dependencies.js";
|
|
8
8
|
import { setupTailwind } from "../setup/tailwind.js";
|
|
9
9
|
import { copyTemplate } from "../utils/file.js";
|
|
10
|
-
import { logError, logInfo } from "../utils/logger.js";
|
|
10
|
+
import { logError, logInfo, logStep, logSuccess, getLogo } from "../utils/logger.js";
|
|
11
|
+
import boxen from "boxen";
|
|
12
|
+
import chalk from "chalk";
|
|
11
13
|
|
|
12
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
15
|
const __dirname = path.dirname(__filename);
|
|
@@ -62,7 +64,7 @@ export async function createProject(
|
|
|
62
64
|
const isCurrentDir = [".", "./"].includes(projectName);
|
|
63
65
|
|
|
64
66
|
try {
|
|
65
|
-
|
|
67
|
+
logStep(`Creating project in ${chalk.cyan(projectDir)}...`);
|
|
66
68
|
|
|
67
69
|
// Ensure the project directory exists
|
|
68
70
|
await fs.ensureDir(projectDir);
|
|
@@ -109,18 +111,35 @@ export async function createProject(
|
|
|
109
111
|
await updateReadme(readmePath, finalProjectName);
|
|
110
112
|
|
|
111
113
|
// Install dependencies
|
|
112
|
-
|
|
114
|
+
logStep("Installing dependencies...");
|
|
113
115
|
await installDependencies(projectDir);
|
|
114
116
|
|
|
115
117
|
// If Tailwind is selected, set it up
|
|
116
118
|
if (useTailwind) {
|
|
119
|
+
logStep("Setting up Tailwind CSS...");
|
|
117
120
|
await setupTailwind(projectDir);
|
|
118
121
|
}
|
|
119
122
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
logSuccess(`Project created at ${chalk.cyan(projectDir)}`);
|
|
124
|
+
|
|
125
|
+
const successContent = [
|
|
126
|
+
getLogo(),
|
|
127
|
+
"",
|
|
128
|
+
`${chalk.bold.green("Success!")} Your Revine project is ready.`,
|
|
129
|
+
"",
|
|
130
|
+
`${chalk.white("Next steps:")}`,
|
|
131
|
+
!isCurrentDir ? `${chalk.dim("1.")} ${chalk.cyan(`cd ${projectName}`)}` : "",
|
|
132
|
+
`${isCurrentDir ? chalk.dim("1.") : chalk.dim("2.")} ${chalk.cyan("npm run dev")}`,
|
|
133
|
+
"",
|
|
134
|
+
`${chalk.dim("Happy coding!")}`
|
|
135
|
+
].filter(Boolean).join("\n");
|
|
136
|
+
|
|
137
|
+
console.log(boxen(successContent, {
|
|
138
|
+
padding: 1,
|
|
139
|
+
margin: { top: 1, bottom: 1, left: 0, right: 0 },
|
|
140
|
+
borderStyle: "round",
|
|
141
|
+
borderColor: "green",
|
|
142
|
+
}));
|
|
124
143
|
|
|
125
144
|
// Check if Git exists and initialize repository if user agrees
|
|
126
145
|
await initGit(projectDir);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { revineFetch, RevineFetchOptions } from "../runtime/fetch.js";
|
|
3
|
+
|
|
4
|
+
export interface UseFetchResult<T> {
|
|
5
|
+
data: T | null;
|
|
6
|
+
loading: boolean;
|
|
7
|
+
error: Error | null;
|
|
8
|
+
revalidate: () => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A React hook for making cached API calls.
|
|
13
|
+
* @param url The URL to fetch
|
|
14
|
+
* @param options Fetch and cache options
|
|
15
|
+
*/
|
|
16
|
+
export function useFetch<T = any>(
|
|
17
|
+
url: string,
|
|
18
|
+
options: RevineFetchOptions = {}
|
|
19
|
+
): UseFetchResult<T> {
|
|
20
|
+
const [data, setData] = useState<T | null>(null);
|
|
21
|
+
const [loading, setLoading] = useState<boolean>(true);
|
|
22
|
+
const [error, setError] = useState<Error | null>(null);
|
|
23
|
+
|
|
24
|
+
const fetchData = async (isRevalidating: boolean = false) => {
|
|
25
|
+
try {
|
|
26
|
+
setLoading(true);
|
|
27
|
+
const result = await revineFetch<T>(url, {
|
|
28
|
+
...options,
|
|
29
|
+
revalidate: isRevalidating || options.revalidate,
|
|
30
|
+
});
|
|
31
|
+
setData(result);
|
|
32
|
+
setError(null);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
35
|
+
} finally {
|
|
36
|
+
setLoading(false);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
fetchData();
|
|
42
|
+
}, [url, JSON.stringify(options)]);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
data,
|
|
46
|
+
loading,
|
|
47
|
+
error,
|
|
48
|
+
revalidate: () => fetchData(true),
|
|
49
|
+
};
|
|
50
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { readFileSync } from "fs";
|
|
|
4
4
|
import path from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { createProject } from "./commands/createProject.js";
|
|
7
|
+
import { printDevServerInfo, logStep, logSuccess, logBrand } from "./utils/logger.js";
|
|
8
|
+
import chalk from "chalk";
|
|
7
9
|
|
|
8
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
11
|
const __dirname = path.dirname(__filename);
|
|
@@ -40,23 +42,31 @@ const runViteCommand = async (command: string) => {
|
|
|
40
42
|
const config = await generateRevineViteConfig();
|
|
41
43
|
|
|
42
44
|
if (command === "dev") {
|
|
45
|
+
const startTime = Date.now();
|
|
43
46
|
const server = await vite.createServer({
|
|
44
47
|
...config,
|
|
45
48
|
configFile: false, // we pass config directly, no file needed
|
|
46
49
|
});
|
|
47
50
|
await server.listen();
|
|
48
|
-
server.
|
|
51
|
+
const port = server.config.server.port || 3000;
|
|
52
|
+
printDevServerInfo(pkg.version, port, startTime);
|
|
49
53
|
} else if (command === "build") {
|
|
54
|
+
const startTime = Date.now();
|
|
55
|
+
logStep("Building project for production...");
|
|
50
56
|
await vite.build({
|
|
51
57
|
...config,
|
|
52
58
|
configFile: false,
|
|
53
59
|
});
|
|
60
|
+
const duration = Date.now() - startTime;
|
|
61
|
+
logSuccess(`Build completed in ${chalk.bold(duration)}ms`);
|
|
54
62
|
} else if (command === "preview") {
|
|
63
|
+
const startTime = Date.now();
|
|
55
64
|
const server = await vite.preview({
|
|
56
65
|
...config,
|
|
57
66
|
configFile: false,
|
|
58
67
|
});
|
|
59
|
-
server.
|
|
68
|
+
const port = server.config.preview.port || 3000;
|
|
69
|
+
printDevServerInfo(pkg.version, port, startTime);
|
|
60
70
|
}
|
|
61
71
|
};
|
|
62
72
|
|
|
@@ -98,4 +108,6 @@ program
|
|
|
98
108
|
.description("Preview the production build")
|
|
99
109
|
.action(() => runViteCommand("preview"));
|
|
100
110
|
|
|
111
|
+
logBrand();
|
|
112
|
+
|
|
101
113
|
program.parse(process.argv);
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import react from "@vitejs/plugin-react";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { revinePlugin } from "../revinePlugin.js";
|
|
4
|
-
import { revineLoggerPlugin } from "../viteLoggerPlugin.js";
|
|
5
4
|
|
|
6
5
|
export const defaultViteConfig = {
|
|
7
|
-
plugins: [react(), revinePlugin()
|
|
6
|
+
plugins: [react(), revinePlugin()],
|
|
8
7
|
logLevel: "silent",
|
|
9
8
|
// Only expose env variables prefixed with REVINE_PUBLIC_ to the browser bundle.
|
|
10
9
|
// Variables without this prefix are never included in client-side code.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
type CacheEntry<T> = {
|
|
2
|
+
data: T;
|
|
3
|
+
expiry: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
class RevineCache {
|
|
7
|
+
private memoryCache = new Map<string, CacheEntry<any>>();
|
|
8
|
+
|
|
9
|
+
set<T>(key: string, data: T, ttl: number, persist: boolean = false) {
|
|
10
|
+
const entry: CacheEntry<T> = {
|
|
11
|
+
data,
|
|
12
|
+
expiry: Date.now() + ttl,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
this.memoryCache.set(key, entry);
|
|
16
|
+
|
|
17
|
+
if (persist && typeof window !== "undefined") {
|
|
18
|
+
try {
|
|
19
|
+
localStorage.setItem(`revine_cache_${key}`, JSON.stringify(entry));
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.warn("Revine Cache: Failed to persist to localStorage", e);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get<T>(key: string): T | null {
|
|
27
|
+
// Check memory cache first
|
|
28
|
+
let entry = this.memoryCache.get(key);
|
|
29
|
+
|
|
30
|
+
// If not in memory, check localStorage
|
|
31
|
+
if (!entry && typeof window !== "undefined") {
|
|
32
|
+
try {
|
|
33
|
+
const persisted = localStorage.getItem(`revine_cache_${key}`);
|
|
34
|
+
if (persisted) {
|
|
35
|
+
entry = JSON.parse(persisted);
|
|
36
|
+
// Sync back to memory cache
|
|
37
|
+
if (entry) this.memoryCache.set(key, entry);
|
|
38
|
+
}
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.warn("Revine Cache: Failed to read from localStorage", e);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!entry) return null;
|
|
45
|
+
|
|
46
|
+
if (Date.now() > entry.expiry) {
|
|
47
|
+
this.delete(key);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return entry.data;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
delete(key: string) {
|
|
55
|
+
this.memoryCache.delete(key);
|
|
56
|
+
if (typeof window !== "undefined") {
|
|
57
|
+
localStorage.removeItem(`revine_cache_${key}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
clear() {
|
|
62
|
+
this.memoryCache.clear();
|
|
63
|
+
if (typeof window !== "undefined") {
|
|
64
|
+
Object.keys(localStorage)
|
|
65
|
+
.filter((key) => key.startsWith("revine_cache_"))
|
|
66
|
+
.forEach((key) => localStorage.removeItem(key));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const revineCache = new RevineCache();
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { revineCache } from "./cache.js";
|
|
2
|
+
|
|
3
|
+
export interface RevineFetchOptions extends RequestInit {
|
|
4
|
+
cacheTTL?: number; // TTL in milliseconds
|
|
5
|
+
persist?: boolean; // Whether to persist cache to localStorage
|
|
6
|
+
revalidate?: boolean; // If true, force fetch and update cache
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Enhanced fetch with caching capabilities.
|
|
11
|
+
* @param url The URL to fetch
|
|
12
|
+
* @param options Fetch options plus cache configuration
|
|
13
|
+
*/
|
|
14
|
+
export async function revineFetch<T = any>(
|
|
15
|
+
url: string,
|
|
16
|
+
options: RevineFetchOptions = {}
|
|
17
|
+
): Promise<T> {
|
|
18
|
+
const { cacheTTL = 0, persist = false, revalidate = false, ...fetchOptions } = options;
|
|
19
|
+
|
|
20
|
+
// Cache works if cacheTTL > 0. Usually only for GET, but GraphQL uses POST.
|
|
21
|
+
const isCacheable = cacheTTL > 0;
|
|
22
|
+
|
|
23
|
+
// Create a more robust cache key that includes the method and body for POST requests
|
|
24
|
+
const method = (fetchOptions.method || "GET").toUpperCase();
|
|
25
|
+
const bodyKey = fetchOptions.body ? `_body:${fetchOptions.body}` : "";
|
|
26
|
+
const cacheKey = `fetch_${method}_${url}_${JSON.stringify(fetchOptions.headers || {})}${bodyKey}`;
|
|
27
|
+
|
|
28
|
+
if (isCacheable && !revalidate) {
|
|
29
|
+
const cachedData = revineCache.get<T>(cacheKey);
|
|
30
|
+
if (cachedData !== null) {
|
|
31
|
+
return cachedData;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const response = await fetch(url, fetchOptions);
|
|
36
|
+
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
const error: any = new Error(`Revine Fetch Error: ${response.status} ${response.statusText}`);
|
|
39
|
+
error.status = response.status;
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
|
|
45
|
+
if (isCacheable) {
|
|
46
|
+
revineCache.set(cacheKey, data, cacheTTL, persist);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return data;
|
|
50
|
+
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import boxen from "boxen";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import gradient from "gradient-string";
|
|
2
5
|
|
|
3
6
|
export function logInfo(message: string) {
|
|
4
7
|
console.log(chalk.cyan(message));
|
|
@@ -7,3 +10,70 @@ export function logInfo(message: string) {
|
|
|
7
10
|
export function logError(message: string, error?: any) {
|
|
8
11
|
console.error(chalk.red(message), error || "");
|
|
9
12
|
}
|
|
13
|
+
|
|
14
|
+
const revineGradient = gradient(["#7c3aed", "#a78bfa", "#f472b6"]);
|
|
15
|
+
|
|
16
|
+
export function logBrand() {
|
|
17
|
+
console.log(chalk.bold(revineGradient("\n ◆ REVINE\n")));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getLogo() {
|
|
21
|
+
return chalk.bold(revineGradient.multiline([
|
|
22
|
+
" ____ _______ _____ _ _ _____ ",
|
|
23
|
+
" | _ \\| ____\\ \\ / /_ _| \\ | || ____|",
|
|
24
|
+
" | |_) | _| \\ V / | || \\| || _| ",
|
|
25
|
+
" | _ <| |___ \\ / | || |\\ || |___ ",
|
|
26
|
+
" |_| \\_\\_____| \\_/ |___|_| \\_||_____|"
|
|
27
|
+
].join("\n")));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function logStep(message: string) {
|
|
31
|
+
console.log(`${revineGradient("⚡")} ${message}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function logSuccess(message: string) {
|
|
35
|
+
console.log(`${chalk.green("✔")} ${message}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function printDevServerInfo(version: string, port: number, startTime: number) {
|
|
39
|
+
const duration = Date.now() - startTime;
|
|
40
|
+
const localUrl = `http://localhost:${port}/`;
|
|
41
|
+
|
|
42
|
+
const networkInterfaces = os.networkInterfaces();
|
|
43
|
+
const networkUrls: string[] = [];
|
|
44
|
+
|
|
45
|
+
for (const interfaceName in networkInterfaces) {
|
|
46
|
+
const interfaces = networkInterfaces[interfaceName];
|
|
47
|
+
if (interfaces) {
|
|
48
|
+
for (const iface of interfaces) {
|
|
49
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
50
|
+
networkUrls.push(`http://${iface.address}:${port}/`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const logo = getLogo();
|
|
57
|
+
|
|
58
|
+
const content = [
|
|
59
|
+
logo,
|
|
60
|
+
"",
|
|
61
|
+
`${chalk.dim("Framework Version:")} ${chalk.bold.white(`v${version}`)}`,
|
|
62
|
+
"",
|
|
63
|
+
`${chalk.cyan("➜")} ${chalk.bold("Local:")} ${chalk.blue(localUrl)}`,
|
|
64
|
+
...networkUrls.map(url => `${chalk.cyan("➜")} ${chalk.bold("Network:")} ${chalk.blue(url)}`),
|
|
65
|
+
"",
|
|
66
|
+
`${chalk.dim("Ready in")} ${chalk.bold.white(duration)}${chalk.dim("ms")}`,
|
|
67
|
+
].join("\n");
|
|
68
|
+
|
|
69
|
+
const boxed = boxen(content, {
|
|
70
|
+
padding: 1,
|
|
71
|
+
margin: { top: 1, bottom: 1, left: 0, right: 0 },
|
|
72
|
+
borderStyle: "round",
|
|
73
|
+
borderColor: "dim",
|
|
74
|
+
title: chalk.bold.white(" DEV SERVER "),
|
|
75
|
+
titleAlignment: "left",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
console.log("\n" + boxed + "\n");
|
|
79
|
+
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import os from "os";
|
|
3
|
-
import type { Plugin, ViteDevServer } from "vite";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Reads all non-internal IPv4 addresses from the host machine's
|
|
7
|
-
* network interfaces so we can display them regardless of Vite's
|
|
8
|
-
* resolvedUrls behaviour (which can be empty when logLevel is silent).
|
|
9
|
-
*/
|
|
10
|
-
function getNetworkAddresses(): string[] {
|
|
11
|
-
const interfaces = os.networkInterfaces();
|
|
12
|
-
const addresses: string[] = [];
|
|
13
|
-
|
|
14
|
-
for (const nets of Object.values(interfaces)) {
|
|
15
|
-
if (!nets) continue;
|
|
16
|
-
for (const net of nets) {
|
|
17
|
-
// Only include external IPv4 addresses
|
|
18
|
-
if (net.family === "IPv4" && !net.internal) {
|
|
19
|
-
addresses.push(net.address);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return addresses;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function revineLoggerPlugin(): Plugin {
|
|
28
|
-
const indigo = chalk.hex("#6d28d9");
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
name: "revine-logger",
|
|
32
|
-
configureServer(server: ViteDevServer) {
|
|
33
|
-
server.httpServer?.once("listening", () => {
|
|
34
|
-
const protocol = server.config.server.https ? "https" : "http";
|
|
35
|
-
const port = server.config.server.port ?? 3000;
|
|
36
|
-
const localUrl =
|
|
37
|
-
server.resolvedUrls?.local[0] ?? `${protocol}://localhost:${port}/`;
|
|
38
|
-
|
|
39
|
-
// Always derive network URLs from OS interfaces — reliable even with logLevel: silent
|
|
40
|
-
const networkUrls =
|
|
41
|
-
server.resolvedUrls?.network?.length
|
|
42
|
-
? server.resolvedUrls.network
|
|
43
|
-
: getNetworkAddresses().map((addr) => `${protocol}://${addr}:${port}/`);
|
|
44
|
-
|
|
45
|
-
console.log(indigo("─────────────────────────────────────────────"));
|
|
46
|
-
console.log(indigo.bold("🚀 Revine Dev Server is now running!"));
|
|
47
|
-
console.log(indigo("─────────────────────────────────────────────"));
|
|
48
|
-
console.log(indigo(`Local: ${chalk.green(localUrl)}`));
|
|
49
|
-
|
|
50
|
-
if (networkUrls.length) {
|
|
51
|
-
networkUrls.forEach((url: string) => {
|
|
52
|
-
console.log(indigo(`Network: ${chalk.green(url)}`));
|
|
53
|
-
});
|
|
54
|
-
} else {
|
|
55
|
-
console.log(indigo(`Network: ${chalk.dim("not available")}`));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
console.log(indigo("─────────────────────────────────────────────"));
|
|
59
|
-
console.log("");
|
|
60
|
-
});
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
}
|